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 }