Is there a way to make a custom implementation of Nashorn JSObject work with Object.keys()? - java-8

I recently asked this question How can I pass a proper method reference in so Nashorn can execute it? and got an answer that helped me get much further along with my project, but I discovered a limitation around providing a custom JSObject implementation that I don't know how to resolve.
Given this simple working JSObject that can handle most of the methods JS would invoke on it such as map:
import javax.script.*;
import jdk.nashorn.api.scripting.*;
import java.util.*;
import java.util.function.*;
public class scratch_6 {
public static void main(String[] args) throws Exception {
ScriptEngineManager m = new ScriptEngineManager();
ScriptEngine e = m.getEngineByName("nashorn");
// The following JSObject wraps this list
List<Object> l = new ArrayList<>();
l.add("hello");
l.add("world");
l.add(true);
l.add(1);
JSObject jsObj = new AbstractJSObject() {
#Override
public Object getMember(String name) {
if (name.equals("map")) {
// return a functional interface object - nashorn will treat it like
// script function!
final Function<JSObject, Object> jsObjectObjectFunction = callback -> {
List<Object> res = new ArrayList<>();
for (Object obj : l) {
// call callback on each object and add the result to new list
res.add(callback.call(null, obj));
}
// return fresh list as result of map (or this could be another wrapper)
return res;
};
return jsObjectObjectFunction;
} else {
// unknown property
return null;
}
}
};
e.put("obj", jsObj);
// map each String to it's uppercase and print result of map
e.eval("print(obj.map(function(x) '\"'+x.toString()+'\"'))");
//PROBLEM
//e.eval("print(Object.keys(obj))");
}
}
If you uncomment the last line where Object.keys(obj) is called, it will fail with the error ... is not an Object.
This appears to be because Object.keys() [ NativeObject.java:376 ] only checks whether the object is an instance of ScriptObject or of ScriptObjectMirror. If it is neither of those things, it throws the notAnObject error. :(

Ideally, user implemented JSObject objects should be exactly equivalent to script objects. But, user implemented JSObjects are almost script objects - but not quite. This is documented here -> https://wiki.openjdk.java.net/display/Nashorn/Nashorn+jsr223+engine+notes
Object.keys is one such case where it breaks. However, if you just want for..in javascript iteration support for your objects, you can implement JSObject.keySet in your class.
Example code:
import javax.script.*;
import jdk.nashorn.api.scripting.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
ScriptEngineManager m = new ScriptEngineManager();
ScriptEngine e = m.getEngineByName("nashorn");
// This JSObject wraps the following Properties object
Properties props = System.getProperties();
JSObject jsObj = new AbstractJSObject() {
#Override
public Set<String> keySet() {
return props.stringPropertyNames();
}
#Override
public Object getMember(String name) {
return props.getProperty(name);
}
};
e.put("obj", jsObj);
e.eval("for (i in obj) print(i, ' = ', obj[i])");
}
}

Related

Mockito, how to mock call by reference method on same class

