JavaScript na Javi

Nedavni post JavaLobby Top 10 neiskorištenih značajki na Javi bio je izuzetno popularan. U vrijeme pisanja ovog članka, to je najbolje rangirani post u kategoriji DZone Top Links. Uz to je objavljen i odgovor na njega. U oba bloga postoji mnogo zanimljivih zapažanja o nedovoljno iskorištenim značajkama Jave, a ja se slažem s nekima više nego s drugima. Međutim, stavka koja mi je zaista privukla pažnju bila je tvrdnja da je Java SE 6 jedna od najneiskorištenijih Java značajki.

Stvarno uživam u radu s Javom SE 6, a već sam nekoliko puta pisao ili pisao blogove o značajkama Java SE 6. U ovom objavljivanju na blogu namjeravam pokazati dio sposobnosti Java SE 6 za hostiranje izvršenja JavaScript koda.

Većina programera Java i programera JavaScript razumije da osim četiri slova "JAVA", JavaScript i Java imaju vrlo malo zajedničkog, osim neke baštine slične C-u. Ipak, ponekad može biti korisno pokrenuti skriptni jezik iz Java koda, a Java SE 6 to omogućuje.

Paket javax.script predstavljen je s Javom SE 6 i uključuje klase, sučelja i provjerenu iznimku povezanu s korištenjem skriptnih mehanizama u Javi. Ovo objavljivanje na blogu usredotočit će se na ScriptEngineFactory, ScriptEngineManager, ScriptEngine i ScriptException.

Jedna od prvih stvari koje bi netko mogao učiniti jest utvrditi koji su skriptni motori već dostupni. Sljedeći isječak koda pokazuje koliko je to lako učiniti s 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 + " "); } } 

Kôd prikazan gore daje izlaz poput onog prikazanog na sljedećem snimku zaslona.

Kao što ova slika pokazuje, Mozilla Rhino JavaScript motor uključen je u Sunovu Java SE 6. Također vidimo i neka "uobičajena imena" koja su povezana s ovim određenim motorom. Bilo koje od ovih imena može se koristiti za traženje ovog motora. U kasnijim primjerima u ovom postu koristit ću uobičajeni naziv "js" za ovo traženje.

Sljedeći uzorak koda iskoristit će ponuđeni Rhino JavaScript mehanizam za izvršavanje nekog JavaScript koda iz Java koda. U ovom ćemo slučaju iskoristiti JavaScript-ovu funkciju toExponential.

 /** * 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()); } } 

Gornji kod izravno poziva JavaScript pomoću ScriptEngine.eval (String) metode za procjenu pruženog Stringa koji sadrži JavaScript sintaksu. Prije pozivanja evalmetode, dva parametra se "prosljeđuju" (vežu) na JavaScript kôd putem poziva ScriptEngine.put (String, Object). Rezultatnom objektu izvršenog JavaScripta pristupa se u Java kodu pomoću poziva ScriptEngine.get (String).

Da toExponentialbih demonstrirao gornji kod pomoću funkcije, upotrijebit ću sljedeći "klijentski" kôd.

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); 

Kada se gornji kôd pokrene protiv ranije prikazane metode writeNumberAsExponential i upotrijebi JavaScript, izlaz se čini sličan onome prikazanom na sljedećoj snimci zaslona.

Ovaj je primjer dovoljan da pokaže kako je lako pozvati JavaScript funkcionalnost unutar Java SE 6. Međutim, ovo bi se moglo primijeniti još općenitije kao što će pokazati sljedeća dva primjera. Prvi primjer prikazuje pozivanje relativno proizvoljnog JavaScript-a bez prosljeđenih / vezanih parametara, a drugi primjer prikazuje pozivanje relativno proizvoljnog JavaScript-a s prosljeđenim / vezanim parametrima.

Relativno proizvoljan JavaScript niz može se obraditi kodom sličnim onome koji je sljedeći prikazan.

 /** * 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; } 

Gornji kod pruža prilično malu fleksibilnost u pogledu JavaScript-a koji se može obraditi. Ovo vjerojatno nije najbolja ideja za produkcijski kod, ali olakšava demonstraciju upotrebe različitih JavaScript značajki unutar Jave.

Prvi primjer koji koristi ovu relativno proizvoljnu JavaScript obradu iskorištava JavaScriptov objekt Date. Sljedeći je prikazan uzorak koda.

 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) ); 

Ovaj kôd navodi da treba dohvatiti JavaScript datum (koji će biti trenutni datum), a taj mjesec, datum mjeseca i cijelu godinu treba izdvojiti iz tog instanciranog datuma. Izlaz za ovo pojavljuje se sljedeći.

Posljednji je primjer radio na proizvoljnom JavaScript nizu, ali nije koristio nikakve parametre. Sljedeći primjer pokazuje pružanje parametara za ovu proizvoljnu obradu JavaScript niza, jer pokazuje upotrebu JavaScript-ove funkcije pow. Kôd za ovaj primjer naveden je sljedeći.

 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) ); 

Izlaz iz izvođenja ovog primjera prikazan je na sljedećoj snimci zaslona.

Kao posljednji primjer ovog objavljivanja na blogu, demonstriram standardni toString()izlaz ScriptExceptiondeklariranog u nekim od prethodnih primjera. ScriptEngine.evalMetoda baca ovo provjeri iznimku ako postoji pogreška u izvršavanju / vrednovanje navedenu skriptu. Ova metoda također baca NullPointerException ako je navedeni String null. Kôd koji se koristi za forsiranje pogreške skripte prikazan je sljedeći.

 /** * 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)); } 

Ovaj kod pruža besmislenu skriptu (u smislu sintakse JavaScript-a), ali upravo je to potrebno za demonstraciju ScriptException.toString (), koja se poziva kao dio rukovanja iznimkama u gore prikazanoj metodi za rukovanje proizvoljnim JavaScript nizom . Kada se kôd izvrši, vidimo informacije o iznimci kao što je prikazano na sljedećoj slici.

Dio izlaza koji dolazi iz ScriptException.toString()dijela je koji navodi: "javax.script.ScriptException: sun.org.mozilla.javascript.internal.EvaluatorException: nedostaje; prije izraza (# 1) u retku broj 1."

ScriptExceptionSadrži naziv datoteke, broj linija i broj stupca izuzetak, što je posebno korisno ako je osiguran datoteka s JavaScript kodom za ocjenjivanje.

Zaključak

Java SE 6 olakšava upotrebu JavaScript-a unutar Java koda. Ostali skriptni motori također se mogu povezati s Javom, ali zgodno je imati jedan koji se nudi odmah uz Mozilla Rhino.

Kompletna snimka koda i izlaznog zaslona

Za cjelovitost ovdje na jednom mjestu uključujem kompletni popis kodova i rezultirajući izlaz nakon toga.

JavaScriptInJavaExample.java