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);
}
Related
I am trying to understand why a synchronized block is used in this method from the vertx handlebars library io.vertx.ext.web.templ.handlebars.impl.HandlebarsTemplateEngineImpl class:
#Override
public void render(Map<String, Object> context, String templateFile, Handler<AsyncResult<Buffer>> handler) {
try {
int idx = templateFile.lastIndexOf('/');
String prefix = "";
String basename = templateFile;
if (idx != -1) {
prefix = templateFile.substring(0, idx);
basename = templateFile.substring(idx + 1);
}
Template template = isCachingEnabled() ? cache.get(templateFile) : null;
if (template == null) {
synchronized (this) {
loader.setPrefix(prefix);
// Strip leading slash from Utils##normalizePath
template = handlebars.compile(basename);
if (isCachingEnabled()) {
cache.put(templateFile, template);
}
}
}
Context engineContext = Context.newBuilder(context).resolver(getResolvers()).build();
handler.handle(Future.succeededFuture(Buffer.buffer(template.apply(engineContext))));
} catch (Exception ex) {
handler.handle(Future.failedFuture(ex));
}
}
Please explain it to me like I'm an idiot!
First, synchronization questions are never "idiot" questions.
I spent some time looking at this code too, and still not 100% sure it's totally correct.
The main reason to have synchronized block here is to protect the following two methods from executing out of order:
loader.setPrefix(prefix);
...
template = handlebars.compile(basename);
You see, Handlebars has a reference to loader:
loader = new Loader(vertx);
...
handlebars = new Handlebars(loader);
Possible scenario without sync block would be
T1 sets prefix to A and switches
T2 sets prefix to B and switches
T1 compiles template with prefix set to B, while thinking it's still A
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;
}
}
I am trying to compile Lua code that has two functions which I want to invoke and get some information from but when I use invokemethod on the LuaValue object, I get this error
LuaError: attempt to index ? (a function value)
The code is inside a LuaScript class I created for convenience
This method is first called to compile the file
public void compile(File file) {
try {
Globals globals = JmePlatform.standardGlobals();
compiledcode = globals.load(new FileReader(file), "script");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
And then this is used to invoke the function getSameTiles from my lua script
public Object invoke(String func, Object... parameters) {
if (parameters != null && parameters.length > 0) {
LuaValue[] values = new LuaValue[parameters.length];
for (int i = 0; i < parameters.length; i++)
values[i] = CoerceJavaToLua.coerce(parameters[i]);
return compiledcode.invokemethod(func, LuaValue.listOf(values));
} else
return compiledcode.invokemethod(func);
}
The error LuaError: attempt to index ? (a function value) occurs at the line return compiledcode.invokemethod(func); where "getSameTiles" is passed as the string for func
This is my Lua code
function getSameTiles()
--My code here
end
There are a couple of issues that needed fixing.
Firstly, in lua, load() returns a function which you'd then need to call to execute the script.
Secondly, what the script does is add a function to the global table _G. In order to invoke that function you'll need to get the function from the Globals table and call that.
The following code does this
Globals globals = JmePlatform.standardGlobals();
public void compile(File file) {
try {
globals.load(new FileReader(file), "script").call();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public Object invoke(String func, Object... parameters) {
if (parameters != null && parameters.length > 0) {
LuaValue[] values = new LuaValue[parameters.length];
for (int i = 0; i < parameters.length; i++)
values[i] = CoerceJavaToLua.coerce(parameters[i]);
return globals.get(func).call(LuaValue.listOf(values));
} else
return globals.get(func).call();
}
In Javascript i have the following code:
var r=applet.foo({var0:99,var1:'foo',var2:applet});
In my Java applet i have the following:
public JSObject foo(JSObject args){
System.out.println("The function is correctly invoked");
//In fact, the following works perfectly:
System.out.println("var1 is:"+(String)args.getMember("var1"));
JSObject w=JSObject.getWindow(this);
JSObject j=(JSObject)w.eval("new Object();");
Map m=new Hashmap();
//TODO here all the keys and values of args should be added to m
m.put("hello","world");
//TODO here all the keys and values of m should be added to j
return j;
}
How can this be done? (TODOs)
Reading http://docstore.mik.ua/orelly/web/jscript/ch19_06.html, i noticed theres a getSlot method for JSObject but if i do
args.getSlot(0)
all i have is one Exception:
netscape.javascript.JSException: No such slot 0 on JavaScript object
...
Unfortunately, Errandir's solution works only when you know a name of global variable that can be used to access an object you want to get properties' names of. You need to know this name to be able to add keys method to the object, and invoke it using JSObject's call method later. Of course, you can pass a global name of your object to Java if you have it. This solution doesn't look so good especially when you can't refer to your object in global context.
As an alternative, I proposed to use this of JSObject's eval method in the comment supposing that it will do all the work. And it does. But a big disappointent was that it works as expected only in Mozilla Firefox and Opera. In Internet Explorer 9 and Google Chrome (tested under Windows 7 and Ubuntu 12.04 LTS) this of eval method always refers to applet's document window ignoring which JavaScript object JSObject instance actually represents. I don't know whether it's a bug or simply LiveConnect is supported in these browsers very poorly.
The good news is that call method of JSObject executes specified function on the proper context. Keeping that in mind I finally found a solution how a list of names of JavaScript object's properties can be retrieved. The idea is to define a temporary function in global context using eval method. This function has to receive a JavaScript object we want to get properties of and to return names of these properties as an array. After that we can invoke the temporary function through JSObject's call method passing a Java representation of concerned JavaScript object (jsObject in my method below or args as it sounds in the question). At last, temporary function can be removed.
public static ArrayList<String> getJsObjectPropertiesNames(Applet applet, JSObject jsObject) {
if (applet == null || jsObject == null)
return null;
// Retrieving global context - a JSObject representing a window applet belongs to
JSObject globalContext;
try {
globalContext = JSObject.getWindow(applet);
}
catch (JSException ex) {
return null;
}
// Checking whether passed object is not an array
try {
jsObject.getSlot(0);
return null;
}
catch (JSException e) {
}
String keysFunctionName = String.format("_getKeys%d", Calendar.getInstance().getTimeInMillis());
jsObject.eval("window['" + keysFunctionName + "'] = function(jsObject) { return Object.keys(jsObject) }");
JSObject propertiesNamesJsObject = (JSObject)globalContext.call(keysFunctionName, new Object[] { jsObject });
jsObject.eval("delete(window['" + keysFunctionName + "'])");
ArrayList<String> propertiesNames = new ArrayList<>();
try {
int slotIndex = 0;
while (true) {
Object propertyName = propertiesNamesJsObject.getSlot(slotIndex);
if (propertyName instanceof String)
propertiesNames.add((String)propertyName);
slotIndex++;
}
}
catch (JSException e) {
}
return propertiesNames;
}
As a solution, you could define method keys as proposed here (You can do it within your java-code using JSObject.eval(...)). Then you could get keys like:
JSObject keys = (JSObject)args.call("keys", Collections.EMPTY_LIST);
keys.getSlot(0);
Here below I print a String, please modify it to get whatever you need.
public final static String getKeys = "{var keys = [];for (var key in this) {keys.push(key);} keys;}";
private static String printProperties(final Object o,
final boolean printType,
final int level,
final String tab) {
final StringBuilder sb = new StringBuilder(100);
if (printType) {
sb.append("(");
sb.append(o.getClass().getSimpleName());
sb.append(") ");
}
if (o instanceof JSObject) {
sb.append("{\n");
final JSObject js = (JSObject) o;
final JSObject keys = (JSObject) js.eval(getKeys);
boolean needComma = false;
for (int i = 0;; i++) {
final String key = (String) keys.getSlot(i);
if ((key != null) && !(key.equals("undefined"))) {
final Object val = js.getMember(key);
if (!needComma) {
needComma = true;
} else {
sb.append(",\n");
}
sb.append(multitab(tab, level));
sb.append(key);
sb.append(":");
sb.append(printProperties(val, printType, level + 1, tab));
} else {
break;
}
}
sb.append("\n");
sb.append(multitab(tab, level - 1));
sb.append("}");
} else {
sb.append(o);
}
return sb.toString();
}
private final static String tab = " ";
private static String multitab(final String tab,
int i) {
final StringBuilder sb = new StringBuilder();
while (i-- > 0) {
sb.append(tab);
}
return sb.toString();
}
I was writing a toString() for a class in Java the other day by manually writing out each element of the class to a String and it occurred to me that using reflection it might be possible to create a generic toString() method that could work on ALL classes. I.E. it would figure out the field names and values and send them out to a String.
Getting the field names is fairly simple, here is what a co-worker came up with:
public static List initFieldArray(String className) throws ClassNotFoundException {
Class c = Class.forName(className);
Field field[] = c.getFields();
List<String> classFields = new ArrayList(field.length);
for (int i = 0; i < field.length; i++) {
String cf = field[i].toString();
classFields.add(cf.substring(cf.lastIndexOf(".") + 1));
}
return classFields;
}
Using a factory I could reduce the performance overhead by storing the fields once, the first time the toString() is called. However finding the values could be a lot more expensive.
Due to the performance of reflection this may be more hypothetical then practical. But I am interested in the idea of reflection and how I can use it to improve my everyday programming.
Apache commons-lang ReflectionToStringBuilder does this for you.
import org.apache.commons.lang3.builder.ReflectionToStringBuilder
// your code goes here
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
Another option, if you are ok with JSON, is Google's GSON library.
public String toString() {
return new GsonBuilder().setPrettyPrinting().create().toJson(this);
}
It's going to do the reflection for you. This produces a nice, easy to read JSON file. Easy-to-read being relative, non tech folks might find the JSON intimidating.
You could make the GSONBuilder a member variable too, if you don't want to new it up every time.
If you have data that can't be printed (like a stream) or data you just don't want to print, you can just add #Expose tags to the attributes you want to print and then use the following line.
new GsonBuilder()
.setPrettyPrinting()
.excludeFieldsWithoutExposeAnnotation()
.create()
.toJson(this);
W/reflection, as I hadn't been aware of the apache library:
(be aware that if you do this you'll probably need to deal with subobjects and make sure they print properly - in particular, arrays won't show you anything useful)
#Override
public String toString()
{
StringBuilder b = new StringBuilder("[");
for (Field f : getClass().getFields())
{
if (!isStaticField(f))
{
try
{
b.append(f.getName() + "=" + f.get(this) + " ");
} catch (IllegalAccessException e)
{
// pass, don't print
}
}
}
b.append(']');
return b.toString();
}
private boolean isStaticField(Field f)
{
return Modifier.isStatic(f.getModifiers());
}
If you're using Eclipse, you may also have a look at JUtils toString generator, which does it statically (generating the method in your source code).
You can use already implemented libraries, as ReflectionToStringBuilder from Apache commons-lang. As was mentioned.
Or write smt similar by yourself with reflection API.
Here is some example:
class UniversalAnalyzer {
private ArrayList<Object> visited = new ArrayList<Object>();
/**
* Converts an object to a string representation that lists all fields.
* #param obj an object
* #return a string with the object's class name and all field names and
* values
*/
public String toString(Object obj) {
if (obj == null) return "null";
if (visited.contains(obj)) return "...";
visited.add(obj);
Class cl = obj.getClass();
if (cl == String.class) return (String) obj;
if (cl.isArray()) {
String r = cl.getComponentType() + "[]{";
for (int i = 0; i < Array.getLength(obj); i++) {
if (i > 0) r += ",";
Object val = Array.get(obj, i);
if (cl.getComponentType().isPrimitive()) r += val;
else r += toString(val);
}
return r + "}";
}
String r = cl.getName();
// inspect the fields of this class and all superclasses
do {
r += "[";
Field[] fields = cl.getDeclaredFields();
AccessibleObject.setAccessible(fields, true);
// get the names and values of all fields
for (Field f : fields) {
if (!Modifier.isStatic(f.getModifiers())) {
if (!r.endsWith("[")) r += ",";
r += f.getName() + "=";
try {
Class t = f.getType();
Object val = f.get(obj);
if (t.isPrimitive()) r += val;
else r += toString(val);
} catch (Exception e) {
e.printStackTrace();
}
}
}
r += "]";
cl = cl.getSuperclass();
} while (cl != null);
return r;
}
}
Not reflection, but I had a look at generating the toString method (along with equals/hashCode) as a post-compilation step using bytecode manipulation. Results were mixed.
Here is the Netbeans equivalent to Olivier's answer; smart-codegen plugin for Netbeans.