Why I can not mock callRefMethod method (call method by reference) on below code? The problem is real method of callRefMethod always being called.
public class ManageUserService {
public void callRefMethod(List<String> lsStr, boolean flag){
if (flag){
lsStr.add("one");
lsStr.add("two");
}
}
public void methodA(){
List<String> lsStr = new ArrayList<>();
lsStr.add("zero");
this.callRefMethod(lsStr, true);
for(String str : lsStr){
System.out.println(str);
}
}
}
Unit tests:
public class ManageUserServiceTest {
#InjectMocks
private ManageUserService manageUserService;
private AutoCloseable closeable;
#BeforeEach
public void init() {
closeable = MockitoAnnotations.openMocks(this);
}
#AfterEach
void closeService() throws Exception {
closeable.close();
}
#Test
void methodATest(){
List<String> lsData = new ArrayList<>();
lsData.add("start");
ManageUserService manageUserServiceA = new ManageUserService();
ManageUserService userSpy = spy(manageUserServiceA);
doNothing().when(userSpy).callRefMethod(lsData, true);
userSpy.methodA();
verify(userSpy).callRefMethod(ArgumentMatchers.any(ArrayList.class), ArgumentMatchers.any(Boolean.class));
}
}
The result :
zero
one
two
The problem is the difference between the list you're creating in the test method, which is used to match the expected parameters when "doing nothing":
List<String> lsData = new ArrayList<>();
lsData.add("start");
...
doNothing().when(userSpy).callRefMethod(lsData, true);
and the list created in the tested method, passed to the spy object:
List<String> lsStr = new ArrayList<>();
lsStr.add("zero");
this.callRefMethod(lsStr, true);
You're telling Mockito to doNothing if the list is: ["start"], but such list is never passed to the callRefMethod. ["zero"] is passed there, which does not match the expected params, so actual method is called.
Mockito uses equals to compare the actual argument with an expected parameter value - see: the documentation. To work around that ArgumentMatchers can be used.
You can either fix the value added to the list in the test or match the expected parameter in a less strict way (e.g. using anyList() matcher).
ok i did it by using : where manageUserServiceOne is spy of ManageUserService class
void methodATest(){
List<String> lsData = new ArrayList<>();
lsData.add("start");
doAnswer((invocation) -> {
System.out.println(invocation.getArgument(0).toString());
List<String> lsModify = invocation.getArgument(0);
lsModify.add("mockA");
lsModify.add("mockB");
return null;
}).when(manageUserServiceOne).callRefMethod(anyList(), anyBoolean());
manageUserServiceOne.methodA();
verify(manageUserServiceOne).callRefMethod(ArgumentMatchers.any(ArrayList.class), ArgumentMatchers.any(Boolean.class));
}

Get a specific service implementation based on a parameter

