Stumbling with dynamic parameters, passing Object[] to Object - java

I've been using a system in which I could tack on as many parameters as I want and the method determines the data-type based on the object, this methods skeleton is as follows:
public void sendPacket(int id, Object... data) {
....
}
This has allowed me to easily send packets with all sorts of information, by just supplying the ID and then the data in the order that I wanted it to be sent over the network.
This became a problem when I needed to dynamically call sendPacket(Integer, Object);
Usually I know exactly how much data I need to pass to the sendPacket method, and I pass it manually, however in this case I don't know how many parameters I'm going to send, thus the amount of data I'm sending over the network is unknown.
The method I used to try to do this was to create an Object[] buffer which isn't doing what I wanted it to, example below:
Object[] buffer = new Object[list.size() * 3];
int bufferIndex = 0;
for(int i = 0; i < list.size(); i++) {
buffer[bufferIndex++] = list.get(i).getId();
buffer[bufferIndex++] = list.get(i).getName();
buffer[bufferIndex++] = list.get(i).getLevel();
}
sendPacket(5, true, list.size(), buffer);
This presents the following [DEBUG] output.
[DEBUG]: Packet ID: 5 Data Passed[Boolean]: true
[DEBUG]: Packet ID: 5 Data Passed[Integer]: 1
[Ljava.lang.Object;
The [Ljava.lang.Object output is because I have it setup to tell me the class-name of the Object that failed to be converted into usable data.
Here's an example as to how I'm currently interpreting the data being passed to sendPacket
for(Object o : data) {
if(o.getClass().getName().endsWith("Integer")) {
out.writeInt((int)o);
}
}
There's probably more efficient ways to figure out which type to cast the data to, so if you know one, that information would also be beneficial to myself.
Thanks for any help.

public class ConvertUtil {
private ConvertUtil() {}
private final static Map<Class<?>, Method> METHOD_MAP = new HashMap<Class<?>, Method>();
private static Logger log = LoggerFactory.getLogger(ConvertUtil.class);
static {
try {
METHOD_MAP.put(Byte.class, Byte.class.getMethod("valueOf", String.class));
METHOD_MAP.put(Short.class, Short.class.getMethod("valueOf", String.class));
METHOD_MAP.put(Integer.class, Integer.class.getMethod("valueOf", String.class));
METHOD_MAP.put(Long.class, Long.class.getMethod("valueOf", String.class));
METHOD_MAP.put(Boolean.class, Boolean.class.getMethod("valueOf", String.class));
METHOD_MAP.put(Float.class, Float.class.getMethod("valueOf", String.class));
METHOD_MAP.put(Double.class, Double.class.getMethod("valueOf", String.class));
METHOD_MAP.put(String.class, String.class.getMethod("valueOf", Object.class));
} catch (Exception e) {
log.error("ConvertUtil static is error" + e.getLocalizedMessage());
}
}
#SuppressWarnings("unchecked")
public static <T> T castValue(Object val, T defaultVal) {
Method method = METHOD_MAP.get(defaultVal.getClass());
try {
if (val != null && val instanceof String) {
defaultVal = (T) method.invoke(defaultVal.getClass(), val.toString());
}
if (val != null && val.getClass().getName().equals(defaultVal.getClass().getName())) {
defaultVal = (T) val;
}
} catch (Exception e) {
log.error("ConvertUtil castValue is error" + e.getLocalizedMessage());
}
return defaultVal;
}
}

Related

Converting binary response from JInterface Java app back into list of strings in Elixir

I have a small java app and I have used JInterface to essentially expose it as an OTP process in my elixir app. I can call it and get a response successfully.
My problem is that the response I get back in elixir is of a binary but I cannot figure out how to convert a binary to a list of strings which is what the response is.
The code for my OTP node in Java using JInterface is below:
public void performAction(Object requestData, OtpMbox mbox, OtpErlangPid lastPid){
List<String> sentences = paragraphSplitter.splitParagraphIntoSentences((String) requestData, Locale.JAPAN);
mbox.send(lastPid, new OtpErlangBinary(getOtpStrings(sentences)));
System.out.println("OK");
}
private List<OtpErlangString> getOtpStrings(List<String> sentences) {
List<OtpErlangString> erlangStrings = new ArrayList<>();
for(int i = 0; i < sentences.size(); i++){
erlangStrings.add(new OtpErlangString(sentences.get(i)));
}
return erlangStrings;
}
It is necessary to wrap the response in an OtpErlangBinary and I have concerted the strings to OTPErlangString. I have also tried without converting the strings to OTPErlangString.
On the elixir side I can receive the binary response and IO.inspect it.
Does anybody know how to use JInterface to deserialise the results correctly when it's anything other than a single string? Or maybe, if I have made some mistake, how to build the correct response type so that I can deserialise it correctly?
Any help would be really appreciated as I have been trying to figure this out for ages.
Thanks in advance.
I have been playing around with JInterface and Elixir and I think I've got your problem figured out.
So you are trying to send a list of strings from an Elixir/Erlang node to a Java node, but you cannot get it to de-serialize properly.
Elixir has its own types (e.g., atoms, tuples, ..) and Java has its own types (e.g., Object, String, List<String>,..). There needs to be a conversion from the one type to the other if they're supposed to talk to each other. In the end it's just a bunch of 1's and 0's that get sent over the wire anyway.
If an Erlang list is sent to Java, what arrives can always be interpreted as an OtpErlangObject. It's up to you to then try and guess what the actual type is before we can even begin turning it into a Java value.
// We know that everything is at least an OtpErlangObject value!
OtpErlangObject o = mbox.receive();
But given that you know that it's in fact a list, we can turn it into an OtpErlangList value.
// We know o is an Erlang list!
OtpErlangList erlList = (OtpErlangList) o;
The elements of this list however, are still unknown. So at this point its still a list of OtpErlangObjects.
But, we know that it's a list of strings, so we can interpret the list of OtpErlangObjects as list of OtpErlangStrings, and convert those to Java strings.
public static List<String> ErlangListToStringList(OtpErlangList estrs) {
OtpErlangObject[] erlObjs = estrs.elements();
List<String> strs = new LinkedList<String>();
for (OtpErlangObject erlO : erlObjs) {
strs.add(erlO.toString());
}
return strs;
}
Note that I used the term list here a lot, because it's in fact an Erlang list, in Java it's all represented as an array!
My entire code is listed below.
The way to run this is to paste it into a Java IDE, and start a REPL with the following parameters:
iex --name bob#127.0.0.1 --cookie "secret"
Java part:
import com.ericsson.otp.erlang.*;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public class Main {
public static OtpErlangList StringListToErlangList(List<String> strs) {
OtpErlangObject[] elems = new OtpErlangObject[strs.size()];
int idx = 0;
for (String str : strs) {
elems[idx] = new OtpErlangString(str);
idx++;
}
return new OtpErlangList(elems);
}
public static List<String> ErlangListToStringList(OtpErlangList estrs) {
OtpErlangObject[] erlObjs = estrs.elements();
List<String> strs = new LinkedList<String>();
for (OtpErlangObject erlO : erlObjs) {
strs.add(erlO.toString());
}
return strs;
}
public static void main(String[] args) throws IOException, InterruptedException {
// Do some initial setup.
OtpNode node = new OtpNode("alice", "secret");
OtpMbox mbox = node.createMbox();
mbox.registerName("alice");
// Check that the remote node is actually online.
if (node.ping("bob#127.0.0.1", 2000)) {
System.out.println("remote is up");
} else {
System.out.println("remote is not up");
}
// Create the list of strings that needs to be sent to the other node.
List<String> strs = new LinkedList<String>();
strs.add("foo");
strs.add("bar");
OtpErlangList erlangStrs = StringListToErlangList(strs);
// Create a tuple so the other node can reply to use.
OtpErlangObject[] msg = new OtpErlangObject[2];
msg[0] = mbox.self();
msg[1] = erlangStrs;
OtpErlangTuple tuple = new OtpErlangTuple(msg);
// Send the tuple to the other node.
mbox.send("echo", "bob#127.0.0.1", tuple);
// Await the reply.
while (true) {
try {
System.out.println("Waiting for response!");
OtpErlangObject o = mbox.receive();
if (o instanceof OtpErlangList) {
OtpErlangList erlList = (OtpErlangList) o;
List<String> receivedStrings = ErlangListToStringList(erlList);
for (String s : receivedStrings) {
System.out.println(s);
}
}
if (o instanceof OtpErlangTuple) {
OtpErlangTuple m = (OtpErlangTuple) o;
OtpErlangPid from = (OtpErlangPid) (m.elementAt(0));
OtpErlangList value = (OtpErlangList) m.elementAt(1);
List<String> receivedStrings = ErlangListToStringList(value);
for (String s : receivedStrings) {
System.out.println(s);
}
}
} catch (OtpErlangExit otpErlangExit) {
otpErlangExit.printStackTrace();
} catch (OtpErlangDecodeException e) {
e.printStackTrace();
}
}
}
}

How to determine message type in protobuf so that I can use that type.parsefrom(byte[ ])

I am trying to send protobuf data from cpp side to java side.
I have multiple message types defined in .proto
On Cpp side, I have enums for every message type and I am adding it to the buf output as follows:
uint8_t* __temp = (uint8_t*)(buf);
*__temp++ = (type) >> 8;
*__temp = (type) & 0x00FF;
How do I get this 'type' that I have added to the buf, so that I can achieve something like
MessageType parseFrom(byte[] data);
It is not clear what is the exact requirement. But I assume you are trying to send different types of messages and the the receiver should be able to parse the correct object out of the received bytes. This can be done as shown in the example below:
message Message1 {
required string a = 1;
required string b = 2;
}
message Message2 {
required int64 id = 1;
required string data = 2;
}
message WrapperMessage {
required int64 commonField = 1;
oneof msg {
Message1 m1 = 2;
Message2 m2 = 3;
}
}
Basically, always WrapperMessage object is sent over the wire which wraps a Message1 or Message2 object.
Then on the receiving side we may parse the WrapperMessage object first and then use HasField method to check if m1 or m2 fields is present in the wrapped object and then parse the Message1 or Message2 object out of it.
"oneof" feature may not be available on older version of protobuf compiler.
Protobuf 3 introduced a new concept, Any, that handles this. A good description can be found here.
Below is the example code for reading and writing with ANY type in Proto 3. Used Bigtable for read and write examples.
public void writeToBigtable(Item item){
try {
RowMutation rowMutation = RowMutation.create("item", String.join("#", item.getHqLine(), item.getPartNo()))
.setCell("item-info-cf", ByteString.copyFromUtf8("item-info-proto"), ByteString.copyFrom(**Any.pack(item).toByteArray()**));
bigtableDataClient.mutateRow(rowMutation);
} catch (RuntimeException exception){
log.error("Error occurred while inserting data into DB");
}
}
public Set<Item> readFromBigtable(String rowKey){
Row row = bigtableDataClient.readRow("item",rowKey,FILTERS.chain().filter(FILTERS.limit().cellsPerColumn(1)));
return row.getCells("item-info-cf", ByteString.copyFromUtf8("item-info-proto"))
.stream()
.map(rowCell->{
Item item = null;
try {
Any any = **Any.parseFrom(rowCell.getValue().toByteArray()**);
if(any.is(Item.class)) {
item = any.unpack(Item.class);
}
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
return item;
}).collect(Collectors.toSet());
}

Generics and type erasure in Java issue

I currently face the following issue:
I am trying to refactor a recursive algorithm to an iterative one. What this recursive algorithm does is this:
method1 is passed some initial parameters. Based on a processing that takes place at the beginning of method1, method2 is invoked with these parameters. Now method2 uses some conditions and based on the one that is satisfied method1 is invoked again with the appropriate parameters.
Now, based on the answer on the link I've provided above I did the same thing. But I have to pass parameters around so I did this:
Stack<ArrayList<Object>> stack (the stack for the ArrayList<Object> objects)
ArrayList<Object> parametersForSync = new ArrayList<Object>();
ArrayList<Object> paramForHandle = new ArrayList<Object>();
(Each array list of objects is a list of the parameters to be passed to both the methods. The first array list is for the first method and the second for the second method.)
Assuming I pop and push array lists down the stack correctly I face the following issue which is my main problem and the reason of this question:
Within method2 I have to check whether the object (that was on the array list and is passed to the method) is an instanceof another class of mine. Now I have some conditions there which do not get satisfied when in fact they should.
Is this because of java's type erasure?
Is there anyway to overcome this?
If am not clear at a certain point in my explanations please ask me to clarify.
Edit:
What follows is the code that replaces the recursion that goes like this:
syncWithServer(parameter set x){
handleResultArray(parameter set y);
};
handleResultArray(parameter set ){
syncWithServer(parameter set w)
}
===========================================================
Stack<ArrayList<Object>> stack = new Stack<ArrayList<Object>>();
ArrayList<Object> paramList = new ArrayList<Object>();
paramList.add(oeHelper);
paramList.add(false);
paramList.add(domain);
paramList.add(null);
paramList.add(true);
paramList.add(10000);
paramList.add(true);
stack.push(paramList);
int counter = 0;
ArrayList<Object> parametersForSync = new ArrayList<Object>();
ArrayList<Object> paramForHandle = new ArrayList<Object>();
while (!stack.isEmpty()) {
Log.d(TAG, "Loop: " + counter);
parametersForSync = stack.pop();
paramForHandle = ((OEHelper) parametersForSync.get(0))
.syncWithServer(
// why error here?
(boolean) parametersForSync.get(1),
(OEDomain) parametersForSync.get(2),
(List<Object>) parametersForSync.get(3),
(boolean) parametersForSync.get(4),
(int) parametersForSync.get(5),
(boolean) parametersForSync.get(6));
parametersForSync = ((OEHelper) paramForHandle.get(3))
.handleResultArray(
(OEFieldsHelper) paramForHandle.get(0),
(JSONArray) paramForHandle.get(1),
(boolean) paramForHandle.get(2));
if (parametersForSync.size() != 0) {
stack.push(parametersForSync);
}
counter++;
Now the first method:
public ArrayList<Object> syncWithServer(boolean twoWay, OEDomain domain,
List<Object> ids, boolean limitedData, int limits,
boolean removeLocalIfNotExists) {
Log.d(TAG, "syncWithServer");
List<OEColumn> dbCols = mDatabase.getDatabaseColumns();
List<OEColumn> dbFinalList = new ArrayList<OEColumn>();
ArrayList<Object> parametersList = new ArrayList<Object>();
Log.d(TAG, "Columns & finalList created");
for (OEColumn col : dbCols) {
if (!mOne2ManyCols.contains(col.getName())) {
dbFinalList.add(col);
}
}
OEFieldsHelper fields = new OEFieldsHelper(dbFinalList);
try {
if (domain == null) {
domain = new OEDomain();
}
if (ids != null) {
domain.add("id", "in", ids);
}
if (limitedData) {
mPref = new PreferenceManager(mContext);
int data_limit = mPref.getInt("sync_data_limit", 60);
domain.add("create_date", ">=",
OEDate.getDateBefore(data_limit));
}
if (limits == -1) {
limits = 50;
}
Log.d(TAG, "*****.search_read() started");
JSONObject result = *****.search_read(mDatabase.getModelName(),
fields.get(), domain.get(), 0, limits, null, null);
Log.d(TAG, "***.search_read() returned");
mAffectedRows = result.getJSONArray("records").length();
parametersList.add(fields);
parametersList.add(result.getJSONArray("records"));
parametersList.add(removeLocalIfNotExists);
parametersList.add(OEHelper.this);
//This parametersList contains the parameters that must be used to invoke the next method
Now the second method:
=================================================
public ArrayList<Object> handleResultArray(
OEFieldsHelper fields, JSONArray results,
boolean removeLocalIfNotExists) {
Log.d(TAG, "handleResultArray");
ArrayList<Object> parametersList = new ArrayList<Object>();
// ArrayList<Object> parameterStack = new ArrayList<Object>();
try {
fields.addAll(results);
List<OERelationData> rel_models = fields.getRelationData();
Log.d(TAG, "rel_models: "+rel_models.size());
for (OERelationData rel : rel_models) {
// Handling many2many records
if (rel.getDb().getClass()==OEManyToMany.class
/*instanceof OEManyToMany*/) {//TODO type erasure?
Log.v(TAG, "Syncing ManyToMany Records");
OEManyToMany m2mObj = (OEManyToMany) rel.getDb();
OEHelper oe = ((OEDatabase) m2mObj.getDBHelper())
.getOEInstance();
parametersList.add(oe);
parametersList.add(false);
parametersList.add(null);
parametersList.add(rel.getIds());
parametersList.add(false);
parametersList.add(0);
parametersList.add(false);
return parametersList;
} else if (rel.getDb().getClass()==OEManyToOne.class
/*instanceof OEManyToOne*/) {
// Handling many2One records
Log.v(TAG, "Syncing ManyToOne Records");
// M2OCounter++;
OEManyToOne m2oObj = (OEManyToOne) rel.getDb();
OEHelper oe = ((OEDatabase) m2oObj.getDBHelper())
.getOEInstance();
parametersList.add(oe);
parametersList.add(false);
parametersList.add(null);
parametersList.add(rel.getIds());
parametersList.add(false);
parametersList.add(0);
parametersList.add(false);
// parametersMap.put(Counter, parametersList);
// parameterStack.add(parametersList);
return parametersList;
} else if (rel.getDb().getClass()==OEOneToMany.class
/*instanceof OEOneToMany*/) {
Log.v(TAG, "Syncing OneToMany Records");
// O2MCounter++;
OEOneToMany o2mObj = (OEOneToMany) rel.getDb();
OEHelper oe = ((OEDatabase) o2mObj.getDBHelper())
.getOEInstance();
oe.setOne2ManyCol(o2mObj.getColumnName());
parametersList.add(oe);
parametersList.add(false);
parametersList.add(null);
parametersList.add(rel.getIds());
parametersList.add(false);
parametersList.add(0);
parametersList.add(false);
// parametersMap.put(Counter, parametersList);
// parameterStack.add(parametersList);
return parametersList;
} else {
Log.v(TAG, "Syncing records with no relations"
+ rel.getDb().getClass().getSimpleName());
OEHelper oe = ((OEDatabase) rel.getDb()).getOEInstance();
parametersList.add(oe);
parametersList.add(false);
parametersList.add(null);
parametersList.add(rel.getIds());
parametersList.add(false);
parametersList.add(0);
parametersList.add(false);
return parametersList;//TODO when nothing matches this returns
}
}
List<Long> result_ids = mDatabase.createORReplace(
fields.getValues(), removeLocalIfNotExists);
mResultIds.addAll(result_ids);
mRemovedRecordss.addAll(mDatabase.getRemovedRecords());
} catch (Exception e) {
e.printStackTrace();
}
return parametersList;
}
The second method is supposed to return from within one of the conditions but none of the conditions is met
No - it is not a type erasure problem. As Oracle says:
Type erasure ensures that no new classes are created for parameterized
types; consequently, generics incur no runtime overhead.
So at runtime your classes are just plain classes and your objects are what they are, but it is not going to remove the base type information.
Why not put a log statement in and print out the class of the object?

JNA Structure ByReference

below the method and the unit test for that method.
The problem is that I'm not able to return the value of result from the Load method.
the unit test below fails!
I thought that by default JNA's object were ByRef by default so I tried instantiating and passing LoadResults "without" .ByReference ...
where is my mistake?
#Test
public void testLoad () {
MY_Processor proc = new MY_Processor();
// LoadResults result = new LoadResults ();
LoadResults.ByReference result = new LoadResults.ByReference();
ByteByReference [] pathToFile = new ByteByReference[256];
// fill pathToFile out ...
try {
proc.Load (pathToFile, result);
assertEquals(0, result.errorCode);
assertEquals(1, result.elaborationTime);
assertEquals(2, result.coreItem);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public Integer Load ( ByteByReference[] pathToFile,
LoadResults.ByReference result ) throws Exception {
// here result is correctly filled out !
LoadResults result = null;
result = native.getResult (numCore);
}
added the native code.
UPDATE
// header
typedef struct
{
int errorCode;
int elaborationTime;
int coreItem;
} LoadResults;
//[in] path
//[out] result
int Load (char path[MY_BUFFER_DEFINE], LoadResults* result);
// implementation ...
LoadResults* getResult (int numCore)
{
// some check ...
LoadResults *localResult = new LoadResults();
// fill out ...
return localResult;
}
there is a "free" method exposed by the native code but I didn't show in order to keep the focus on my problem :-)
/UPDATE
thanks!
O.
The problem is that you're passing a Structure as a parameter, then reassigning that parameter within the function. That will have no effect whatsoever on the argument.
The pattern you need to follow is this:
Pointer p = mylib.getResult()
MyStructure m = new MyStructure(p);
// ....
mylib.free(p);
I'd recommend you pass in a native string (const char*) as your path rather than a fixed-size buffer of native char.
UPDATE
If you want to copy a result into the argument, then you'll need to copy the structure's contents, e.g.
public int Load(LoadResults arg) throws Exception {
// Effectively copy memory from result into arg
LoadResults result = native.getResult(numCore);
if (alternative_1) {
// Copy result's native memory into arg's memory, then sync Java fields
result.useMemory(arg.getPointer());
result.write();
arg.read();
}
else {
// Sync result's native memory with arg's Java fields
Pointer p = arg.getPointer();
arg.useMemory(result.getPointer());
arg.read();
arg.useMemory(p);
}
}
Just solved ...
1) I don't need to use LoadResults.ByReference.
2) the problem was that into the Load method I updated the reference passed in input with another one:
public Integer Load ( ByteByReference[] pathToFile, LoadResults result ) throws Exception
{
// that's the problem!!!! storing the value into another object with another "address"
// and not the original "results".
// result = native.getResult (numCore);
// solved with this:
native.getResult (numCore, result);
}

Using ScriptEngine in java, How can I extract function list?

using Jsoup, I extract JavaScript part in html file. and store it as java String Object.
and I want to extract function list, variables list in js's function using javax.script.ScriptEngine
JavaScript part has several function section.
ex)
function a() {
var a_1;
var a_2
...
}
function b() {
var b_1;
var b_2;
...
}
function c() {
var c_1;
var c_2;
...
}
My Goals is right below.
List funcList
a
b
c
List varListA
a_1
a_2
...
List varListB
b_1
b_2
...
List varListC
c_1
c_2
...
How can I extract function list and variables list(or maybe values)?
I think you can do this by using javascript introspection after having loaded the javascript in the Engine - e.g. for functions:
ScriptEngine engine;
// create the engine and have it load your javascript
Bindings bind = engine.getBindings(ScriptContext.ENGINE_SCOPE);
Set<String> allAttributes = bind.keySet();
Set<String> allFunctions = new HashSet<String>();
for ( String attr : allAttributes ) {
if ( "function".equals( engine.eval("typeof " + attr) ) ) {
allFunctions.add(attr);
}
}
System.out.println(allFunctions);
I haven't found a way to extract the variables inside functions (local variables) without delving in internal mechanics (and thus unsafe to use) of the javascript scripting engine.
It is pretty tricky. ScriptEngine API seems not good for inspecting the code. So, I have such kind of pretty ugly solution with instance of and cast operators.
Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
for (Map.Entry<String, Object> scopeEntry : bindings.entrySet()) {
Object value = scopeEntry.getValue();
String name = scopeEntry.getKey();
if (value instanceof NativeFunction) {
log.info("Function -> " + name);
NativeFunction function = NativeFunction.class.cast(value);
DebuggableScript debuggableFunction = function.getDebuggableView();
for (int i = 0; i < debuggableFunction.getParamAndVarCount(); i++) {
log.info("First level arg: " + debuggableFunction.getParamOrVarName(i));
}
} else if (value instanceof Undefined
|| value instanceof String
|| value instanceof Number) {
log.info("Global arg -> " + name);
}
}
I had similar issue. Maybe it will be helpfull for others.
I use groove as script lang. My Task was to retrive all invokable functions from the script. And then filter this functions by some criteria.
Unfortunately this approach is usefull only for groovy...
Get script engine:
public ScriptEngine getEngine() throws Exception {
if (engine == null)
engine = new ScriptEngineManager().getEngineByName(scriptType);
if (engine == null)
throw new Exception("Could not find implementation of " + scriptType);
return engine;
}
Compile and evaluate script:
public void evaluateScript(String script) throws Exception {
Bindings bindings = getEngine().getBindings(ScriptContext.ENGINE_SCOPE);
bindings.putAll(binding);
try {
if (engine instanceof Compilable)
compiledScript = ((Compilable)getEngine()).compile(script);
getEngine().eval(script);
} catch (Throwable e) {
e.printStackTrace();
}
}
Get functions from script. I did not found other ways how to get all invokable methods from script except Reflection. Yeah, i know that this approach depends on ScriptEngine implementation, but it's the only one :)
public List getInvokableList() throws ScriptException {
List list = new ArrayList();
try {
Class compiledClass = compiledScript.getClass();
Field clasz = compiledClass.getDeclaredField("clasz");
clasz.setAccessible(true);
Class scrClass = (Class)clasz.get(compiledScript);
Method[] methods = scrClass.getDeclaredMethods();
clasz.setAccessible(false);
for (int i = 0, j = methods.length; i < j; i++) {
Annotation[] annotations = methods[i].getDeclaredAnnotations();
boolean ok = false;
for (int k = 0, m = annotations.length; k < m; k++) {
ok = annotations[k] instanceof CalculatedField;
if (ok) break;
}
if (ok)
list.add(methods[i].getName());
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
}
return list;
}
In my task i don't need all functions, for this i create custom annotation and use it in the script:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface CalculatedField {
}
Script example:
import com.vssk.CalculatedField;
def utilFunc(s) {
s
}
#CalculatedField
def func3() {
utilFunc('Testing func from groovy')
}
Method to invoke script function by it's name:
public Object executeFunc(String name) throws Exception {
return ((Invocable)getEngine()).invokeFunction(name);
}

Categories