001 package toolbus.adapter.java;
002
003 import java.lang.management.ManagementFactory;
004 import java.lang.management.MemoryMXBean;
005 import java.lang.management.ThreadInfo;
006 import java.lang.management.ThreadMXBean;
007 import java.lang.reflect.InvocationTargetException;
008 import java.lang.reflect.Method;
009 import java.lang.reflect.Modifier;
010 import java.net.InetAddress;
011 import java.util.HashMap;
012 import java.util.HashSet;
013 import java.util.Map;
014 import java.util.Set;
015
016 import toolbus.IOperations;
017 import toolbus.ToolBus;
018 import toolbus.adapter.AbstractTool;
019 import toolbus.adapter.ToolBridge;
020 import toolbus.logging.ILogger;
021 import toolbus.logging.IToolBusLoggerConstants;
022 import toolbus.logging.LoggerFactory;
023 import aterm.ATerm;
024 import aterm.ATermAppl;
025 import aterm.ATermBlob;
026 import aterm.ATermInt;
027 import aterm.ATermList;
028 import aterm.ATermPlaceholder;
029 import aterm.ATermReal;
030 import aterm.pure.PureFactory;
031
032 /**
033 * This class is a specialized ToolBridge version for Java tools. It supplies an interface for the tool
034 * to enable it to communicate with the ToolBus and it handles the invokation of methods on the tool.
035 *
036 * @author Arnold Lankamp
037 */
038 public class JavaToolBridge extends ToolBridge{
039 private final PureFactory termFactory;
040 private final AbstractTool tool;
041 private final Map<CallableMethodSignature, Method> callableFunctions;
042
043 /**
044 * Constructor.
045 *
046 * @param tool
047 * The tool this bridge needs to be associated with.
048 * @param toolName
049 * The name of the with this bridge associated tool.
050 * @param toolID
051 * The id of the with this bridge associated tool.
052 * @param host
053 * The host on which the ToolBus is running.
054 * @param port
055 * The port on which the ToolBus is running.
056 */
057 public JavaToolBridge(PureFactory termFactory, AbstractTool tool, String toolName, int toolID, InetAddress host, int port){
058 super(termFactory, toolName, toolID, host, port);
059
060 this.termFactory = termFactory;
061 this.tool = tool;
062 callableFunctions = new HashMap<CallableMethodSignature, Method>();
063 initCallableMethods();
064 }
065
066 /**
067 * Constructor.
068 *
069 * @param type
070 * The type of the tool (Remote of direct).
071 * @param tool
072 * The tool this bridge needs to be associated with.
073 * @param toolName
074 * The name of the with this bridge associated tool.
075 * @param toolID
076 * The id of the with this bridge associated tool.
077 * @param classLoader
078 * The classLoader to use for loading classes.
079 * @param toolbus
080 * The toolbus to link this bridge to.
081 */
082 public JavaToolBridge(PureFactory termFactory, AbstractTool tool, String toolName, int toolID, ToolBus toolbus){
083 super(termFactory, toolName, toolID, toolbus);
084
085 this.termFactory = termFactory;
086 this.tool = tool;
087 callableFunctions = new HashMap<CallableMethodSignature, Method>();
088 initCallableMethods();
089 }
090
091 /**
092 * @see toolbus.adapter.ToolBridge#doDo(ATerm)
093 */
094 public void doDo(ATerm aTerm){
095 try{
096 invokeMethod(IOperations.DO, aTerm);
097 }catch(MethodInvokationException miex){
098 LoggerFactory.log("Unable to execute DO request.", miex, ILogger.ERROR, IToolBusLoggerConstants.TOOL);
099 }
100 }
101
102 /**
103 * @see toolbus.adapter.ToolBridge#doEval(ATerm)
104 */
105 public ATerm doEval(ATerm aTerm){
106 ATerm result = null;
107 try{
108 result = invokeMethod(IOperations.EVAL, aTerm);
109 }catch(MethodInvokationException miex){
110 LoggerFactory.log("Unable to execute EVAL request.", miex, ILogger.ERROR, IToolBusLoggerConstants.TOOL);
111 }
112 return result;
113 }
114
115 /**
116 * @see toolbus.adapter.ToolBridge#doReceiveAckEvent(ATerm)
117 */
118 public void doReceiveAckEvent(ATerm aTerm){
119 tool.receiveAckEvent(aTerm);
120 }
121
122 /**
123 * @see toolbus.adapter.ToolBridge#doTerminate(ATerm)
124 */
125 public void doTerminate(ATerm aTerm){
126 tool.receiveTerminate(aTerm);
127 }
128
129 /**
130 * @see toolbus.adapter.ToolBridge#doGetPerformanceStats()
131 */
132 public ATerm doGetPerformanceStats(){
133 return getPerformanceStats();
134 }
135
136 /**
137 * This class represents a signature of a method (Name + return type + params).
138 *
139 * @author Arnold Lankamp
140 */
141 protected static class CallableMethodSignature{
142 public String methodName;
143 public Class<?> returnType;
144 public Class<?>[] parameters;
145
146 /**
147 * Custom hashcode function. This is needed because we use this object as a key in a
148 * HashMap.
149 */
150 public int hashCode(){
151 return methodName.hashCode();
152 }
153
154 /**
155 * Custom equals method. Matches when the object we are comparing ourselfs to can be called
156 * by this method signature.<br />
157 * <br />
158 * For example: void method(String o) equals void method(Object o), but method(Object o)
159 * does not equal method(String o).
160 *
161 * @see java.lang.Object#equals(java.lang.Object)
162 */
163 public boolean equals(Object o){
164 if(o == this){
165 return true;
166 }else if(o instanceof CallableMethodSignature){
167 CallableMethodSignature cms = (CallableMethodSignature) o;
168
169 if(!methodName.equals(cms.methodName)) return false;
170
171 if(cms.returnType != returnType && returnType != void.class && !isImplementationOf(cms.returnType, returnType)) return false;
172
173 if(parameters.length != cms.parameters.length) return false;
174 for(int i = 0; i < parameters.length; i++){
175 if(parameters[i] != cms.parameters[i] && !isImplementationOf(parameters[i], cms.parameters[i]) && cms.parameters[i] != ATerm.class) return false;
176 }
177
178 return true;
179 }
180
181 return false;
182 }
183 }
184
185 /**
186 * Custom exception. Thrown whenever the method associated with a certain DO or EVAL request
187 * could not be located, or something goes wrong during execution of that request.
188 *
189 * @author Arnold Lankamp
190 */
191 private static class MethodInvokationException extends Exception{
192 private final static long serialVersionUID = 7944846564197962451L;
193
194 /**
195 * Constructor.
196 *
197 * @see Exception#Exception(java.lang.String)
198 */
199 public MethodInvokationException(String message){
200 super(message);
201 }
202
203 /**
204 * Constructor.
205 *
206 * @see Exception#Exception(java.lang.Throwable)
207 */
208 public MethodInvokationException(Throwable exception){
209 super(exception);
210 }
211
212 /**
213 * Constructor.
214 *
215 * @see Exception#Exception(java.lang.String, java.lang.Throwable)
216 */
217 public MethodInvokationException(String message, Throwable exception){
218 super(message, exception);
219 }
220 }
221
222 /**
223 * Checks the the given class satisfies the given interface.
224 *
225 * @param clazz
226 * The class from which we want to know if it satisfies the given interface.
227 * @param superInterface
228 * The interface to satisfy.
229 * @return True if clazz implements the specified interface; false otherwise.
230 */
231 protected static boolean isImplementationOf(Class<?> clazz, Class<?> superInterface){
232 Class<?>[] interfaces = clazz.getInterfaces();
233
234 for(int i = 0; i < interfaces.length; i++){
235 Class<?> interfaceX = interfaces[i];
236 if(interfaceX.equals(superInterface) || isImplementationOf(interfaceX, superInterface)){
237 return true;
238 }
239 }
240
241 if(!clazz.isInterface()){
242 Class<?> superClass = clazz.getSuperclass();
243 if(superClass != null) return isImplementationOf(superClass, superInterface);
244 }
245
246 return false;
247 }
248
249 /**
250 * Reads all the callable methods of the tool associated with this tool bridge.
251 */
252 private void initCallableMethods(){
253 Class<?> toolClass = tool.getClass();
254 Method[] toolMethods = toolClass.getMethods();
255 for(int i = 0; i < toolMethods.length; i++){
256 Method toolMethod = toolMethods[i];
257 if(Modifier.isPublic(toolMethod.getModifiers())){
258 Class<?> returnType = toolMethod.getReturnType();
259
260 if(isImplementationOf(returnType, ATerm.class)) returnType = ATerm.class;
261
262 if(returnType == void.class || returnType == ATerm.class || isImplementationOf(returnType, ATerm.class)){
263 Class<?>[] parameters = toolMethod.getParameterTypes();
264
265 CallableMethodSignature cms = new CallableMethodSignature();
266 cms.methodName = toolMethod.getName();
267 cms.returnType = returnType;
268 cms.parameters = parameters;
269
270 callableFunctions.put(cms, toolMethod);
271 }
272 }
273 }
274 }
275
276 /**
277 * @see toolbus.adapter.ToolBridge#checkSignature(ATerm)
278 */
279 public boolean checkSignature(ATerm signatures){
280 boolean correct = true;
281
282 ATermList inputSignatures = (ATermList) ((ATermAppl) signatures).getArgument(0);
283 while(!inputSignatures.isEmpty()){
284 ATermAppl sig = (ATermAppl) inputSignatures.getFirst();
285 String operation = sig.getName();
286 if(operation.equals("rec-eval")){
287 ATermAppl methodTerm = (ATermAppl) sig.getArgument(1);
288
289 ATerm[] arguments = methodTerm.getArgumentArray();
290 Class<?>[] parameters = new Class[arguments.length];
291 for(int i = 0; i < parameters.length; i++){
292 // All the arguments are placeholders, so retrieve the type they represent.
293 parameters[i] = determainTermType(arguments[i]);
294 }
295
296 Method toolMethod = findMethod(EVAL, methodTerm.getName(), parameters);
297 if(toolMethod == null){
298 LoggerFactory.log("Unable to locate method matching the following signature: " + sig, ILogger.ERROR, IToolBusLoggerConstants.TOOL);
299 correct = false;
300 break;
301 }
302 }else if(operation.equals("rec-do")){
303 ATermAppl methodTerm = (ATermAppl) sig.getArgument(1);
304
305 ATerm[] arguments = methodTerm.getArgumentArray();
306 Class<?>[] parameters = new Class[arguments.length];
307 for(int i = 0; i < parameters.length; i++){
308 // Retrieve the types of the parameters.
309 parameters[i] = determainTermType(arguments[i]);
310
311 }
312
313 Method toolMethod = findMethod(DO, methodTerm.getName(), parameters);
314 if(toolMethod == null){
315 LoggerFactory.log("Unable to locate method matching the following signature: " + sig, ILogger.ERROR, IToolBusLoggerConstants.TOOL);
316 correct = false;
317 break;
318 }
319 }else if(operation.equals("rec-ack-event")){
320 // Ignore this, you can always handle event acknowledgements.
321 }else if(operation.equals("rec-terminate")){
322 // Why even bother to check this?
323 // It's impossible to write a tool that doesn't have it.
324 }else{
325 LoggerFactory.log("Unknown operation in signature: " + sig, ILogger.ERROR, IToolBusLoggerConstants.TOOL);
326 correct = false;
327 break;
328 }
329 inputSignatures = inputSignatures.getNext();
330 }
331
332 return correct;
333 }
334
335 /**
336 * Invokes the method associated with the given term and operation.
337 *
338 * @param operation
339 * Indicates the return type of the method (DO = void, EVAL = ATerm).
340 * @param aTerm
341 * The ATerm that indicates with method should be invoked.
342 * @return The ATerm the invoked method returned; null in case it returned void.
343 * @throws MethodInvokationException
344 * Thrown when something goes wrong.
345 */
346 private ATerm invokeMethod(byte operation, ATerm aTerm) throws MethodInvokationException{
347 ATerm returnValue = null;
348 try{
349 // We will always get an appl.
350 ATermAppl methodTerm = (ATermAppl) aTerm;
351
352 String methodName = methodTerm.getName();
353
354 ATerm[] arguments = methodTerm.getArgumentArray();
355 Object[] convertedArguments = new Object[arguments.length];
356 Class<?>[] parameters = new Class[arguments.length];
357 for(int i = 0; i < parameters.length; i++){
358 parameters[i] = determainTermType(arguments[i]);
359 convertedArguments[i] = convertArgument(arguments[i]);
360 }
361
362 Method toolMethod = findMethod(operation, methodName, parameters);
363
364 if(toolMethod == null){
365 String error = "No such method: " + methodName + ", with " + parameters.length + " arguments.";
366 LoggerFactory.log(error, ILogger.ERROR, IToolBusLoggerConstants.TOOL);
367 throw new MethodInvokationException(error);
368 }
369
370 Class<?>[] parameterTypes = toolMethod.getParameterTypes();
371 for(int i = 0; i < parameters.length; i++){
372 convertedArguments[i] = arguments[i];
373
374 if(parameterTypes[i] != ATerm.class){
375 convertedArguments[i] = convertArgument(arguments[i]);
376 }
377 }
378
379 returnValue = (ATerm) toolMethod.invoke(tool, convertedArguments);
380 }catch(ClassCastException ccex){
381 LoggerFactory.log("A class cast exception occured during the invokation of a method. Discarding term ....", ccex, ILogger.ERROR, IToolBusLoggerConstants.TOOL);
382 throw new MethodInvokationException(ccex);
383 }catch(InvocationTargetException itex){
384 LoggerFactory.log("Something went wrong during the invokation of a method. Discarding associated term ....", itex, ILogger.ERROR, IToolBusLoggerConstants.TOOL);
385 throw new MethodInvokationException(itex);
386 }catch(IllegalAccessException iaex){
387 LoggerFactory.log("Unable to invoke the with the term associated method. Discarding term ....", iaex, ILogger.ERROR, IToolBusLoggerConstants.TOOL);
388 throw new MethodInvokationException(iaex);
389 }
390
391 return returnValue;
392 }
393
394 private static String toMethodName(String str){
395 StringBuilder name = new StringBuilder();
396
397 boolean capNext = false;
398 for(int i = 0; i < str.length(); i++){
399 if(str.charAt(i) == '-'){
400 capNext = true;
401 }else if(capNext){
402 name.append(Character.toUpperCase(str.charAt(i)));
403 capNext = false;
404 }else{
405 name.append(str.charAt(i));
406 }
407 }
408 return name.toString();
409 }
410
411 /**
412 * Returns the tool method that matches the given name, arguments and operation.
413 *
414 * @param operation
415 * The operation associated with the given term.
416 * @param methodName
417 * The name of the method we want to find.
418 * @param parameters
419 * The list of parameters that the method needs be able to accept.
420 * @return The method corresponding to the given term.
421 */
422 private Method findMethod(byte operation, String methodName, Class<?>[] parameters){
423 CallableMethodSignature cms = new CallableMethodSignature();
424 cms.methodName = toMethodName(methodName);
425 cms.parameters = parameters;
426
427 if(operation == EVAL){
428 cms.returnType = ATerm.class;
429 }else if(operation == DO){
430 cms.returnType = void.class;
431 }
432
433 return callableFunctions.get(cms);
434 }
435
436 /**
437 * Determain the class type of the given aterm (depending on its signature).
438 *
439 * @param aTerm
440 * The aterm of which we need to determain the type.
441 * @return The Java class type the aterm corresponds to.
442 */
443 private Class<?> determainTermType(ATerm aTerm){
444 if(aTerm instanceof ATermPlaceholder){
445 ATermPlaceholder placeHolder = (ATermPlaceholder) aTerm;
446 ATermAppl typeTerm = (ATermAppl) placeHolder.getPlaceholder();
447 String typeName = typeTerm.getName();
448
449 if(typeName.equals("str")) return String.class;
450 if(typeName.equals("blob")) return byte[].class;
451 if(typeName.equals("int")) return int.class;
452 if(typeName.equals("real")) return double.class;
453 if(typeName.equals("list")) return ATermList.class;
454 if(typeName.equals("appl")) return ATermAppl.class;
455 if(typeName.equals("term")) return ATerm.class;
456 }else if(aTerm instanceof ATermAppl){
457 ATermAppl appl = (ATermAppl) aTerm;
458 if(appl.getAFun().isQuoted()) return String.class;
459 return ATermAppl.class;
460 }else if(aTerm instanceof ATermList){
461 return ATermList.class;
462 }else if(aTerm instanceof ATermInt){
463 return int.class;
464 }else if(aTerm instanceof ATermReal){
465 return double.class;
466 }else if(aTerm instanceof ATermBlob){
467 return byte[].class;
468 }
469
470 // Unable to determain type (or it's a type that doesn't need conversion).
471 return ATerm.class;
472 }
473
474 /**
475 * Converts the given aterm to its corresponding Java type object (if needed).
476 *
477 * @param aTerm
478 * The aterm to convert.
479 * @return The resulting Java type object.
480 */
481 private Object convertArgument(ATerm aTerm){
482 int type = aTerm.getType();
483 switch(type){
484 case ATerm.APPL:
485 ATermAppl appl = (ATermAppl) aTerm;
486 if(appl.getAFun().isQuoted()) return appl.getAFun().getName();
487 return appl;
488 case ATerm.LIST:
489 return aTerm;
490 case ATerm.INT:
491 ATermInt integer = (ATermInt) aTerm;
492 return new Integer(integer.getInt());
493 case ATerm.REAL:
494 ATermReal real = (ATermReal) aTerm;
495 return new Double(real.getReal());
496 case ATerm.BLOB:
497 ATermBlob blob = (ATermBlob) aTerm;
498 return blob.getBlobData();
499 default:
500 return aTerm; // No conversion needed, just return it.
501 }
502 }
503
504 /**
505 * Gathers performance statistics about this tool, like memory usage and the user-/system-time
506 * spend per thread.
507 *
508 * @return Performance statictics.
509 */
510 private ATerm getPerformanceStats(){
511 String type = getType();
512
513 // Type stuff
514 ATerm remote = termFactory.makeAppl(termFactory.makeAFun(type, 0, true));
515 ATerm toolType = termFactory.makeAppl(termFactory.makeAFun("type", 1, false), remote);
516
517 ATerm java = termFactory.makeAppl(termFactory.makeAFun("Java", 0, true));
518 ATerm toolLanguage = termFactory.makeAppl(termFactory.makeAFun("language", 1, false), java);
519
520 ATerm toolData = termFactory.makeAppl(termFactory.makeAFun("tool", 2, false), toolType, toolLanguage);
521
522 // Memory stuff
523 MemoryMXBean mmxb = ManagementFactory.getMemoryMXBean();
524 long heapMemoryUsage = mmxb.getHeapMemoryUsage().getUsed();
525 long nonHeapMemoryUsage = mmxb.getNonHeapMemoryUsage().getUsed();
526
527 ATerm heapUsage = termFactory.makeAppl(termFactory.makeAFun("heap-usage", 1, false), termFactory.makeInt(((int) (heapMemoryUsage / 1024))));
528 ATerm nonHeapUsage = termFactory.makeAppl(termFactory.makeAFun("non-heap-usage", 1, false), termFactory.makeInt(((int) (nonHeapMemoryUsage / 1024))));
529
530 ATerm memory = termFactory.makeAppl(termFactory.makeAFun("memory-usage", 2, false), heapUsage, nonHeapUsage);
531
532 // Thread stuff
533 ThreadGroup currentThreadGroup = Thread.currentThread().getThreadGroup();
534 Thread[] relevantThreads = new Thread[currentThreadGroup.activeCount() * 2 + 10]; // Create an array that's more then big enough.
535 currentThreadGroup.enumerate(relevantThreads);
536
537 Set<Long> relevantThreadIds = new HashSet<Long>();
538 for(int i = 0; i < relevantThreads.length; i++){
539 Thread thread = relevantThreads[i];
540 if(thread != null){
541 relevantThreadIds.add(new Long(thread.getId()));
542 }
543 }
544
545 ThreadMXBean tmxb = ManagementFactory.getThreadMXBean();
546
547 ATerm threads;
548
549 long[] threadIds = tmxb.getAllThreadIds();
550 int nrOfThreads = threadIds.length;
551 try{
552 ATermList threadsList = termFactory.makeList();
553 for(int i = 0; i < nrOfThreads; i++){
554 long threadId = threadIds[i];
555 if(relevantThreadIds.contains(new Long(threadId))){ // Only list the info if we're interested in it.
556 ThreadInfo ti = tmxb.getThreadInfo(threadId);
557 if(ti != null){
558 String threadName = ti.getThreadName();
559 long userTime = tmxb.getThreadUserTime(threadIds[i]);
560 long systemTime = tmxb.getThreadCpuTime(threadIds[i]) - userTime;
561
562 if((userTime + systemTime) <= 0) continue;
563
564 ATerm userTimeTerm = termFactory.makeAppl(termFactory.makeAFun("user-time", 1, false), termFactory.makeInt(((int) (userTime / 1000000))));
565 ATerm systemTimeTerm = termFactory.makeAppl(termFactory.makeAFun("system-time", 1, false), termFactory.makeInt(((int) (systemTime / 1000000))));
566 ATerm thread = termFactory.makeAppl(termFactory.makeAFun(threadName, 2, false), userTimeTerm, systemTimeTerm);
567
568 threadsList = termFactory.makeList(thread, threadsList);
569 }
570 }
571 }
572
573 threads = termFactory.makeAppl(termFactory.makeAFun("threads", 1, false), threadsList);
574 }catch(UnsupportedOperationException uoex){
575 threads = termFactory.make("threads(unsupported-operation)");
576 LoggerFactory.log("Thread time profiling is not supported by this JVM.", ILogger.ERROR, IToolBusLoggerConstants.TOOL);
577 }
578
579 return termFactory.makeAppl(termFactory.makeAFun("performance-stats", 3, false), toolData, memory, threads);
580 }
581 }