In my Sling app I have data presenting documents, with pages, and content nodes. We mostly server those documents as HTML, but now I would like to have a servlet to serve these documents as PDF and PPT.
Basically, I thought about implementing the factory pattern : in my servlet, dependending on the extension of the request (pdf or ppt), I would get from a DocumentBuilderFactory, the proper DocumentBuilder implementation, either PdfDocumentBuilder or PptDocumentBuilder.
So first I had this:
public class PlanExportBuilderFactory {
public PlanExportBuilder getBuilder(String type) {
PlanExportBuilder builder = null;
switch (type) {
case "pdf":
builder = new PdfPlanExportBuilder();
break;
default:
logger.error("Unsupported plan export builder, type: " + type);
}
return builder;
}
}
In the servlet:
#Component(metatype = false)
#Service(Servlet.class)
#Properties({
#Property(name = "sling.servlet.resourceTypes", value = "myApp/document"),
#Property(name = "sling.servlet.extensions", value = { "ppt", "pdf" }),
#Property(name = "sling.servlet.methods", value = "GET")
})
public class PlanExportServlet extends SlingSafeMethodsServlet {
#Reference
PlanExportBuilderFactory builderFactory;
#Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
Resource resource = request.getResource();
PlanExportBuilder builder = builderFactory.getBuilder(request.getRequestPathInfo().getExtension());
}
}
But the problem is that in the builder I would like to reference other services to access Sling resources, and with this solution, they're not bound.
I looked at Services Factory with OSGi but from what I've understood, you use them to configure differently the same implementation of a service.
Then I found that you can get a specific implementation by naming it, or use a property and a filter.
So I've ended up with this:
public class PlanExportBuilderFactory {
#Reference(target = "(builderType=pdf)")
PlanExportBuilder pdfPlanExportBuilder;
public PlanExportBuilder getBuilder(String type) {
PlanExportBuilder builder = null;
switch (type) {
case "pdf":
return pdfPlanExportBuilder;
default:
logger.error("Unsupported plan export builder, type: " + type);
}
return builder;
}
}
The builder defining a "builderType" property :
// AbstractPlanExportBuilder implements PlanExportBuilder interface
#Component
#Service(value=PlanExportBuilder.class)
public class PdfPlanExportBuilder extends AbstractPlanExportBuilder {
#Property(name="builderType", value="pdf")
public PdfPlanExportBuilder() {
planDocument = new PdfPlanDocument();
}
}
I would like to know if it's a good way to retrieve my PDF builder implementation regarding OSGi good practices.
EDIT 1
From Peter's answer I've tried to add multiple references but with Felix it doesn't seem to work:
#Reference(name = "planExportBuilder", cardinality = ReferenceCardinality.MANDATORY_MULTIPLE, policy = ReferencePolicy.DYNAMIC)
private Map<String, PlanExportBuilder> builders = new ConcurrentHashMap<String, PlanExportBuilder>();
protected final void bindPlanExportBuilder(PlanExportBuilder b, Map<String, Object> props) {
final String type = PropertiesUtil.toString(props.get("type"), null);
if (type != null) {
this.builders.put((String) props.get("type"), b);
}
}
protected final void unbindPlanExportBuilder(final PlanExportBuilder b, Map<String, Object> props) {
final String type = PropertiesUtil.toString(props.get("type"), null);
if (type != null) {
this.builders.remove(type);
}
}
I get these errors :
#Reference(builders) : Missing method bind for reference planExportBuilder
#Reference(builders) : Something went wrong: false - true - MANDATORY_MULTIPLE
#Reference(builders) : Missing method unbind for reference planExportBuilder
The Felix documentation here http://felix.apache.org/documentation/subprojects/apache-felix-maven-scr-plugin/scr-annotations.html#reference says for the bind method:
The default value is the name created by appending the reference name to the string bind. The method must be declared public or protected and take single argument which is declared with the service interface type
So according to this, I understand it cannot work with Felix, as I'm trying to pass two arguments. However, I found an example here that seems to match what you've suggested but I cannot make it work: https://github.com/Adobe-Consulting-Services/acs-aem-samples/blob/master/bundle/src/main/java/com/adobe/acs/samples/services/impl/SampleMultiReferenceServiceImpl.java
EDIT 2
Just had to move the reference above the class to make it work:
#References({
#Reference(
name = "planExportBuilder",
referenceInterface = PlanExportBuilder.class,
policy = ReferencePolicy.DYNAMIC,
cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE)
})
public class PlanExportServlet extends SlingSafeMethodsServlet {
Factories are evil :-) Main reason is of course the yucky class loading hacks that are usually used but also because they tend to have global knowledge. In general, you want to be able to add a bundle with a new DocumentBuilder and then that type should become available.
A more OSGi oriented solution is therefore to use service properties. This could look like:
#Component( property=HTTP_WHITEBOARD_FILTER_REGEX+"=/as")
public class DocumentServlet {
final Map<String,DocBuilder> builders = new ConcurrentHashMap<>();
public void doGet( HttpServletRequest rq, HttpServletResponse rsp )
throws IOException, ServletException {
InputStream in = getInputStream( rq.getPathInfo() );
if ( in == null )
....
String type = toType( rq.getPathInfo(), rq.getParameter("type") );
DocBuilder docbuilder = builders.get( type );
if ( docbuilder == null)
....
docbuilder.convert( type, in, rsp.getOutputStream() );
}
#Reference( cardinality=MULTIPLE, policy=DYNAMIC )
void addDocBuilder( DocBuilder db, Map<String,Object> props ) {
docbuilders.put(props.get("type"), db );
}
void removeDocBuilder(Map<String,Object> props ) {
docbuilders.remove(props.get("type"));
}
}
A DocBuilder could look like:
#Component( property = "type=ppt-pdf" )
public class PowerPointToPdf implements DocBuilder {
...
}

ANTLR: expression evaluation with custom object and method

