/*
* SwingOSC.java
* SwingOSC
*
* Copyright (c) 2005-2011 Hanns Holger Rutz. All rights reserved.
*
* This software is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either
* version 2, june 1991 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License (gpl.txt) along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
* For further information, please contact Hanns Holger Rutz at
* contact@sciss.de
*
*
* Changelog:
* 19-Aug-05 created
* 05-Sep-05 lot's of changes
* - deferred messages to event thread
* - added /n_get ; added nested propertyNames (i.e. "size.width")
* - added special node names "GraphicsEnvironment", "Toolkit"
* - -U option is now -u for consistency with scsynth
* 10-Nov-05 OSC command set completely reworked to make it as simple as possible
* and allow any kind of extensions not limited to the awt/swing sphere.
* special string<->object conversions are replaced by a nesting mechanism
* using message-blobs as automatically generated by supercollider when nesting arrays
* 14-Feb-06 - fixed bug in /free
* - mostly exchanged decodeMessageArgs with step-by-step
* decoding using decodeMessageArg such as to provide a consistent causality
* when arguments include OSCMessages with side effects
* - added /field(r)
* - all commands are now subclasses of BasicCmd, more efficient lookup
* - removed pre-initialized object "toolkit" which would force the application
* to become a swing app (in Mac OS X)
* 25-Mar-06 - new String based message nesting format
* - uses new OSCClient class
* 08-Aug-06 - uses custom class loader ; added /classes command
* 01-Oct-06 uses new NetUtil version
* 18-Jan-08 fixed checkMethodArgs to put lower priority to Object assignment
*/
package de.sciss.swingosc;
import java.awt.EventQueue;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.ArrayList;
//import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import de.sciss.app.BasicEvent;
import de.sciss.app.EventManager;
import de.sciss.net.OSCBundle;
import de.sciss.net.OSCChannel;
import de.sciss.net.OSCListener;
import de.sciss.net.OSCMessage;
import de.sciss.net.OSCPacket;
import de.sciss.net.OSCPacketCodec;
import de.sciss.net.OSCServer;
import de.sciss.net.OSCTransmitter;
//import de.sciss.util.DynamicURLClassLoader;
import de.sciss.util.URLClassLoaderManager;
/**
* A one-in-all class launching an OSC widget server. Windows and
* gadgets can be created by sending OSC commands to the server.
* Action listeners can be registered and are notified about button
* state changes.
*
* @author Hanns Holger Rutz
* @version 0.66, 20-Oct-11
*
* @todo rendezvous option (jmDNS)
* @todo [NOT?] /n_notify (sending things like /n_go, n_end)
* @todo since we invoke runLater already, we could add timetag flavors easily
* @todo /import !!!check ImportText.xcodeproj!!! XXX
*/
public class SwingOSC
implements OSCListener, OSCProcessor, EventManager.Processor
{
public static final double VERSION = 0.66;
protected OSCServer serv = null;
// private final List collMessages = Collections.synchronizedList( new ArrayList() ); // elements = IncomingMessage instances
private final Map mapClients = new HashMap(); // SocketAddr to Client
protected final Map globals = new HashMap(); // objectID to value
private final Map constants = new HashMap(); // objectID to value
private static SwingOSC instance;
private SwingClient currentClient = null;
protected final Map oscCmds = new HashMap( 20 ); // OSC-command-name to OSCProcessor
private final EventManager elm;
private static int numTraceLines = 3;
// private final DynamicURLClassLoader classLoaderMgr;
protected final URLClassLoaderManager classLoaderMgr;
public static void main( String[] args )
{
System.exit( launch( args ));
}
/**
* Launches the server with given command line options.
*
* @param args the array of command line options, e.g.
* <code>-t 57111</code>.
* @return the return code (0 for success, 1 for failure)
*/
public static int launch( String[] args )
{
String arg;
int port = 0;
boolean loopBack = false;
boolean initSwing = false;
InetSocketAddress hello = null;
int bufSize = 65536;
String protocol = OSCChannel.UDP;
int i, j;
for( i = 0; i < args.length; i++ ) {
arg = args[ i ];
if( arg.equals( "-u" )) {
i++;
if( i < args.length ) {
try {
port = Integer.parseInt( args[ i ]);
}
catch( NumberFormatException e1 ) {
printException( e1, "-u" );
}
} else {
System.out.println( "-u option requires port specification! (-u <portNum>)" );
}
} else if( arg.equals( "-t" )) {
i++;
if( i < args.length ) {
try {
port = Integer.parseInt( args[ i ]);
protocol = OSCChannel.TCP;
}
catch( NumberFormatException e1 ) {
printException( e1, "-t" );
}
} else {
System.out.println( "-t option requires port specification! (-t <portNum>)" );
}
} else if( arg.equals( "-L" )) {
loopBack = true;
} else if( arg.equals( "-b" )) {
i++;
if( i < args.length ) {
try {
bufSize = Integer.parseInt( args[ i ]);
}
catch( NumberFormatException e1 ) {
printException( e1, "-b" );
}
} else {
System.out.println( "-b option requires size specification! (-b <size>)" );
}
} else if( arg.equals( "-i" )) {
initSwing = true;
} else if( arg.equals( "-h" )) {
i++;
if( i < args.length ) {
j = args[ i ].indexOf( ':' );
try {
hello = new InetSocketAddress( args[ i ].substring( 0, j ),
Integer.parseInt( args[ i ].substring( j + 1 )));
}
catch( IndexOutOfBoundsException e1 ) {
printException( e1, "-h" );
}
catch( NumberFormatException e1 ) {
printException( e1, "-h" );
}
catch( IllegalArgumentException e1 ) {
printException( e1, "-h" );
}
} else {
System.out.println( "-h option requires address specification! (-h <hostName:port>)" );
}
} else if( arg.equals( "--help" )) {
System.out.println( "SwingOSC command line options:\n\n" +
" -u <portNum> at which UDP port the server shall receive.\n"+
" may be omitted (a number is automatically\n"+
" generated by the system).\n\n"+
" -t <portNum> at which TCP port the server shall receive.\n"+
" -L use the loopback address for reception.\n"+
" this restricts access to the local machine.\n\n"+
" -b <size> maximum size of OSC messages (default 65536).\n"+
" -i initialize GUI environment upon startup,\n"+
" i.e. create menubar and dock icon on mac os\n"+
" -h <host:port> send /swing hello message to specified address" );
return 0;
} else {
System.out.println( "Ignoring unknown option " + args[ i ] +
"\nUse --help for a list of options." );
}
}
new SwingOSC();
if (!loopBack) {
System.out.println("\n WARNING : your system is very vulnerable\n"
+ " when connected to a network!\n"
+ " using SwingOSC, it is easy to\n"
+ " hijack the machine or even erase\n"
+ " the hardisk.\n");
}
try {
synchronized( instance ) {
instance.start( protocol, port, loopBack, bufSize, initSwing, hello );
instance.wait();
return instance.quit();
}
}
catch( InterruptedException e2 ) {
System.out.println( e2.getClass().getName() + " : " + e2.getLocalizedMessage() );
}
catch( IOException e1 ) {
printException( e1, instance.getClass().getName() );
}
return 1;
}
/*
* @synchronization needn't be called in event thread
*/
public SwingOSC()
{
instance = this;
// classLoaderMgr = new DynamicURLClassLoader( this.getClass().getClassLoader() );
classLoaderMgr = new URLClassLoaderManager( this.getClass().getClassLoader() );
// helper symbols
constants.put( "null", null );
constants.put( "brko", "[" );
constants.put( "brkc", "]" );
constants.put( "swing", this );
// constants.put( "toolkit", Toolkit.getDefaultToolkit() );
// OSC commands (they register themselves)
new CmdQuit( this );
new CmdNew();
new CmdRef();
new CmdLocal();
new CmdGlobal();
new CmdSet();
new CmdGet();
new CmdMethod();
new CmdMethodR();
new CmdField();
new CmdFieldR();
new CmdFree();
new CmdArray();
new CmdQuery();
new CmdPrint();
new CmdDumpOSC();
new CmdClasses();
// try {
// final Class[] classes = AudioFileFormat.class.getClasses();
// for( int i = 0; i < classes.length; i++ ) {
// System.out.println( "i = "+i+"; name = "+classes[i].getName() );
// }
// }
// catch( Exception e ) {
// System.out.println( e );
// }
elm = new EventManager( this );
}
public static boolean isMacOS() {
return System.getProperty( "os.name" ).indexOf( "Mac" ) >= 0;
}
public static boolean isWindows() {
return System.getProperty( "os.name" ).indexOf( "Windows" ) >= 0;
}
public static void setNumTraceLines( int num )
{
numTraceLines = num;
}
public static int notNull( Object o )
{
return o == null ? 0 : 1;
}
public static SwingOSC getInstance()
{
return instance;
}
public SwingClient getCurrentClient()
{
return currentClient;
}
public OSCPacketCodec getDefaultCodec()
{
return OSCPacketCodec.getDefaultCodec();
}
public void setReplyAddress( SocketAddress addr )
{
currentClient.setReplyAddress( addr );
}
public void setReplyPort( int port )
{
currentClient.setReplyPort( port );
}
public double getVersion()
{
return VERSION;
}
public InetSocketAddress getLocalAddress()
throws IOException
{
return serv.getLocalAddress();
}
public String getProtocol()
{
return serv.getProtocol();
}
public void send( OSCPacket p, SocketAddress addr )
throws IOException
{
serv.send( p, addr );
}
public void start( String protocol, int port, boolean loopBack, int bufSize,
boolean initSwing, InetSocketAddress helloAddr )
throws IOException
{
final InetSocketAddress ourAddress;
final String ourHost;
final int ourPort;
serv = OSCServer.newUsing( protocol, port, loopBack );
serv.setBufferSize( bufSize );
ourAddress = serv.getLocalAddress();
// ourHost = ourAddress.getHostName();
ourHost = ourAddress.getAddress().getHostAddress();
ourPort = ourAddress.getPort();
serv.addOSCListener( this );
final String vStr = String.valueOf( VERSION + 0.001 ); // use double prec!
System.out.println( "SwingOSC v" + vStr.substring( 0, vStr.length() - 1 ) +". receiving " + protocol.toUpperCase() +
" at address " + ourHost + ":" + ourPort );
// serv.dumpIncomingOSC( 3, System.out );
serv.getCodec().setSupportMode( (OSCPacketCodec.MODE_GRACEFUL & ~OSCPacketCodec.MODE_WRITE_DOUBLE_AS_FLOAT) | OSCPacketCodec.MODE_WRITE_DOUBLE );
serv.start();
if( initSwing ) {
// calling one of AWT's method will make Mac OS X recognize we're
// a GUI app and hence launch the screen menu bar and put an icon in the dock
// ; since this takes a moment it can be useful to do it at startup
// and not lazily when the first OSC message comes in
EventQueue.invokeLater( new Runnable() { public void run() { /* empty */ }});
}
if( helloAddr != null ) {
// serv.send( new OSCMessage( "/swing", new Object[] {
// "hello", ourHost, new Integer( ourPort )}),
// hello );
final OSCMessage helloMsg = new OSCMessage( "/swing", new Object[] {
"hello", ourHost, new Integer( ourPort ), protocol });
// final OSCTransmitter helloTrns = OSCTransmitter.newUsing( OSCChannel.UDP );
final OSCTransmitter helloTrns = OSCTransmitter.newUsing( OSCChannel.UDP, 0, helloAddr.getAddress().isLoopbackAddress() );
helloTrns.connect();
helloTrns.send( helloMsg, helloAddr );
helloTrns.dispose();
}
}
private int quit()
{
if( serv != null ) serv.dispose();
return 0;
}
public static void printException( Throwable e, String opName )
{
System.out.println( opName + " : " + e.getClass().getName() + " : " + e.getLocalizedMessage() );
final StackTraceElement[] trace = e.getStackTrace();
for( int i = 0; i < Math.min( numTraceLines, trace.length ); i++ ) {
System.out.println( "\tat " + trace[ i ]);
}
if( trace.length > numTraceLines ) {
System.out.println( "\t..." );
}
}
public static void printException( Throwable e, OSCMessage msg )
{
printException( e, msg.getName() );
}
private static void printWarning( OSCMessage msg, String text )
{
System.out.println( "WARNING " + msg.getName() + " " + text );
}
protected static void printFailed( OSCMessage msg, String text )
{
System.out.println( "FAILURE " + msg.getName() + " " + text );
}
protected static void printArgMismatch( OSCMessage msg )
{
printFailed( msg, "Argument mismatch" );
}
protected static void printWrongArgCount( OSCMessage msg )
{
printWarning( msg, "Called with illegal arg count" );
}
protected static void printNotFound( OSCMessage msg, Object id )
{
printFailed( msg, "Object not found : " + id );
}
public void messageReceived( OSCMessage msg, SocketAddress addr, long when )
{
// collMessages.add( new IncomingMessage( msg, addr, when ));
// EventQueue.invokeLater( this );
elm.dispatchEvent( new IncomingMessage( msg, addr, when ));
}
// called from the event thread
// when new messages have been queued
public void processEvent( BasicEvent e )
{
final IncomingMessage imsg = (IncomingMessage) e;
SwingClient c;
c = (SwingClient) mapClients.get( imsg.addr );
if( c == null ) {
c = new SwingClient( this, imsg.addr );
mapClients.put( imsg.addr, c );
}
processMessage( imsg.msg, c );
}
protected Object[] decodeMessageArgs( OSCMessage msg, SwingClient c )
throws IOException
{
final Object[] result = new Object[ msg.getArgCount() ];
for( int idx = 0; idx < result.length; idx++ ) {
result[ idx ] = decodeMessageArg( msg, c, idx );
// o = msg.getArg( i );
// if( o instanceof byte[] ) {
// o = OSCPacket.decode( ByteBuffer.wrap( (byte[]) o), null );
// }
// if( o instanceof OSCMessage ) {
// o = processMessage( (OSCMessage) o, addr );
// } else if( o instanceof OSCBundle ) {
// o = processBundle( (OSCBundle) o, addr );
// }
// result[ i ] = o;
//System.out.println( "result[ "+i+" ] = "+o );
}
return result;
}
protected Object decodeMessageArg( OSCMessage msg, SwingClient c, int idx )
throws IOException
{
Object o;
// OSCPacketCodec codec;
o = msg.getArg( idx );
if( o instanceof byte[] ) {
// o = c.getCodec().decode( ByteBuffer.wrap( (byte[]) o) );
o = serv.getCodec( c.getReplyAddress() ).decode( ByteBuffer.wrap( (byte[]) o) );
}
if( o instanceof OSCMessage ) {
o = processMessage( (OSCMessage) o, c );
} else if( o instanceof OSCBundle ) {
o = processBundle( (OSCBundle) o, c );
}
return o;
}
// best possible match : numArgs * 4
protected static int checkMethodArgs( Class[] signature, Object[] msgArgs, int msgArgOff, Object[] result )
{
Class type;
Object msgArg;
int match = 0;
for( int j = 0, k = msgArgOff; j < signature.length; j++, k++ ) {
type = signature[ j ];
msgArg = msgArgs[ k ];
if( msgArg == null ) {
result[ j ] = msgArg;
match += 4;
} else if( type.isPrimitive() ) {
if( msgArg instanceof Number ) {
if( type.equals( Boolean.TYPE )) {
result[ j ] = new Boolean( ((Number) msgArg).intValue() != 0 );
match += (msgArg instanceof Byte) ? 3 : ((msgArg instanceof Integer) ? 2 : 1);
} else if( type.equals( Integer.TYPE )) {
if( msgArg instanceof Integer ) {
result[ j ] = msgArg;
match += 4;
} else {
result[ j ] = new Integer( ((Number) msgArg).intValue() );
match += ((msgArg instanceof Long) || (msgArg instanceof Byte) ||
(msgArg instanceof Short)) ? 3 : 1;
}
} else if( type.equals( Long.TYPE )) {
if( msgArg instanceof Long ) {
result[ j ] = msgArg;
match += 4;
} else {
result[ j ] = new Long( ((Number) msgArg).longValue() );
match += ((msgArg instanceof Integer) || (msgArg instanceof Byte) ||
(msgArg instanceof Short)) ? 3 : 1;
}
} else if( type.equals( Float.TYPE )) {
if( msgArg instanceof Float ) {
result[ j ] = msgArg;
match += 4;
} else {
result[ j ] = new Float( ((Number) msgArg).floatValue() );
match += (msgArg instanceof Double) ? 3 : 1;
}
} else if( type.equals( Double.TYPE )) {
if( msgArg instanceof Double ) {
result[ j ] = msgArg;
match += 4;
} else {
result[ j ] = new Double( ((Number) msgArg).doubleValue() );
match += (msgArg instanceof Float) ? 3 : 1;
}
} else if( type.equals( Byte.TYPE )) {
if( msgArg instanceof Byte ) {
result[ j ] = msgArg;
match += 4;
} else {
result[ j ] = new Byte( ((Number) msgArg).byteValue() );
match += ((msgArg instanceof Long) || (msgArg instanceof Integer) ||
(msgArg instanceof Short)) ? 3 : 1;
}
} else if( type.equals( Short.TYPE )) {
if( msgArg instanceof Short ) {
result[ j ] = msgArg;
match += 4;
} else {
result[ j ] = new Short( ((Number) msgArg).shortValue() );
match += ((msgArg instanceof Long) || (msgArg instanceof Integer) ||
(msgArg instanceof Byte)) ? 3 : 1;
}
} else {
return -1;
}
} else if( type.equals( Boolean.TYPE )) {
if( msgArg instanceof Boolean ) {
result[ j ] = msgArg;
match += 4;
} else {
return -1;
}
} else if( type.equals( Character.TYPE )) {
if( (msgArg instanceof String) && ((String) msgArg).length() == 1 ) {
result[ j ] = new Character( ((String) msgArg).charAt( 0 ));
match += 3;
} else {
return -1;
}
}
} else if( type.equals( String.class )) {
if( msgArg instanceof String ) {
result[ j ] = msgArg;
match += 4;
} else {
result[ j ] = msgArg.toString();
}
// direct assignment always preferred
} else if( type.isAssignableFrom( msgArg.getClass() )) {
result[ j ] = msgArg;
match += type.equals( Object.class ) ? 2 : 4;
} else { // not assignable
return -1;
}
} // for signature types
return match;
}
protected Object getObject( Object id )
{
Object result = null;
result = globals.get( id );
if( result != null ) return result;
result = constants.get( id );
if( result == null ) {
final String className = id.toString();
if( (className.length() > 0) &&
(Character.isUpperCase( className.charAt( 0 )) || (className.indexOf( '.' ) >= 0))) {
// ok, maybe it's a class reference
try {
result = Class.forName( className, true, classLoaderMgr.getCurrentLoader() );
}
catch( LinkageError e ) {
printException( e, "getObject" );
}
catch( ClassNotFoundException e ) {
printException( e, "getObject" );
}
}
}
return result;
}
protected Object setProperty( OSCMessage msg, String propName, Object propValue, Object root )
throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException
{
final int subIdx = propName.indexOf( '.' );
final boolean hasSub = subIdx > 0;
final String setterName, getterSuffix;
final Object[] methodArgs;
final Method[] methods;
final Object o;
final Class c = root instanceof Class ? (Class) root : root.getClass();
Method m;
Object methodArg;
Class[] paramTypes;
Class paramClass;
// ----------------- call getter first to find the target object -----------------
if( hasSub ) {
getterSuffix = Character.toUpperCase( propName.charAt( 0 )) + propName.substring( 1, subIdx );
try {
m = c.getMethod( "get" + getterSuffix, (Class[]) null );
}
catch( NoSuchMethodException e11 ) { // ok, retry with "isXY"
m = c.getMethod( "is" + getterSuffix, (Class[]) null );
}
o = m.invoke( root, (Object[]) null );
return setProperty( msg, propName.substring( subIdx + 1 ), propValue, o );
// ----------------- find setter method and invoke it -----------------
} else {
if( propName.length() > 0 ) {
setterName = "set" + Character.toUpperCase( propName.charAt( 0 )) + propName.substring( 1 );
} else {
setterName = "set";
}
methods = c.getMethods();
methodArgs = new Object[ 1 ];
for( int i = 0; i < methods.length; i++ ) {
if( methods[ i ].getName().equals( setterName )) {
paramTypes = methods[ i ].getParameterTypes();
methodArg = null;
if( (paramTypes.length == 1) && (propValue != null) ) {
paramClass = paramTypes[ 0 ];
if( paramClass.isAssignableFrom( propValue.getClass() )) {
methodArg = propValue;
} else if( paramClass.equals( String.class )) {
methodArg = propValue.toString();
} else if( paramClass.isPrimitive() ) {
if( paramClass.equals( Boolean.TYPE )) {
if( propValue instanceof Boolean ) {
methodArg = propValue;
} else if( propValue instanceof Number ) {
methodArg = new Boolean( ((Number) propValue).intValue() != 0 );
}
} else if( paramClass.equals( Integer.TYPE )) {
if( propValue instanceof Number ) {
methodArg = new Integer( ((Number) propValue).intValue() );
}
} else if( paramClass.equals( Long.TYPE )) {
if( propValue instanceof Number ) {
methodArg = new Long( ((Number) propValue).longValue() );
}
} else if( paramClass.equals( Float.TYPE )) {
if( propValue instanceof Number ) {
methodArg = new Float( ((Number) propValue).floatValue() );
}
} else if( paramClass.equals( Double.TYPE )) {
if( propValue instanceof Number ) {
methodArg = new Double( ((Number) propValue).doubleValue() );
}
} else if( paramClass.equals( Byte.TYPE )) {
if( propValue instanceof Number ) {
methodArg = new Byte( ((Number) propValue).byteValue() );
}
} else if( paramClass.equals( Short.TYPE )) {
if( propValue instanceof Number ) {
methodArg = new Short( ((Number) propValue).shortValue() );
}
} else if( paramClass.equals( Character.TYPE )) {
if( propValue instanceof Character ) {
methodArg = propValue;
} else if( (propValue instanceof String) && ((String) propValue).length() == 1 ) {
methodArg = new Character( ((String) propValue).charAt( 0 ));
}
}
}
}
if( (methodArg != null) || (propValue == null) ) {
methodArgs[ 0 ] = methodArg;
return methods[ i ].invoke( root, methodArgs );
}
}
}
printFailed( msg, "No matching setter method for " + propName );
return null;
}
}
protected Object getProperty( String propName, Object root )
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
{
final int subIdx = propName.indexOf( '.' );
final boolean hasSub = subIdx > 0;
final String getterSuffix = Character.toUpperCase( propName.charAt( 0 )) + propName.substring( 1, hasSub ? subIdx : propName.length() );
final Object o;
final Class c = root instanceof Class ? (Class) root : root.getClass();
Method m;
try {
m = c.getMethod( "get" + getterSuffix, (Class[]) null );
}
catch( NoSuchMethodException e11 ) { // ok, retry with "isXY"
m = c.getMethod( "is" + getterSuffix, (Class[]) null );
}
o = m.invoke( root, (Object[]) null );
if( hasSub ) {
return getProperty( propName.substring( subIdx + 1 ), o );
} else {
if( o instanceof Boolean ) {
return new Integer( ((Boolean) o).booleanValue() ? 1 : 0 );
} else {
return o;
}
}
}
protected Object getProperties( Object o, Object[] replyArgs, int replyOff,
Object[] propNames, int propOff, int numArgs )
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
{
Object result = null;
String propName;
for( int i = 0; i < numArgs; i++ ) {
propName = propNames[ propOff++ ].toString();
replyArgs[ replyOff++ ] = propName;
result = getProperty( propName, o );
replyArgs[ replyOff++ ] = result;
}
return result;
}
protected Object invokeField( OSCMessage msg, Object o, String name )
{
final Class c = o instanceof Class ? (Class) o : o.getClass();
try {
return( c.getField( name ).get( o ));
} catch( IllegalAccessException e ) {
printException( e, msg );
} catch( IllegalArgumentException e ) {
printException( e, msg );
} catch( NullPointerException e) {
printException( e, msg );
} catch( ExceptionInInitializerError e ) {
printException( e, msg );
} catch( NoSuchFieldException e ) {
printException( e, msg );
} catch( SecurityException e ) {
printException( e, msg );
}
return null;
}
/**
* Fines a best matching method for a given argument set.
*
* @param o the instance which will be searched for the method.
* if o is a Class, a static method
* will be looked up.
* @param methodName the name of the method to invoke.
* @param methodArgs the arguments to parse to the method invocation. according to
* the heuristics used, a best matching method is tried to be found
* by checking all methods with methodName and the given number
* of arguments. For example, an Integer in methodArgs might be translated
* into a primitive int, or even a float.
* @return the result of the invoked method.
*
* @throws NoSuchMethodException if no method could be found for the given set of arguments
*/
protected Method findBestMethod( Object o, String methodName, Object[] methodArgs, Object[] methodCArgs )
throws NoSuchMethodException
{
final Class c = o instanceof Class ? (Class) o : o.getClass();
final int numArgs = methodArgs.length;
final Method[] methods;
Object[] methodTArgs;
Class[] types;
Method method, bestMethod;
int match, bestMatch;
final int bestPossible;
if( numArgs == 0 ) { // the easy way
return c.getMethod( methodName, (Class[]) null );
} else {
methods = c.getMethods();
methodTArgs = new Object[ numArgs ]; // test converted args
bestMatch = -1;
bestMethod = null;
bestPossible = numArgs << 2;
for( int i = 0; i < methods.length; i++ ) {
method = methods[ i ];
if( !method.getName().equals( methodName )) continue;
types = method.getParameterTypes();
if( types.length != numArgs ) continue;
match = checkMethodArgs( types, methodArgs, 0, methodTArgs );
if( match > bestMatch ) {
bestMatch = match;
bestMethod = method;
System.arraycopy( methodTArgs, 0, methodCArgs, 0, numArgs );
if( bestMatch == bestPossible ) {
break;
}
// if( i + 1 < methods.length ) methodCArgs = new Object[ numArgs ];
}
} // for methods
if( bestMethod != null ) return bestMethod;
else throw new NoSuchMethodException( "No matching method signature for " + methodName );
}
}
/**
* Invokes a method by finding the best match for a given argument set.
*
* @param o the object on which to call the method. the method must be
* an instance method of o, or if o is a Class, a static method
* will be looked up.
* @param methodName the name of the method to invoke.
* @param methodArgs the arguments to parse to the method invocation. according to
* the heuristics used, a best matching method is tried to be found
* by checking all methods with methodName and the given number
* of arguments. For example, an Integer in methodArgs might be translated
* into a primitive int, or even a float.
* @return the result of the invoked method.
*
* @throws NoSuchMethodException if no method could be found for the given set of arguments
* @throws LinkageError
* @throws SecurityException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
* @throws ClassCastException
*/
protected Object invokeMethod( Object o, String methodName, Object[] methodArgs )
throws LinkageError, SecurityException, NoSuchMethodException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException, ClassCastException
{
final Object[] methodCArgs = new Object[ methodArgs.length ];
final Method method = findBestMethod( o, methodName, methodArgs, methodCArgs );
return method.invoke( o, methodCArgs );
}
protected Object invokeMethod( OSCMessage msg, Object o, String methodName, Object[] msgArgs )
{
try {
return invokeMethod( o, methodName, msgArgs );
}
catch( LinkageError e ) {
printException( e, msg );
}
catch( SecurityException e ) {
printException( e, msg );
}
catch( NoSuchMethodException e ) {
printException( e, msg );
}
catch( IllegalAccessException e ) {
printException( e, msg );
}
catch( IllegalArgumentException e ) {
printException( e, msg );
}
catch( InvocationTargetException e ) {
printException( e.getTargetException(), msg );
}
catch( ClassCastException e ) {
printException( e, msg );
}
return null;
}
private Object processBundle( OSCBundle bndl, SwingClient c )
{
OSCPacket p;
Object result = null;
for( int j = 0; j < bndl.getPacketCount(); j++ ) {
p = bndl.getPacket( j );
if( p instanceof OSCMessage ) {
result = processMessage( (OSCMessage) p, c );
} else {
result = processBundle( (OSCBundle) p, c );
}
}
return result;
}
// ------------------- OSCProcessor interface -------------------
// all messages are deferred to the swing event thread
// for obvious reasons. also frees us from thread concurrency issues
public Object processMessage( OSCMessage msg, SwingClient c )
{
try { // catch everything because if a runtime exception occurs, OSC receiver thread dies
final OSCProcessor cmd = (OSCProcessor) oscCmds.get( msg.getName() );
currentClient = c;
if( cmd != null ) {
return cmd.processMessage( msg, c );
} else {
printFailed( msg, "Command not found" );
}
}
catch( Exception e1 ) {
printException( e1, "" );
}
return null;
}
// ------------------- internal classes -------------------
private static class IncomingMessage
extends BasicEvent
{
protected final OSCMessage msg;
protected final SocketAddress addr;
protected IncomingMessage( OSCMessage msg, SocketAddress addr, long when )
{
super( addr, 0, when );
this.msg = resolveMessage( msg );
this.addr = addr;
}
public boolean incorporate( BasicEvent e ) { return false; }
private static OSCMessage resolveMessage( OSCMessage msg )
{
Object o;
for( int idx = 0; idx < msg.getArgCount(); idx++ ) {
o = msg.getArg( idx );
if( o.equals( "[" )) {
final List newArgs = new ArrayList();
for( int j = 0; j < idx; j++ ) {
newArgs.add( msg.getArg( j ));
}
idx = resolveNested( msg, newArgs, idx );
while( idx < msg.getArgCount() ) {
o = msg.getArg( idx );
if( o.equals( "[" )) {
idx = resolveNested( msg, newArgs, idx );
} else {
newArgs.add( o );
idx++;
}
}
return new OSCMessage( msg.getName(), newArgs.toArray() );
}
}
return msg;
}
private static int resolveNested( OSCMessage msg, List parentArgs, int idx )
{
idx++; // skip opening bracket
final List subArgs = new ArrayList();
final String subName = msg.getArg( idx++ ).toString();
Object o;
do {
o = msg.getArg( idx );
if( o.equals( "[" )) {
idx = resolveNested( msg, subArgs, idx );
} else if( o.equals( "]" )) {
parentArgs.add( new OSCMessage( subName, subArgs.toArray() ));
idx++;
return idx;
} else {
subArgs.add( o );
idx++;
}
} while( true );
// throw new ArrayIndexOutOfBoundsException( idx );
}
}
private abstract class BasicCmd
implements OSCProcessor
{
protected BasicCmd( String name )
{
oscCmds.put( name, this );
}
}
/**
* Command: /quit
*/
private class CmdQuit
extends BasicCmd
{
private final Object sync;
protected CmdQuit( Object sync )
{
super( "/quit" );
this.sync = sync;
}
public Object processMessage( OSCMessage msg, SwingClient c )
throws IOException
{
synchronized( sync ) {
sync.notifyAll();
return null;
}
}
} // class CmdQuit
/**
* Command: /new, String <className>, [ Object <arg1> ... ]
*/
private class CmdNew
extends BasicCmd
{
protected CmdNew()
{
super( "/new" );
}
/**
* Note that message args are processed stricly after another
* which means for example that the className statement, if it is an OSCMessage
* and not a constant string, will not be able to access
* object bindings made by later arguments.
*
* @param msg the /new message
* @param c the client
* @return the newly created object
*/
public Object processMessage( OSCMessage msg, SwingClient c )
throws IOException
{
try {
// final Object[] msgArgs = decodeMessageArgs( msg, addr );
final Object[] msgArgs;
final String className;
final Class theClass;
final int numArgs = msg.getArgCount() - 1;
final Constructor[] cons;
Object[] consArgs, bestArgs;
Class[] types;
Constructor bestCons;
int match, bestMatch;
final int bestPossible;
// if( msgArgs.length >= 1 ) {
if( numArgs >= 0 ) {
className = decodeMessageArg( msg, c, 0 ).toString();
theClass = Class.forName( className, true, classLoaderMgr.getCurrentLoader() );
if( numArgs == 0 ) { // the easy way
return theClass.newInstance();
} else {
cons = theClass.getConstructors();
consArgs = new Object[ numArgs ];
bestMatch = -1;
bestCons = null;
bestArgs = null;
bestPossible = numArgs << 2;
msgArgs = decodeMessageArgs( msg, c );
for( int i = 0; i < cons.length; i++ ) {
types = cons[ i ].getParameterTypes();
if( types.length != numArgs ) continue;
match = checkMethodArgs( types, msgArgs, 1, consArgs );
//System.out.print( "checking cons with " );
//for( int jjj = 0; jjj < types.length; jjj++ ) {
// System.out.print( types[ jjj ].getName() + "; " );
//}
//System.out.println( " -> match "+ match );
if( match > bestMatch ) {
bestMatch = match;
bestCons = cons[ i ];
bestArgs = consArgs;
if( bestMatch == bestPossible ) {
// System.out.println( "... best possible" );
break;
}
if( i + 1 < cons.length ) consArgs = new Object[ numArgs ];
}
} // for constructors
if( bestCons != null ) {
return bestCons.newInstance( bestArgs );
}
}
printFailed( msg, "No matching constructor for " + className );
} else {
printArgMismatch( msg );
}
}
catch( LinkageError e ) {
printException( e, msg );
}
catch( ClassNotFoundException e ) {
printException( e, msg );
}
catch( SecurityException e ) {
printException( e, msg );
}
catch( IllegalAccessException e ) {
printException( e, msg );
}
catch( InstantiationException e ) {
printException( e, msg );
}
catch( IllegalArgumentException e ) {
printException( e, msg );
}
catch( InvocationTargetException e ) {
printException( e.getTargetException(), msg );
}
catch( ClassCastException e ) {
printException( e, msg );
}
return null;
}
} // class CmdNew
/**
* Command: /ref, String <objectID>
*/
private class CmdRef
extends BasicCmd
{
protected CmdRef()
{
super( "/ref" );
}
/**
* @param msg the /ref message
* @param c the client
* @return the referred object
*/
public Object processMessage( OSCMessage msg, SwingClient c )
throws IOException
{
if( msg.getArgCount() == 1 ) {
return c.getObject( decodeMessageArg( msg, c, 0 ));
} else {
printArgMismatch( msg );
return null;
}
}
} // class CmdRef
/**
* Command: /local, [ String <objectID1>, Object <value1> ... ]
*/
private class CmdLocal
extends BasicCmd
{
protected CmdLocal()
{
super( "/local" );
}
/**
* Note that each message argument is parsed strictly after another
* so that later arguments can make use of assignments made by earlier arguments.
*
* @param msg the /local message
* @param c the client
* @return the last objectID (for convenience)
*/
public Object processMessage( OSCMessage msg, SwingClient c )
throws IOException
{
// final Object[] msgArgs = decodeMessageArgs( msg, addr );
final int numArgs = msg.getArgCount(); // msgArgs.length;
Object id = null;
Object val;
if( (numArgs & 1) != 0 ) {
printWrongArgCount( msg );
}
for( int i = 0; i < numArgs; ) {
id = decodeMessageArg( msg, c, i++ );
val = decodeMessageArg( msg, c, i++ );
c.locals.put( id, val );
}
return id;
// return numArgs < 2 ? null : msgArgs[ numArgs - 2 ];
}
} // class CmdLocal
/**
* Command: /global, [ String <objectID1>, Object <value1> ... ]
*/
private class CmdGlobal
extends BasicCmd
{
protected CmdGlobal()
{
super( "/global" );
}
/**
* Note that each message argument is parsed strictly after another
* so that later arguments can make use of assignments made by earlier arguments.
*
* @param msg the /global message
* @param c the client
* @return the last objectID (for convenience)
*/
public Object processMessage( OSCMessage msg, SwingClient c )
throws IOException
{
// final Object[] msgArgs = decodeMessageArgs( msg, addr );
final int numArgs = msg.getArgCount(); // msgArgs.length;
Object id = null;
Object val;
if( (numArgs & 1) != 0 ) {
printWrongArgCount( msg );
}
for( int i = 0; i < numArgs; ) {
id = decodeMessageArg( msg, c, i++ );
val = decodeMessageArg( msg, c, i++ );
globals.put( id, val );
}
return id;
// return numArgs < 2 ? null : msgArgs[ numArgs - 2 ];
}
} // class CmdGlobal
/**
* Command: /set String <objectID>, [ String <propertyName1>, Object <propertyValue1> ... ]
*/
private class CmdSet
extends BasicCmd
{
protected CmdSet()
{
super( "/set" );
}
/**
* Note that each message argument is parsed strictly after another
* so that side effect statements in later arguments may assume that earlier properties
* have already been set.
*
* @param msg the /set message
* @param c the client
* @return the last value
*/
public Object processMessage( OSCMessage msg, SwingClient c )
throws IOException
{
// final Object[] msgArgs = decodeMessageArgs( msg, addr );
final Object id;
final Object o;
int numArgs = msg.getArgCount(); // msgArgs.length;
Object result = null;
String propName;
Object propValue;
if( numArgs >= 1 ) {
if( (numArgs & 1) == 0 ) {
printWrongArgCount( msg );
numArgs--; // avoid array index exception
}
id = decodeMessageArg( msg, c, 0 );
o = c.getObject( id );
if( o != null ) {
for( int i = 1; i < numArgs; ) {
propName = decodeMessageArg( msg, c, i++ ).toString();
propValue = decodeMessageArg( msg, c, i++ );
try {
result = setProperty( msg, propName, propValue, o );
}
catch( LinkageError e ) {
printException( e, msg );
}
catch( SecurityException e ) {
printException( e, msg );
}
catch( NoSuchMethodException e ) {
printException( e, msg );
}
catch( IllegalAccessException e ) {
printException( e, msg );
}
catch( IllegalArgumentException e ) {
printException( e, msg );
}
catch( InvocationTargetException e ) {
printException( e.getTargetException(), msg );
}
catch( ClassCastException e ) {
printException( e, msg );
}
catch( NoSuchFieldException e ) {
printException( e, msg );
}
}
} else {
printNotFound( msg, id );
}
} else {
printArgMismatch( msg );
}
return result;
}
} // class CmdSet
/**
* Command: /get String <objectID>, [ String <propertyName1>, ... ]
*/
private class CmdGet
extends BasicCmd
{
protected CmdGet()
{
super( "/get" );
}
/**
* The message args are processed strictly after another.
*
* @param msg the /get message
* @param c the client
* @return the last value
*/
public Object processMessage( OSCMessage msg, SwingClient c )
throws IOException
{
// final Object[] msgArgs = decodeMessageArgs( msg, addr );
final Object o;
final Object id;
final Object[] replyArgs;
int numArgs = msg.getArgCount(); // msgArgs.length;
Object result = null;
String propName;
if( numArgs >= 1 ) {
id = decodeMessageArg( msg, c, 0 );
o = c.getObject( id );
if( o != null ) {
replyArgs = new Object[ (numArgs << 1) - 1 ];
replyArgs[ 0 ] = id;
try {
for( int replyOff = 1, propOff = 1; propOff < numArgs; propOff++ ) {
propName = decodeMessageArg( msg, c, propOff ).toString();
replyArgs[ replyOff++ ] = propName;
result = getProperty( propName, o );
replyArgs[ replyOff++ ] = result;
}
c.reply( new OSCMessage( "/set", replyArgs ));
}
catch( LinkageError e ) {
printException( e, msg );
}
catch( SecurityException e ) {
printException( e, msg );
}
catch( NoSuchMethodException e ) {
printException( e, msg );
}
catch( IllegalAccessException e ) {
printException( e, msg );
}
catch( IllegalArgumentException e ) {
printException( e, msg );
}
catch( InvocationTargetException e ) {
printException( e.getTargetException(), msg );
}
catch( ClassCastException e ) {
printException( e, msg );
}
} else {
printNotFound( msg, id );
}
} else {
printArgMismatch( msg );
}
return result;
}
} // class CmdGet
/**
* Command: /method, String <objectID>, String <methodName>, [ Object <arg1> ... ]
*/
private class CmdMethod
extends BasicCmd
{
protected CmdMethod()
{
super( "/method" );
}
/**
* @param msg the /method message
* @param c the client
* @return the return value of the method
*/
public Object processMessage( OSCMessage msg, SwingClient c )
throws IOException
{
// final Object[] msgArgs = decodeMessageArgs( msg, addr );
final Object[] msgArgs;
final int numArgs = msg.getArgCount() - 2;
final Object id;
final Object o;
final Object name;
if( numArgs >= 0 ) {
id = decodeMessageArg( msg, c, 0 );
o = c.getObject( id ); // de-reference
if( o != null ) {
name = decodeMessageArg( msg, c, 1 );
if( name != null ) {
msgArgs = new Object[ numArgs ];
for( int i = 0, j = 2; i < numArgs; i++, j++ ) {
msgArgs[ i ] = decodeMessageArg( msg, c, j );
}
return invokeMethod( msg, o, name.toString(), msgArgs );
} else {
printFailed( msg, "Method name is null" );
}
} else {
printNotFound( msg, id );
}
} else {
printArgMismatch( msg );
}
return null;
}
} // class CmdMethod
/**
* Command: /methodr, Object <object>, String <methodName>, [ Object <arg1> ... ]
*/
private class CmdMethodR
extends BasicCmd
{
protected CmdMethodR()
{
super( "/methodr" );
}
/**
* @param msg the /methodr message
* @param c the client
* @return the return value of the method
*/
public Object processMessage( OSCMessage msg, SwingClient c )
throws IOException
{
// final Object[] msgArgs = decodeMessageArgs( msg, addr );
final Object[] msgArgs;
final int numArgs = msg.getArgCount() - 2;
final Object o;
final Object name;
if( numArgs >= 0 ) {
o = decodeMessageArg( msg, c, 0 );
if( o != null ) {
name = decodeMessageArg( msg, c, 1 );
if( name != null ) {
msgArgs = new Object[ numArgs ];
for( int i = 0, j = 2; i < numArgs; i++, j++ ) {
msgArgs[ i ] = decodeMessageArg( msg, c, j );
}
return invokeMethod( msg, o, name.toString(), msgArgs );
} else {
printFailed( msg, "Method name is null" );
}
} else {
printFailed( msg, "Cannot operate on null result" );
}
} else {
printArgMismatch( msg );
}
return null;
}
} // class CmdMethodR
/**
* Command: /field, String <objectID>, String <fieldName>
*/
private class CmdField
extends BasicCmd
{
protected CmdField()
{
super( "/field" );
}
/**
* @param msg the /field message
* @param c the client
* @return the value of the field
*/
public Object processMessage( OSCMessage msg, SwingClient c )
throws IOException
{
// final Object[] msgArgs = decodeMessageArgs( msg, addr );
final Object o;
final Object id;
final Object name;
if( msg.getArgCount() >= 2 ) {
id = decodeMessageArg( msg, c, 0 );
o = c.getObject( id ); // de-reference
if( o != null ) {
name = decodeMessageArg( msg, c, 1 );
if( name != null ) {
return invokeField( msg, o, name.toString() );
} else {
printFailed( msg, "Field name is null" );
}
} else {
printNotFound( msg, id );
}
} else {
printArgMismatch( msg );
}
return null;
}
} // class CmdField
/**
* Command: /fieldr, Object <object>, String <fieldName>
*/
private class CmdFieldR
extends BasicCmd
{
protected CmdFieldR()
{
super( "/fieldr" );
}
/**
* @param msg the /fieldr message
* @param c the client
* @return the value of the field
*/
public Object processMessage( OSCMessage msg, SwingClient c )
throws IOException
{
// final Object[] msgArgs = decodeMessageArgs( msg, addr );
final Object o;
final Object name;
if( msg.getArgCount() >= 2 ) {
o = decodeMessageArg( msg, c, 0 );
if( o != null ) {
name = decodeMessageArg( msg, c, 1 );
if( name != null ) {
return invokeField( msg, o, name.toString() );
} else {
printFailed( msg, "Field name is null" );
}
} else {
printFailed( msg, "Cannot operate on null result" );
}
} else {
printArgMismatch( msg );
}
return null;
}
} // class CmdFieldR
/**
* Command: /free, [ String <objectID1>, ... ]
*/
private class CmdFree
extends BasicCmd
{
protected CmdFree()
{
super( "/free" );
}
/**
* Note that each argument is strictly parsed after another which
* means that later statements cannot access earlier (now freed)
* object bindings. That is:
* <pre>
* [ "/free", "schoko", [ "/method", "schoko", "getAnotherObjectID" ]]
* </pre>
* will fail.
*
* @param msg the /free message
* @param c the client
* @return the value of the last object in the list
*/
public Object processMessage( OSCMessage msg, SwingClient c )
throws IOException
{
final int numArgs = msg.getArgCount(); // msgArgs.length;
Object id;
Object result = null;
for( int i = 0; i < numArgs; i++ ) {
id = decodeMessageArg( msg, c, i );
result = c.locals.remove( id );
if( result == null ) {
result = globals.remove( id );
}
if( result == null ) {
printNotFound( msg, id );
}
}
return result;
}
} // class CmdFree
/**
* Command: /array, [ Object <object1>, ... ]
*/
private class CmdArray
extends BasicCmd
{
protected CmdArray()
{
super( "/array" );
}
/**
* @param msg the /array message
* @param c the client
* @return the object array
*/
public Object processMessage( OSCMessage msg, SwingClient c )
throws IOException
{
return decodeMessageArgs( msg, c );
}
} // class CmdArray
/**
* Command: /query, [ String <returnID1>, Object <anObject1> ... ]
*/
private class CmdQuery
extends BasicCmd
{
protected CmdQuery()
{
super( "/query" );
}
/**
* @param msg the /query message
* @param c the client
* @return the last object's return value
*/
public Object processMessage( OSCMessage msg, SwingClient c )
throws IOException
{
final Object[] msgArgs = decodeMessageArgs( msg, c );
final int numArgs = msgArgs.length;
if( (numArgs & 1) != 0 ) {
printWrongArgCount( msg );
}
c.reply( new OSCMessage( "/info", msgArgs ));
return numArgs == 0 ? null : msgArgs[ numArgs - 1 ];
}
} // class CmdQuery
/**
* Command: /print, [ String <objectID1>, ... ]
*/
private class CmdPrint
extends BasicCmd
{
protected CmdPrint()
{
super( "/print" );
}
/**
* Note that arguments are processed strictly after another.
*
* @param msg the /print message
* @param c the client
* @return the last object's value
*/
public Object processMessage( OSCMessage msg, SwingClient c )
throws IOException
{
// final Object[] msgArgs = decodeMessageArgs( msg, addr );
final int numArgs = msg.getArgCount(); // msgArgs.length;
Object id;
Object o = null;
for( int i = 0; i < numArgs; i++ ) {
id = decodeMessageArg( msg, c, i );
o = c.getObject( id );
System.out.println( id + " : " + o );
}
return o;
}
} // class CmdPrint
/**
* Command: /dumpOSC, int <incomingMode> [, int <outgoingMode> ]
*/
private class CmdDumpOSC
extends BasicCmd
{
protected CmdDumpOSC()
{
super( "/dumpOSC" );
}
public Object processMessage( OSCMessage msg, SwingClient c )
throws IOException
{
final int numArgs = msg.getArgCount();
if( (numArgs >= 1) && (numArgs <= 2) && (msg.getArg( 0 ) instanceof Number) ) {
serv.dumpIncomingOSC( ((Number) msg.getArg( 0 )).intValue(), System.out );
if( numArgs == 2 ) {
if( msg.getArg( 1 ) instanceof Number ) {
serv.dumpOutgoingOSC( ((Number) msg.getArg( 1 )).intValue(), System.out );
} else {
printArgMismatch( msg );
}
}
} else {
printArgMismatch( msg );
}
return null;
}
} // class CmdDumpOSC
/**
* Command: /classes, String <cmd>, [ String <classPathURL1>, ... ]
*
* where cmd is one of "add", "remove", "update"
*/
private class CmdClasses
extends BasicCmd
{
protected CmdClasses()
{
super( "/classes" );
}
/**
* @param msg the /classes message
* @param c the client
* @return the last path
*/
public Object processMessage( OSCMessage msg, SwingClient c )
throws IOException
{
final int numArgs = msg.getArgCount();
final int numURLs = numArgs - 1;
final URL[] paths;
final String cmd;
String path = null;
if( numArgs > 0 ) {
cmd = decodeMessageArg( msg, c, 0 ).toString();
paths = new URL[ numURLs ];
for( int i = 0; i < numURLs; i++ ) {
path = decodeMessageArg( msg, c, i + 1 ).toString();
paths[ i ] = new URL( path );
}
if( cmd.equals( "add" )) {
classLoaderMgr.addURLs( paths );
return path;
} else if( cmd.equals( "remove" )) {
classLoaderMgr.removeURLs( paths );
return path;
} else if( cmd.equals( "update" )) {
classLoaderMgr.removeURLs( paths );
classLoaderMgr.addURLs( paths );
return path;
} else {
printArgMismatch( msg );
}
} else {
printArgMismatch( msg );
}
return null;
}
} // class CmdClasses
} // class SwingOSC