JavaScript en Java

La publicación reciente de JavaLobby Las 10 funciones principales no utilizadas en Java ha sido extremadamente popular. En el momento de escribir este artículo, es la publicación mejor clasificada en la categoría DZone Top Links. Además, también se ha publicado una respuesta. Hay muchas observaciones interesantes sobre las características infrautilizadas en Java en las publicaciones de ambos blogs y estoy de acuerdo con algunas más que con otras. Sin embargo, el elemento que realmente me llamó la atención fue la afirmación de que Java SE 6 es una de las características de Java que menos se utiliza.

Realmente disfruto trabajar con Java SE 6 y he escrito o blogueado sobre las características de Java SE 6 varias veces en el pasado. En esta publicación de blog, tengo la intención de demostrar una parte de la capacidad de Java SE 6 para ejecutar código JavaScript.

La mayoría de los desarrolladores de Java y los desarrolladores de JavaScript entienden que además de las cuatro letras "JAVA", JavaScript y Java tienen muy poco en común, aparte de una herencia similar a C Aún así, a veces puede ser útil ejecutar un lenguaje de secuencias de comandos desde el código Java y Java SE 6 lo permite.

El paquete javax.script se introdujo con Java SE 6 e incluye clases, interfaces y una excepción verificada relacionada con el uso de motores de secuencias de comandos en Java. Esta publicación de blog se centrará en ScriptEngineFactory, ScriptEngineManager, ScriptEngine y ScriptException.

Una de las primeras cosas que uno podría querer hacer es determinar qué motores de secuencias de comandos ya están disponibles. El siguiente fragmento de código muestra lo fácil que es hacer esto con Java SE 6.

final ScriptEngineManager manager = new ScriptEngineManager(); for (final ScriptEngineFactory scriptEngine : manager.getEngineFactories()) { System.out.println( scriptEngine.getEngineName() + " (" + scriptEngine.getEngineVersion() + ")" ); System.out.println( "\tLanguage: " + scriptEngine.getLanguageName() + "(" + scriptEngine.getLanguageVersion() + ")" ); System.out.println("\tCommon Names/Aliases: "); for (final String engineAlias : scriptEngine.getNames()) { System.out.println(engineAlias + " "); } } 

El código que se muestra arriba genera una salida como la que se muestra en la siguiente captura de pantalla.

Como muestra esta imagen, el motor de JavaScript Mozilla Rhino se incluye con Java SE 6. de Sun. También vemos algunos "nombres comunes" asociados con este motor en particular. Cualquiera de estos nombres se puede utilizar para buscar este motor. En ejemplos posteriores de esta publicación, usaré el nombre común "js" para esta búsqueda.

La siguiente muestra de código aprovechará el motor de JavaScript de Rhino proporcionado para ejecutar código JavaScript desde código Java. En este caso, aprovecharemos la función toExponential de JavaScript.

 /** * Write number in exponential form. * * @param numberToWriteInExponentialForm The number to be represented in * exponential form. * @param numberDecimalPlaces The number of decimal places to be used in the * exponential representation. */ public static void writeNumberAsExponential( final Number numberToWriteInExponentialForm, final int numberDecimalPlaces) { final ScriptEngine engine = manager.getEngineByName("js"); try { engine.put("inputNumber", numberToWriteInExponentialForm); engine.put("decimalPlaces", numberDecimalPlaces); engine.eval("var outputNumber = inputNumber.toExponential(decimalPlaces);"); final String exponentialNumber = (String) engine.get("outputNumber"); System.out.println("Number: " + exponentialNumber); } catch (ScriptException scriptException) { LOGGER.severe( "ScriptException encountered trying to write exponential: " + scriptException.toString()); } } 

El código anterior invoca directamente a JavaScript mediante el método ScriptEngine.eval (String) para evaluar la cadena proporcionada que contiene la sintaxis de JavaScript. Antes de la invocación del evalmétodo, dos parámetros se "pasan" (enlazados) al código JavaScript a través de llamadas ScriptEngine.put (String, Object). Se accede al objeto de resultado del JavaScript ejecutado en el código Java mediante una llamada ScriptEngine.get (String).

Para demostrar el código anterior usando la toExponentialfunción, usaré el siguiente código de "cliente".

final int sourceNumber = 675456; writeNumberAsExponential(sourceNumber, 1, System.out); writeNumberAsExponential(sourceNumber, 2, System.out); writeNumberAsExponential(sourceNumber, 3, System.out); writeNumberAsExponential(sourceNumber, 4, System.out); writeNumberAsExponential(sourceNumber, 5, System.out); 

Cuando el código anterior se ejecuta con el método writeNumberAsExponential mostrado anteriormente y se emplea JavaScript, la salida parece similar a la que se muestra en la siguiente captura de pantalla.

Este ejemplo es suficiente para demostrar lo fácil que es invocar la funcionalidad de JavaScript desde Java SE 6. Sin embargo, esto podría implementarse de manera aún más genérica, como lo demostrarán los dos ejemplos siguientes. El primer ejemplo muestra la invocación de JavaScript relativamente arbitrario sin parámetros pasados ​​/ enlazados y el segundo ejemplo demuestra la invocación de JavaScript relativamente arbitrario con parámetros pasados ​​/ enlazados.