Is it possible to use ANTLR internally in a Java program and use custom classes to execute actual operations ? I would like to be able to parse simple expressions and execute custom operations for them defined in my own Java classes.
Sample expression :
(( AB1 op1 BC2) op2 BB )
AB1, BC2 and BB represent custom Java classes for example MyObj. op1 and op2 are very simple operations and could be thought as + or -
Let's say I have:
interface Operation {
public MyObj operation(MyObj obj1, MyObj obj2);
}
class SomeOperation implements Operation {
public MyObj operation(MyObj obj1, MyObj obj2) {
};
}
class Some2Operation implements Operation {
public MyObj operation(MyObj obj1, MyObj obj2) {
};
}
SomeOperation refers to op1 and Some2Operation refers to op2.
Before evaluating the expression of course actual objects for AB1, BC2 and BB are assigned.
If there's easier/simpler way to accomplish this than using ANTLR please let me know.
EDIT: Continues here...
grammar MyTest;
ID: [a-z]+;
Operator_I: 'I';
Operator_U: 'U';
prog: (expr)* #myProg;
expr: expr op=(Operator_I | Operator_U) expr #myExpr
| ID #myID
| '(' expr ')' #myWithPar
;
import org.antlr.v4.runtime.misc.NotNull;
import org.antlr.v4.runtime.tree.TerminalNode;
import java.util.*;
public class EvalVisitor extends MyTestBaseVisitor<Value> {
#Override
public Value visitMyProg(MyTestParser.MyProgContext ctx) {
// TODO Auto-generated method stub
System.out.println("prog:"+ctx.getText());
return super.visitMyProg(ctx);
}
#Override
public Value visitMyWithPar(MyTestParser.MyWithParContext ctx) {
// TODO Auto-generated method stub
System.out.println("par:"+ctx.getText());
return super.visitMyWithPar(ctx);
}
#Override
public Value visitMyID(MyTestParser.MyIDContext ctx) {
// TODO Auto-generated method stub
System.out.println("id:"+ctx.getText());
return super.visitMyID(ctx);
}
#Override
public Value visitMyExpr(MyTestParser.MyExprContext ctx) {
// TODO Auto-generated method stub
System.out.println("expr:"+ctx.getText());
return super.visitMyExpr(ctx);
}
private Map<String, Value> memory = new HashMap<String, Value>();
private Map<String, Value> values = new HashMap<String, Value>();
public EvalVisitor() {
values.put("a", new Value(new Double(5)));
values.put("u", new Value(new Double(9)));
}
}
public static void main(String[] args) throws Exception {
MyTestLexer lexer = new MyTestLexer(new ANTLRFileStream("c:\\test.mu"));
MyTestParser parser = new MyTestParser(new CommonTokenStream(lexer));
ParseTree tree = parser.prog();
EvalVisitor visitor = new EvalVisitor();
visitor.visit(tree);
}
test.mu contains:
(aIu)U(aUu)
output of the program:
prog:(aIu)U(aUu)
expr:(aIu)U(aUu)
par:(aIu)
expr:aIu
id:a
id:u
par:(aUu)
expr:aUu
id:a
id:u
ANTLR only provides a parser for your language. An actual interpreter (evaluating your input) is up to you. You can use ANTLR's visitors to easily create an interpreter. A demo can be found in this Q&A: If/else statements in ANTLR using listeners
If you don't want to write the interpreter yourself, you'll need to find a library that suits your needs. See this Q&A for a list of available libraries: Evaluating a math expression given in string form

how do I track metrics in jmeter for 'java requests' with sub results?

