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    }