Una cadena de JavaScript relativamente arbitraria se puede procesar con un código similar al que se muestra a continuación.

 /** * Process the passed-in JavaScript script that should include an assignment * to a variable with the name prescribed by the provided nameOfOutput and * may include parameters prescribed by inputParameters. * * @param javaScriptCodeToProcess The String containing JavaScript code to * be evaluated. This String is not checked for any type of validity and * might possibly lead to the throwing of a ScriptException, which would * be logged. * @param nameOfOutput The name of the output variable associated with the * provided JavaScript script. * @param inputParameters Optional map of parameter names to parameter values * that might be employed in the provided JavaScript script. This map * may be null if no input parameters are expected in the script. */ public static Object processArbitraryJavaScript( final String javaScriptCodeToProcess, final String nameOfOutput, final Map inputParameters) { Object result = null; final ScriptEngine engine = manager.getEngineByName("js"); try { if (inputParameters != null) { for (final Map.Entry parameter : inputParameters.entrySet()) { engine.put(parameter.getKey(), parameter.getValue()); } } engine.eval(javaScriptCodeToProcess); result = engine.get(nameOfOutput); } catch (ScriptException scriptException) { LOGGER.severe( "ScriptException encountered trying to write arbitrary JavaScript '" + javaScriptCodeToProcess + "': " + scriptException.toString()); } return result; } 

El código anterior proporciona bastante flexibilidad en términos del JavaScript que se puede procesar. Probablemente esta no sea la mejor idea para el código de producción, pero facilita la demostración del uso de varias funciones de JavaScript dentro de Java.

El primer ejemplo que utiliza este procesamiento de JavaScript relativamente arbitrario aprovecha el objeto Date de JavaScript. El código de muestra se muestra a continuación.

 System.out.println( "Today's Date: " + processArbitraryJavaScript( "var date = new Date(); var month = (date.getMonth()+1).toFixed(0)", "month", null) + "/" + processArbitraryJavaScript( "var date = new Date(); var day = date.getDate().toFixed(0)", "day", null) + "/" + processArbitraryJavaScript( "var date = new Date(); var year = date.getFullYear().toFixed(0)", "year", null) ); 

Este código especifica que se debe recuperar una fecha de JavaScript (que será la fecha actual) y que el mes, la fecha del mes y el año completo deben extraerse de esa fecha instanciada. La salida para esto aparece a continuación.

El último ejemplo funcionó en una cadena JavaScript arbitraria pero no usó ningún parámetro. El siguiente ejemplo demuestra el suministro de parámetros a este procesamiento arbitrario de cadenas de JavaScript, ya que demuestra el uso de la función pow de JavaScript. El código de este ejemplo se enumera a continuación.

 final Map exponentParameters = new HashMap(); exponentParameters.put("base", 2); exponentParameters.put("exponent", 5); System.out.println( "2 to the 5 is: " + processArbitraryJavaScript( "var answer = Math.pow(base,exponent)", "answer", exponentParameters) ); 

El resultado de ejecutar este ejemplo se muestra en la siguiente captura de pantalla.

Para mi ejemplo final de esta publicación en el blog, demuestro el toString()resultado estándar de lo ScriptExceptiondeclarado en algunos de los ejemplos anteriores. El ScriptEngine.evalmétodo lanza esta excepción marcada si hay un error al ejecutar / evaluar la secuencia de comandos proporcionada. Este método también arroja una NullPointerException si la cadena proporcionada es nula. El código utilizado para forzar un error de secuencia de comandos se muestra a continuación.

 /** * Intentionally cause script handling error to show the type of information * that a ScriptException includes. */ public static void testScriptExceptionHandling() { System.out.println(processArbitraryJavaScript("Garbage In", "none", null)); } 

Este código proporciona una secuencia de comandos sin sentido (en términos de sintaxis de JavaScript), pero eso es exactamente lo que se necesita para demostrar ScriptException.toString (), que se llama como parte del manejo de excepciones en el método que se muestra arriba para manejar una cadena JavaScript arbitraria. . Cuando se ejecuta el código, vemos la información de la excepción como se muestra en la siguiente imagen.

La parte de la salida que proviene ScriptException.toString()es la parte que dice: "javax.script.ScriptException: sun.org.mozilla.javascript.internal.EvaluatorException: missing; antes de la declaración (# 1) en la línea número 1."

El ScriptExceptioncontiene el nombre del archivo, número de línea, y el número de columna de la excepción, que es especialmente útil si se proporciona un archivo con código JavaScript para su evaluación.

Conclusión

Java SE 6 simplifica el uso de JavaScript dentro del código Java. Otros motores de secuencias de comandos también pueden asociarse con Java, pero es útil tener uno listo para usar con Mozilla Rhino.

Instantánea de pantalla de salida y código completo

Para completar, incluyo la lista de códigos completa en un lugar aquí y la salida resultante después de eso.

JavaScriptInJavaExample.java