浏览代码

Merge pull request #11939 from richardkchapman/embed-java

HPCC-20980 Automatically build and distribute class files for embedded Java code

Reviewed-by: Gavin Halliday <ghalliday@hpccsystems.com>
Gavin Halliday 6 年之前
父节点
当前提交
b1bfe597e4

+ 1 - 5
CMakeLists.txt

@@ -69,11 +69,7 @@
 
 project(hpccsystems-platform)
 
-if(UNIX AND NOT APPLE)
-    cmake_minimum_required(VERSION 3.3.2)
-else()
-    cmake_minimum_required(VERSION 2.8.11)
-endif()
+cmake_minimum_required(VERSION 3.12.0)
 
 set(TOP_LEVEL_PROJECT ON)
 if(NOT CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)

+ 0 - 313
cmake_modules/FindJNI.cmake

@@ -1,313 +0,0 @@
-# - Find JNI java libraries.
-# This module finds if Java is installed and determines where the
-# include files and libraries are. It also determines what the name of
-# the library is. This code sets the following variables:
-#
-#  JNI_INCLUDE_DIRS      = the include dirs to use
-#  JNI_LIBRARIES         = the libraries to use
-#  JNI_FOUND             = TRUE if JNI headers and libraries were found.
-#  JAVA_AWT_LIBRARY      = the path to the jawt library
-#  JAVA_JVM_LIBRARY      = the path to the jvm library
-#  JAVA_INCLUDE_PATH     = the include path to jni.h
-#  JAVA_INCLUDE_PATH2    = the include path to jni_md.h
-#  JAVA_AWT_INCLUDE_PATH = the include path to jawt.h
-#
-
-# Derived from the version included with CMake 2.8.9, with fixes for
-# the location that openjdk installs to on Ubuntu 12.10
-#=============================================================================
-#CMake - Cross Platform Makefile Generator
-#Copyright 2000-2011 Kitware, Inc., Insight Software Consortium
-#All rights reserved.
-#
-#Redistribution and use in source and binary forms, with or without
-#modification, are permitted provided that the following conditions
-#are met:
-#
-#* Redistributions of source code must retain the above copyright
-#  notice, this list of conditions and the following disclaimer.
-#
-#* Redistributions in binary form must reproduce the above copyright
-#  notice, this list of conditions and the following disclaimer in the
-#  documentation and/or other materials provided with the distribution.
-#
-#* Neither the names of Kitware, Inc., the Insight Software Consortium,
-#  nor the names of their contributors may be used to endorse or promote
-#  products derived from this software without specific prior written
-#  permission.
-#
-#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-#"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-#LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-#A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-#HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-#SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-#LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-#DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-#THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-#OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#
-#------------------------------------------------------------------------------
-#
-#The above copyright and license notice applies to distributions of
-#CMake in source and binary form.  Some source files contain additional
-#notices of original copyright by their contributors; see each source
-#for details.  Third-party software packages supplied with CMake under
-#compatible licenses provide their own copyright notices documented in
-#corresponding subdirectories.
-#
-#------------------------------------------------------------------------------
-#
-#CMake was initially developed by Kitware with the following sponsorship:
-#
-# * National Library of Medicine at the National Institutes of Health
-#   as part of the Insight Segmentation and Registration Toolkit (ITK).
-#
-# * US National Labs (Los Alamos, Livermore, Sandia) ASC Parallel
-#   Visualization Initiative.
-#
-# * National Alliance for Medical Image Computing (NAMIC) is funded by the
-#   National Institutes of Health through the NIH Roadmap for Medical Research,
-#   Grant U54 EB005149.
-#
-# * Kitware, Inc.
-#=============================================================================
-
-# Expand {libarch} occurences to java_libarch subdirectory(-ies) and set ${_var}
-MACRO(java_append_library_directories _var)
-    # Determine java arch-specific library subdir
-    # Mostly based on openjdk/jdk/make/common/shared/Platform.gmk as of openjdk
-    # 1.6.0_18 + icedtea patches. However, it would be much better to base the
-    # guess on the first part of the GNU config.guess platform triplet.
-    IF(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
-        SET(_java_libarch "amd64")
-    ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$")
-        SET(_java_libarch "i386")
-    ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES "^alpha")
-        SET(_java_libarch "alpha")
-    ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES "^arm")
-        # Subdir is "arm" for both big-endian (arm) and little-endian (armel).
-        SET(_java_libarch "arm")
-    ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES "^mips")
-        # mips* machines are bi-endian mostly so processor does not tell
-        # endianess of the underlying system.
-        SET(_java_libarch "${CMAKE_SYSTEM_PROCESSOR}" "mips" "mipsel" "mipseb")
-    ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64")
-        SET(_java_libarch "ppc64")
-    ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)")
-        SET(_java_libarch "ppc")
-    ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES "^sparc")
-        # Both flavours can run on the same processor
-        SET(_java_libarch "${CMAKE_SYSTEM_PROCESSOR}" "sparc" "sparcv9")
-    ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES "^(parisc|hppa)")
-        SET(_java_libarch "parisc" "parisc64")
-    ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES "^s390")
-        # s390 binaries can run on s390x machines
-        SET(_java_libarch "${CMAKE_SYSTEM_PROCESSOR}" "s390" "s390x")
-    ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES "^sh")
-        SET(_java_libarch "sh")
-    ELSE(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
-        SET(_java_libarch "${CMAKE_SYSTEM_PROCESSOR}")
-    ENDIF(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
-
-    # Append default list architectures if CMAKE_SYSTEM_PROCESSOR was empty or
-    # system is non-Linux (where the code above has not been well tested)
-    IF(NOT _java_libarch OR NOT (CMAKE_SYSTEM_NAME MATCHES "Linux"))
-        LIST(APPEND _java_libarch "i386" "amd64" "ppc")
-    ENDIF(NOT _java_libarch OR NOT (CMAKE_SYSTEM_NAME MATCHES "Linux"))
-
-    # Sometimes ${CMAKE_SYSTEM_PROCESSOR} is added to the list to prefer
-    # current value to a hardcoded list. Remove possible duplicates.
-    LIST(REMOVE_DUPLICATES _java_libarch)
-
-    FOREACH(_path ${ARGN})
-        IF(_path MATCHES "{libarch}")
-            FOREACH(_libarch ${_java_libarch})
-                STRING(REPLACE "{libarch}" "${_libarch}" _newpath "${_path}")
-                LIST(APPEND ${_var} "${_newpath}")
-            ENDFOREACH(_libarch)
-        ELSE(_path MATCHES "{libarch}")
-            LIST(APPEND ${_var} "${_path}")
-        ENDIF(_path MATCHES "{libarch}")
-    ENDFOREACH(_path)
-ENDMACRO(java_append_library_directories)
-
-GET_FILENAME_COMPONENT(java_install_version
-  "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit;CurrentVersion]" NAME)
-
-SET(JAVA_AWT_LIBRARY_DIRECTORIES
-  "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.4;JavaHome]/lib"
-  "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.3;JavaHome]/lib"
-  "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\${java_install_version};JavaHome]/lib"
-  )
-
-FILE(TO_CMAKE_PATH "$ENV{JAVA_HOME}" _JAVA_HOME)
-
-JAVA_APPEND_LIBRARY_DIRECTORIES(JAVA_AWT_LIBRARY_DIRECTORIES
-  ${_JAVA_HOME}/jre/lib/{libarch}
-  ${_JAVA_HOME}/jre/lib
-  ${_JAVA_HOME}/lib
-  ${_JAVA_HOME}
-  /usr/lib
-  /usr/local/lib
-  /usr/lib/jvm/java/lib
-  /usr/lib/java/jre/lib/{libarch}
-  /usr/lib/jvm/jre/lib/{libarch}
-  /usr/local/lib/java/jre/lib/{libarch}
-  /usr/local/share/java/jre/lib/{libarch}
-  /usr/lib/j2sdk1.7-sun/jre/lib/{libarch}
-  /usr/lib/jvm/java-7-sun/jre/lib/{libarch}
-  /usr/lib/jvm/java-1.7.0-sun/jre/lib/{libarch}
-  /usr/lib/jvm/java-7-sun-1.7.0.00/jre/lib/{libarch}       # can this one be removed according to #8821 ? Alex
-  /usr/lib/jvm/java-7-openjdk-{libarch}/jre/lib/{libarch}  # Ubuntu 13.04 location
-  /usr/lib/jvm/java-8-openjdk-{libarch}/jre/lib/{libarch}  # Ubuntu 16.04 location
-  /usr/lib/jvm/java-9-openjdk-{libarch}/jre/lib/{libarch}
-  # Debian specific paths for default JVM
-  /usr/lib/jvm/default-java/jre/lib/{libarch}
-  /usr/lib/jvm/default-java/jre/lib
-  /usr/lib/jvm/default-java/lib
-  )
-
-SET(JAVA_JVM_LIBRARY_DIRECTORIES)
-FOREACH(dir ${JAVA_AWT_LIBRARY_DIRECTORIES})
-  SET(JAVA_JVM_LIBRARY_DIRECTORIES
-    ${JAVA_JVM_LIBRARY_DIRECTORIES}
-    "${dir}"
-    "${dir}/client"
-    "${dir}/server"
-    )
-ENDFOREACH(dir)
-
-
-JAVA_APPEND_LIBRARY_DIRECTORIES(JAVA_AWT_EXPANDED_INCLUDE_DIRECTORIES
-  ${_JAVA_HOME}/include
-  /usr/include
-  /usr/local/include
-  /usr/lib/java/include
-  /usr/local/lib/java/include
-  /usr/lib/jvm/java/include
-  /usr/lib/jvm/java-7-sun/include
-  /usr/lib/jvm/java-1.7.0-sun/include
-  /usr/lib/jvm/java-7-sun-1.7.0.00/include       # can this one be removed according to #8821 ? Alex
-  /usr/lib/jvm/java-7-openjdk-{libarch}/include  # Ubuntu 13.04 location
-  /usr/lib/jvm/java-8-openjdk-{libarch}/include  # Ubuntu 16.04 location
-  /usr/lib/jvm/java-9-openjdk-{libarch}/include  #
-  /usr/local/share/java/include
-  /usr/lib/j2sdk1.7-sun/include
-  /usr/lib/j2sdk1.8-sun/include
-  /usr/lib/j2sdk1.9-sun/include
-  # Debian specific path for default JVM
-  /usr/lib/jvm/default-java/include
-  )
-
-SET (JAVA_AWT_INCLUDE_DIRECTORIES
-  "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.4;JavaHome]/include"
-  "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\1.3;JavaHome]/include"
-  "[HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\${java_install_version};JavaHome]/include"
-  ${JAVA_AWT_EXPANDED_INCLUDE_DIRECTORIES}
-  )
-
-FOREACH(JAVA_PROG "${JAVA_RUNTIME}" "${JAVA_COMPILE}" "${JAVA_ARCHIVE}")
-  GET_FILENAME_COMPONENT(jpath "${JAVA_PROG}" PATH)
-  FOREACH(JAVA_INC_PATH ../include ../java/include ../share/java/include)
-    IF(EXISTS ${jpath}/${JAVA_INC_PATH})
-      SET(JAVA_AWT_INCLUDE_DIRECTORIES ${JAVA_AWT_INCLUDE_DIRECTORIES} "${jpath}/${JAVA_INC_PATH}")
-    ENDIF(EXISTS ${jpath}/${JAVA_INC_PATH})
-  ENDFOREACH(JAVA_INC_PATH)
-  FOREACH(JAVA_LIB_PATH
-    ../lib ../jre/lib ../jre/lib/i386
-    ../java/lib ../java/jre/lib ../java/jre/lib/i386
-    ../share/java/lib ../share/java/jre/lib ../share/java/jre/lib/i386)
-    IF(EXISTS ${jpath}/${JAVA_LIB_PATH})
-      SET(JAVA_AWT_LIBRARY_DIRECTORIES ${JAVA_AWT_LIBRARY_DIRECTORIES} "${jpath}/${JAVA_LIB_PATH}")
-    ENDIF(EXISTS ${jpath}/${JAVA_LIB_PATH})
-  ENDFOREACH(JAVA_LIB_PATH)
-ENDFOREACH(JAVA_PROG)
-
-IF(APPLE)
-  IF(EXISTS ~/Library/Frameworks/JavaVM.framework)
-    SET(JAVA_HAVE_FRAMEWORK 1)
-  ENDIF(EXISTS ~/Library/Frameworks/JavaVM.framework)
-  IF(EXISTS /Library/Frameworks/JavaVM.framework)
-    SET(JAVA_HAVE_FRAMEWORK 1)
-  ENDIF(EXISTS /Library/Frameworks/JavaVM.framework)
-  IF(EXISTS /System/Library/Frameworks/JavaVM.framework)
-    SET(JAVA_HAVE_FRAMEWORK 1)
-  ENDIF(EXISTS /System/Library/Frameworks/JavaVM.framework)
-
-  IF(JAVA_HAVE_FRAMEWORK)
-    IF(NOT JAVA_AWT_LIBRARY)
-      SET (JAVA_AWT_LIBRARY "-framework JavaVM" CACHE FILEPATH "Java Frameworks" FORCE)
-    ENDIF(NOT JAVA_AWT_LIBRARY)
-
-    IF(NOT JAVA_JVM_LIBRARY)
-      SET (JAVA_JVM_LIBRARY "-framework JavaVM" CACHE FILEPATH "Java Frameworks" FORCE)
-    ENDIF(NOT JAVA_JVM_LIBRARY)
-
-    IF(NOT JAVA_AWT_INCLUDE_PATH)
-      IF(EXISTS /System/Library/Frameworks/JavaVM.framework/Headers/jawt.h)
-        SET (JAVA_AWT_INCLUDE_PATH "/System/Library/Frameworks/JavaVM.framework/Headers" CACHE FILEPATH "jawt.h location" FORCE)
-      ENDIF(EXISTS /System/Library/Frameworks/JavaVM.framework/Headers/jawt.h)
-    ENDIF(NOT JAVA_AWT_INCLUDE_PATH)
-
-    # If using "-framework JavaVM", prefer its headers *before* the others in
-    # JAVA_AWT_INCLUDE_DIRECTORIES... (*prepend* to the list here)
-    #
-    SET(JAVA_AWT_INCLUDE_DIRECTORIES
-      ~/Library/Frameworks/JavaVM.framework/Headers
-      /Library/Frameworks/JavaVM.framework/Headers
-      /System/Library/Frameworks/JavaVM.framework/Headers
-      ${JAVA_AWT_INCLUDE_DIRECTORIES}
-      )
-  ENDIF(JAVA_HAVE_FRAMEWORK)
-ELSE(APPLE)
-  FIND_LIBRARY(JAVA_AWT_LIBRARY jawt
-    PATHS ${JAVA_AWT_LIBRARY_DIRECTORIES}
-  )
-  FIND_LIBRARY(JAVA_JVM_LIBRARY NAMES jvm JavaVM
-    PATHS ${JAVA_JVM_LIBRARY_DIRECTORIES}
-  )
-ENDIF(APPLE)
-
-# add in the include path
-FIND_PATH(JAVA_INCLUDE_PATH jni.h
-  ${JAVA_AWT_INCLUDE_DIRECTORIES}
-)
-
-FIND_PATH(JAVA_INCLUDE_PATH2 jni_md.h
-  ${JAVA_INCLUDE_PATH}
-  ${JAVA_INCLUDE_PATH}/win32
-  ${JAVA_INCLUDE_PATH}/linux
-  ${JAVA_INCLUDE_PATH}/freebsd
-  ${JAVA_INCLUDE_PATH}/solaris
-  ${JAVA_INCLUDE_PATH}/hp-ux
-  ${JAVA_INCLUDE_PATH}/alpha
-)
-
-FIND_PATH(JAVA_AWT_INCLUDE_PATH jawt.h
-  ${JAVA_INCLUDE_PATH}
-)
-
-#INCLUDE(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake)
-FIND_PACKAGE_HANDLE_STANDARD_ARGS(JNI  DEFAULT_MSG  JAVA_AWT_LIBRARY JAVA_JVM_LIBRARY
-                                                    JAVA_INCLUDE_PATH  JAVA_INCLUDE_PATH2 JAVA_AWT_INCLUDE_PATH)
-
-MARK_AS_ADVANCED(
-  JAVA_AWT_LIBRARY
-  JAVA_JVM_LIBRARY
-  JAVA_AWT_INCLUDE_PATH
-  JAVA_INCLUDE_PATH
-  JAVA_INCLUDE_PATH2
-)
-
-SET(JNI_LIBRARIES
-  ${JAVA_AWT_LIBRARY}
-  ${JAVA_JVM_LIBRARY}
-)
-
-SET(JNI_INCLUDE_DIRS
-  ${JAVA_INCLUDE_PATH}
-  ${JAVA_INCLUDE_PATH2}
-  ${JAVA_AWT_INCLUDE_PATH}
-)

+ 6 - 0
ecl/hql/hqlatoms.cpp

@@ -52,6 +52,7 @@ IIdAtom * getUTF8ResultId;
 IIdAtom * importId;
 IIdAtom * lineId;
 IIdAtom * loadId;
+IIdAtom * loadCompiledScriptId;
 IIdAtom * macroId;
 IIdAtom * maxLengthId;
 IIdAtom * maxSizeId;
@@ -59,6 +60,7 @@ IIdAtom * __optionsId;
 IIdAtom * outputId;
 IIdAtom * physicalLengthId;
 IIdAtom * prebindId;
+IIdAtom * precompileId;
 IIdAtom * __queryId;
 IIdAtom * selfId;
 IIdAtom * sharedId;
@@ -341,6 +343,7 @@ IAtom * persistAtom;
 IAtom * physicalFilenameAtom;
 IAtom * pluginAtom;
 IAtom * prebindAtom;
+IAtom * precompileAtom;
 IAtom * prefetchAtom;
 IAtom * preloadAtom;
 IAtom * priorityAtom;
@@ -518,6 +521,7 @@ MODULE_INIT(INIT_PRIORITY_HQLATOM)
     MAKEID(import);
     MAKEID(line);
     MAKEID(load);
+    MAKEID(loadCompiledScript);
     MAKEID(macro);
     MAKEID(maxLength);
     MAKEID(maxSize);
@@ -525,6 +529,7 @@ MODULE_INIT(INIT_PRIORITY_HQLATOM)
     MAKEID(output);
     MAKEID(physicalLength);
     MAKEID(prebind);
+    MAKEID(precompile);
     MAKEID(__query);
     MAKEID(self);
     MAKEID(shared);
@@ -808,6 +813,7 @@ MODULE_INIT(INIT_PRIORITY_HQLATOM)
     MAKEATOM(physicalFilename);
     MAKEATOM(plugin);
     MAKEATOM(prebind);
+    MAKEATOM(precompile);
     MAKEATOM(prefetch);
     MAKEATOM(preload);
     MAKEATOM(priority);

+ 3 - 0
ecl/hql/hqlatoms.hpp

@@ -54,6 +54,7 @@ extern HQL_API IIdAtom * getUTF8ResultId;
 extern HQL_API IIdAtom * importId;
 extern HQL_API IIdAtom * lineId;
 extern HQL_API IIdAtom * loadId;
+extern HQL_API IIdAtom * loadCompiledScriptId;
 extern HQL_API IIdAtom * macroId;
 extern HQL_API IIdAtom * maxLengthId;
 extern HQL_API IIdAtom * maxSizeId;
@@ -61,6 +62,7 @@ extern HQL_API IIdAtom * __optionsId;
 extern HQL_API IIdAtom * outputId;
 extern HQL_API IIdAtom * physicalLengthId;
 extern HQL_API IIdAtom * prebindId;
+extern HQL_API IIdAtom * precompileId;
 extern HQL_API IIdAtom * __queryId;
 extern HQL_API IIdAtom * selfId;
 extern HQL_API IIdAtom * sharedId;
@@ -346,6 +348,7 @@ extern HQL_API IAtom * persistAtom;
 extern HQL_API IAtom * physicalFilenameAtom;
 extern HQL_API IAtom * pluginAtom;
 extern HQL_API IAtom * prebindAtom;
+extern HQL_API IAtom * precompileAtom;
 extern HQL_API IAtom * prefetchAtom;
 extern HQL_API IAtom * preloadAtom;
 extern HQL_API IAtom * priorityAtom;

+ 2 - 0
ecl/hql/hqlerror.cpp

@@ -74,6 +74,8 @@ WarnErrorCategory getCategory(const char * category)
         return CategorySecurity;
     if (strieq(category, "dfs"))
         return CategoryDFS;
+    if (strieq(category, "embed"))
+        return CategoryEmbed;
     return CategoryUnknown;
 }
 

+ 2 - 0
ecl/hql/hqlerrors.hpp

@@ -70,6 +70,7 @@
 #define WRN_UNRESOLVED_SYMBOL       1053
 #define WRN_REQUIRES_SIGNED         1054
 #define WRN_DISALLOWED              1055
+#define WRN_EMBEDWARNING            1056
 
 //Do not define any warnings > 1099 - use the range below instead
 
@@ -436,6 +437,7 @@
 #define ERR_ASSOCIATED_SIDEEFFECT   2402
 #define ERR_INVALID_PROBABILITY     2403
 #define ERR_DEFAULT_VIRTUAL_CLASH   2404
+#define ERR_EMBEDERROR              2405
 
 #define ERR_CPP_COMPILE_ERROR       2999
 

+ 20 - 8
ecl/hql/hqlfold.cpp

@@ -1666,14 +1666,19 @@ IHqlExpression * foldEmbeddedCall(IHqlExpression* expr, unsigned foldOptions, IT
 
     IValue *query = body->queryChild(0)->queryValue();
     assertex(query);
-    StringBuffer queryText;
-    query->getUTF8Value(queryText);
     if (!body->hasAttribute(prebindAtom))
     {
-        if (isImport)
-            __ctx->importFunction(queryText.lengthUtf8(), queryText.str());
+        if (body->hasAttribute(precompileAtom))
+            __ctx->loadCompiledScript(query->getSize(), query->queryValue());
         else
-            __ctx->compileEmbeddedScript(queryText.lengthUtf8(), queryText.str());
+        {
+            StringBuffer queryText;
+            query->getUTF8Value(queryText);
+            if (isImport)
+                __ctx->importFunction(queryText.lengthUtf8(), queryText.str());
+            else
+                __ctx->compileEmbeddedScript(queryText.lengthUtf8(), queryText.str());
+        }
     }
     // process all the parameters passed in
     unsigned numParam = expr->numChildren();
@@ -1784,10 +1789,17 @@ IHqlExpression * foldEmbeddedCall(IHqlExpression* expr, unsigned foldOptions, IT
     }
     if (body->hasAttribute(prebindAtom))
     {
-        if (isImport)
-            __ctx->importFunction(queryText.lengthUtf8(), queryText.str());
+        if (body->hasAttribute(precompileAtom))
+            __ctx->loadCompiledScript(query->getSize(), query->queryValue());
         else
-            __ctx->compileEmbeddedScript(queryText.lengthUtf8(), queryText.str());
+        {
+            StringBuffer queryText;
+            query->getUTF8Value(queryText);
+            if (isImport)
+                __ctx->importFunction(queryText.lengthUtf8(), queryText.str());
+            else
+                __ctx->compileEmbeddedScript(queryText.lengthUtf8(), queryText.str());
+        }
     }
     __ctx->callFunction();
 

+ 65 - 1
ecl/hql/hqlgram2.cpp

@@ -945,9 +945,73 @@ IHqlExpression * HqlGram::processEmbedBody(const attribute & errpos, IHqlExpress
         if (matchesBoolean(prebind, true))
             args.append(*createAttribute(prebindAtom));
         OwnedHqlExpr syntaxCheckFunc = pluginScope->lookupSymbol(syntaxCheckId, LSFpublic, lookupCtx);
+        bool failedSyntaxCheck = false;
         if (syntaxCheckFunc && !isImport)
         {
-            // MORE - create an expression that calls it, and const fold it, I guess....
+            HqlExprArray syntaxCheckArgs;
+            embedText->unwindList(syntaxCheckArgs, no_comma);
+            OwnedHqlExpr syntax = createBoundFunction(this, syntaxCheckFunc, syntaxCheckArgs, lookupCtx.functionCache, true);
+            OwnedHqlExpr folded = foldHqlExpression(syntax);
+            if (folded->queryValue())
+            {
+                StringBuffer errors;
+                folded->queryValue()->getStringValue(errors);
+                if (errors.length())
+                {
+                    StringArray errlines;
+                    errlines.appendList(errors, "\n");
+                    ForEachItemIn(idx, errlines)
+                    {
+                        const char *err = errlines.item(idx);
+                        if (strlen(err))
+                        {
+                            ECLlocation pos(errpos.pos);
+                            unsigned line, col;
+                            char dummy;
+                            if (sscanf(err, "(%u,%u):%c", &line, &col, &dummy)==3)
+                            {
+                                err = strchr(err, ':') + 1;
+                                while (isspace(*err))
+                                    err++;
+                                pos.lineno = embedText->getStartLine()+line-1;
+                                pos.column = col;
+                                pos.position = 0;
+                            }
+                            if (strnicmp(err, "warning:", 8)==0)
+                            {
+                                err = strchr(err, ':') + 1;
+                                while (isspace(*err))
+                                    err++;
+                                reportWarning(CategoryEmbed, WRN_EMBEDWARNING, pos, "%s", err);
+                            }
+                            else
+                            {
+                                if (strnicmp(err, "error:", 6)==0)
+                                {
+                                    err = strchr(err, ':') + 1;
+                                    while (isspace(*err))
+                                        err++;
+                                }
+                                reportError(ERR_EMBEDERROR, pos, "%s", err);
+                                failedSyntaxCheck = true;
+                            }
+                        }
+                    }
+                }
+            }
+            else
+            {
+                DBGLOG("INTERNAL: syntaxCheck was not foldable");  // Ignore as no fatal? Warning? Terminate? Warning that defaults to error?
+            }
+        }
+        OwnedHqlExpr precompile = pluginScope->lookupSymbol(precompileId, LSFpublic, lookupCtx);
+        if (precompile && !failedSyntaxCheck && !lookupCtx.syntaxChecking())
+        {
+            HqlExprArray precompileArgs;
+            embedText->unwindList(precompileArgs, no_comma);
+            // Replace queryText with compiled version of it
+            args.replace(*createBoundFunction(this, precompile, precompileArgs, lookupCtx.functionCache, true), 0);
+            args.append(*createAttribute(precompileAtom));
         }
         args.append(*createExprAttribute(languageAtom, getEmbedContextFunc.getClear()));
         IHqlExpression *projectedAttr = queryAttribute(projectedAtom, args);

+ 1 - 1
ecl/hql/hqlutil.hpp

@@ -256,7 +256,7 @@ extern HQL_API bool isTimed(IHqlExpression * expr);
 inline bool isInternalEmbedAttr(IAtom *name)
 {
     return name == languageAtom || name == projectedAtom || name == streamedAtom || name == _linkCounted_Atom || 
-           name == importAtom || name==foldAtom || name==timeAtom || name==prebindAtom ||
+           name == importAtom || name==foldAtom || name==timeAtom || name==prebindAtom || name==precompileAtom ||
            name == activityAtom || name == localAtom || name == parallelAtom;
 }
 

+ 9 - 2
ecl/hqlcpp/hqlcpp.cpp

@@ -12099,8 +12099,15 @@ void HqlCppTranslator::buildScriptFunctionDefinition(BuildCtx &ctx, IHqlExpressi
         else
             scriptArgs.append(*LINK(bodyCode->queryChild(0)));
     }
+    IIdAtom *id;
+    if (isImport)
+        id = importId;
+    else if (bodyCode->hasAttribute(precompileAtom))
+        id = loadCompiledScriptId;
+    else
+        id = compileEmbeddedScriptId;
     if (!bodyCode->hasAttribute(prebindAtom))
-        buildFunctionCall(funcctx, isImport ? importId : compileEmbeddedScriptId, scriptArgs);
+        buildFunctionCall(funcctx, id, scriptArgs);
     ForEachChild(i, formals)
     {
         IHqlExpression * param = formals->queryChild(i);
@@ -12178,7 +12185,7 @@ void HqlCppTranslator::buildScriptFunctionDefinition(BuildCtx &ctx, IHqlExpressi
         buildFunctionCall(funcctx, bindFunc, args);
     }
     if (bodyCode->hasAttribute(prebindAtom))
-        buildFunctionCall(funcctx, isImport ? importId : compileEmbeddedScriptId, scriptArgs);
+        buildFunctionCall(funcctx, id, scriptArgs);
     funcctx.addQuotedLiteral("__ctx->callFunction();");
     IIdAtom * returnFunc;
     HqlExprArray retargs;

+ 1 - 0
ecl/hqlcpp/hqlcppsys.ecl

@@ -894,6 +894,7 @@ const char * cppSystemText[]  = {
     "   set of any getSetResult(integer4 typeCode, unsigned4 elemSize) : method,entrypoint='getSetResult';",
 
     "   compileEmbeddedScript(const utf8 script) : method,entrypoint='compileEmbeddedScript';",
+    "   loadCompiledScript(const data script) : method,entrypoint='loadCompiledScript';",
     "   utf8 substituteEmbeddedScript(const utf8 script, const utf8 fields, const utf8 substitute) : eclrtl,include,pure,entrypoint='rtlSubstituteEmbeddedScript';",
     "   import(const utf8 script) : method,entrypoint='importFunction';",
     "   varstring registerTimer(unsigned4 id, const varstring name) : ctxmethod;",

+ 4 - 1
ecl/hqlcpp/hqlttcpp.cpp

@@ -6557,7 +6557,10 @@ IHqlExpression * WorkflowTransformer::transformInternalFunction(IHqlExpression *
         IHqlExpression *query = bodyCode->queryChild(0);
         if (!query->queryValue())
         {
-            newFormals.append(*createParameter(__queryId, newFormals.length(), LINK(unknownUtf8Type), attrs));
+            if (bodyCode->hasAttribute(precompileAtom))
+                newFormals.append(*createParameter(__queryId, newFormals.length(), LINK(unknownDataType), attrs));
+            else
+                newFormals.append(*createParameter(__queryId, newFormals.length(), LINK(unknownUtf8Type), attrs));
             newDefaults.append(*LINK(query));
         }
 

+ 4 - 0
plugins/Rembed/Rembed.cpp

@@ -1345,6 +1345,10 @@ public:
         text.stripChar('\r');
         func.set(text.str());
     }
+    virtual void loadCompiledScript(size32_t chars, const void *_script) override
+    {
+        throwUnexpected();
+    }
 
     virtual void callFunction()
     {

+ 4 - 0
plugins/cassandra/cassandraembed.cpp

@@ -1806,6 +1806,10 @@ public:
             throw makeStringException(E->errorCode(), msg);
         }
     }
+    virtual void loadCompiledScript(size32_t chars, const void *_script) override
+    {
+        throwUnexpected();
+    }
 protected:
     void lazyExecute()
     {

+ 4 - 0
plugins/couchbase/couchbaseembed.hpp

@@ -452,6 +452,10 @@ namespace couchbaseembed
            }
            virtual void compileEmbeddedScript(size32_t chars, const char *script);
            virtual void callFunction();
+           virtual void loadCompiledScript(size32_t chars, const void *_script) override
+           {
+               UNSUPPORTED("loadCompiledScript");
+           }
        protected:
            void execute();
            unsigned countBindings(const char *query);

+ 1 - 1
plugins/javaembed/CMakeLists.txt

@@ -60,7 +60,7 @@ if(JAVAEMBED)
             TARGETS javaembed
             DESTINATION plugins)
         install(
-            FILES ${CMAKE_CURRENT_SOURCE_DIR}/HpccUtils.class
+            FILES ${CMAKE_CURRENT_SOURCE_DIR}/HpccUtils.class ${CMAKE_CURRENT_SOURCE_DIR}/HpccClassLoader.class
             DESTINATION classes/com/HPCCSystems
             COMPONENT Runtime)
 

二进制
plugins/javaembed/HpccClassLoader.class


+ 41 - 0
plugins/javaembed/HpccClassLoader.java

@@ -0,0 +1,41 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2018 HPCC Systems®.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+############################################################################## */
+
+package com.HPCCSystems;
+
+import java.net.*;
+public class HpccClassLoader extends java.net.URLClassLoader
+{
+    private long bytecode;
+    private int bytecodeLen;
+    private native Class defineClassForEmbed(int bytecodeLen, long bytecode, String name);
+    private HpccClassLoader(java.net.URL [] urls, ClassLoader parent, int _bytecodeLen, long _bytecode, String dllname)
+    {
+        super(urls, parent);
+        System.load(dllname);
+        bytecodeLen = _bytecodeLen;
+        bytecode = _bytecode;
+    }
+    public Class<?> findClass(String className) throws ClassNotFoundException
+    {
+        return defineClassForEmbed(bytecodeLen, bytecode, className.replace(".","/"));
+    }
+    public static HpccClassLoader newInstance(java.net.URL [] urls, ClassLoader parent, int _bytecodeLen, long _bytecode, String dllname)
+    {
+        return new HpccClassLoader(urls, parent, _bytecodeLen, _bytecode, dllname); 
+    }
+}

二进制
plugins/javaembed/HpccUtils.class


+ 5 - 1
plugins/javaembed/java.ecllib

@@ -17,7 +17,11 @@
 
 EXPORT Language := SERVICE : plugin('javaembed')
   integer getEmbedContext():cpp,pure,namespace='javaembed',entrypoint='getEmbedContext',prototype='IEmbedContext* getEmbedContext()',fold;
+  DATA precompile(UTF8 body):cpp,pure,namespace='javaembed',entrypoint='precompile',fold;
+  STRING syntaxCheck(UTF8 body):cpp,pure,namespace='javaembed',entrypoint='syntaxCheck',fold;
 END;
 EXPORT getEmbedContext := Language.getEmbedContext;
+EXPORT precompile := Language.precompile;
+EXPORT syntaxCheck := Language.syntaxCheck;
 EXPORT boolean supportsImport := true;
-EXPORT boolean supportsScript := false;
+EXPORT boolean supportsScript := true;

+ 620 - 30
plugins/javaembed/javaembed.cpp

@@ -19,6 +19,7 @@
 #include <jni.h>
 #include "jexcept.hpp"
 #include "jthread.hpp"
+#include "junicode.hpp"
 #include "hqlplugins.hpp"
 #include "deftype.hpp"
 #include "eclhelper.hpp"
@@ -43,6 +44,10 @@ static const char * compatibleVersions[] = {
 
 static const char *version = "Java Embed Helper 1.0.0";
 
+#ifdef _DEBUG
+//#define TRACE_CLASSFILE
+#endif
+
 extern "C" DECL_EXPORT bool getECLPluginDefinition(ECLPluginDefinitionBlock *pb)
 {
     if (pb->size == sizeof(ECLPluginDefinitionBlockEx))
@@ -130,6 +135,7 @@ public:
 
         // Options we know we always want set
         optionStrings.append("-Xrs");
+        //optionStrings.append("-XX:+TraceClassLoading");
 #ifdef RLIMIT_STACK
         // JVM has a habit of reducing the stack limit on main thread to 1M - probably dates back to when it was actually an increase...
         StringBuffer stackOption("-Xss");
@@ -162,7 +168,7 @@ public:
         vm_args.nOptions = optionStrings.length();
         vm_args.options = options;
         vm_args.ignoreUnrecognized = true;
-        vm_args.version = JNI_VERSION_1_6;
+        vm_args.version = JNI_VERSION_1_8;
 
         /* load and initialize a Java VM, return a JNI interface pointer in env */
         JNIEnv *env;       /* receives pointer to native method interface */
@@ -172,6 +178,7 @@ public:
 
         if (createResult != 0)
             throw MakeStringException(0, "javaembed: Unable to initialize JVM (%d)",createResult);
+        // DBGLOG("JNI environment version %x loaded", env->GetVersion()); // Comes out a bit too early
     }
     ~JavaGlobalState()
     {
@@ -252,11 +259,11 @@ protected:
     {
         Class = (jclass) JNIenv->NewGlobalRef(JNIenv->GetObjectClass(row));
     }
-    JavaObjectAccessor(JNIEnv *_JNIenv, const RtlFieldInfo *_outerRow)
+    JavaObjectAccessor(JNIEnv *_JNIenv, const RtlFieldInfo *_outerRow, jclass _Class)
     : JNIenv(_JNIenv), outerRow(_outerRow), idx(0), limit(0), inSet(false), inDataSet(false)
     {
         row = NULL;
-        Class = NULL;
+        Class = (jclass) JNIenv->NewGlobalRef(_Class);
     }
     ~JavaObjectAccessor()
     {
@@ -678,12 +685,9 @@ class JavaObjectBuilder : public JavaObjectAccessor, implements IFieldProcessor
 {
 public:
     IMPLEMENT_IINTERFACE;
-    JavaObjectBuilder(JNIEnv *_JNIenv, const RtlFieldInfo *_outerRow, const char *className)
-    : JavaObjectAccessor(_JNIenv, _outerRow)
+    JavaObjectBuilder(JNIEnv *_JNIenv, const RtlFieldInfo *_outerRow, jclass _Class)
+    : JavaObjectAccessor(_JNIenv, _outerRow, _Class)
     {
-        JNIenv->ExceptionClear();
-        Class = (jclass) JNIenv->NewGlobalRef(JNIenv->FindClass(className));  // MORE - should use the custom classloader, once that fix is merged
-        checkException();
         setConstructor();
     }
     virtual void processString(unsigned numchars, const char *text, const RtlFieldInfo * field)
@@ -1035,7 +1039,7 @@ protected:
             jstring name = (jstring) JNIenv->CallObjectMethod(Class, getNameMethod);
             checkException();
             const char *nameText = JNIenv->GetStringUTFChars(name, NULL);
-            VStringBuffer message("javaembed: no suitable constructor for field %s", nameText);
+            VStringBuffer message("javaembed: no suitable constructor for class %s", nameText);
             JNIenv->ReleaseStringUTFChars(name, nameText);
             rtlFail(0, message.str());
         }
@@ -1050,7 +1054,7 @@ protected:
 class ECLDatasetIterator : public CInterfaceOf<IInterface>
 {
 public:
-    ECLDatasetIterator(JNIEnv *JNIenv, const RtlTypeInfo *_typeInfo, const char *className, IRowStream * _val)
+    ECLDatasetIterator(JNIEnv *JNIenv, const RtlTypeInfo *_typeInfo, jclass className, IRowStream * _val)
     : typeInfo(_typeInfo), val(_val),
       dummyField("<row>", NULL, typeInfo),
       javaBuilder(JNIenv, &dummyField, className)
@@ -1507,7 +1511,7 @@ public:
         javaClass = NULL;
         javaMethodID = NULL;
         prevClassPath.set("dummy");  // Forces the call below to actually do something...
-        setThreadClassLoader("");
+        setThreadClassLoader("", 0, nullptr);
     }
     ~JavaThreadContext()
     {
@@ -1565,7 +1569,7 @@ public:
         return systemClassLoaderObj;
     }
 
-    void setThreadClassLoader(jobject classLoader)
+    void setThreadClassLoader(jobject classLoader, size32_t bytecodeLen, const byte *bytecode)
     {
         JNIenv->ExceptionClear();
         jclass javaLangThreadClass = JNIenv->FindClass("java/lang/Thread");
@@ -1597,11 +1601,11 @@ public:
         return contextClassLoaderObj;
     }
 
-    void setThreadClassLoader(const char *classPath)
+    void setThreadClassLoader(const char *classPath, size32_t bytecodeLen, const byte *bytecode)
     {
-        if (classPath && *classPath)
+        if (bytecodeLen || (classPath && *classPath))
         {
-            if (prevClassPath && strcmp(classPath, prevClassPath) == 0)
+            if (!bytecodeLen && prevClassPath && classPath && strcmp(classPath, prevClassPath) == 0)  // MORE - caching of inline classes is important too...
                 return;
             jclass URLcls = JNIenv->FindClass("java/net/URL");
             checkException();
@@ -1626,29 +1630,40 @@ public:
                 JNIenv->DeleteLocalRef(jstr);
             }
             checkException();
-            jclass customLoaderClass = JNIenv->FindClass("java/net/URLClassLoader");
+            jclass customLoaderClass = JNIenv->FindClass("com/HPCCSystems/HpccClassLoader");
             checkException();
-            jmethodID newInstance = JNIenv->GetStaticMethodID(customLoaderClass, "newInstance","([Ljava/net/URL;Ljava/lang/ClassLoader;)Ljava/net/URLClassLoader;");
+            jmethodID newInstance = JNIenv->GetStaticMethodID(customLoaderClass, "newInstance","([Ljava/net/URL;Ljava/lang/ClassLoader;IJLjava/lang/String;)Lcom/HPCCSystems/HpccClassLoader;");
             checkException();
-            jobject contextClassLoaderObj = JNIenv->NewGlobalRef(JNIenv->CallStaticObjectMethod(customLoaderClass, newInstance, URLArray, getSystemClassLoader()));
+            jobject contextClassLoaderObj = JNIenv->NewGlobalRef(JNIenv->CallStaticObjectMethod(customLoaderClass, newInstance, URLArray, getSystemClassLoader(), bytecodeLen, (uint64_t) bytecode, JNIenv->NewStringUTF(helperLibraryName)));
             checkException();
             assertex(contextClassLoaderObj);
-            setThreadClassLoader(contextClassLoaderObj);
+            setThreadClassLoader(contextClassLoaderObj, bytecodeLen, bytecode);
             prevClassPath.set(classPath);
         }
         else
         {
             if (prevClassPath)
-                setThreadClassLoader(getSystemClassLoader());
+                setThreadClassLoader(getSystemClassLoader(), 0, nullptr);
             prevClassPath.clear();
         }
     }
 
-    inline void importFunction(size32_t lenChars, const char *utf, const char *options, jobject instance)
+    jclass loadClass(const char *className)
+    {
+        JNIenv->ExceptionClear();
+        jobject classLoader = getThreadClassLoader();
+        jmethodID loadClassMethod = JNIenv->GetMethodID(JNIenv->GetObjectClass(classLoader), "loadClass","(Ljava/lang/String;)Ljava/lang/Class;");
+        jstring classNameString = JNIenv->NewStringUTF(className);
+        jclass Class = (jclass) JNIenv->CallObjectMethod(classLoader, loadClassMethod, classNameString);
+        checkException();
+        return Class;
+    }
+
+    inline void importFunction(size32_t lenChars, const char *utf, const char *classpath, size32_t bytecodeLen, const byte *bytecode, jobject instance)
     {
         size32_t bytes = rtlUtf8Size(lenChars, utf);
         StringBuffer text(bytes, utf);
-        setThreadClassLoader(options);
+        setThreadClassLoader(classpath, bytecodeLen, bytecode);
 
         if (!prevtext || strcmp(text, prevtext) != 0)
         {
@@ -2508,6 +2523,318 @@ public:
 // Each call to a Java function will use a new JavaEmbedScriptContext object
 #define MAX_JNI_ARGS 10
 
+class JavaClassReader
+{
+public:
+    JavaClassReader(const char *filename)
+    {
+        // Pull apart a class file to see its name and signature.
+        /* From https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1
+        ClassFile {
+            u4             magic;
+            u2             minor_version;
+            u2             major_version;
+            u2             constant_pool_count;
+            cp_info        constant_pool[constant_pool_count-1];
+            u2             access_flags;
+            u2             this_class;
+            u2             super_class;
+            u2             interfaces_count;
+            u2             interfaces[interfaces_count];
+            u2             fields_count;
+            field_info     fields[fields_count];
+            u2             methods_count;
+            method_info    methods[methods_count];
+            u2             attributes_count;
+            attribute_info attributes[attributes_count];
+        }
+       */
+#ifdef TRACE_CLASSFILE
+        DBGLOG("Reading class file created in %s", filename);
+#endif
+        Owned<IFile> file = createIFile(filename);
+        OwnedIFileIO io = file->open(IFOread);
+        assertex(io);
+        read(io, 0, (size32_t)-1, b);
+        b.setEndian(__BIG_ENDIAN);
+        uint32_t magic;
+        b.read(magic);
+        if (magic != 0xcafebabe)
+            throwUnexpected();
+        uint16_t major, minor, cpc;
+        b.read(major);
+        b.read(minor);
+        b.read(cpc);
+        constOffsets = new unsigned[cpc];
+        constOffsets[0] = 0;
+        for (int i = 0; i < cpc-1; i++)  // There are only cpc-1 entries, for reasons best known to the java designers
+        {
+            constOffsets[i+1] = b.getPos();
+            byte tag;
+            b.read(tag);
+            switch (tag)
+            {
+            case CONSTANT_Class:
+                uint16_t idx;
+                b.read(idx);
+#ifdef TRACE_CLASSFILE
+                DBGLOG("%u: Class %u", i+1, idx);
+#endif
+                break;
+            case CONSTANT_Fieldref:
+            case CONSTANT_Methodref:
+            case CONSTANT_InterfaceMethodref:
+                uint16_t classIdx;
+                uint16_t nametypeIdx;
+                b.read(classIdx);
+                b.read(nametypeIdx);
+#ifdef TRACE_CLASSFILE
+                DBGLOG("%u: ref(%u) class %u nametype %u", i+1, tag, classIdx, nametypeIdx);
+#endif
+                break;
+            case CONSTANT_String:
+#ifdef TRACE_CLASSFILE
+                DBGLOG("%u: Tag %u", i+1, tag);
+#endif
+                b.skip(2);
+                break;
+            case CONSTANT_Integer:
+            case CONSTANT_Float:
+#ifdef TRACE_CLASSFILE
+                DBGLOG("%u: Tag %u", i+1, tag);
+#endif
+                b.skip(4);
+                break;
+            case CONSTANT_Long:
+            case CONSTANT_Double:
+#ifdef TRACE_CLASSFILE
+                DBGLOG("%u: Tag %u", i+1, tag);
+#endif
+                b.skip(8);
+                break;
+            case CONSTANT_NameAndType:
+                uint16_t nameIdx;
+                uint16_t descIdx;
+                b.read(nameIdx);
+                b.read(descIdx);
+#ifdef TRACE_CLASSFILE
+                DBGLOG("%u: NameAndType(%u) name %u desc %u", i+1, tag, nameIdx, descIdx);
+#endif
+                break;
+            case CONSTANT_Utf8:
+                // length-prefixed
+                uint16_t length;
+                b.read(length);
+                const byte *val;
+                val = b.readDirect(length);
+#ifdef TRACE_CLASSFILE
+                DBGLOG("%u: %.*s", i+1, length, val);
+#endif
+                break;
+            case CONSTANT_MethodHandle:
+#ifdef TRACE_CLASSFILE
+                DBGLOG("%u: Tag %u", i+1, tag);
+#endif
+                b.skip(3);
+                break;
+            case CONSTANT_MethodType:
+#ifdef TRACE_CLASSFILE
+                DBGLOG("%u: Tag %u", i+1, tag);
+#endif
+                b.skip(2);
+                break;
+            case CONSTANT_InvokeDynamic:
+#ifdef TRACE_CLASSFILE
+                DBGLOG("%u: Tag %u", i+1, tag);
+#endif
+                b.skip(4);
+                break;
+            default:
+                DBGLOG("Unexpected tag %u reading bytecode file", tag);
+                throwUnexpected();
+            }
+        }
+        uint16_t access_flags; b.read(access_flags);
+        uint16_t this_class; b.read(this_class);
+        uint16_t super_class; b.read(super_class);
+        uint16_t interfaces_count; b.read(interfaces_count);
+        b.skip(interfaces_count*sizeof(uint16_t));
+        uint16_t fields_count; b.read(fields_count);
+#ifdef TRACE_CLASSFILE
+        DBGLOG("Access flags %x this_class=%u super_class=%u interfaces_count=%u fields_count=%u", access_flags, this_class, super_class, interfaces_count, fields_count);
+#endif
+        for (unsigned i = 0; i < fields_count; i++)
+        {
+            b.skip(6);
+            uint16_t attr_count;
+            b.read(attr_count);
+            for (unsigned j = 0; j < attr_count; j++)
+            {
+                b.skip(2);
+                uint32_t attr_length;
+                b.read(attr_length);
+                b.skip(attr_length);
+            }
+        }
+        uint16_t methods_count; b.read(methods_count);
+#ifdef TRACE_CLASSFILE
+        DBGLOG("methods_count %u", methods_count);
+#endif
+        for (unsigned i = 0; i < methods_count; i++)
+        {
+            uint16_t flags; b.read(flags);
+            uint16_t name; b.read(name);
+            uint16_t desc; b.read(desc);
+#ifdef TRACE_CLASSFILE
+            DBGLOG("Method %u name %u desc %u flags %x", i, name, desc, flags);
+#endif
+            if ((flags & (ACC_PUBLIC|ACC_STATIC)) == (ACC_PUBLIC|ACC_STATIC))
+            {
+                StringAttr thisName;
+                readUtf(thisName, name);
+                if (!streq(thisName, "<init>"))
+                {
+                    StringAttr thisSig;
+                    readUtf(thisSig, desc);
+                    methodNames.append(thisName);
+                    methodSigs.append(thisSig);
+                }
+            }
+            uint16_t attr_count;
+            b.read(attr_count);
+            for (unsigned j = 0; j < attr_count; j++)
+            {
+                b.skip(2);
+                uint32_t attr_length;
+                b.read(attr_length);
+                b.skip(attr_length);
+            }
+        }
+        /* Don't bother reading attributes as they are not really interesting to us
+        uint16_t attributes_count; b.read(attributes_count);
+#ifdef TRACE_CLASSFILE
+        DBGLOG("attributes_count %u", attributes_count);
+#endif
+        for (unsigned i = 0; i < attributes_count; i++)
+        {
+            b.skip(2);
+            uint32_t attr_length;
+            b.read(attr_length);
+            b.skip(attr_length);
+        }
+#ifdef TRACE_CLASSFILE
+        DBGLOG("%u of %u bytes remaining", b.remaining(), b.length());
+#endif
+        */
+        // Now we can find this class name
+        readTag(this_class, CONSTANT_Class);
+        readUtf(className, readIdx());
+    }
+    ~JavaClassReader()
+    {
+        delete [] constOffsets;
+    }
+    StringBuffer & getSignature(StringBuffer &ret, unsigned idx)
+    {
+        if (!methodNames.isItem(idx))
+            throw makeStringException(0, "No public static method found");
+        return ret.appendf("%s.%s:%s", className.get(), methodNames.item(idx), methodSigs.item(idx));
+    }
+    MemoryBuffer &getEmbedData(MemoryBuffer &result, bool mainClass)
+    {
+        if (mainClass && methodNames.length() != 1)
+        {
+            StringBuffer err;
+            ForEachItemIn(idx, methodNames)
+            {
+                if (idx)
+                    err.append(", ");
+                if (idx == 5)
+                {
+                    err.append("...");
+                    break;
+                }
+                else
+                    err.append(methodNames[idx]);
+            }
+            if (err)
+                throw makeStringExceptionV(0, "Embedded java should export exactly one public static method (%s seen)", err.str());
+            else
+                throw makeStringException(0, "Embedded java did not export any public static methods");
+        }
+        result.setEndian(__BIG_ENDIAN);
+        StringBuffer signature;
+        if (mainClass)
+            getSignature(signature, 0);
+        else
+            signature.set(className);
+        result.append((size32_t) signature.length());
+        result.append(signature.length(), signature.str());
+        result.append((size32_t) b.length());
+        result.append(b);
+        return result;
+    }
+private:
+    uint16_t readIdx()
+    {
+        uint16_t idx;
+        b.read(idx);
+        return idx;
+    }
+    void readTag(unsigned idx, byte expected)
+    {
+        b.reset(constOffsets[idx]);
+        byte tag;
+        b.read(tag);
+        assertex(tag == expected);
+    }
+    void readUtf(StringAttr &dest, unsigned idx)
+    {
+        auto savepos = b.getPos();
+        readTag(idx, CONSTANT_Utf8);
+        uint16_t length;
+        b.read(length);
+        dest.set((const char *) b.readDirect(length), length);
+        b.reset(savepos);
+    }
+    enum const_type
+    {
+        CONSTANT_Class = 7,
+        CONSTANT_Fieldref = 9,
+        CONSTANT_Methodref = 10,
+        CONSTANT_InterfaceMethodref = 11,
+        CONSTANT_String = 8,
+        CONSTANT_Integer = 3,
+        CONSTANT_Float = 4,
+        CONSTANT_Long = 5,
+        CONSTANT_Double = 6,
+        CONSTANT_NameAndType = 12,
+        CONSTANT_Utf8 = 1,
+        CONSTANT_MethodHandle = 15,
+        CONSTANT_MethodType = 16,
+        CONSTANT_InvokeDynamic = 18
+    };
+    enum access_flag : uint16_t
+    {
+        ACC_PUBLIC    = 0x0001, //  Declared public; may be accessed from outside its package.
+        ACC_PRIVATE   = 0x0002, //  Declared private; accessible only within the defining class.
+        ACC_PROTECTED = 0x0004, //  Declared protected; may be accessed within subclasses.
+        ACC_STATIC    = 0x0008, //  Declared static.
+        ACC_FINAL     = 0x0010, //  Declared final; must not be overridden (§5.4.5).
+        ACC_SYNCHRONIZED = 0x0020, //  Declared synchronized; invocation is wrapped by a monitor use.
+        ACC_BRIDGE    = 0x0040, //  A bridge method, generated by the compiler.
+        ACC_VARARGS   = 0x0080, //  Declared with variable number of arguments.
+        ACC_NATIVE    = 0x0100, //  Declared native; implemented in a language other than Java.
+        ACC_ABSTRACT  = 0x0400, //  Declared abstract; no implementation is provided.
+        ACC_STRICT    = 0x0800, //  Declared strictfp; floating-point mode is FP-strict.
+        ACC_SYNTHETIC = 0x1000, //  Declared synthetic; not present in the source code.
+    };
+    MemoryBuffer b;
+    unsigned *constOffsets = nullptr;
+    StringAttr className;
+    StringArray methodNames;
+    StringArray methodSigs;
+};
 class JavaEmbedImportContext : public CInterfaceOf<IEmbedFunctionContext>
 {
 public:
@@ -2939,7 +3266,7 @@ public:
         const RtlTypeInfo *typeInfo = metaVal.queryTypeInfo();
         assertex(typeInfo);
         RtlFieldStrInfo dummyField("<row>", NULL, typeInfo);
-        JavaObjectBuilder javaBuilder(sharedCtx->JNIenv, &dummyField, className);
+        JavaObjectBuilder javaBuilder(sharedCtx->JNIenv, &dummyField, sharedCtx->loadClass(className));
         typeInfo->process(val, val, &dummyField, javaBuilder); // Creates a java object from the incoming ECL row
         jvalue v;
         v.l = javaBuilder.getObject();
@@ -3009,7 +3336,8 @@ public:
             const RtlTypeInfo *typeInfo = metaVal.queryTypeInfo();
             assertex(typeInfo);
             RtlFieldStrInfo dummyField("<row>", NULL, typeInfo);
-            JavaObjectBuilder javaBuilder(sharedCtx->JNIenv, &dummyField, className);
+            jclass Class = sharedCtx->loadClass(className);
+            JavaObjectBuilder javaBuilder(sharedCtx->JNIenv, &dummyField, Class);
             for (;;)
             {
                 roxiemem::OwnedConstRoxieRow thisRow = val->ungroupedNextRow();
@@ -3019,7 +3347,7 @@ public:
                 typeInfo->process(brow, brow, &dummyField, javaBuilder); // Creates a java object from the incoming ECL row
                 allRows.append(javaBuilder.getObject());
             }
-            jobjectArray array = sharedCtx->JNIenv->NewObjectArray(allRows.length(), sharedCtx->JNIenv->FindClass(className), NULL);
+            jobjectArray array = sharedCtx->JNIenv->NewObjectArray(allRows.length(), Class, NULL);
             ForEachItemIn(idx, allRows)
             {
                 sharedCtx->JNIenv->SetObjectArrayElement(array, idx, allRows.item(idx));
@@ -3037,7 +3365,7 @@ public:
             sharedCtx->checkException();
             jvalue param;
             const RtlTypeInfo *typeInfo = metaVal.queryTypeInfo();
-            ECLDatasetIterator *iterator = new ECLDatasetIterator(sharedCtx->JNIenv, typeInfo, className, val);
+            ECLDatasetIterator *iterator = new ECLDatasetIterator(sharedCtx->JNIenv, typeInfo, sharedCtx->loadClass(className), val);
             param.j = (jlong) iterator;
             iterators.append(*iterator);
             jobject proxy = sharedCtx->JNIenv->NewObject(proxyClass, constructor, param, sharedCtx->JNIenv->NewStringUTF(helperLibraryName));
@@ -3052,7 +3380,7 @@ public:
     }
     virtual void importFunction(size32_t lenChars, const char *utf)
     {
-        sharedCtx->importFunction(lenChars, utf, classpath, instance);
+        sharedCtx->importFunction(lenChars, utf, classpath, 0, nullptr, instance);
         argsig = sharedCtx->querySignature();
         assertex(*argsig == '(');
         argsig++;
@@ -3067,10 +3395,23 @@ public:
             sharedCtx->callFunction(result, args);
     }
 
-    virtual void compileEmbeddedScript(size32_t lenChars, const char *script)
+    virtual void compileEmbeddedScript(size32_t lenChars, const char *_script)
     {
-        throwUnexpected();  // The java language helper supports only imported functions, not embedding java code in ECL.
+        throwUnexpected();
     }
+    virtual void loadCompiledScript(size32_t bytecodeLen, const void *bytecode) override
+    {
+        MemoryBuffer b;
+        b.setBuffer(bytecodeLen, (void *) bytecode, false);
+        b.setEndian(__BIG_ENDIAN);
+        uint32_t siglen; b.read(siglen);
+        const char *sig = (const char *) b.readDirect(siglen);
+        sharedCtx->importFunction(siglen, sig, classpath, bytecodeLen, (const byte *) bytecode, instance);
+        argsig = sharedCtx->querySignature();
+        assertex(*argsig == '(');
+        argsig++;
+    }
+
 protected:
     JavaThreadContext *sharedCtx;
     jvalue result;
@@ -3238,7 +3579,6 @@ public:
     }
     virtual IEmbedFunctionContext *createFunctionContextEx(ICodeContext * ctx, const IThorActivityContext *activityCtx, unsigned flags, const char *options) override
     {
-        assertex(flags & EFimport);
         return new JavaEmbedImportContext(queryContext(), NULL, options);
     }
     virtual IEmbedServiceContext *createServiceContext(const char *service, unsigned flags, const char *options) override
@@ -3254,6 +3594,227 @@ extern DECL_EXPORT IEmbedContext* getEmbedContext()
     return new JavaEmbedContext;
 }
 
+static bool isValidIdentifier(const char *source)
+{
+    return isalnum(*source) || *source=='_' || *source=='$' || ::readUtf8Size(source)>1;   // This is not strictly accurate but probably good enough
+}
+
+static bool isFullClassFile(StringBuffer &className, bool &seenPublic, size32_t len, const char *source)
+{
+    // A heuristic to determine whether the supplied embedded source is a full class file or just a single method
+    // Basically, if we see keyword "class" before we see { then we assume it's a full file
+    // Also track whether the public keyword has been supplied
+    bool inLineComment = false;
+    bool inBlockComment = false;
+    seenPublic = false;
+    while (len)
+    {
+        if (inLineComment)
+        {
+            if (*source=='\n')
+                inLineComment = false;
+        }
+        else if (inBlockComment)
+        {
+            if (*source=='*' && len > 1 && source[1]=='/')
+            {
+                inBlockComment = false;
+                len--;
+                source++;
+            }
+        }
+        else switch(*source)
+        {
+        case '/':
+            if (len > 1)
+            {
+                if (source[1]=='*')
+                {
+                    inBlockComment = true;
+                    len--;
+                    source++;
+                }
+                else if (source[1]=='/')
+                    inLineComment = true;
+            }
+            break;
+        case '{':
+            return false;
+        default:
+            if (isValidIdentifier(source))
+            {
+                const char *start = source;
+                while (len && isValidIdentifier(source))
+                {
+                    source+=::readUtf8Size(source);
+                    len--;
+                }
+                if (source-start == 5 && memcmp(start, "class", source-start)==0)
+                {
+                    while (len && isspace(*source)) // MORE - a comment between the keyword and the classname will fail - tough.
+                    {
+                        source += ::readUtf8Size(source);
+                        len--;
+                    }
+                    start = source;
+                    while (len && isValidIdentifier(source))
+                    {
+                        source += ::readUtf8Size(source);
+                        len--;
+                    }
+                    className.append(source-start, start);
+                    return true;
+
+                }
+                else if (source-start == 6 && memcmp(start, "public", source-start)==0)
+                    seenPublic = true;
+            }
+            break;
+        }
+        source += ::readUtf8Size(source);
+        len--;
+    }
+    // If we get here then it doesn't have a { at all - we COULD say it needs the prototype too but for now, who knows...
+    return false;
+}
+
+static StringBuffer & cleanupJavaError(StringBuffer &ret, const char *err, unsigned lineNumberOffset)
+{
+    // Remove filename (as it's generated) and fix up line number. Skip errors that do not have line number in
+    const char *colon = strchr(err, ':');
+    if (colon && isdigit(colon[1]))
+    {
+        char *end;
+        unsigned lineno = strtoul(colon+1, &end, 10) - lineNumberOffset;
+        ret.appendf("(%u,1)%s", lineno, end);
+    }
+    return ret;
+}
+
+static void cleanupJavaErrors(StringBuffer &errors, unsigned lineNumberOffset)
+{
+    StringArray errlines;
+    errlines.appendList(errors, "\n");
+    errors.clear();
+    ForEachItemIn(idx, errlines)
+    {
+        StringBuffer cleaned;
+        cleanupJavaError(cleaned, errlines.item(idx), lineNumberOffset);
+        if (cleaned.length())
+            errors.append(cleaned).append('\n');
+    }
+}
+
+static thread_local unsigned prevHash = 0;
+static thread_local MemoryBuffer prevCompile;
+
+extern DECL_EXPORT void precompile(size32_t & __lenResult,void * & __result,size32_t charsBody,const char * body)
+{
+    unsigned sizeBody = rtlUtf8Size(charsBody, body);  // size in bytes
+    unsigned hash = rtlHash32Data(sizeBody,body,0xcafebabe);
+    if (hash==prevHash)  // Reusing result from the syntax check that normally immediately precedes a precompile
+    {
+        __lenResult = prevCompile.length();
+        __result = prevCompile.detachOwn();
+        prevHash = 0;
+        return;
+    }
+    StringBuffer tmpDirName;
+    getTempFilePath(tmpDirName, "javaembed", nullptr);
+    tmpDirName.append(PATHSEPCHAR).append("tmp.XXXXXX");
+    if (!mkdtemp((char *) tmpDirName.str()))
+        throw makeStringExceptionV(0, "Failed to create temporary directory %s (error %d)", tmpDirName.str(), errno);
+    Owned<IFile> tempDir = createIFile(tmpDirName);
+    StringBuffer classname;
+    bool seenPublic = false;
+    bool isFullClass = isFullClassFile(classname, seenPublic, charsBody, body);  // note - we pass in length in characters, not bytes
+    if (!isFullClass)
+        classname.set("embed");
+
+    VStringBuffer javafile("%s" PATHSEPSTR "%s.java", tmpDirName.str(), classname.str());
+    FILE *source = fopen(javafile.str(), "wt");
+    fprintf(source, "package com.HPCCSystems.embed.x%x;\n", hash);
+    unsigned lineNumberOffset = 1;  // for the /n above
+    if (isFullClass)
+        fprintf(source, "%.*s", sizeBody, body);
+    else
+    {
+        if (seenPublic)
+            fprintf(source, "public class embed\n{\n  %.*s\n}", sizeBody, body);
+        else
+            fprintf(source, "public class embed\n{\n  public static %.*s\n}", sizeBody, body);
+        lineNumberOffset += 2;  // for the /n's above
+    }
+    fclose(source);
+
+    MemoryBuffer result;
+    Owned<IPipeProcess> pipe = createPipeProcess();
+    VStringBuffer javac("javac %s", javafile.str());
+    if (!pipe->run("javac", javac, tmpDirName, false, false, true, 0, false))
+    {
+        throw makeStringException(0, "Failed to run javac");
+    }
+    else
+    {
+        StringBuffer errors;
+        Owned<ISimpleReadStream> pipeReader = pipe->getErrorStream();
+        readSimpleStream(errors, *pipeReader);
+        pipe->closeError();
+        unsigned retcode = pipe->wait();
+        if (retcode)
+        {
+            if (errors.length())
+            {
+                DBGLOG("javaembed: %s", errors.str());
+                cleanupJavaErrors(errors, lineNumberOffset);
+                throw makeStringExceptionV(0, "%s", errors.str());
+            }
+            else
+                throw makeStringException(0, "Failed to precompile java code");
+        }
+        VStringBuffer mainfile("%s" PATHSEPSTR "%s.class", tmpDirName.str(), classname.str());
+        JavaClassReader reader(mainfile);
+        reader.getEmbedData(result, true);
+        removeFileTraceIfFail(mainfile);
+        // Now read nested classes
+        Owned<IDirectoryIterator> classFiles = tempDir->directoryFiles("*$*.class",false,false);
+        ForEach(*classFiles)
+        {
+            const char *thisFile = classFiles->query().queryFilename();
+            JavaClassReader reader(thisFile);
+            reader.getEmbedData(result, false);
+            removeFileTraceIfFail(thisFile);
+        }
+    }
+    removeFileTraceIfFail(javafile);
+    tempDir->remove();
+    __lenResult = result.length();
+    __result = result.detachOwn();
+}
+
+extern DECL_EXPORT void syntaxCheck(size32_t & __lenResult,char * & __result,size32_t charsBody,const char * body)
+{
+    StringBuffer result;
+    try
+    {
+        size32_t ds;
+        rtlDataAttr d;
+        precompile(ds, d.refdata(), charsBody, body);
+        // Reuse result in the precompile that normally immediately follows
+        unsigned sizeBody = rtlUtf8Size(charsBody, body);  // size in bytes
+        prevHash = rtlHash32Data(sizeBody,body,0xcafebabe);
+        prevCompile.setBuffer(ds, d.detachdata(), true);
+    }
+    catch (IException *E)
+    {
+        StringBuffer msg;
+        result.append(E->errorMessage(msg));
+        E->Release();
+    }
+    __lenResult = result.length();
+    __result = result.detach();
+}
+
 } // namespace
 
 // Callbacks from java
@@ -3261,6 +3822,7 @@ extern DECL_EXPORT IEmbedContext* getEmbedContext()
 extern "C" {
 JNIEXPORT jboolean JNICALL Java_com_HPCCSystems_HpccUtils__1hasNext (JNIEnv *, jclass, jlong);
 JNIEXPORT jobject JNICALL Java_com_HPCCSystems_HpccUtils__1next (JNIEnv *, jclass, jlong);
+JNIEXPORT jclass JNICALL Java_com_HPCCSystems_HpccClassLoader_defineClassForEmbed(JNIEnv *env, jobject loader, jint bytecodeLen, jlong bytecode, jstring name);
 }
 
 JNIEXPORT jboolean JNICALL Java_com_HPCCSystems_HpccUtils__1hasNext (JNIEnv *JNIenv, jclass, jlong proxy)
@@ -3301,6 +3863,34 @@ JNIEXPORT jobject JNICALL Java_com_HPCCSystems_HpccUtils__1next (JNIEnv *JNIenv,
     }
 }
 
+JNIEXPORT jclass JNICALL Java_com_HPCCSystems_HpccClassLoader_defineClassForEmbed(JNIEnv *env, jobject loader, jint datalen, jlong data, jstring name)
+{
+    const char *nameChars = env->GetStringUTFChars(name, nullptr);
+    size32_t namelen = strlen(nameChars);
+    MemoryBuffer b;
+    b.setBuffer(datalen, (void *) data, false);
+    b.setEndian(__BIG_ENDIAN);
+    jclass ret = nullptr;
+    while (b.remaining())
+    {
+        uint32_t siglen; b.read(siglen);
+        const char *sig = (const char *) b.readDirect(siglen);
+        uint32_t bytecodeLen; b.read(bytecodeLen);
+        const jbyte * bytecode = (const jbyte *) b.readDirect(bytecodeLen);
+        if (siglen >= namelen && memcmp(sig, nameChars, namelen)==0 && (namelen == siglen || sig[namelen] == '.'))
+        {
+#ifdef TRACE_CLASSFILE
+            DBGLOG("javaembed: loading class %s", nameChars);
+#endif
+            ret = env->DefineClass(nameChars, loader, bytecode, bytecodeLen);
+            break;
+        }
+    }
+    env->ReleaseStringUTFChars(name, nameChars);
+    return ret;
+
+}
+
 // Used for dynamically loading in ESDL
 
 extern "C" DECL_EXPORT IEmbedContext *getEmbedContextDynamic()

+ 4 - 0
plugins/mysql/mysqlembed.cpp

@@ -1632,6 +1632,10 @@ public:
         if (stmtInfo->queryResultBindings().numColumns() == 0)
             lazyExecute();
     }
+    virtual void loadCompiledScript(size32_t chars, const void *_script) override
+    {
+        throwUnexpected();
+    }
 protected:
     void lazyExecute()
     {

+ 8 - 0
plugins/py3embed/py3embed.cpp

@@ -1675,6 +1675,10 @@ public:
     {
         script.setown(sharedCtx->compileEmbeddedScript(lenChars, utf, argstring));
     }
+    virtual void loadCompiledScript(size32_t chars, const void *_script) override
+    {
+        throwUnexpected();
+    }
     virtual void setActivityOptions(const IThorActivityContext *ctx) override
     {
         Python3xEmbedContextBase::setActivityOptions(ctx);
@@ -1739,6 +1743,10 @@ public:
     {
         throwUnexpected();
     }
+    virtual void loadCompiledScript(size32_t chars, const void *_script) override
+    {
+        throwUnexpected();
+    }
     virtual void callFunction()
     {
         result.setown(PyObject_CallObject(script, args));

+ 8 - 0
plugins/pyembed/pyembed.cpp

@@ -1652,6 +1652,10 @@ public:
     {
         script.setown(sharedCtx->compileEmbeddedScript(lenChars, utf, argstring));
     }
+    virtual void loadCompiledScript(size32_t chars, const void *_script) override
+    {
+        throwUnexpected();
+    }
     virtual void setActivityOptions(const IThorActivityContext *ctx) override
     {
         Python27EmbedContextBase::setActivityOptions(ctx);
@@ -1717,6 +1721,10 @@ public:
     {
         throwUnexpected();
     }
+    virtual void loadCompiledScript(size32_t chars, const void *_script) override
+    {
+        throwUnexpected();
+    }
     virtual void callFunction()
     {
         result.setown(PyObject_CallObject(script, args));

+ 4 - 0
plugins/sqlite3/sqlite3.cpp

@@ -566,6 +566,10 @@ public:
                 checkSqliteError(rc);
         }
     }
+    virtual void loadCompiledScript(size32_t chars, const void *script) override
+    {
+        throwUnexpected();
+    }
 protected:
     sqlite3_value *getScalarResult()
     {

+ 4 - 0
plugins/v8embed/v8embed.cpp

@@ -892,6 +892,10 @@ public:
         v8::Handle<v8::Script> lscript = v8::Script::Compile(source);
         script = v8::Persistent<v8::Script>::New(lscript);
     }
+    virtual void loadCompiledScript(size32_t chars, const void *_script) override
+    {
+        throwUnexpected();
+    }
     virtual void importFunction(size32_t lenChars, const char *utf)
     {
         UNIMPLEMENTED; // Not sure if meaningful for js

+ 1 - 0
rtl/eclrtl/eclrtl.hpp

@@ -842,6 +842,7 @@ interface IEmbedFunctionContext : extends IInterface
     virtual IInterface *bindParamWriter(IInterface *esdl, const char *esdlservice, const char *esdltype, const char *name)=0;
     virtual void paramWriterCommit(IInterface *writer)=0;
     virtual void writeResult(IInterface *esdl, const char *esdlservice, const char *esdltype, IInterface *writer)=0;
+    virtual void loadCompiledScript(size32_t len, const void *script) = 0;
 };
 
 interface IEmbedServiceContext : extends IInterface

+ 1 - 0
system/jlib/jexcept.hpp

@@ -208,6 +208,7 @@ enum WarnErrorCategory
     CategoryCpp,        // Warning passed through from C++ compiler
     CategorySecurity,   // Security warnings - operations that will be refused at codegen time unless unused.
     CategoryDFS,        // DFS resolution issues - field translation may not have occurred even though requested
+    CategoryEmbed,      // Warning passed through from embedded code syntax check
 
     CategoryError,      // Typically severity fatal
     CategoryAll,

+ 209 - 0
testing/regress/ecl/javaembed.ecl

@@ -0,0 +1,209 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2014 HPCC Systems.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+############################################################################## */
+
+//class=embedded
+//class=3rdparty
+
+import java;
+string jcat(string a, string b) := EMBED(java)
+  public static String cat(String a, String b)
+  {
+    return a + b;
+  }
+ENDEMBED;
+
+integer jadd(integer a, integer b) := EMBED(java)
+  public static int add(int a, int b)
+  {
+    return a + b;
+  }
+ENDEMBED;
+integer jaddl(integer a, integer b) := EMBED(java)
+  public static long addL(int a, int b)
+  {
+    return a + b;
+  }
+ENDEMBED;
+integer jaddi(integer a, integer b) := EMBED(java)
+  public static Integer addI(int a, int b)
+  {
+    return a + b;
+  }
+ENDEMBED;
+
+nrec := record
+  utf8 ufield;
+end;
+
+jret := RECORD
+  boolean bfield;
+  integer4 ifield;
+  integer8 lfield;
+  real8 dfield;
+  real4 ffield;
+  string1 cfield1;
+  string1 cfield2;
+  string sfield;
+  nrec n;
+  set of boolean bset;
+  set of data dset;
+  set of string sset;
+  LINKCOUNTED DATASET(nrec) sub;
+end;
+
+DATASET(jret) passDataset2(LINKCOUNTED DATASET(jret) d) := EMBED(java)
+import java.util.*;
+public class myClass {
+  public static class MyRecord {
+
+    public static class NestedClass
+    {
+      String ufield;
+      public NestedClass(String s)
+      {
+        ufield = s;
+      }
+      public NestedClass()
+      {
+      }
+    };
+
+    boolean bfield;
+    int ifield;
+    long lfield;
+    double dfield;
+    float ffield;
+    String sfield;
+    char cfield1;
+    String cfield2;
+    NestedClass n;
+    boolean bset[];
+    byte [] dset[];
+    String sset[];
+    NestedClass sub[];
+
+    public MyRecord(boolean b, int i, double d)
+    {
+      bfield = b;
+      ifield = i;
+      lfield = i * 100000000;
+      dfield = d;
+      ffield = (float) d;
+      sfield = "Yoohoo";
+      cfield1 = 'X';
+      cfield2 = "Z";
+      n = new NestedClass("nest");
+      bset = new boolean [5];
+      bset[3] = b;
+      dset = new byte[1][];
+      dset[0] = new byte[1];
+      dset[0][0] = 14;
+      sset = new String[1];
+      sset[0] = "Hello";
+      sub = new NestedClass[1];
+      sub[0] = new NestedClass("subnest");
+    }
+    public MyRecord()  // This will be called to construct objects being passed in from ECL
+    {
+      n = new NestedClass("nest2");
+    }
+  };
+  public static Iterator<MyRecord> passDataset2(MyRecord d[])
+  {
+    return Arrays.asList(d).iterator();
+  }
+}
+ENDEMBED;
+
+ds := DATASET(
+  [
+     {true, 1,2,3,4,'a', 'b', 'cd', u'ef', [true,false], [], ['Hello from ECL'], [{'1'},{'2'},{'3'},{'4'},{'5'}]}
+    ,{true, 2,4,3,4,'a', 'b', 'cd', u'ef', [true,false], [], [], []}
+    ,{true, 3,6,3,4,'a', 'b', 'cd', u'ef', [true,false], [], [], []}
+    ,{true, 8,8,3,4,'a', 'b', 'cd', u'ef', [true,false], [d'AA55'], [], []}
+  ], jret);
+
+transform(jret) testTransform(jret in, integer lim) := EMBED(java)
+public class myClass {
+  public static class MyRecord {
+
+    public static class NestedClass
+    {
+      String ufield;
+      public NestedClass(String s)
+      {
+        ufield = s;
+      }
+      public NestedClass()
+      {
+      }
+    };
+
+    boolean bfield;
+    int ifield;
+    long lfield;
+    double dfield;
+    float ffield;
+    String sfield;
+    char cfield1;
+    String cfield2;
+    NestedClass n;
+    boolean bset[];
+    byte [] dset[];
+    String sset[];
+    NestedClass sub[];
+
+    public MyRecord(boolean b, int i, double d)
+    {
+      bfield = b;
+      ifield = i;
+      lfield = i * 100000000;
+      dfield = d;
+      ffield = (float) d;
+      sfield = "Yoohoo";
+      cfield1 = 'X';
+      cfield2 = "Z";
+      n = new NestedClass("nest");
+      bset = new boolean [5];
+      bset[3] = b;
+      dset = new byte[1][];
+      dset[0] = new byte[1];
+      dset[0][0] = 14;
+      sset = new String[1];
+      sset[0] = "Hello";
+      sub = new NestedClass[1];
+      sub[0] = new NestedClass("subnest");
+    }
+    public MyRecord()  // This will be called to construct objects being passed in from ECL
+    {
+      n = new NestedClass("nest2");
+    }
+  };
+  public static MyRecord transform(MyRecord in, int lim)
+  {
+    return new MyRecord(in.bfield, lim, in.dfield);
+  }
+}
+ENDEMBED;
+
+jcat('Hello, ', 'Java');
+jadd(1,2);
+jaddl(3,4);
+jaddi(5,6);
+
+output(passDataset2(ds));
+output(project(ds, testTransform(LEFT, COUNTER)));

+ 24 - 0
testing/regress/ecl/key/javaembed.xml

@@ -0,0 +1,24 @@
+<Dataset name='Result 1'>
+ <Row><Result_1>Hello, Java</Result_1></Row>
+</Dataset>
+<Dataset name='Result 2'>
+ <Row><Result_2>3</Result_2></Row>
+</Dataset>
+<Dataset name='Result 3'>
+ <Row><Result_3>7</Result_3></Row>
+</Dataset>
+<Dataset name='Result 4'>
+ <Row><Result_4>11</Result_4></Row>
+</Dataset>
+<Dataset name='Result 5'>
+ <Row><bfield>true</bfield><ifield>1</ifield><lfield>2</lfield><dfield>3.0</dfield><ffield>4.0</ffield><cfield1>a</cfield1><cfield2>b</cfield2><sfield>cd</sfield><n><ufield>ef</ufield></n><bset><Item>true</Item><Item>false</Item></bset><dset></dset><sset><Item>Hello from ECL</Item></sset><sub><Row><ufield>1</ufield></Row><Row><ufield>2</ufield></Row><Row><ufield>3</ufield></Row><Row><ufield>4</ufield></Row><Row><ufield>5</ufield></Row></sub></Row>
+ <Row><bfield>true</bfield><ifield>2</ifield><lfield>4</lfield><dfield>3.0</dfield><ffield>4.0</ffield><cfield1>a</cfield1><cfield2>b</cfield2><sfield>cd</sfield><n><ufield>ef</ufield></n><bset><Item>true</Item><Item>false</Item></bset><dset></dset><sset></sset><sub></sub></Row>
+ <Row><bfield>true</bfield><ifield>3</ifield><lfield>6</lfield><dfield>3.0</dfield><ffield>4.0</ffield><cfield1>a</cfield1><cfield2>b</cfield2><sfield>cd</sfield><n><ufield>ef</ufield></n><bset><Item>true</Item><Item>false</Item></bset><dset></dset><sset></sset><sub></sub></Row>
+ <Row><bfield>true</bfield><ifield>8</ifield><lfield>8</lfield><dfield>3.0</dfield><ffield>4.0</ffield><cfield1>a</cfield1><cfield2>b</cfield2><sfield>cd</sfield><n><ufield>ef</ufield></n><bset><Item>true</Item><Item>false</Item></bset><dset><Item>41413535</Item></dset><sset></sset><sub></sub></Row>
+</Dataset>
+<Dataset name='Result 6'>
+ <Row><bfield>true</bfield><ifield>1</ifield><lfield>100000000</lfield><dfield>3.0</dfield><ffield>3.0</ffield><cfield1>X</cfield1><cfield2>Z</cfield2><sfield>Yoohoo</sfield><n><ufield>nest</ufield></n><bset><Item>false</Item><Item>false</Item><Item>false</Item><Item>true</Item><Item>false</Item></bset><dset><Item>0E</Item></dset><sset><Item>Hello</Item></sset><sub><Row><ufield>subnest</ufield></Row></sub></Row>
+ <Row><bfield>true</bfield><ifield>2</ifield><lfield>200000000</lfield><dfield>3.0</dfield><ffield>3.0</ffield><cfield1>X</cfield1><cfield2>Z</cfield2><sfield>Yoohoo</sfield><n><ufield>nest</ufield></n><bset><Item>false</Item><Item>false</Item><Item>false</Item><Item>true</Item><Item>false</Item></bset><dset><Item>0E</Item></dset><sset><Item>Hello</Item></sset><sub><Row><ufield>subnest</ufield></Row></sub></Row>
+ <Row><bfield>true</bfield><ifield>3</ifield><lfield>300000000</lfield><dfield>3.0</dfield><ffield>3.0</ffield><cfield1>X</cfield1><cfield2>Z</cfield2><sfield>Yoohoo</sfield><n><ufield>nest</ufield></n><bset><Item>false</Item><Item>false</Item><Item>false</Item><Item>true</Item><Item>false</Item></bset><dset><Item>0E</Item></dset><sset><Item>Hello</Item></sset><sub><Row><ufield>subnest</ufield></Row></sub></Row>
+ <Row><bfield>true</bfield><ifield>4</ifield><lfield>400000000</lfield><dfield>3.0</dfield><ffield>3.0</ffield><cfield1>X</cfield1><cfield2>Z</cfield2><sfield>Yoohoo</sfield><n><ufield>nest</ufield></n><bset><Item>false</Item><Item>false</Item><Item>false</Item><Item>true</Item><Item>false</Item></bset><dset><Item>0E</Item></dset><sset><Item>Hello</Item></sset><sub><Row><ufield>subnest</ufield></Row></sub></Row>
+</Dataset>