I am using jmeter with Java Request samplers. These call java classes I have written which returns a SampleResult object which contains the timing metrics for the use case. SampleResult is a tree and can have child SampleResult objects (SampleResult.addSubResult method). I cant seem to find a good way in jmeter to track the sub results so I can only easily get the results for the parent SampleResult.
Is there a listener in jmeter that allows me to see statistics / graphs for sub results (for instance see the average time across all sub results with the same name).
I have just succeeded in doing this, and wanted to share it. If you follow the instructions I provide here, it will work for you as well. I did this for the summary table listener. And, I did it on Windows. And, I used Eclipse
Steps:
Go to JMeter's web site and download the source code. You can find that here, for version 3.0.
http://jmeter.apache.org/download_jmeter.cgi
One there, I clicked the option to download the Zip file for the Source.
Then, on that same page, download the binary for version 3.0, if you have not already done so. Then, extract that zip file onto your hard drive.
Once you've extracted the zip file to your hard drive, grab the file "SummaryReport.java". It can be found here: "\apache-jmeter-3.0\src\components\org\apache\jmeter\visualizers\SummaryReport.java"
Create a new class in Eclipse, then Copy/Paste all of that code into your new class. Then, rename your class from what it is, "SummaryReport" to a different name. And everywhere in the code, replace "SummaryReport" with the new name of your class.
I am using Java 8. So, there is one line of code that won't compile for me. It's the line below.
private final Map tableRows = new ConcurrentHashMap<>();
You need to remove the <> on that line, as Java 1.8 doesn't support it. Then, it will compile
There was one more line that gave a compile error. It was the one below.
CSVSaveService.saveCSVStats(StatGraphVisualizer.getAllTableData(model, FORMATS),writer,`
saveHeaders.isSelected() ? StatGraphVisualizer.getLabels(COLUMNS) : null);
Firstly, it wasn't finding the source for class StatGraphVisualizer. So, I imported it, as below.
import org.apache.jmeter.visualizers.StatGraphVisualizer;
Secondly, it wasn't finding the method "getLabels" in "StatGraphVisualizer.getLabels." So, here is what this line of code looked like after I fixed it. It is seen below.
CSVSaveService.saveCSVStats(StatGraphVisualizer.getAllTableData(model, FORMATS),writer);
That compiles. That method doesn't need the second argument.
Now, everything should compile.
Find this method below. This is where you will begin adding your customizations.
#Override
public void add(final SampleResult res) {
You need to create an array of all of your sub results, as I did, as seen below. The line in Bold is the new code. (All new code is seen in Bold).
public void add(final SampleResult res) {
final String sampleLabel = res.getSampleLabel(); // useGroupName.isSelected());
**final SampleResult[] theSubResults = res.getSubResults();**
Then, create a String for each label for your sub results objects, as seen below.
**final String writesampleLabel = theSubResults[0].getSampleLabel(); // (useGroupName.isSelected());
final String readsampleLabel = theSubResults[1].getSampleLabel(); // (useGroupName.isSelected());**
Next, go to the method below.
JMeterUtils.runSafe(false, new Runnable() {
#Override
public void run() {
The new code added is below, in Bold.
JMeterUtils.runSafe(false, new Runnable() {
#Override
public void run() {
Calculator row = null;
**Calculator row1 = null;
Calculator row2 = null;**
synchronized (lock) {
row = tableRows.get(sampleLabel);
**row1 = tableRows.get(writesampleLabel);
row2 = tableRows.get(readsampleLabel);**
if (row == null) {
row = new Calculator(sampleLabel);
tableRows.put(row.getLabel(), row);
model.insertRow(row, model.getRowCount() - 1);
}
**if (row1 == null) {
row1 = new Calculator(writesampleLabel);
tableRows.put(row1.getLabel(), row1);
model.insertRow(row1, model.getRowCount() - 1);
}
if (row2 == null) {
row2 = new Calculator(readsampleLabel);
tableRows.put(row2.getLabel(), row2);
model.insertRow(row2, model.getRowCount() - 1);
}**
} // close lock
/*
* Synch is needed because multiple threads can update the counts.
*/
synchronized(row) {
row.addSample(res);
}
**synchronized(row1) {
row1.addSample(theSubResults[0]);
}**
**synchronized(row2) {
row2.addSample(theSubResults[1]);
}**
That is all that needs to be customized.
In Eclipse, export your new class into a Jar file. Then place it inside of the lib/ext folder of your binary of Jmeter that you extracted, from Step 1 above.
Start up Jmeter, as you normally would.
In your Java sampler, add a new Listener. You will now see two "Summary Table" listeners. One of these will be the new one that you have just created. Once you have brought that new one into your Java Sampler, rename it to something unique. Then run your test and look at your new "Summary Table" listener. You will see summary results/stats for all of your sample results.
My next step is to perform these same steps for all of the other Listeners that I would like to customize.
I hope that this post helps.
Here is some of my plugin code which you can use as a starting point in writing your own plugin. I cant really post everything as there are really dozens of classes. Few things to know are:
my plugin like all visualizer plugins extends the jmeter class
AbstractVisualizer
you need the following jars in eclipse to complile:
jfxrt.jar,ApacheJMeter_core.jar
you need java 1.8 for javafx (the jar file comes in the sdk)
if you compile a plugin you need to put that in jmeter/lib/ext.
You also need to put the jars from bullet 2 in jmeter/lib
there is a method called "add(SampleResult)" in my class. This
will get called by the jmeter framework every time a java sample
completes and will pass the SampleResult as a parameter. Assuming you
have your own Java Sample classes that extend
AbstractJavaSamplerClient your class will have a method called
runTest which returns a sampleresult. That same return object will be
passed into your plugins add method.
my plugin puts all the sample results into a buffer and only
updates the screen every 5 results.
Here is the code:
import java.awt.BorderLayout;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.TestStateListener;
import org.apache.jmeter.visualizers.gui.AbstractVisualizer;
public class FxVisualizer extends AbstractVisualizer implements TestStateListener {
int currentId = 0;
/**
*
*/
private static final long serialVersionUID = 1L;
private static final int BUFFER_SIZE = 5;
#Override
public String getName()
{
return super.getName();//"George's sub result viewer.";
}
#Override
public String getStaticLabel()
{
return "Georges FX Visualizer";
}
#Override
public String getComment()
{
return "George wrote this plugin. There are many plugins like it but this one is mine.";
}
static Long initCount = new Long(0);
public FxVisualizer()
{
init();
}
private void init()
{
//LoggingUtil.debug("in FxVisualizer init()");
try
{
FxTestListener.setListener(this);
this.setLayout(new BorderLayout());
Border margin = new EmptyBorder(10, 10, 5, 10);
this.setBorder(margin);
//this.add(makeTitlePanel(), BorderLayout.NORTH);
final JFXPanel fxPanel = new JFXPanel();
add(fxPanel);
//fxPanel.setScene(getScene());
Platform.runLater(new Runnable() {
#Override
public void run() {
initFX(fxPanel);
}
});
}
catch(Exception e)
{
e.printStackTrace();
}
}
static FxVisualizerScene fxScene;
private static void initFX(JFXPanel fxPanel) {
// This method is invoked on the JavaFX thread
fxScene = new FxVisualizerScene();
fxPanel.setScene(fxScene.getScene());
}
final List <Event> bufferedEvents = new ArrayList<Event>();
#Override
public void add(SampleResult result)
{
final List <Event> events = ...;//here you need to take the result.getSubResults() parameter and get all the children events.
final List<Event> eventsToAdd = new ArrayList<Event>();
synchronized(bufferedEvents)
{
for (Event evt : events)
{
bufferedEvents.add(evt);
}
if (bufferedEvents.size() >= BUFFER_SIZE)
{
eventsToAdd.addAll(bufferedEvents);
bufferedEvents.clear();
}
}
if (eventsToAdd.size() > 0)
{
Platform.runLater(new Runnable() {
#Override
public void run() {
updatePanel(eventsToAdd);
}
});
}
}
public void updatePanel(List <Event> events )
{
for (Event evt: events)
{
fxScene.addEvent(evt);
}
}
#Override
public void clearData()
{
synchronized(bufferedEvents)
{
Platform.runLater(new Runnable() {
#Override
public void run() {
bufferedEvents.clear();
fxScene.clearData();
}
});
}
}
#Override
public String getLabelResource() {
return "Georges Java Sub FX Sample Listener";
}
Boolean isRunning = false;
#Override
public void testEnded()
{
final List<Event> eventsToAdd = new ArrayList<Event>();
synchronized(bufferedEvents)
{
eventsToAdd.addAll(bufferedEvents);
bufferedEvents.clear();
}
if (eventsToAdd.size() > 0)
{
Platform.runLater(new Runnable() {
#Override
public void run() {
updatePanel(eventsToAdd);
fxScene.testStopped();
}
});
}
}
Long testCount = new Long(0);
#Override
public void testStarted() {
synchronized(bufferedEvents)
{
Platform.runLater(new Runnable() {
#Override
public void run() {
updatePanel(bufferedEvents);
bufferedEvents.clear();
fxScene.testStarted();
}
});
}
}
#Override
public void testEnded(String arg0)
{
//LoggingUtil.debug("testEnded 2:" + arg0);
testEnded();
}
int registeredCount = 0;
#Override
public void testStarted(String arg0) {
//LoggingUtil.debug("testStarted 2:" + arg0);
testStarted();
}
}
OK so I just decided to write my own jmeter plugin and it is dead simple. Ill share the code for posterity when it is complete. Just write a class that extends AbstractVisualizer, compile it into a jar, then throw it into the jmeter lib/ext directory. That plugin will show up in the listeners section of jmeter when you go to add visualizers.

Is it worked Nashorn JS object to java.util.Map?

I have java method
void someMethod(String str, Map map) {
...
}
From JS call this method
var map = new Object()
map.key1 = "val1"
...someMethod(str, map)
Exception:
java.lang.NoSuchMethodException: None of the fixed arity signatures
[(java.lang.String, java.util.Map)] of method org.prjctor.shell.Bash.eval
match the argument types [java.lang.String, jdk.nashorn.internal.scripts.JO]
But in Nashorn docs "Mapping of Data Types Between Java and JavaScript" said "Every JavaScript object is also a java.util.Map so APIs receiving maps will receive them directly".
What am I doing wrong?
Agree with the previous answers that you cannot do this as the docs have implied.
However you could create and pass a map as follows
..
var HashMap = Java.type('java.util.HashMap');
var map = new HashMap();
map.put('1', 'val1');
...someMethod(str, map)
The doc says ""Every JavaScript object implements the java.util.Map interface". But this sample test program shows thats not the case.
public final class NashornTestMap {
public static void main(String args[]) throws Exception{
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine nashorn = factory.getEngineByName("nashorn");
nashorn.eval(""
+"load(\"nashorn:mozilla_compat.js\");"
+ "importClass(Packages.NashornTestMap);"
+ "var map={};"
+ "map[\"Key\"]=String(\"Val\"); "
+ "var test = new NashornTestMap();"
+ "test.test(map);"
+ "");
}
public void test(Map<String, String> obj){
System.out.println(obj);
}
}
The above code give exception "Exception in thread "main" java.lang.ClassCastException: Cannot cast jdk.nashorn.internal.scripts.JO4 to java.util.Map". This link confirms this.
However you can use Map inside your script and call the java objects directly, like this.
public final class NashornTestMap {
public static void main(String args[]) throws Exception{
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine nashorn = factory.getEngineByName("nashorn");
nashorn.eval(""
+"load(\"nashorn:mozilla_compat.js\");"
+ "importClass(Packages.NashornTestMap);"
+ "var HashMap = Java.type(\"java.util.HashMap\");"
+ "var map = new HashMap();"
+ "map.put(0, \"value1\");"
+ "var test = new NashornTestMap();"
+ "test.test(map);"
+ "");
}
public void test(Map<String, String> obj){
System.out.println(obj);
}
}
Returns "{0=value1}"

Resources