001/* An actor that wraps (an instance of) a Java class. 002 003 Copyright (c) 2001-2014 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 */ 028package ptolemy.actor.lib; 029 030import java.lang.reflect.Constructor; 031import java.lang.reflect.InvocationTargetException; 032import java.lang.reflect.Method; 033import java.lang.reflect.Modifier; 034import java.util.Hashtable; 035import java.util.Iterator; 036 037import ptolemy.actor.IOPort; 038import ptolemy.actor.TypedAtomicActor; 039import ptolemy.data.BooleanToken; 040import ptolemy.data.ComplexToken; 041import ptolemy.data.DoubleToken; 042import ptolemy.data.FixToken; 043import ptolemy.data.IntToken; 044import ptolemy.data.LongToken; 045import ptolemy.data.RecordToken; 046import ptolemy.data.StringToken; 047import ptolemy.data.Token; 048import ptolemy.data.expr.ConversionUtilities; 049import ptolemy.kernel.CompositeEntity; 050import ptolemy.kernel.util.IllegalActionException; 051import ptolemy.kernel.util.NameDuplicationException; 052import ptolemy.kernel.util.StringAttribute; 053import ptolemy.math.Complex; 054import ptolemy.math.FixPoint; 055 056/////////////////////////////////////////////////////////////////// 057//// ClassWrapper 058 059/** 060 This actor wraps (an instance of) a Java class specified by the 061 <i>className</i> parameter. The actor has no ports when created. 062 If an input port is added to the actor, the name of the port is 063 interpreted as the name of a method of the Java class. When the 064 actor is fired and a token is received from this input port, the 065 value of the token is treated as the argument(s) for invoking 066 the method. If the method has a return value and the actor has 067 an output port named <i>methodName</i>Result, the return value 068 is wrapped in a token that is sent to the output port. 069 <p> 070 For example, suppose the specified class has a method named foo 071 and the actor has an input port of the same name. If method foo 072 takes no argument, the token received from port <i>foo</i> is 073 treated as the trigger for invoking the method, and its content 074 is ignored. If method foo takes arguments, the input token 075 should be a record token whose field values are used as the 076 arguments. The field labels of the record token should be "arg1", 077 "arg2", etc. For example, if method foo takes two double arguments, 078 the record token "{arg1 = 0.0, arg2 = 1.0}" can be the input. 079 080 A special case is when method foo takes one argument, the token 081 containing the argument value can be input directly, and does not 082 need to be put into a record token. 083 <p> 084 FIXME: Need to set type constraints appropriately. 085 Need (and how) to handle overloaded methods. 086 087 @author Xiaojun Liu 088 @version $Id$ 089 @since Ptolemy II 2.0 090 @Pt.ProposedRating Red (liuxj) 091 @Pt.AcceptedRating Red (liuxj) 092 */ 093public class ClassWrapper extends TypedAtomicActor { 094 /** Construct an actor with the given container and name. 095 * In addition to invoking the base class constructor, create 096 * the <i>className</i> parameter. 097 * @param container The container of this actor. 098 * @param name The name of this actor. 099 * @exception IllegalActionException If the actor cannot be contained 100 * by the proposed container. 101 * @exception NameDuplicationException If the container already has an 102 * actor with the specified name. 103 */ 104 public ClassWrapper(CompositeEntity container, String name) 105 throws NameDuplicationException, IllegalActionException { 106 super(container, name); 107 className = new StringAttribute(this, "className"); 108 } 109 110 /////////////////////////////////////////////////////////////////// 111 //// ports and parameters //// 112 113 /** The name of the Java class. 114 */ 115 public StringAttribute className; 116 117 /////////////////////////////////////////////////////////////////// 118 //// public methods //// 119 120 /** Read at most one token from each input port. If an input port 121 * has a token, the content of the token is used as argument(s) 122 * for invoking (on the wrapped instance or class) the method of 123 * the same name as the port. If the method has a return value, 124 * the value is wrapped in a token, and is sent to the output port 125 * named <i>methodName</i>Result. 126 * @exception IllegalActionException If the method invocation fails. 127 */ 128 @Override 129 public void fire() throws IllegalActionException { 130 super.fire(); 131 Iterator inPorts = inputPortList().iterator(); 132 133 while (inPorts.hasNext()) { 134 IOPort inPort = (IOPort) inPorts.next(); 135 136 if (inPort.hasToken(0)) { 137 _invoke(inPort, inPort.get(0)); 138 } 139 } 140 } 141 142 /** Get the Class object of the specified class. Gather method 143 * invocation information corresponding to each input port. If 144 * at least one method corresponding to a port is not static, 145 * create an instance of the specified class. 146 * @exception IllegalActionException If the specified class cannot 147 * be loaded, or there is no method of the same name as an input 148 * port, or an instance of the class cannot be created. 149 */ 150 @Override 151 public void preinitialize() throws IllegalActionException { 152 super.preinitialize(); 153 try { 154 _class = Class.forName(className.getExpression()); 155 } catch (ClassNotFoundException ex) { 156 throw new IllegalActionException(this, ex, "Cannot find specified " 157 + "class " + className.getExpression()); 158 } 159 160 _methodTable = new Hashtable(); 161 162 Method[] methods = _class.getMethods(); 163 Iterator inPorts = inputPortList().iterator(); 164 boolean needInstance = false; 165 166 while (inPorts.hasNext()) { 167 IOPort inPort = (IOPort) inPorts.next(); 168 String portName = inPort.getName(); 169 Method m = null; 170 171 for (int i = 0; i < methods.length; ++i) { 172 if (methods[i].getName().equals(portName)) { 173 m = methods[i]; 174 break; 175 } 176 } 177 178 if (m == null) { 179 throw new IllegalActionException(this, "The specified class " 180 + "does not have a method of the same name as input " 181 + "port " + portName); 182 } 183 184 Object[] methodInfo = new Object[3]; 185 methodInfo[0] = m; 186 methodInfo[1] = m.getParameterTypes(); 187 188 IOPort outPort = (IOPort) getPort(portName + "Result"); 189 190 if (outPort != null && outPort.isOutput()) { 191 methodInfo[2] = outPort; 192 } else { 193 methodInfo[2] = null; 194 } 195 196 _methodTable.put(inPort, methodInfo); 197 198 if (!Modifier.isStatic(m.getModifiers())) { 199 needInstance = true; 200 } 201 } 202 203 _instance = null; 204 205 if (needInstance) { 206 try { 207 // FIXME: here only try to use a constructor with no argument 208 Constructor constructor = _class.getConstructor(new Class[0]); 209 _instance = constructor.newInstance(new Object[0]); 210 } catch (Exception ex) { 211 throw new IllegalActionException(this, ex, "Cannot create an " 212 + "instance of the specified class"); 213 } 214 } 215 } 216 217 /////////////////////////////////////////////////////////////////// 218 //// private methods //// 219 // Invoke on the wrapped instance the method with the same name as the 220 // specified port, treating argv as the arguments. 221 // NOTE: Should this method return a boolean, false when the invocation 222 // fails? The actor may have an error output port, and send a token 223 // to this port when invocation fails. 224 private void _invoke(IOPort port, Token argv) 225 throws IllegalActionException { 226 // assert port.isInput() 227 Object[] methodInfo = (Object[]) _methodTable.get(port); 228 229 // when _methodTable is built, an entry for each input port is 230 // guaranteed 231 Method m = (Method) methodInfo[0]; 232 Class[] argTypes = (Class[]) methodInfo[1]; 233 int args = argTypes.length; 234 IOPort outPort = (IOPort) methodInfo[2]; 235 236 // The following code is mostly copied from data.expr.ASTPtFunctionNode 237 Object[] argValues = new Object[args]; 238 239 if (args > 0) { 240 RecordToken argRecord = null; 241 242 if (argv instanceof RecordToken) { 243 argRecord = (RecordToken) argv; 244 } else if (args > 1) { 245 throw new IllegalActionException(this, "cannot convert " 246 + "input token to method call arguments."); 247 } 248 249 for (int i = 0; i < args; ++i) { 250 Token arg = null; 251 252 if (argRecord != null) { 253 arg = argRecord.get("arg" + (i + 1)); 254 } else { 255 // this is the case when the method takes one argument 256 // and the input token is not a record token 257 arg = argv; 258 } 259 260 if (argTypes[i].isAssignableFrom(arg.getClass())) { 261 argValues[i] = arg; 262 } else { 263 argValues[i] = ConversionUtilities 264 .convertTokenToJavaType(arg); 265 } 266 } 267 } 268 269 Object result = null; 270 271 try { 272 result = m.invoke(_instance, argValues); 273 } catch (InvocationTargetException ex) { 274 // get the exception produced by the invoked function 275 ex.getTargetException().printStackTrace(); 276 throw new IllegalActionException(this, ex.getTargetException(), 277 "Error invoking method " + m.getName()); 278 } catch (Exception ex) { 279 throw new IllegalActionException(this, ex, 280 "Error invoking method " + m.getName()); 281 } 282 283 Token resultToken = null; 284 285 if (result == null) { 286 // the method does not return value 287 return; 288 } else if (result instanceof Token) { 289 resultToken = (Token) result; 290 } else if (result instanceof Double) { 291 resultToken = new DoubleToken(((Double) result).doubleValue()); 292 } else if (result instanceof Integer) { 293 resultToken = new IntToken(((Integer) result).intValue()); 294 } else if (result instanceof Long) { 295 resultToken = new LongToken(((Long) result).longValue()); 296 } else if (result instanceof String) { 297 resultToken = new StringToken((String) result); 298 } else if (result instanceof Boolean) { 299 resultToken = new BooleanToken(((Boolean) result).booleanValue()); 300 } else if (result instanceof Complex) { 301 resultToken = new ComplexToken((Complex) result); 302 } else if (result instanceof FixPoint) { 303 resultToken = new FixToken((FixPoint) result); 304 } else { 305 throw new IllegalActionException(this, "Result of method call " 306 + port.getName() + " is not a supported type: boolean, " 307 + "complex, fixpoint, double, int, long and String, " 308 + "or a Token."); 309 } 310 311 if (outPort != null) { 312 outPort.send(0, resultToken); 313 } 314 } 315 316 /////////////////////////////////////////////////////////////////// 317 //// private variables //// 318 // A hash table containing the method invocation information for the 319 // input ports. For each input port, the entry in the hash table is 320 // an array of three objects. The first is the Method object of the 321 // method to be invoked. The second is the array of argument types 322 // of the method. The third is the output port to which the return 323 // value of the method is sent. 324 private Hashtable _methodTable = null; 325 326 // The instance of the specified class. 327 private Object _instance = null; 328 329 // The Class object of the specified class. 330 private Class _class = null; 331}