From 2c03b11f2727052f7380c72f20995d0c1aff741b Mon Sep 17 00:00:00 2001 From: Adam Childs Date: Sat, 4 Mar 2023 19:25:47 -0500 Subject: [PATCH] First pass at implementing ability to extend default serialization and provided new builder pattern for JythonScript object. No longer static method based. --- pom.xml | 14 +- .../github/adchilds/jython/JythonScript.java | 336 +++++++++-------- .../JythonScriptRuntimeException.java | 40 ++ .../AbstractPyObjectSerializer.java | 33 ++ .../DefaultPyObjectSerializationFactory.java | 47 +++ .../serialization/PyBooleanSerializer.java | 34 ++ .../serialization/PyDictionarySerializer.java | 50 +++ .../serialization/PyFloatSerializer.java | 34 ++ .../serialization/PyIntegerSerializer.java | 34 ++ .../serialization/PyListSerializer.java | 47 +++ .../serialization/PyLongSerializer.java | 30 ++ .../PyObjectSerializationFactory.java | 18 + .../jython/serialization/PySetSerializer.java | 46 +++ .../serialization/PyStringSerializer.java | 29 ++ .../jython/serialization/Serializer.java | 14 + .../com/github/adchilds/util/FileUtils.java | 8 +- .../adchilds/jython/JythonScriptTest.java | 343 ++++++++++-------- .../JythonResultNotFoundExceptionTest.java | 9 +- .../exception/JythonScriptExceptionTest.java | 7 +- .../JythonScriptNotFoundExceptionTest.java | 7 +- ...faultPyObjectSerializationFactoryTest.java | 48 +++ .../PyBooleanSerializerTest.java | 47 +++ .../serialization/PyFloatSerializerTest.java | 57 +++ .../PyIntegerSerializerTest.java | 55 +++ 24 files changed, 1055 insertions(+), 332 deletions(-) create mode 100644 src/main/java/com/github/adchilds/jython/exception/JythonScriptRuntimeException.java create mode 100644 src/main/java/com/github/adchilds/jython/serialization/AbstractPyObjectSerializer.java create mode 100644 src/main/java/com/github/adchilds/jython/serialization/DefaultPyObjectSerializationFactory.java create mode 100644 src/main/java/com/github/adchilds/jython/serialization/PyBooleanSerializer.java create mode 100644 src/main/java/com/github/adchilds/jython/serialization/PyDictionarySerializer.java create mode 100644 src/main/java/com/github/adchilds/jython/serialization/PyFloatSerializer.java create mode 100644 src/main/java/com/github/adchilds/jython/serialization/PyIntegerSerializer.java create mode 100644 src/main/java/com/github/adchilds/jython/serialization/PyListSerializer.java create mode 100644 src/main/java/com/github/adchilds/jython/serialization/PyLongSerializer.java create mode 100644 src/main/java/com/github/adchilds/jython/serialization/PyObjectSerializationFactory.java create mode 100644 src/main/java/com/github/adchilds/jython/serialization/PySetSerializer.java create mode 100644 src/main/java/com/github/adchilds/jython/serialization/PyStringSerializer.java create mode 100644 src/main/java/com/github/adchilds/jython/serialization/Serializer.java create mode 100644 src/test/java/com/github/adchilds/jython/serialization/DefaultPyObjectSerializationFactoryTest.java create mode 100644 src/test/java/com/github/adchilds/jython/serialization/PyBooleanSerializerTest.java create mode 100644 src/test/java/com/github/adchilds/jython/serialization/PyFloatSerializerTest.java create mode 100644 src/test/java/com/github/adchilds/jython/serialization/PyIntegerSerializerTest.java diff --git a/pom.xml b/pom.xml index cd7932b..fe4a292 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.github.adchilds jythonscript - 3.0-SNAPSHOT + 3.0.0-SNAPSHOT jar JythonScript @@ -51,9 +51,9 @@ 1.8 UTF-8 - 2.7.2 - 5.6.1 - 1.6.1 + 2.7.3 + 5.9.2 + 1.9.2 @@ -61,7 +61,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.11.0 ${java.version} ${java.version} @@ -94,7 +94,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.2.0 + 3.5.0 attach-javadocs @@ -132,7 +132,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.7 + 1.6.13 true ossrh diff --git a/src/main/java/com/github/adchilds/jython/JythonScript.java b/src/main/java/com/github/adchilds/jython/JythonScript.java index a71fc36..3ac980a 100644 --- a/src/main/java/com/github/adchilds/jython/JythonScript.java +++ b/src/main/java/com/github/adchilds/jython/JythonScript.java @@ -3,28 +3,32 @@ import com.github.adchilds.jython.exception.JythonResultNotFoundException; import com.github.adchilds.jython.exception.JythonScriptException; import com.github.adchilds.jython.exception.JythonScriptNotFoundException; +import com.github.adchilds.jython.serialization.DefaultPyObjectSerializationFactory; +import com.github.adchilds.jython.serialization.PyObjectSerializationFactory; import com.github.adchilds.util.FileUtils; import com.github.adchilds.util.StringUtils; -import org.python.core.*; +import org.python.core.Py; +import org.python.core.PyCode; +import org.python.core.PyObject; +import org.python.core.PySystemState; import org.python.util.PythonInterpreter; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; /** - * {@link JythonScript} provides an easy to use wrapper for executing and/or evaluating Python expressions or scripts - * for JVM-based languages. JythonScript packages the Jython standalone JAR as part of it's distribution, so no further + * {@link JythonScript} provides an easy-to-use wrapper for executing and/or evaluating Python expressions or scripts + * for JVM-based languages. JythonScript packages the Jython standalone JAR as part of its distribution, so no further * dependencies should be required. * + *
+ * * JythonScript operates on a couple of select criteria: *
    - *
  1. A Jython or Python script accessible by the JVM via a {@link String} file path, {@link File}, or {@link InputStream}
  2. + *
  3. A Jython or Python script accessible by the JVM via a {@link String} of compilable Jython, a {@link String} + * representing a file path, a {@link File}, or an {@link InputStream}
  4. *
  5. Optional arguments to be passed to the given script
  6. *
* @@ -73,28 +77,34 @@ * * For more information, follow the respective links to documentation: *
    - *
  • JythonScript - https://github.com/adchilds/JythonScript
  • - *
  • Python - https://www.python.org
  • - *
  • Jython - http://www.jython.org
  • + *
  • JythonScript
  • + *
  • Python
  • + *
  • Jython
  • *
* * @author Adam Childs - * @since 1.0 + * @since 1.0.0 */ public class JythonScript { private static final String EVALUATION_RESULT_LOCAL_VARIABLE = "result"; + private final PyObjectSerializationFactory serializationFactory; + + private JythonScript(final PyObjectSerializationFactory serializationFactory) { + this.serializationFactory = serializationFactory; + } + /** * Compiles the Jython script at the given {@code filePath} into a {@link PyCode} object. * * @param filePath the absolute path of the Jython file to compile * @return a compiled Jython script * @throws JythonScriptException when the given file path is empty or null - * @since 1.0 + * @since 1.0.0 */ - public static PyCode compile(final String filePath) throws JythonScriptException { - // Make sure the file path is is not null or empty + public PyCode compile(final String filePath) throws JythonScriptException { + // Make sure the file path is not null or empty if (StringUtils.isBlank(filePath)) { throw new JythonScriptException("Null or empty path is not a file."); } @@ -107,11 +117,11 @@ public static PyCode compile(final String filePath) throws JythonScriptException * Compiles the Jython script at the given {@code fileUrl} into a {@link PyCode} object. * * @param fileUrl the {@link URL} to a Jython file to compile - * @return a compile Jython script + * @return a compiled Jython script * @throws JythonScriptException when the given URL is empty, null, or not a valid path - * @since 2.0 + * @since 2.0.0 */ - public static PyCode compile(final URL fileUrl) throws JythonScriptException { + public PyCode compile(final URL fileUrl) throws JythonScriptException { if (fileUrl == null) { throw new JythonScriptException("Null path is not a URL."); } @@ -132,9 +142,9 @@ public static PyCode compile(final URL fileUrl) throws JythonScriptException { * @param file the Jython file to compile * @return a compiled Jython script * @throws JythonScriptException when the given file is null or is not a file (i.e. a directory) - * @since 1.0 + * @since 1.0.0 */ - public static PyCode compile(final File file) throws JythonScriptException { + public PyCode compile(final File file) throws JythonScriptException { if (file == null) { throw new JythonScriptException("Given file is null; cannot be compiled into PyCode."); } @@ -163,17 +173,15 @@ public static PyCode compile(final File file) throws JythonScriptException { * @param script the Jython script to compile (this should be actual Jython code represented as a {@link String}) * @return a compiled Jython script * @throws JythonScriptException when the given script is null or empty - * @since 2.0 + * @since 2.0.0 */ - public static PyCode compileString(final String script) throws JythonScriptException { + public PyCode compileString(final String script) throws JythonScriptException { if (StringUtils.isBlank(script)) { throw new JythonScriptException("Given script was null or empty; cannot be compiled into PyCode."); } - final PythonInterpreter interpreter = new PythonInterpreter(); - // Compile the script, returning the associated PyCode object - try { + try (final PythonInterpreter interpreter = new PythonInterpreter()) { return interpreter.compile(script); } catch (Exception e) { throw new JythonScriptException("Could not compile the given script.", e); @@ -190,9 +198,9 @@ public static PyCode compileString(final String script) throws JythonScriptExcep * @param args arguments to be passed to the script * @return the result from executing the given script * @throws JythonScriptException when the given file path is null, a directory, or cannot be found - * @since 1.0 + * @since 1.0.0 */ - public static Object evaluate(final String scriptPath, final Object... args) throws JythonScriptException { + public Object evaluate(final String scriptPath, final Object... args) throws JythonScriptException { // Ensure that the scriptRelativePath is not null or empty if (StringUtils.isBlank(scriptPath)) { throw new JythonScriptNotFoundException("File not found at path=[" + scriptPath + "]"); @@ -203,7 +211,8 @@ public static Object evaluate(final String scriptPath, final Object... args) thr try { inputStream = FileUtils.getFileInputStream(scriptPath); } catch (Exception e) { - throw new JythonScriptNotFoundException("Could not open Jython script from location=[" + scriptPath + "]", e); + throw new JythonScriptNotFoundException("Could not open Jython script from location=[" + scriptPath + + "]", e); } return evaluate(inputStream, args); @@ -219,9 +228,9 @@ public static Object evaluate(final String scriptPath, final Object... args) thr * @param args arguments to be passed to the script * @return the result from executing the given script * @throws JythonScriptException when the given script is null, a directory, or cannot be found - * @since 2.0 + * @since 2.0.0 */ - public static Object evaluate(final URL scriptUrl, final Object... args) throws JythonScriptException { + public Object evaluate(final URL scriptUrl, final Object... args) throws JythonScriptException { if (scriptUrl == null) { throw new JythonScriptException("Null path is not a URL."); } @@ -239,7 +248,8 @@ public static Object evaluate(final URL scriptUrl, final Object... args) throws try { inputStream = FileUtils.getFileInputStream(file); } catch (Exception e) { - throw new JythonScriptNotFoundException("Could not open Jython script from location=[" + scriptUrl.getPath() + "]", e); + throw new JythonScriptNotFoundException("Could not open Jython script from location=[" + + scriptUrl.getPath() + "]", e); } return evaluate(inputStream, args); @@ -254,9 +264,9 @@ public static Object evaluate(final URL scriptUrl, final Object... args) throws * @param args arguments to be passed to the script * @return the result from executing the given script * @throws JythonScriptException when the given file is null, a directory, or cannot be found - * @since 1.0 + * @since 1.0.0 */ - public static Object evaluate(final File scriptFile, final Object... args) throws JythonScriptException { + public Object evaluate(final File scriptFile, final Object... args) throws JythonScriptException { // Ensure that the script is not null if (scriptFile == null) { throw new JythonScriptNotFoundException("Could not open Jython script, the file was null."); @@ -267,7 +277,8 @@ public static Object evaluate(final File scriptFile, final Object... args) throw try { inputStream = FileUtils.getFileInputStream(scriptFile); } catch (Exception e) { - throw new JythonScriptNotFoundException("Could not open Jython script from location=[" + scriptFile.getAbsolutePath() + "]", e); + throw new JythonScriptNotFoundException("Could not open Jython script from location=[" + + scriptFile.getAbsolutePath() + "]", e); } // Execute the script @@ -282,21 +293,25 @@ public static Object evaluate(final File scriptFile, final Object... args) throw * @param inputStream the {@link InputStream} that represents the Jython script to be executed * @param args arguments to be passed to the script * @return the result from executing the given script - * @throws JythonScriptException when a script execution error occurs or when a local Python variable named 'result' is not found - * @since 1.0 + * @throws JythonScriptException when a script execution error occurs or when a local Python variable named 'result' + * is not found + * @since 1.0.0 */ - public static Object evaluate(final InputStream inputStream, final Object... args) throws JythonScriptException { + public Object evaluate(final InputStream inputStream, final Object... args) throws JythonScriptException { // Execute the script - final PythonInterpreter interpreter = executeWithState(inputStream, args); + final PyObject result; + try (final PythonInterpreter interpreter = executeWithState(inputStream, args)) { - // Obtain the value of a local variable named 'result' from the executed script - final PyObject result = interpreter.get(EVALUATION_RESULT_LOCAL_VARIABLE); + // Obtain the value of a local variable named 'result' from the executed script + result = interpreter.get(EVALUATION_RESULT_LOCAL_VARIABLE); + } if (result == null) { throw new JythonResultNotFoundException("Local variable 'result' not found during script execution."); } - return parseResult(result); + return serializationFactory.getSerializer(result) + .serialize(); } /** @@ -307,21 +322,63 @@ public static Object evaluate(final InputStream inputStream, final Object... arg * @param pyCode the compiled Jython script to evaluate * @param args arguments to be passed to the script * @return the result from executing the given script - * @throws JythonScriptException when a script execution error occurs or when a local Python variable named 'result' is not found - * @since 1.0 + * @throws JythonScriptException when a script execution error occurs or when a local Python variable named 'result' + * is not found + * @since 1.0.0 */ - public static Object evaluate(final PyCode pyCode, final Object... args) throws JythonScriptException { + public Object evaluate(final PyCode pyCode, final Object... args) throws JythonScriptException { // Execute the script - final PythonInterpreter interpreter = executeWithState(pyCode, args); + final PyObject result; + try (final PythonInterpreter interpreter = executeWithState(pyCode, args)) { - // Obtain the value of a local variable named 'result' from the executed script - final PyObject result = interpreter.get(EVALUATION_RESULT_LOCAL_VARIABLE); + // Obtain the value of a local variable named 'result' from the executed script + result = interpreter.get(EVALUATION_RESULT_LOCAL_VARIABLE); + } if (result == null) { throw new JythonResultNotFoundException("Local variable 'result' not found during script execution."); } - return parseResult(result); + return serializationFactory.getSerializer(result) + .serialize(); + } + + /** + * Evaluates the given Jython script, returning the result as it's equivalent Java type {@link T}. Accepts optional + * arguments to be passed to the script at runtime. {@code args} should be interpreted as 'sys.args' arguments in + * the given script. Note that the arguments passed in here will begin at the first index in a Jython scripts + * sys.argv list. + * + * @param clazz the expected return type of the Jython script + * @param pyCode the compiled Jython script to evaluate + * @param args arguments to be passed to the script + * @param a Java type that represents the expected return type of the Jython script + * @return the result from executing the given script + * @throws JythonScriptException when a script execution error occurs or when a local Python variable named 'result' + * is not found + * @since 3.0.0 + */ + public T evaluate(final Class clazz, final PyCode pyCode, final Object... args) throws JythonScriptException { + // Execute the script + final PyObject result; + try (final PythonInterpreter interpreter = executeWithState(pyCode, args)) { + + // Obtain the value of a local variable named 'result' from the executed script + result = interpreter.get(EVALUATION_RESULT_LOCAL_VARIABLE); + } + + if (result == null) { + throw new JythonResultNotFoundException("Local variable 'result' not found during script execution."); + } + + final Object parsedResult = serializationFactory.getSerializer(result) + .serialize(); + if (!parsedResult.getClass().isAssignableFrom(clazz)) { + throw new JythonScriptException("Result of type [" + parsedResult.getClass().getSimpleName() + "] cannot " + + "be assigned to expected type [" + clazz.getTypeName() + "]"); + } + + return clazz.cast(parsedResult); } /** @@ -332,9 +389,9 @@ public static Object evaluate(final PyCode pyCode, final Object... args) throws * @param scriptPath the fully qualified path of the Jython script to execute * @param args arguments to be passed to the script * @throws JythonScriptException when the given file path is null, a directory, or cannot be found - * @since 1.0 + * @since 1.0.0 */ - public static void execute(final String scriptPath, final Object... args) throws JythonScriptException { + public void execute(final String scriptPath, final Object... args) throws JythonScriptException { // Ensure that the scriptRelativePath is not null or empty if (StringUtils.isBlank(scriptPath)) { throw new JythonScriptNotFoundException("File not found at path=[" + scriptPath + "]"); @@ -345,7 +402,8 @@ public static void execute(final String scriptPath, final Object... args) throws try { inputStream = FileUtils.getFileInputStream(scriptPath); } catch (Exception e) { - throw new JythonScriptNotFoundException("Could not open Jython script from location=[" + scriptPath + "]", e); + throw new JythonScriptNotFoundException("Could not open Jython script from location=[" + scriptPath + + "]", e); } // Execute the script @@ -360,9 +418,9 @@ public static void execute(final String scriptPath, final Object... args) throws * @param scriptUrl the {@link URL} to a Jython script to execute * @param args arguments to be passed to the script * @throws JythonScriptException when the given file is null, a directory, or cannot be found - * @since 2.0 + * @since 2.0.0 */ - public static void execute(final URL scriptUrl, final Object... args) throws JythonScriptException { + public void execute(final URL scriptUrl, final Object... args) throws JythonScriptException { if (scriptUrl == null) { throw new JythonScriptException("Null path is not a URL."); } @@ -380,7 +438,8 @@ public static void execute(final URL scriptUrl, final Object... args) throws Jyt try { inputStream = FileUtils.getFileInputStream(file); } catch (Exception e) { - throw new JythonScriptNotFoundException("Could not open Jython script from location=[" + scriptUrl.getPath() + "]", e); + throw new JythonScriptNotFoundException("Could not open Jython script from location=[" + + scriptUrl.getPath() + "]", e); } execute(inputStream, args); @@ -394,9 +453,9 @@ public static void execute(final URL scriptUrl, final Object... args) throws Jyt * @param scriptFile the Jython script to execute * @param args arguments to be passed to the script * @throws JythonScriptException when the given file is null, a directory, or cannot be found - * @since 1.0 + * @since 1.0.0 */ - public static void execute(final File scriptFile, final Object... args) throws JythonScriptException { + public void execute(final File scriptFile, final Object... args) throws JythonScriptException { // Ensure that the script is not null if (scriptFile == null) { throw new JythonScriptNotFoundException("Could not open Jython script, the file was null."); @@ -407,7 +466,8 @@ public static void execute(final File scriptFile, final Object... args) throws J try { inputStream = FileUtils.getFileInputStream(scriptFile); } catch (Exception e) { - throw new JythonScriptNotFoundException("Could not open Jython script from location=[" + scriptFile.getAbsolutePath() + "]", e); + throw new JythonScriptNotFoundException("Could not open Jython script from location=[" + + scriptFile.getAbsolutePath() + "]", e); } // Execute the script @@ -421,22 +481,20 @@ public static void execute(final File scriptFile, final Object... args) throws J * * @param inputStream the {@link InputStream} that represents the Jython script to be executed * @param args arguments to be passed to the script - * @throws JythonScriptException when the given inputstream is null or a script execution error occurs - * @since 1.0 + * @throws JythonScriptException when the given {@link InputStream} is null or a script execution error occurs + * @since 1.0.0 */ - public static void execute(final InputStream inputStream, final Object... args) throws JythonScriptException { + public void execute(final InputStream inputStream, final Object... args) throws JythonScriptException { if (inputStream == null) { throw new JythonScriptException("Cannot execute a Jython script that doesn't exist! InputStream is null."); } // Set the arguments on the Python System State - final PythonInterpreter interpreter = updateInterpreterState(args); - - try { + try (final PythonInterpreter interpreter = updateInterpreterState(args)) { // Execute the script interpreter.execfile(inputStream); } catch (Exception e) { - throw new JythonScriptException("An error occurred during script execution. cause=[\n\t" + e.toString() + "]"); + throw new JythonScriptException("An error occurred during script execution.", e); } } @@ -448,21 +506,19 @@ public static void execute(final InputStream inputStream, final Object... args) * @param pyCode the compiled Jython script to evaluate * @param args arguments to be passed to the script * @throws JythonScriptException when the given PyCode is null or a script execution error occurs - * @since 1.0 + * @since 1.0.0 */ - public static void execute(final PyCode pyCode, final Object... args) throws JythonScriptException { + public void execute(final PyCode pyCode, final Object... args) throws JythonScriptException { if (pyCode == null) { throw new JythonScriptException("Cannot execute a Jython script that doesn't exist! InputStream is null."); } // Set the arguments on the Python System State - final PythonInterpreter interpreter = updateInterpreterState(args); - - try { + try (final PythonInterpreter interpreter = updateInterpreterState(args)) { // Execute the script interpreter.exec(pyCode); } catch (Exception e) { - throw new JythonScriptException("An error occurred during script execution. cause=[\n\t" + e.toString() + "]"); + throw new JythonScriptException("An error occurred during script execution.", e); } } @@ -471,14 +527,16 @@ public static void execute(final PyCode pyCode, final Object... args) throws Jyt * interpreted as 'sys.argv' arguments in the given script. Note that the arguments passed in here will begin at * the first index in a Jython scripts sys.argv list. * + *
+ * * This function returns the {@link PythonInterpreter} state after executing the given Jython code. * * @param inputStream the {@link InputStream} that represents the Jython script to be executed * @param args arguments to be passed to the script - * @throws JythonScriptException when the given inputstream is null or a script execution error occurs + * @throws JythonScriptException when the given {@link InputStream} is null or a script execution error occurs * @since 2.0.1 */ - private static PythonInterpreter executeWithState(final InputStream inputStream, final Object... args) throws JythonScriptException { + private PythonInterpreter executeWithState(final InputStream inputStream, final Object... args) throws JythonScriptException { if (inputStream == null) { throw new JythonScriptException("Cannot execute a Jython script that doesn't exist! InputStream is null."); } @@ -490,7 +548,7 @@ private static PythonInterpreter executeWithState(final InputStream inputStream, // Execute the script interpreter.execfile(inputStream); } catch (Exception e) { - throw new JythonScriptException("An error occurred during script execution. cause=[\n\t" + e.toString() + "]"); + throw new JythonScriptException("An error occurred during script execution.", e); } return interpreter; @@ -501,6 +559,8 @@ private static PythonInterpreter executeWithState(final InputStream inputStream, * interpreted as 'sys.argv' arguments in the given script. Note that the arguments passed in here will begin at * the first index in a Jython scripts sys.argv list. * + *
+ * * This function returns the {@link PythonInterpreter} state after executing the given Jython code. * * @param pyCode the compiled Jython script to evaluate @@ -508,7 +568,7 @@ private static PythonInterpreter executeWithState(final InputStream inputStream, * @throws JythonScriptException when the given PyCode is null or a script execution error occurs * @since 2.0.1 */ - private static PythonInterpreter executeWithState(final PyCode pyCode, final Object... args) throws JythonScriptException { + private PythonInterpreter executeWithState(final PyCode pyCode, final Object... args) throws JythonScriptException { if (pyCode == null) { throw new JythonScriptException("Cannot execute a Jython script that doesn't exist! InputStream is null."); } @@ -520,7 +580,7 @@ private static PythonInterpreter executeWithState(final PyCode pyCode, final Obj // Execute the script interpreter.exec(pyCode); } catch (Exception e) { - throw new JythonScriptException("An error occurred during script execution. cause=[\n\t" + e.toString() + "]"); + throw new JythonScriptException("An error occurred during script execution.", e); } return interpreter; @@ -532,10 +592,10 @@ private static PythonInterpreter executeWithState(final PyCode pyCode, final Obj * sys.argv[1]). Note: the first index is reserved. * * @param args the arguments to set on the {@link PySystemState} for the current {@link PythonInterpreter} - * @since 1.0 + * @since 1.0.0 */ - private static PythonInterpreter updateInterpreterState(final Object... args) { - // Setup the Python System State by appending the given arguments + private PythonInterpreter updateInterpreterState(final Object... args) { + // Set up the Python System State by appending the given arguments final PySystemState state = parseArguments(args); // Add the arguments to the PythonInterpreter @@ -547,9 +607,9 @@ private static PythonInterpreter updateInterpreterState(final Object... args) { * value to the set of arguments to be passed to the executing script. * * @param args the arguments to parse before being sent to a Python script - * @since 1.0 + * @since 1.0.0 */ - private static PySystemState parseArguments(final Object... args) { + private PySystemState parseArguments(final Object... args) { final PySystemState systemState = new PySystemState(); for (final Object arg : args) { @@ -560,109 +620,37 @@ private static PySystemState parseArguments(final Object... args) { } /** - * Given a {@link PyObject} attempts to convert the object to it's Java representation. If an equivalent java type - * cannot be found, the original PyObject is returned. + * Utility class for constructing a new instance of {@link JythonScript}. * - * Current supported type conversions: - *
    - *
  • {@link PyBoolean} to boolean
  • - *
  • {@link PyInteger} to int
  • - *
  • {@link PyString} to {@link String}
  • - *
  • {@link PyFloat} to float
  • - *
  • {@link PyLong} to long
  • - *
  • {@link PyList} to an array of {@link Object}s
  • - *
  • {@link PyDictionary} to a {@link Map} of {@link Object}s
  • - *
  • {@link PySet} to a {@link Set} of {@link Object}s
  • - *
- * - * @param object the object to convert to it's equivalent Java type, if supported; otherwise, returns the unconverted {@link PyObject} - * @return the Java type representation of the given {@link PyObject} - * @since 1.0 + * @author Adam Childs + * @since 3.0.0 */ - private static Object parseResult(final PyObject object) { - if (object == null) { - // We should never get here since evaluate provides this check; but, just in case. - return null; - } - - if (object instanceof PyBoolean) { - return Py.py2boolean(object); - } else if (object instanceof PyInteger) { - return Py.py2int(object); - } else if (object instanceof PyString) { - return ((PyString) object).getString(); - } else if (object instanceof PyFloat) { - return Py.py2float(object); - } else if (object instanceof PyLong) { - return Py.py2long(object); - } else if (object instanceof PyList) { - return parsePyObjectList(((PyList) object).getArray()); - } else if (object instanceof PyDictionary) { - return parsePyObjectDict(((PyDictionary) object).getMap()); - } else if (object instanceof PySet) { - return parsePyObjectSet(((PySet) object).getSet()); - } - - return object; - } - - /** - * Converts the given array of {@link PyObject}s to an array of the corresponding Java types for each value in the array. - * - * @param pyObjects an array of {@link PyObject}s to parse - * @return a new array of {@link Object}s - * @since 1.0 - */ - private static Object[] parsePyObjectList(final PyObject[] pyObjects) { - final Object[] objects = new Object[pyObjects.length]; - - int index = 0; - for (final PyObject pyObject : pyObjects) { - objects[index] = parseResult(pyObject); + public static class Builder { - index++; - } - - return objects; - } - - /** - * Converts the given {@link Map} of {@link PyObject}s to a Map of the corresponding Java types. - * - * @param pyDict the dictionary to parse - * @return a new {@link Map} of {@link Object}s - */ - private static Map parsePyObjectDict(final Map pyDict) { - final Map objects = new HashMap<>(); + private PyObjectSerializationFactory serializationFactory = new DefaultPyObjectSerializationFactory(); - for (final Map.Entry entry : pyDict.entrySet()) { - final Object key = parseResult(entry.getKey()); - final Object value = parseResult(entry.getValue()); + /** + * Sets a {@link PyObjectSerializationFactory} on this builder. + * + * @param serializationFactory a serialization factory to use for converting between {@link PyObject}s and + * java objects. + * @return the current {@link Builder} context + */ + public Builder serializationFactory(final PyObjectSerializationFactory serializationFactory) { + this.serializationFactory = serializationFactory; - objects.put(key, value); + return this; } - return objects; - } - - /** - * Converts the given {@link Set} of {@link PyObject}s to a Set of the corresponding Java types. - * - * @param pySet the set to parse - * @return a new {@link Set} of {@link Object}s - * @since 1.0 - */ - private static Set parsePyObjectSet(final Set pySet) { - final Set objects = new HashSet<>(); - - for (final PyObject pyObject : pySet) { - objects.add(parseResult(pyObject)); + /** + * Constructs a new {@link JythonScript} instance with state configured based on builder's context. + * + * @return a new {@link JythonScript} + */ + public JythonScript build() { + return new JythonScript(serializationFactory); } - return objects; } - // Don't allow this class to be instantiated - private JythonScript() { } - } \ No newline at end of file diff --git a/src/main/java/com/github/adchilds/jython/exception/JythonScriptRuntimeException.java b/src/main/java/com/github/adchilds/jython/exception/JythonScriptRuntimeException.java new file mode 100644 index 0000000..63ef9cc --- /dev/null +++ b/src/main/java/com/github/adchilds/jython/exception/JythonScriptRuntimeException.java @@ -0,0 +1,40 @@ +package com.github.adchilds.jython.exception; + +/** + * Extension of a {@link RuntimeException} thrown in potentially unknown circumstances during Jython script execution or + * evaluation. + * + * @author Adam Childs + * @since 3.0.0 + */ +public class JythonScriptRuntimeException extends RuntimeException { + + /** + * {@inheritDoc} + */ + public JythonScriptRuntimeException() { + super(); + } + + /** + * {@inheritDoc} + */ + public JythonScriptRuntimeException(final String message) { + super(message); + } + + /** + * {@inheritDoc} + */ + public JythonScriptRuntimeException(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * {@inheritDoc} + */ + public JythonScriptRuntimeException(final Throwable cause) { + super(cause); + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/adchilds/jython/serialization/AbstractPyObjectSerializer.java b/src/main/java/com/github/adchilds/jython/serialization/AbstractPyObjectSerializer.java new file mode 100644 index 0000000..42a8358 --- /dev/null +++ b/src/main/java/com/github/adchilds/jython/serialization/AbstractPyObjectSerializer.java @@ -0,0 +1,33 @@ +package com.github.adchilds.jython.serialization; + +import org.python.core.PyObject; + +/** + * + * @param + */ +public abstract class AbstractPyObjectSerializer implements Serializer { + + private final PyObject value; + + protected AbstractPyObjectSerializer(final PyObject value) { + this.value = value; + } + + /** + * + * @return + */ + protected PyObject getValue() { + return value; + } + + /** + * + * @return + */ + protected T getValue(final Class clazz) { + return clazz.cast(value); + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/adchilds/jython/serialization/DefaultPyObjectSerializationFactory.java b/src/main/java/com/github/adchilds/jython/serialization/DefaultPyObjectSerializationFactory.java new file mode 100644 index 0000000..7e3caa1 --- /dev/null +++ b/src/main/java/com/github/adchilds/jython/serialization/DefaultPyObjectSerializationFactory.java @@ -0,0 +1,47 @@ +package com.github.adchilds.jython.serialization; + +import com.github.adchilds.jython.exception.JythonScriptRuntimeException; +import org.python.core.*; + +/** + * Implementation of a {@link PyObjectSerializationFactory} that provides serialization capabilities for several + * extensions of the {@link PyObject} type. + * + * @author Adam Childs + * @since 3.0.0 + */ +public class DefaultPyObjectSerializationFactory implements PyObjectSerializationFactory { + + /** + * {@inheritDoc} + */ + @Override + public Serializer getSerializer(final T object) { + if (object == null) { + // We should never get here since evaluate provides this check; but, just in case. + return null; + } + + if (object instanceof PyBoolean) { + return new PyBooleanSerializer(object); + } else if (object instanceof PyInteger) { + return new PyIntegerSerializer(object); + } else if (object instanceof PyString) { + return new PyStringSerializer(object); + } else if (object instanceof PyFloat) { + return new PyFloatSerializer(object); + } else if (object instanceof PyLong) { + return new PyLongSerializer(object); + } else if (object instanceof PyList) { + return new PyListSerializer((PyList) object, this); + } else if (object instanceof PyDictionary) { + return new PyDictionarySerializer((PyDictionary) object, this); + } else if (object instanceof PySet) { + return new PySetSerializer((PySet) object, this); + } + + throw new JythonScriptRuntimeException(String.format("Serialization not supported for type [%s].", + object.getClass().getSimpleName())); + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/adchilds/jython/serialization/PyBooleanSerializer.java b/src/main/java/com/github/adchilds/jython/serialization/PyBooleanSerializer.java new file mode 100644 index 0000000..77e0c0d --- /dev/null +++ b/src/main/java/com/github/adchilds/jython/serialization/PyBooleanSerializer.java @@ -0,0 +1,34 @@ +package com.github.adchilds.jython.serialization; + +import com.github.adchilds.jython.exception.JythonScriptRuntimeException; +import org.python.core.Py; +import org.python.core.PyBoolean; +import org.python.core.PyObject; + +/** + * + */ +public class PyBooleanSerializer extends AbstractPyObjectSerializer { + + protected PyBooleanSerializer(final PyObject value) { + super(value); + } + + /** + * {@inheritDoc} + */ + @Override + public Boolean serialize() { + if (getValue() == null) { + return null; + } + + if (!(getValue() instanceof PyBoolean)) { + throw new JythonScriptRuntimeException(String.format("Cannot serialize type [%s] as Boolean.", + getValue().getClass().getSimpleName())); + } + + return Py.py2boolean(getValue()); + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/adchilds/jython/serialization/PyDictionarySerializer.java b/src/main/java/com/github/adchilds/jython/serialization/PyDictionarySerializer.java new file mode 100644 index 0000000..55f87d5 --- /dev/null +++ b/src/main/java/com/github/adchilds/jython/serialization/PyDictionarySerializer.java @@ -0,0 +1,50 @@ +package com.github.adchilds.jython.serialization; + +import com.github.adchilds.jython.exception.JythonScriptRuntimeException; +import org.python.core.PyDictionary; +import org.python.core.PyObject; + +import java.util.HashMap; +import java.util.Map; + +/** + * + */ +public class PyDictionarySerializer extends AbstractPyObjectSerializer> { + + private final PyObjectSerializationFactory serializationFactory; + + public PyDictionarySerializer(final PyDictionary dictionary, + final PyObjectSerializationFactory serializationFactory) { + super(dictionary); + + this.serializationFactory = serializationFactory; + } + + /** + * {@inheritDoc} + */ + @Override + public Map serialize() { + if (!(getValue() instanceof PyDictionary)) { + throw new JythonScriptRuntimeException(String.format("Cannot serialize type [%s] as Map.", + getValue().getClass().getSimpleName())); + } + + final Map objects = new HashMap<>(); + + final Map pyObjectMap = getValue(PyDictionary.class).getMap(); + for (final Map.Entry entry : pyObjectMap.entrySet()) { + final Object key = serializationFactory.getSerializer(entry.getKey()) + .serialize(); + + final Object value = serializationFactory.getSerializer(entry.getValue()) + .serialize(); + + objects.put(key, value); + } + + return objects; + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/adchilds/jython/serialization/PyFloatSerializer.java b/src/main/java/com/github/adchilds/jython/serialization/PyFloatSerializer.java new file mode 100644 index 0000000..63096c6 --- /dev/null +++ b/src/main/java/com/github/adchilds/jython/serialization/PyFloatSerializer.java @@ -0,0 +1,34 @@ +package com.github.adchilds.jython.serialization; + +import com.github.adchilds.jython.exception.JythonScriptRuntimeException; +import org.python.core.Py; +import org.python.core.PyFloat; +import org.python.core.PyObject; + +/** + * + */ +public class PyFloatSerializer extends AbstractPyObjectSerializer { + + protected PyFloatSerializer(final PyObject value) { + super(value); + } + + /** + * {@inheritDoc} + */ + @Override + public Float serialize() { + if (getValue() == null) { + return null; + } + + if (!(getValue() instanceof PyFloat)) { + throw new JythonScriptRuntimeException(String.format("Cannot serialize type [%s] as Float.", + getValue().getClass().getSimpleName())); + } + + return Py.py2float(getValue()); + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/adchilds/jython/serialization/PyIntegerSerializer.java b/src/main/java/com/github/adchilds/jython/serialization/PyIntegerSerializer.java new file mode 100644 index 0000000..4f3dc53 --- /dev/null +++ b/src/main/java/com/github/adchilds/jython/serialization/PyIntegerSerializer.java @@ -0,0 +1,34 @@ +package com.github.adchilds.jython.serialization; + +import com.github.adchilds.jython.exception.JythonScriptRuntimeException; +import org.python.core.Py; +import org.python.core.PyInteger; +import org.python.core.PyObject; + +/** + * + */ +public class PyIntegerSerializer extends AbstractPyObjectSerializer { + + protected PyIntegerSerializer(final PyObject value) { + super(value); + } + + /** + * {@inheritDoc} + */ + @Override + public Integer serialize() { + if (getValue() == null) { + return null; + } + + if (!(getValue() instanceof PyInteger)) { + throw new JythonScriptRuntimeException(String.format("Cannot serialize type [%s] as Integer.", + getValue().getClass().getSimpleName())); + } + + return Py.py2int(getValue()); + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/adchilds/jython/serialization/PyListSerializer.java b/src/main/java/com/github/adchilds/jython/serialization/PyListSerializer.java new file mode 100644 index 0000000..d303b3a --- /dev/null +++ b/src/main/java/com/github/adchilds/jython/serialization/PyListSerializer.java @@ -0,0 +1,47 @@ +package com.github.adchilds.jython.serialization; + +import com.github.adchilds.jython.exception.JythonScriptRuntimeException; +import org.python.core.PyList; +import org.python.core.PyObject; + +import java.util.Arrays; +import java.util.List; + +/** + * + */ +public class PyListSerializer extends AbstractPyObjectSerializer> { + + private final PyObjectSerializationFactory serializationFactory; + + public PyListSerializer(final PyList list, final PyObjectSerializationFactory serializationFactory) { + super(list); + + this.serializationFactory = serializationFactory; + } + + /** + * {@inheritDoc} + */ + @Override + public List serialize() { + if (!(getValue() instanceof PyList)) { + throw new JythonScriptRuntimeException(String.format("Cannot serialize type [%s] as Long.", + getValue().getClass().getSimpleName())); + } + + final PyObject[] objects = getValue(PyList.class).getArray(); + final Object[] serializedObjects = new Object[objects.length]; + + int index = 0; + for (final PyObject pyObject : objects) { + serializedObjects[index] = serializationFactory.getSerializer(pyObject) + .serialize(); + + index++; + } + + return Arrays.asList(serializedObjects); + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/adchilds/jython/serialization/PyLongSerializer.java b/src/main/java/com/github/adchilds/jython/serialization/PyLongSerializer.java new file mode 100644 index 0000000..18eb59a --- /dev/null +++ b/src/main/java/com/github/adchilds/jython/serialization/PyLongSerializer.java @@ -0,0 +1,30 @@ +package com.github.adchilds.jython.serialization; + +import com.github.adchilds.jython.exception.JythonScriptRuntimeException; +import org.python.core.Py; +import org.python.core.PyLong; +import org.python.core.PyObject; + +/** + * + */ +public class PyLongSerializer extends AbstractPyObjectSerializer { + + protected PyLongSerializer(final PyObject value) { + super(value); + } + + /** + * {@inheritDoc} + */ + @Override + public Long serialize() { + if (!(getValue() instanceof PyLong)) { + throw new JythonScriptRuntimeException(String.format("Cannot serialize type [%s] as Long.", + getValue().getClass().getSimpleName())); + } + + return Py.py2long(getValue()); + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/adchilds/jython/serialization/PyObjectSerializationFactory.java b/src/main/java/com/github/adchilds/jython/serialization/PyObjectSerializationFactory.java new file mode 100644 index 0000000..15b4b3f --- /dev/null +++ b/src/main/java/com/github/adchilds/jython/serialization/PyObjectSerializationFactory.java @@ -0,0 +1,18 @@ +package com.github.adchilds.jython.serialization; + +import org.python.core.PyObject; + +/** + * + */ +public interface PyObjectSerializationFactory { + + /** + * + * @param object + * @return + * @param + */ + Serializer getSerializer(T object); + +} \ No newline at end of file diff --git a/src/main/java/com/github/adchilds/jython/serialization/PySetSerializer.java b/src/main/java/com/github/adchilds/jython/serialization/PySetSerializer.java new file mode 100644 index 0000000..6d70073 --- /dev/null +++ b/src/main/java/com/github/adchilds/jython/serialization/PySetSerializer.java @@ -0,0 +1,46 @@ +package com.github.adchilds.jython.serialization; + +import com.github.adchilds.jython.exception.JythonScriptRuntimeException; +import org.python.core.PyObject; +import org.python.core.PySet; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +/** + * + */ +public class PySetSerializer extends AbstractPyObjectSerializer> { + + private final PyObjectSerializationFactory serializationFactory; + + public PySetSerializer(final PySet set, final PyObjectSerializationFactory serializationFactory) { + super(set); + + this.serializationFactory = serializationFactory; + } + + /** + * {@inheritDoc} + */ + @Override + public Set serialize() { + if (!(getValue() instanceof PySet)) { + throw new JythonScriptRuntimeException(String.format("Cannot serialize type [%s] as Set.", + getValue().getClass().getSimpleName())); + } + + final Set objects = new HashSet<>(); + + final Set pySet = getValue(PySet.class).getSet(); + for (final PyObject pyObject : pySet) { + Optional.ofNullable(serializationFactory.getSerializer(pyObject)) + .map(Serializer::serialize) + .ifPresent(objects::add); + } + + return objects; + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/adchilds/jython/serialization/PyStringSerializer.java b/src/main/java/com/github/adchilds/jython/serialization/PyStringSerializer.java new file mode 100644 index 0000000..210c0b1 --- /dev/null +++ b/src/main/java/com/github/adchilds/jython/serialization/PyStringSerializer.java @@ -0,0 +1,29 @@ +package com.github.adchilds.jython.serialization; + +import com.github.adchilds.jython.exception.JythonScriptRuntimeException; +import org.python.core.PyObject; +import org.python.core.PyString; + +/** + * + */ +public class PyStringSerializer extends AbstractPyObjectSerializer { + + protected PyStringSerializer(final PyObject value) { + super(value); + } + + /** + * {@inheritDoc} + */ + @Override + public String serialize() { + if (!(getValue() instanceof PyString)) { + throw new JythonScriptRuntimeException(String.format("Cannot serialize type [%s] as String.", + getValue().getClass().getSimpleName())); + } + + return getValue(PyString.class).getString(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/adchilds/jython/serialization/Serializer.java b/src/main/java/com/github/adchilds/jython/serialization/Serializer.java new file mode 100644 index 0000000..fb2fdfa --- /dev/null +++ b/src/main/java/com/github/adchilds/jython/serialization/Serializer.java @@ -0,0 +1,14 @@ +package com.github.adchilds.jython.serialization; + +/** + * + */ +public interface Serializer { + + /** + * + * @return + */ + T serialize(); + +} \ No newline at end of file diff --git a/src/main/java/com/github/adchilds/util/FileUtils.java b/src/main/java/com/github/adchilds/util/FileUtils.java index 39084e7..7d3a1b2 100644 --- a/src/main/java/com/github/adchilds/util/FileUtils.java +++ b/src/main/java/com/github/adchilds/util/FileUtils.java @@ -1,6 +1,8 @@ package com.github.adchilds.util; import java.io.*; +import java.nio.file.Files; +import java.nio.file.Paths; /** * Provides static file operations, such as converting a {@link String} or {@link File} to an {@link InputStream}. @@ -14,6 +16,8 @@ public final class FileUtils { * Attempts to convert the given {@code object} to an {@link InputStream}. If the object cannot be converted, throws * an {@link Exception}. * + *
+ * * Current supported object conversions include: *
    *
  • {@link String} - as a fully qualified file path
  • @@ -29,7 +33,7 @@ public static InputStream getFileInputStream(T object) throws IOException { if (object instanceof String) { return new FileInputStream((String) object); } else if (object instanceof File) { - return new FileInputStream((File) object); + return Files.newInputStream(((File) object).toPath()); } throw new IOException("Could not convert the given object to an InputStream. object=[" + object + "]"); @@ -48,7 +52,7 @@ public static String readFully(File file) throws IOException { return ""; } - return readFully(new FileInputStream(file), "UTF-8"); + return readFully(Files.newInputStream(file.toPath()), "UTF-8"); } /** diff --git a/src/test/java/com/github/adchilds/jython/JythonScriptTest.java b/src/test/java/com/github/adchilds/jython/JythonScriptTest.java index 30a0d65..cf5e40b 100644 --- a/src/test/java/com/github/adchilds/jython/JythonScriptTest.java +++ b/src/test/java/com/github/adchilds/jython/JythonScriptTest.java @@ -2,6 +2,9 @@ import com.github.adchilds.jython.exception.JythonResultNotFoundException; import com.github.adchilds.jython.exception.JythonScriptException; +import com.github.adchilds.jython.exception.JythonScriptRuntimeException; +import com.github.adchilds.jython.serialization.DefaultPyObjectSerializationFactory; +import com.github.adchilds.jython.serialization.PyObjectSerializationFactory; import org.junit.jupiter.api.Test; import org.python.core.*; @@ -13,6 +16,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.net.URL; +import java.util.List; import java.util.Map; import java.util.Set; @@ -29,97 +33,100 @@ public class JythonScriptTest { private static final double DELTA = 1E15; public static final String JYTHON_SCRIPT_BASE_PATH = "script/jython/"; - + + private final JythonScript jythonScript = new JythonScript.Builder() + .serializationFactory(new DefaultPyObjectSerializationFactory()) + .build(); @Test void testCompile_filePathNull() { - assertThrows(JythonScriptException.class, () -> JythonScript.compile((String) null)); + assertThrows(JythonScriptException.class, () -> jythonScript.compile((String) null)); } @Test void testCompile_filePathEmpty() { - assertThrows(JythonScriptException.class, () -> JythonScript.compile("")); - assertThrows(JythonScriptException.class, () -> JythonScript.compile(" ")); - assertThrows(JythonScriptException.class, () -> JythonScript.compile("\n")); - assertThrows(JythonScriptException.class, () -> JythonScript.compile("\t")); - assertThrows(JythonScriptException.class, () -> JythonScript.compile("\r")); + assertThrows(JythonScriptException.class, () -> jythonScript.compile("")); + assertThrows(JythonScriptException.class, () -> jythonScript.compile(" ")); + assertThrows(JythonScriptException.class, () -> jythonScript.compile("\n")); + assertThrows(JythonScriptException.class, () -> jythonScript.compile("\t")); + assertThrows(JythonScriptException.class, () -> jythonScript.compile("\r")); } @Test void testCompile_filePathInvalid() { - assertThrows(JythonScriptException.class, () -> JythonScript.compile("/Users/test/notfound.py")); + assertThrows(JythonScriptException.class, () -> jythonScript.compile("/Users/test/notfound.py")); } @Test void testCompile_filePathValid() throws JythonScriptException { final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testEvaluate.py").getPath(); - assertNotNull(JythonScript.compile(filePath)); + assertNotNull(jythonScript.compile(filePath)); } @Test void testCompile_urlNull() { - assertThrows(JythonScriptException.class, () -> JythonScript.compile((URL) null)); + assertThrows(JythonScriptException.class, () -> jythonScript.compile((URL) null)); } @Test void testCompile_urlInvalid() { - assertThrows(JythonScriptException.class, () -> JythonScript.compile(new URL("http://test"))); + assertThrows(JythonScriptException.class, () -> jythonScript.compile(new URL("http://test"))); } @Test void testCompile_urlValid() throws JythonScriptException { final URL scriptUrl = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testEvaluate.py"); - assertNotNull(JythonScript.compile(scriptUrl)); + assertNotNull(jythonScript.compile(scriptUrl)); } @Test void testCompile_fileNull() { - assertThrows(JythonScriptException.class, () -> JythonScript.compile((File) null)); + assertThrows(JythonScriptException.class, () -> jythonScript.compile((File) null)); } @Test void testCompile_fileEmpty() { - assertThrows(JythonScriptException.class, () -> JythonScript.compile(new File("/"))); + assertThrows(JythonScriptException.class, () -> jythonScript.compile(new File("/"))); } @Test void testCompile_fileInvalid() { - assertThrows(JythonScriptException.class, () -> JythonScript.compile(new File("/Users/test/notfound.py"))); + assertThrows(JythonScriptException.class, () -> jythonScript.compile(new File("/Users/test/notfound.py"))); } @Test void testCompile_fileValid() throws JythonScriptException { final File file = new File(ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testEvaluate.py").getPath()); - assertNotNull(JythonScript.compile(file)); + assertNotNull(jythonScript.compile(file)); } @Test void testCompile_directory() { final File file = new File(ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH).getPath()); - assertThrows(JythonScriptException.class, () -> JythonScript.compile(file)); + assertThrows(JythonScriptException.class, () -> jythonScript.compile(file)); } @Test void testCompileString_null() { - assertThrows(JythonScriptException.class, () -> JythonScript.compileString(null)); + assertThrows(JythonScriptException.class, () -> jythonScript.compileString(null)); } @Test void testCompileString_empty() { - assertThrows(JythonScriptException.class, () -> JythonScript.compileString("")); - assertThrows(JythonScriptException.class, () -> JythonScript.compileString(" ")); - assertThrows(JythonScriptException.class, () -> JythonScript.compileString("\n")); - assertThrows(JythonScriptException.class, () -> JythonScript.compileString("\t")); - assertThrows(JythonScriptException.class, () -> JythonScript.compileString("\r")); + assertThrows(JythonScriptException.class, () -> jythonScript.compileString("")); + assertThrows(JythonScriptException.class, () -> jythonScript.compileString(" ")); + assertThrows(JythonScriptException.class, () -> jythonScript.compileString("\n")); + assertThrows(JythonScriptException.class, () -> jythonScript.compileString("\t")); + assertThrows(JythonScriptException.class, () -> jythonScript.compileString("\r")); } @Test void testCompile_invalidPythonCode() { - assertThrows(JythonScriptException.class, () -> JythonScript.compileString("Invalid Python code...")); + assertThrows(JythonScriptException.class, () -> jythonScript.compileString("Invalid Python code...")); } @Test @@ -139,26 +146,26 @@ void testCompileString_valid() throws JythonScriptException { "if __name__ == '__main__':\n" + " test()"; - assertNotNull(JythonScript.compileString(script)); + assertNotNull(jythonScript.compileString(script)); } @Test void testEvaluate_filePathNull() { - assertThrows(JythonScriptException.class, () -> JythonScript.evaluate((String) null)); + assertThrows(JythonScriptException.class, () -> jythonScript.evaluate((String) null)); } @Test void testEvaluate_filePathEmpty() { - assertThrows(JythonScriptException.class, () -> JythonScript.evaluate("")); - assertThrows(JythonScriptException.class, () -> JythonScript.evaluate(" ")); - assertThrows(JythonScriptException.class, () -> JythonScript.evaluate("\n")); - assertThrows(JythonScriptException.class, () -> JythonScript.evaluate("\t")); - assertThrows(JythonScriptException.class, () -> JythonScript.evaluate("\r")); + assertThrows(JythonScriptException.class, () -> jythonScript.evaluate("")); + assertThrows(JythonScriptException.class, () -> jythonScript.evaluate(" ")); + assertThrows(JythonScriptException.class, () -> jythonScript.evaluate("\n")); + assertThrows(JythonScriptException.class, () -> jythonScript.evaluate("\t")); + assertThrows(JythonScriptException.class, () -> jythonScript.evaluate("\r")); } @Test void testEvaluate_filePathInvalid() { - assertThrows(JythonScriptException.class, () -> JythonScript.evaluate("/Users/test/notfound.py")); + assertThrows(JythonScriptException.class, () -> jythonScript.evaluate("/Users/test/notfound.py")); } @@ -166,102 +173,102 @@ void testEvaluate_filePathInvalid() { void testEvaluate_filePath() throws JythonScriptException { final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testEvaluate.py").getPath(); - assertEquals(25, JythonScript.evaluate(filePath)); - assertEquals(100, JythonScript.evaluate(filePath, 10, 10)); - assertEquals(0, JythonScript.evaluate(filePath, 0, 0)); - assertEquals(-20, JythonScript.evaluate(filePath, -1, 20)); + assertEquals(25, jythonScript.evaluate(filePath)); + assertEquals(100, jythonScript.evaluate(filePath, 10, 10)); + assertEquals(0, jythonScript.evaluate(filePath, 0, 0)); + assertEquals(-20, jythonScript.evaluate(filePath, -1, 20)); } @Test void testExecute_filePathNull() { - assertThrows(JythonScriptException.class, () -> JythonScript.execute((String) null)); + assertThrows(JythonScriptException.class, () -> jythonScript.execute((String) null)); } @Test void testExecute_filePathEmpty() { - assertThrows(JythonScriptException.class, () -> JythonScript.execute("")); - assertThrows(JythonScriptException.class, () -> JythonScript.execute(" ")); - assertThrows(JythonScriptException.class, () -> JythonScript.execute("\n")); - assertThrows(JythonScriptException.class, () -> JythonScript.execute("\t")); - assertThrows(JythonScriptException.class, () -> JythonScript.execute("\r")); + assertThrows(JythonScriptException.class, () -> jythonScript.execute("")); + assertThrows(JythonScriptException.class, () -> jythonScript.execute(" ")); + assertThrows(JythonScriptException.class, () -> jythonScript.execute("\n")); + assertThrows(JythonScriptException.class, () -> jythonScript.execute("\t")); + assertThrows(JythonScriptException.class, () -> jythonScript.execute("\r")); } @Test void testExecute_filePathInvalid() { - assertThrows(JythonScriptException.class, () -> JythonScript.execute("/Users/test/notfound.py")); + assertThrows(JythonScriptException.class, () -> jythonScript.execute("/Users/test/notfound.py")); } @Test void testExecute_filePathValid() throws JythonScriptException { final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testExecute.py").getPath(); - JythonScript.execute(filePath); + jythonScript.execute(filePath); } @Test void testEvaluate_urlNull() { - assertThrows(JythonScriptException.class, () -> JythonScript.evaluate((URL) null)); + assertThrows(JythonScriptException.class, () -> jythonScript.evaluate((URL) null)); } @Test void testEvaluate_urlInvalid() { - assertThrows(JythonScriptException.class, () -> JythonScript.evaluate(new URL("http://test"))); + assertThrows(JythonScriptException.class, () -> jythonScript.evaluate(new URL("http://test"))); } @Test void testEvaluate_urlNotFound() { - assertThrows(JythonScriptException.class, () -> JythonScript.evaluate(new URL("file:///test/test"))); + assertThrows(JythonScriptException.class, () -> jythonScript.evaluate(new URL("file:///test/test"))); } @Test void testEvaluate_urlValid() throws JythonScriptException { final URL scriptUrl = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testEvaluate.py"); - assertEquals(25, JythonScript.evaluate(scriptUrl)); - assertEquals(100, JythonScript.evaluate(scriptUrl, 10, 10)); - assertEquals(0, JythonScript.evaluate(scriptUrl, 0, 0)); - assertEquals(-20, JythonScript.evaluate(scriptUrl, -1, 20)); + assertEquals(25, jythonScript.evaluate(scriptUrl)); + assertEquals(100, jythonScript.evaluate(scriptUrl, 10, 10)); + assertEquals(0, jythonScript.evaluate(scriptUrl, 0, 0)); + assertEquals(-20, jythonScript.evaluate(scriptUrl, -1, 20)); } @Test void testExecute_urlNull() { - assertThrows(JythonScriptException.class, () -> JythonScript.execute((URL) null)); + assertThrows(JythonScriptException.class, () -> jythonScript.execute((URL) null)); } @Test void testExecute_urlInvalid() { - assertThrows(JythonScriptException.class, () -> JythonScript.execute(new URL("http://test"))); + assertThrows(JythonScriptException.class, () -> jythonScript.execute(new URL("http://test"))); } @Test void testExecute_urlNotFound() { - assertThrows(JythonScriptException.class, () -> JythonScript.execute(new URL("file:///test/test"))); + assertThrows(JythonScriptException.class, () -> jythonScript.execute(new URL("file:///test/test"))); } @Test void testExecute_urlValid() throws JythonScriptException { final URL scriptUrl = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testExecute.py"); - JythonScript.execute(scriptUrl); + jythonScript.execute(scriptUrl); } @Test void testEvaluate_fileNull() { - assertThrows(JythonScriptException.class, () -> JythonScript.evaluate((File) null)); + assertThrows(JythonScriptException.class, () -> jythonScript.evaluate((File) null)); } @Test void testEvaluate_fileEmpty() { - assertThrows(JythonScriptException.class, () -> JythonScript.evaluate(new File(""))); - assertThrows(JythonScriptException.class, () -> JythonScript.evaluate(new File(" "))); - assertThrows(JythonScriptException.class, () -> JythonScript.evaluate(new File("\n"))); - assertThrows(JythonScriptException.class, () -> JythonScript.evaluate(new File("\t"))); - assertThrows(JythonScriptException.class, () -> JythonScript.evaluate(new File("\r"))); + assertThrows(JythonScriptException.class, () -> jythonScript.evaluate(new File(""))); + assertThrows(JythonScriptException.class, () -> jythonScript.evaluate(new File(" "))); + assertThrows(JythonScriptException.class, () -> jythonScript.evaluate(new File("\n"))); + assertThrows(JythonScriptException.class, () -> jythonScript.evaluate(new File("\t"))); + assertThrows(JythonScriptException.class, () -> jythonScript.evaluate(new File("\r"))); } @Test void testEvaluate_fileInvalid() { - assertThrows(JythonScriptException.class, () -> JythonScript.evaluate("/Users/test/notfound.py")); + assertThrows(JythonScriptException.class, () -> jythonScript.evaluate("/Users/test/notfound.py")); } @Test @@ -269,56 +276,56 @@ void testEvaluate_fileValid() throws JythonScriptException { final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testEvaluate.py").getPath(); final File file = new File(filePath); - assertEquals(25, JythonScript.evaluate(file)); - assertEquals(100, JythonScript.evaluate(file, 10, 10)); - assertEquals(0, JythonScript.evaluate(file, 0, 0)); - assertEquals(-20, JythonScript.evaluate(file, -1, 20)); + assertEquals(25, jythonScript.evaluate(file)); + assertEquals(100, jythonScript.evaluate(file, 10, 10)); + assertEquals(0, jythonScript.evaluate(file, 0, 0)); + assertEquals(-20, jythonScript.evaluate(file, -1, 20)); } @Test void testExecute_fileNull() { - assertThrows(JythonScriptException.class, () -> JythonScript.execute((File) null)); + assertThrows(JythonScriptException.class, () -> jythonScript.execute((File) null)); } @Test void testExecute_fileEmpty() { - assertThrows(JythonScriptException.class, () -> JythonScript.execute(new File(""))); - assertThrows(JythonScriptException.class, () -> JythonScript.execute(new File(" "))); - assertThrows(JythonScriptException.class, () -> JythonScript.execute(new File("\n"))); - assertThrows(JythonScriptException.class, () -> JythonScript.execute(new File("\t"))); - assertThrows(JythonScriptException.class, () -> JythonScript.execute(new File("\r"))); + assertThrows(JythonScriptException.class, () -> jythonScript.execute(new File(""))); + assertThrows(JythonScriptException.class, () -> jythonScript.execute(new File(" "))); + assertThrows(JythonScriptException.class, () -> jythonScript.execute(new File("\n"))); + assertThrows(JythonScriptException.class, () -> jythonScript.execute(new File("\t"))); + assertThrows(JythonScriptException.class, () -> jythonScript.execute(new File("\r"))); } @Test void testExecute_fileInvalid() { - assertThrows(JythonScriptException.class, () -> JythonScript.execute(new File("/Users/test/notfound.py"))); + assertThrows(JythonScriptException.class, () -> jythonScript.execute(new File("/Users/test/notfound.py"))); } @Test void testExecute_fileValid() throws JythonScriptException { final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testExecute.py").getPath(); - JythonScript.execute(new File(filePath)); + jythonScript.execute(new File(filePath)); } @Test void testEvaluate_inputStreamNull() { - assertThrows(JythonScriptException.class, () -> JythonScript.evaluate((InputStream) null)); + assertThrows(JythonScriptException.class, () -> jythonScript.evaluate((InputStream) null)); } @Test void testEvaluate_inputStreamEmpty() { - assertThrows(FileNotFoundException.class, () -> JythonScript.evaluate(new FileInputStream(new File("")))); - assertThrows(FileNotFoundException.class, () -> JythonScript.evaluate(new FileInputStream(new File(" ")))); - assertThrows(FileNotFoundException.class, () -> JythonScript.evaluate(new FileInputStream(new File("\n")))); - assertThrows(FileNotFoundException.class, () -> JythonScript.evaluate(new FileInputStream(new File("\t")))); - assertThrows(FileNotFoundException.class, () -> JythonScript.evaluate(new FileInputStream(new File("\r")))); + assertThrows(FileNotFoundException.class, () -> jythonScript.evaluate(new FileInputStream(new File("")))); + assertThrows(FileNotFoundException.class, () -> jythonScript.evaluate(new FileInputStream(new File(" ")))); + assertThrows(FileNotFoundException.class, () -> jythonScript.evaluate(new FileInputStream(new File("\n")))); + assertThrows(FileNotFoundException.class, () -> jythonScript.evaluate(new FileInputStream(new File("\t")))); + assertThrows(FileNotFoundException.class, () -> jythonScript.evaluate(new FileInputStream(new File("\r")))); } @Test void testEvaluate_inputStreamInvalid() { assertThrows(FileNotFoundException.class, () -> - JythonScript.execute(new FileInputStream(new File("/Users/test/notfound.py")))); + jythonScript.execute(new FileInputStream(new File("/Users/test/notfound.py")))); } @Test @@ -326,47 +333,47 @@ void testEvaluate_inputStream() throws Exception { final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testEvaluate.py").getPath(); final File file = new File(filePath); - assertEquals(25, JythonScript.evaluate(new FileInputStream(file))); - assertEquals(100, JythonScript.evaluate(new FileInputStream(file), 10, 10)); - assertEquals(0, JythonScript.evaluate(new FileInputStream(file), 0, 0)); - assertEquals(-20, JythonScript.evaluate(new FileInputStream(file), -1, 20)); + assertEquals(25, jythonScript.evaluate(new FileInputStream(file))); + assertEquals(100, jythonScript.evaluate(new FileInputStream(file), 10, 10)); + assertEquals(0, jythonScript.evaluate(new FileInputStream(file), 0, 0)); + assertEquals(-20, jythonScript.evaluate(new FileInputStream(file), -1, 20)); } @Test void testExecute_inputStreamNull() { - assertThrows(JythonScriptException.class, () -> JythonScript.execute((InputStream) null)); + assertThrows(JythonScriptException.class, () -> jythonScript.execute((InputStream) null)); } @Test void testExecute_inputStreamEmpty() { - assertThrows(FileNotFoundException.class, () -> JythonScript.execute(new FileInputStream(new File("")))); - assertThrows(FileNotFoundException.class, () -> JythonScript.execute(new FileInputStream(new File(" ")))); - assertThrows(FileNotFoundException.class, () -> JythonScript.execute(new FileInputStream(new File("\n")))); - assertThrows(FileNotFoundException.class, () -> JythonScript.execute(new FileInputStream(new File("\t")))); - assertThrows(FileNotFoundException.class, () -> JythonScript.execute(new FileInputStream(new File("\r")))); + assertThrows(FileNotFoundException.class, () -> jythonScript.execute(new FileInputStream(new File("")))); + assertThrows(FileNotFoundException.class, () -> jythonScript.execute(new FileInputStream(new File(" ")))); + assertThrows(FileNotFoundException.class, () -> jythonScript.execute(new FileInputStream(new File("\n")))); + assertThrows(FileNotFoundException.class, () -> jythonScript.execute(new FileInputStream(new File("\t")))); + assertThrows(FileNotFoundException.class, () -> jythonScript.execute(new FileInputStream(new File("\r")))); } @Test void testExecute_inputStreamInvalid() { assertThrows(FileNotFoundException.class, () -> - JythonScript.execute(new FileInputStream(new File("/Users/test/notfound.py")))); + jythonScript.execute(new FileInputStream(new File("/Users/test/notfound.py")))); } @Test void testExecute_inputStreamValid() throws Exception { final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testExecute.py").getPath(); - JythonScript.execute(new FileInputStream(new File(filePath))); + jythonScript.execute(new FileInputStream(new File(filePath))); } @Test void testEvaluate_pycodeNull() { - assertThrows(JythonScriptException.class, () -> JythonScript.evaluate((PyCode) null)); + assertThrows(JythonScriptException.class, () -> jythonScript.evaluate((PyCode) null)); } @Test void testEvaluate_pycodeEmpty() { - assertThrows(JythonScriptException.class, () -> JythonScript.evaluate(new TestPyCode())); + assertThrows(JythonScriptException.class, () -> jythonScript.evaluate(new TestPyCode())); } @Test @@ -374,124 +381,124 @@ void testEvaluate_pycodeNoResult() { final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testResultNotFound.py").getPath(); assertThrows(JythonResultNotFoundException.class, () -> { - final PyCode compiledScript = JythonScript.compile(filePath); + final PyCode compiledScript = jythonScript.compile(filePath); - JythonScript.evaluate(compiledScript); + jythonScript.evaluate(compiledScript); }); } @Test void testEvaluate_pycodeValid() throws JythonScriptException { final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testEvaluate.py").getPath(); - final PyCode compiledScript = JythonScript.compile(filePath); + final PyCode compiledScript = jythonScript.compile(filePath); - assertEquals(25, JythonScript.evaluate(compiledScript)); - assertEquals(100, JythonScript.evaluate(compiledScript, 10, 10)); - assertEquals(0, JythonScript.evaluate(compiledScript, 0, 0)); - assertEquals(-20, JythonScript.evaluate(compiledScript, -1, 20)); + assertEquals(25, jythonScript.evaluate(compiledScript)); + assertEquals(100, jythonScript.evaluate(compiledScript, 10, 10)); + assertEquals(0, jythonScript.evaluate(compiledScript, 0, 0)); + assertEquals(-20, jythonScript.evaluate(compiledScript, -1, 20)); } @Test void testExecute_pycodeNull() { - assertThrows(JythonScriptException.class, () -> JythonScript.execute((PyCode) null)); + assertThrows(JythonScriptException.class, () -> jythonScript.execute((PyCode) null)); } @Test void testExecute_pycodeEmpty() { - assertThrows(JythonScriptException.class, () -> JythonScript.execute(new TestPyCode())); + assertThrows(JythonScriptException.class, () -> jythonScript.execute(new TestPyCode())); } @Test void testExecute_pycodeValid() throws JythonScriptException { final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testEvaluate.py").getPath(); - final PyCode compiledScript = JythonScript.compile(filePath); + final PyCode compiledScript = jythonScript.compile(filePath); - JythonScript.execute(compiledScript); + jythonScript.execute(compiledScript); } @Test void testExecute_oop() throws JythonScriptException { final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testOOP.py").getPath(); - final PyCode compiledScript = JythonScript.compile(filePath); + final PyCode compiledScript = jythonScript.compile(filePath); - JythonScript.execute(compiledScript, 10, 10); + jythonScript.execute(compiledScript, 10, 10); } @Test void testEvaluate_oop() throws JythonScriptException { final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testOOP.py").getPath(); - final PyCode compiledScript = JythonScript.compile(filePath); + final PyCode compiledScript = jythonScript.compile(filePath); - assertEquals(51, JythonScript.evaluate(compiledScript, 10, 10)); + assertEquals(51, jythonScript.evaluate(compiledScript, 10, 10)); } @Test void testEvaluate_returnBoolean() throws JythonScriptException { final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testReturnBoolean.py").getPath(); - final PyCode compiledScript = JythonScript.compile(filePath); + final PyCode compiledScript = jythonScript.compile(filePath); - assertEquals(false, JythonScript.evaluate(compiledScript)); - assertEquals(false, JythonScript.evaluate(compiledScript, false, false)); - assertEquals(true, JythonScript.evaluate(compiledScript, true, false)); - assertEquals(true, JythonScript.evaluate(compiledScript, false, true)); - assertEquals(true, JythonScript.evaluate(compiledScript, true, true)); + assertEquals(false, jythonScript.evaluate(compiledScript)); + assertEquals(false, jythonScript.evaluate(compiledScript, false, false)); + assertEquals(true, jythonScript.evaluate(compiledScript, true, false)); + assertEquals(true, jythonScript.evaluate(compiledScript, false, true)); + assertEquals(true, jythonScript.evaluate(compiledScript, true, true)); } @Test void testEvaluate_returnInteger() throws JythonScriptException { final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testEvaluate.py").getPath(); - final PyCode compiledScript = JythonScript.compile(filePath); + final PyCode compiledScript = jythonScript.compile(filePath); - assertEquals(25, JythonScript.evaluate(compiledScript)); - assertEquals(100, JythonScript.evaluate(compiledScript, 10, 10)); - assertEquals(0, JythonScript.evaluate(compiledScript, 0, 0)); - assertEquals(-20, JythonScript.evaluate(compiledScript, -1, 20)); + assertEquals(25, jythonScript.evaluate(compiledScript)); + assertEquals(100, jythonScript.evaluate(compiledScript, 10, 10)); + assertEquals(0, jythonScript.evaluate(compiledScript, 0, 0)); + assertEquals(-20, jythonScript.evaluate(compiledScript, -1, 20)); } @Test void testEvaluate_returnString() throws JythonScriptException { final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testReturnString.py").getPath(); - final PyCode compiledScript = JythonScript.compile(filePath); + final PyCode compiledScript = jythonScript.compile(filePath); - assertEquals("", JythonScript.evaluate(compiledScript)); - assertEquals("jython", JythonScript.evaluate(compiledScript, "jython", "test")); - assertEquals("jython", JythonScript.evaluate(compiledScript, "test", "jython")); - assertEquals("test", JythonScript.evaluate(compiledScript, "test")); + assertEquals("", jythonScript.evaluate(compiledScript)); + assertEquals("jython", jythonScript.evaluate(compiledScript, "jython", "test")); + assertEquals("jython", jythonScript.evaluate(compiledScript, "test", "jython")); + assertEquals("test", jythonScript.evaluate(compiledScript, "test")); } @Test void testEvaluate_returnFloat() throws JythonScriptException { final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testReturnFloat.py").getPath(); - final PyCode compiledScript = JythonScript.compile(filePath); + final PyCode compiledScript = jythonScript.compile(filePath); - assertEquals(20.6f, (Float) JythonScript.evaluate(compiledScript, 10.5, 10.1), DELTA); - assertEquals(0f, (Float) JythonScript.evaluate(compiledScript, 0.0, 0), DELTA); - assertEquals(19.236588f, (Float) JythonScript.evaluate(compiledScript, -1.5, 20.736587), DELTA); + assertEquals(20.6f, (Float) jythonScript.evaluate(compiledScript, 10.5, 10.1), DELTA); + assertEquals(0f, (Float) jythonScript.evaluate(compiledScript, 0.0, 0), DELTA); + assertEquals(19.236588f, (Float) jythonScript.evaluate(compiledScript, -1.5, 20.736587), DELTA); } @Test void testEvaluate_returnLong() throws JythonScriptException { final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testEvaluate.py").getPath(); - final PyCode compiledScript = JythonScript.compile(filePath); + final PyCode compiledScript = jythonScript.compile(filePath); - assertEquals(227963876171874L, JythonScript.evaluate(compiledScript, 75987958723958L, 3)); + assertEquals(227963876171874L, jythonScript.evaluate(compiledScript, 75987958723958L, 3)); } @Test void testEvaluate_returnList() throws JythonScriptException { final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testReturnList.py").getPath(); - final PyCode compiledScript = JythonScript.compile(filePath); + final PyCode compiledScript = jythonScript.compile(filePath); assertArrayEquals(new Object[] { "a", "b", "c", 1, 2, 3, "do", "re", "mi", 1.0f, 2.0f, 3.0f }, - (Object[]) JythonScript.evaluate(compiledScript)); + ((List) jythonScript.evaluate(compiledScript)).toArray()); } @Test @SuppressWarnings("unchecked") void testEvaluate_returnDict() throws JythonScriptException { final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testReturnDict.py").getPath(); - final PyCode compiledScript = JythonScript.compile(filePath); - final Map result = (Map) JythonScript.evaluate(compiledScript); + final PyCode compiledScript = jythonScript.compile(filePath); + final Map result = (Map) jythonScript.evaluate(compiledScript); assertNotNull(result); assertEquals(3, result.size()); @@ -504,8 +511,8 @@ void testEvaluate_returnDict() throws JythonScriptException { @SuppressWarnings("unchecked") void testEvaluate_returnSet() throws JythonScriptException { final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testReturnSet.py").getPath(); - final PyCode compiledScript = JythonScript.compile(filePath); - final Set result = (Set) JythonScript.evaluate(compiledScript); + final PyCode compiledScript = jythonScript.compile(filePath); + final Set result = (Set) jythonScript.evaluate(compiledScript); assertNotNull(result); assertEquals(9, result.size()); @@ -519,19 +526,39 @@ void testEvaluate_returnSet() throws JythonScriptException { @Test void testEvaluate_unsupportedReturnType() throws JythonScriptException { final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testReturnUnsupportedType.py").getPath(); - final Object result = JythonScript.evaluate(filePath); + final Exception exception = assertThrows(JythonScriptRuntimeException.class, + () -> jythonScript.evaluate(filePath)); + assertEquals("Serialization not supported for type [PyInstance].", exception.getMessage()); + } + + @Test + void testEvaluate_typeNotAsExpected() throws JythonScriptException { + final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testEvaluate.py").getPath(); + final PyCode compiledScript = jythonScript.compile(filePath); + + final Exception exception = assertThrows(JythonScriptException.class, + () -> jythonScript.evaluate(TestReturnType.class, compiledScript)); + assertEquals("Result of type [Integer] cannot be assigned to expected type [com.github.adchilds." + + "jython.JythonScriptTest$TestReturnType]", exception.getMessage()); + } + + @Test + void testEvaluate_typeAsExpected() throws JythonScriptException { + final String filePath = ClassLoader.getSystemResource(JYTHON_SCRIPT_BASE_PATH + "testEvaluate.py").getPath(); + final PyCode compiledScript = jythonScript.compile(filePath); + + final Integer result = jythonScript.evaluate(Integer.class, compiledScript); assertNotNull(result); - assertTrue(result instanceof PyInstance); + assertEquals(25, result); } @Test - void testConstructorIsPrivate() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { - final Constructor constructor = JythonScript.class.getDeclaredConstructor(); + void testConstructorIsPrivate() throws NoSuchMethodException { + final Constructor constructor = + JythonScript.class.getDeclaredConstructor(PyObjectSerializationFactory.class); assertTrue(Modifier.isPrivate(constructor.getModifiers())); - constructor.setAccessible(true); - constructor.newInstance(); } /** @@ -541,7 +568,7 @@ void testConstructorIsPrivate() throws NoSuchMethodException, IllegalAccessExcep * @author Adam Childs * @since 2.0 */ - private class TestPyCode extends PyBaseCode { + private static class TestPyCode extends PyBaseCode { private TestPyCode() { co_freevars = new String[]{ "test", "throws exception", "when this array has contents" }; @@ -554,4 +581,30 @@ protected PyObject interpret(PyFrame pyFrame, ThreadState threadState) { } + /** + * Example test class for deserializing a Jython script custom return type. + * + * @author Adam Childs + * @since 3.0 + */ + private static class TestReturnType { + + private final String foo; + private final Integer bar; + + public TestReturnType(final String foo, final Integer bar) { + this.foo = foo; + this.bar = bar; + } + + public String getFoo() { + return foo; + } + + public Integer getBar() { + return bar; + } + + } + } \ No newline at end of file diff --git a/src/test/java/com/github/adchilds/jython/exception/JythonResultNotFoundExceptionTest.java b/src/test/java/com/github/adchilds/jython/exception/JythonResultNotFoundExceptionTest.java index 73670dc..2abd13d 100644 --- a/src/test/java/com/github/adchilds/jython/exception/JythonResultNotFoundExceptionTest.java +++ b/src/test/java/com/github/adchilds/jython/exception/JythonResultNotFoundExceptionTest.java @@ -2,6 +2,7 @@ import com.github.adchilds.jython.JythonScript; import com.github.adchilds.jython.JythonScriptTest; +import com.github.adchilds.jython.serialization.DefaultPyObjectSerializationFactory; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -14,6 +15,10 @@ */ class JythonResultNotFoundExceptionTest { + private final JythonScript jythonScript = new JythonScript.Builder() + .serializationFactory(new DefaultPyObjectSerializationFactory()) + .build(); + private final String EXCEPTION_MESSAGE = "An exception was thrown."; @Test @@ -21,7 +26,7 @@ void testJythonResultNotFoundException_resultNotFound() { final String filePath = ClassLoader.getSystemResource(JythonScriptTest.JYTHON_SCRIPT_BASE_PATH + "testResultNotFound.py").getPath(); - assertThrows(JythonResultNotFoundException.class, () -> JythonScript.evaluate(filePath)); + assertThrows(JythonResultNotFoundException.class, () -> jythonScript.evaluate(filePath)); } @Test @@ -29,7 +34,7 @@ void testJythonResultNotFoundException_resultNotFoundMultiArgs() { final String filePath = ClassLoader.getSystemResource(JythonScriptTest.JYTHON_SCRIPT_BASE_PATH + "testResultNotFound.py").getPath(); - assertThrows(JythonResultNotFoundException.class, () -> JythonScript.evaluate(filePath, 0, 1, 2, 3, 4, 5)); + assertThrows(JythonResultNotFoundException.class, () -> jythonScript.evaluate(filePath, 0, 1, 2, 3, 4, 5)); } @Test diff --git a/src/test/java/com/github/adchilds/jython/exception/JythonScriptExceptionTest.java b/src/test/java/com/github/adchilds/jython/exception/JythonScriptExceptionTest.java index c3acf3d..6d2d11c 100644 --- a/src/test/java/com/github/adchilds/jython/exception/JythonScriptExceptionTest.java +++ b/src/test/java/com/github/adchilds/jython/exception/JythonScriptExceptionTest.java @@ -1,6 +1,7 @@ package com.github.adchilds.jython.exception; import com.github.adchilds.jython.JythonScript; +import com.github.adchilds.jython.serialization.DefaultPyObjectSerializationFactory; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -13,11 +14,15 @@ */ class JythonScriptExceptionTest { + private final JythonScript jythonScript = new JythonScript.Builder() + .serializationFactory(new DefaultPyObjectSerializationFactory()) + .build(); + private final String EXCEPTION_MESSAGE = "An exception was thrown."; @Test void testJythonScriptException_invalidFile() { - assertThrows(JythonScriptException.class, () -> JythonScript.compile("")); + assertThrows(JythonScriptException.class, () -> jythonScript.compile("")); } @Test diff --git a/src/test/java/com/github/adchilds/jython/exception/JythonScriptNotFoundExceptionTest.java b/src/test/java/com/github/adchilds/jython/exception/JythonScriptNotFoundExceptionTest.java index 5c689ee..9dc03cd 100644 --- a/src/test/java/com/github/adchilds/jython/exception/JythonScriptNotFoundExceptionTest.java +++ b/src/test/java/com/github/adchilds/jython/exception/JythonScriptNotFoundExceptionTest.java @@ -1,6 +1,7 @@ package com.github.adchilds.jython.exception; import com.github.adchilds.jython.JythonScript; +import com.github.adchilds.jython.serialization.DefaultPyObjectSerializationFactory; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -13,11 +14,15 @@ */ class JythonScriptNotFoundExceptionTest { + private final JythonScript jythonScript = new JythonScript.Builder() + .serializationFactory(new DefaultPyObjectSerializationFactory()) + .build(); + private final String EXCEPTION_MESSAGE = "An exception was thrown."; @Test void testJythonScriptNotFoundException_invalidFile() { - assertThrows(JythonScriptNotFoundException.class, () -> JythonScript.evaluate("invalidFile.py")); + assertThrows(JythonScriptNotFoundException.class, () -> jythonScript.evaluate("invalidFile.py")); } @Test diff --git a/src/test/java/com/github/adchilds/jython/serialization/DefaultPyObjectSerializationFactoryTest.java b/src/test/java/com/github/adchilds/jython/serialization/DefaultPyObjectSerializationFactoryTest.java new file mode 100644 index 0000000..d2f1a37 --- /dev/null +++ b/src/test/java/com/github/adchilds/jython/serialization/DefaultPyObjectSerializationFactoryTest.java @@ -0,0 +1,48 @@ +package com.github.adchilds.jython.serialization; + +import com.github.adchilds.jython.exception.JythonScriptRuntimeException; +import org.junit.jupiter.api.Test; +import org.python.core.PyBoolean; +import org.python.core.PyObject; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for the {@link DefaultPyObjectSerializationFactory} class. + * + * @author Adam Childs + * @since 3.0.0 + */ +class DefaultPyObjectSerializationFactoryTest { + + private final PyObjectSerializationFactory factory = new DefaultPyObjectSerializationFactory(); + + @Test + void testGetSerializer_nullObject() { + assertNull(factory.getSerializer(null)); + } + + @Test + void testGetSerializer_unsupportedType() { + final Exception exception = assertThrows(JythonScriptRuntimeException.class, + () -> factory.getSerializer(new UnsupportedType())); + + assertEquals("Serialization not supported for type [UnsupportedType].", exception.getMessage()); + } + + @Test + void testGetSerializer_supportedType() { + final Object result = factory.getSerializer(new PyBoolean(true)) + .serialize(); + assertTrue(result instanceof Boolean); + assertTrue((Boolean) result); + } + + /** + * + */ + private static class UnsupportedType extends PyObject { + + } + +} \ No newline at end of file diff --git a/src/test/java/com/github/adchilds/jython/serialization/PyBooleanSerializerTest.java b/src/test/java/com/github/adchilds/jython/serialization/PyBooleanSerializerTest.java new file mode 100644 index 0000000..c136e4c --- /dev/null +++ b/src/test/java/com/github/adchilds/jython/serialization/PyBooleanSerializerTest.java @@ -0,0 +1,47 @@ +package com.github.adchilds.jython.serialization; + +import com.github.adchilds.jython.exception.JythonScriptRuntimeException; +import org.junit.jupiter.api.Test; +import org.python.core.PyBoolean; +import org.python.core.PyString; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for the {@link PyBooleanSerializer} class. + * + * @author Adam Childs + * @since 3.0.0 + */ +class PyBooleanSerializerTest { + + @Test + void testSerialize_nullValue() { + final Serializer serializer = new PyBooleanSerializer(null); + + assertNull(serializer.serialize()); + } + + @Test + void testSerialize_invalidType() { + final Serializer serializer = new PyBooleanSerializer(new PyString("test")); + + final Exception exception = assertThrows(JythonScriptRuntimeException.class, + () -> serializer.serialize()); + assertEquals("Cannot serialize type [PyString] as Boolean.", exception.getMessage()); + } + + @Test + void testSerialize_false() { + final Serializer serializer = new PyBooleanSerializer(new PyBoolean(false)); + + assertFalse(serializer.serialize()); + } + + @Test + void testSerialize_true() { + final Serializer serializer = new PyBooleanSerializer(new PyBoolean(true)); + assertTrue(serializer.serialize()); + } + +} \ No newline at end of file diff --git a/src/test/java/com/github/adchilds/jython/serialization/PyFloatSerializerTest.java b/src/test/java/com/github/adchilds/jython/serialization/PyFloatSerializerTest.java new file mode 100644 index 0000000..20d3a7e --- /dev/null +++ b/src/test/java/com/github/adchilds/jython/serialization/PyFloatSerializerTest.java @@ -0,0 +1,57 @@ +package com.github.adchilds.jython.serialization; + +import com.github.adchilds.jython.exception.JythonScriptRuntimeException; +import org.junit.jupiter.api.Test; +import org.python.core.PyFloat; +import org.python.core.PyString; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for the {@link PyFloatSerializer} class. + * + * @author Adam Childs + * @since 3.0.0 + */ +class PyFloatSerializerTest { + + private static final double DELTA = 1E-15; + + @Test + void testSerialize_nullValue() { + final Serializer serializer = new PyFloatSerializer(null); + + assertNull(serializer.serialize()); + } + + @Test + void testSerialize_invalidType() { + final Serializer serializer = new PyFloatSerializer(new PyString("test")); + + final Exception exception = assertThrows(JythonScriptRuntimeException.class, + () -> serializer.serialize()); + assertEquals("Cannot serialize type [PyString] as Float.", exception.getMessage()); + } + + @Test + void testSerialize_negative() { + final Serializer serializer = new PyFloatSerializer(new PyFloat(-1.25)); + + assertEquals(-1.25, serializer.serialize(), DELTA); + } + + @Test + void testSerialize_positive() { + final Serializer serializer = new PyFloatSerializer(new PyFloat(1.25)); + + assertEquals(1.25, serializer.serialize(), DELTA); + } + + @Test + void testSerialize_floatMax() { + final Serializer serializer = new PyFloatSerializer(new PyFloat(Float.MAX_VALUE)); + + assertEquals(Float.MAX_VALUE, serializer.serialize(), DELTA); + } + +} \ No newline at end of file diff --git a/src/test/java/com/github/adchilds/jython/serialization/PyIntegerSerializerTest.java b/src/test/java/com/github/adchilds/jython/serialization/PyIntegerSerializerTest.java new file mode 100644 index 0000000..31e16f3 --- /dev/null +++ b/src/test/java/com/github/adchilds/jython/serialization/PyIntegerSerializerTest.java @@ -0,0 +1,55 @@ +package com.github.adchilds.jython.serialization; + +import com.github.adchilds.jython.exception.JythonScriptRuntimeException; +import org.junit.jupiter.api.Test; +import org.python.core.PyInteger; +import org.python.core.PyString; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Unit tests for the {@link PyIntegerSerializer} class. + * + * @author Adam Childs + * @since 3.0.0 + */ +class PyIntegerSerializerTest { + + @Test + void testSerialize_nullValue() { + final Serializer serializer = new PyIntegerSerializer(null); + + assertNull(serializer.serialize()); + } + + @Test + void testSerialize_invalidType() { + final Serializer serializer = new PyIntegerSerializer(new PyString("test")); + + final Exception exception = assertThrows(JythonScriptRuntimeException.class, + () -> serializer.serialize()); + assertEquals("Cannot serialize type [PyString] as Integer.", exception.getMessage()); + } + + @Test + void testSerialize_negative() { + final Serializer serializer = new PyIntegerSerializer(new PyInteger(-10)); + + assertEquals(-10, serializer.serialize()); + } + + @Test + void testSerialize_positive() { + final Serializer serializer = new PyIntegerSerializer(new PyInteger(10)); + + assertEquals(10, serializer.serialize()); + } + + @Test + void testSerialize_integerMax() { + final Serializer serializer = new PyIntegerSerializer(new PyInteger(Integer.MAX_VALUE)); + + assertEquals(Integer.MAX_VALUE, serializer.serialize()); + } + +} \ No newline at end of file