001: /*
002: * $RCSfile: ConfigObject.java,v $
003: *
004: * Copyright (c) 2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * - Redistribution of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * - Redistribution in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * Neither the name of Sun Microsystems, Inc. or the names of
019: * contributors may be used to endorse or promote products derived
020: * from this software without specific prior written permission.
021: *
022: * This software is provided "AS IS," without a warranty of any
023: * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
024: * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
025: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
026: * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
027: * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
028: * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
029: * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
030: * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
031: * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
032: * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
033: * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
034: * POSSIBILITY OF SUCH DAMAGES.
035: *
036: * You acknowledge that this software is not designed, licensed or
037: * intended for use in the design, construction, operation or
038: * maintenance of any nuclear facility.
039: *
040: * $Revision: 1.6 $
041: * $Date: 2007/02/09 17:20:43 $
042: * $State: Exp $
043: */
044:
045: package com.sun.j3d.utils.universe;
046:
047: import java.lang.reflect.*;
048: import java.util.List;
049: import java.util.ArrayList;
050:
051: /**
052: * Base class for all configuration objects. A ConfigObject processes
053: * configuration parameters for a target object, which is instantiated after
054: * the configuration file is parsed. The ConfigObject then applies its
055: * configuration properties to the target object.<p>
056: *
057: * Generic base implementations are provided for the initialize(),
058: * setProperty(), and processProperties() methods. These implementations
059: * assume target objects that are unknown and thus instantiated via
060: * introspection. Property names are assumed to be method names that take an
061: * array of Objects as a parameter; they are invoked through introspection as
062: * well.<p>
063: *
064: * Most ConfigObjects target concrete Java 3D core classes, so these
065: * implementations are usually overridden to instantiate those objects and
066: * call their methods directly.
067: */
068: class ConfigObject {
069: /**
070: * The base name of this object, derived from the configuration command
071: * which created it. This is constructed by stripping off the leading
072: * "New" prefix or the trailing "Attribute", "Property", or "Alias" suffix
073: * of the command name. The name of the ConfigObject subclass which
074: * handles the command is derived by adding "Config" as a prefix to the
075: * base name.
076: */
077: String baseName = null;
078:
079: /**
080: * The instance name of this object, as specified in the configuration
081: * file.
082: */
083: String instanceName = null;
084:
085: /**
086: * The corresponding target object which this ConfigObject is configuring.
087: */
088: Object targetObject = null;
089:
090: /**
091: * The name of the target class this object is configuring.
092: */
093: String targetClassName = null;
094:
095: /**
096: * The Class object for the target.
097: */
098: Class targetClass = null;
099:
100: /**
101: * Configurable properties gathered by this object, represented by the
102: * ConfigCommands that set them.
103: */
104: List properties = new ArrayList();
105:
106: /**
107: * The ConfigContainer in which this ConfigObject is contained.
108: */
109: ConfigContainer configContainer = null;
110:
111: /**
112: * The command that created this class.
113: */
114: ConfigCommand creatingCommand = null;
115:
116: /**
117: * If true, this object is an alias to another.
118: */
119: boolean isAlias = false;
120:
121: /**
122: * If isAlias is true, this references the original object.
123: */
124: ConfigObject original = null;
125:
126: /**
127: * List of alias Strings for this object if it's not an alias itself.
128: */
129: List aliases = new ArrayList();
130:
131: protected ClassLoader classLoader;
132:
133: /**
134: * @param classLoader the ClassLoader to use when loading the implementation
135: * class for this object
136: */
137: void setClassLoader(ClassLoader classLoader) {
138: this .classLoader = classLoader;
139: }
140:
141: /**
142: * The base initialize() implementation. This takes a ConfigCommand with
143: * three arguments: the command name, the instance name, and the name of
144: * the target class this ConfigObject is configuring. The command in the
145: * configuration file should have the form:<p>
146: *
147: * (New{configType} {instanceName} {className})<p>
148: *
149: * For example, (NewDevice tracker com.sun.j3d.input.LogitechTracker) will
150: * first cause ConfigDevice to be instantiated, which will then be
151: * initialized with this method. After all the properties are collected,
152: * ConfigDevice will instantiate com.sun.j3d.input.LogitechTracker,
153: * evaluate its properties, and allow references to it in the
154: * configuration file by the name "tracker".<p>
155: *
156: * It's assumed the target class will be instantiated through
157: * introspection and its properties set through introspection as well.
158: * Most config objects (ConfigScreen, ConfigView, ConfigViewPlatform,
159: * ConfigPhysicalBody, and ConfigPhysicalEnvironment) target a concrete
160: * core Java 3D class and will instantiate them directly, so they override
161: * this method.
162: *
163: * @param c the command that created this ConfigObject
164: */
165: protected void initialize(ConfigCommand c) {
166: if (c.argc != 3) {
167: syntaxError("Wrong number of arguments to " + c.commandName);
168: }
169:
170: if (!isName(c.argv[1])) {
171: syntaxError("The first argument to " + c.commandName
172: + " must be the instance name");
173: }
174:
175: if (!isName(c.argv[2])) {
176: syntaxError("The second argument to " + c.commandName
177: + " must be the class name");
178: }
179:
180: targetClassName = (String) c.argv[2];
181: }
182:
183: /**
184: * The base setProperty() implementation. This implementation assumes the
185: * property needs to be set by introspection on the property name as a
186: * method that accepts an array of Objects. That is, the command in the
187: * configuration file is of the form:<p>
188: *
189: * ({type}Property {instance name} {method name} {arg0} ... {argn})<p>
190: *
191: * For example, (DeviceProperty tracker SerialPort "/dev/ttya") will
192: * invoke the method named "SerialPort" in the object referenced by
193: * "tracker" with an array of 1 Object containing the String
194: * "/dev/ttya".<p>
195: *
196: * The property is stored as the original ConfigCommand and is evaluated
197: * after the configuration file has been parsed. It is overridden by
198: * subclasses that instantiate concrete core Java 3D classes with known
199: * method names.
200: *
201: * @param c the command that invoked this method
202: */
203: protected void setProperty(ConfigCommand c) {
204: if (c.argc < 4) {
205: syntaxError("Wrong number of arguments to " + c.commandName);
206: }
207:
208: if (!isName(c.argv[1])) {
209: syntaxError("The first argument to " + c.commandName
210: + " must be the instance name");
211: }
212:
213: if (!isName(c.argv[2])) {
214: syntaxError("The second argument to " + c.commandName
215: + " must be the property name");
216: }
217:
218: properties.add(c);
219: }
220:
221: /**
222: * Instantiates the target object.
223: */
224: protected Object createTargetObject() {
225: if (targetClassName == null)
226: return null;
227:
228: targetClass = getClassForName(creatingCommand, targetClassName);
229: targetObject = getNewInstance(creatingCommand, targetClass);
230:
231: return targetObject;
232: }
233:
234: /**
235: * Return the class for the specified class name string.
236: *
237: * @param className the name of the class
238: * @return the object representing the class
239: */
240: protected Class getClassForName(ConfigCommand cmd, String className) {
241: try {
242: // Use the system class loader. If the Java 3D jar files are
243: // installed directly in the JVM's lib/ext directory, then the
244: // default class loader won't find user classes outside ext.
245: //
246: // From 1.3.2 we use the classLoader supplied to this object,
247: // normally this will be the system class loader, but for webstart
248: // apps the user can supply another class loader.
249: return Class.forName(className, true, classLoader);
250: } catch (ClassNotFoundException e) {
251: throw new IllegalArgumentException(errorMessage(cmd,
252: "Class \"" + className + "\" not found"));
253: }
254: }
255:
256: /**
257: * Return an instance of the class specified by the given class object.
258: *
259: * @param objectClass the object representing the class
260: * @return a new instance of the class
261: */
262: protected Object getNewInstance(ConfigCommand cmd, Class objectClass) {
263: try {
264: return objectClass.newInstance();
265: } catch (IllegalAccessException e) {
266: throw new IllegalArgumentException(errorMessage(cmd,
267: "Illegal access to object class"));
268: } catch (InstantiationException e) {
269: throw new IllegalArgumentException(errorMessage(cmd,
270: "Instantiation error for object class"));
271: }
272: }
273:
274: /**
275: * Evaluate properties for the the given class instance. The property
276: * names are used as the names of methods to be invoked by the instance.
277: * Each such method takes an array of Objects as its only parameter. The
278: * array will contain Objects corresponding to the property values.
279: */
280: protected void processProperties() {
281: evaluateProperties(this .targetClass, this .targetObject,
282: this .properties);
283:
284: // Potentially holds a lot of references, and not needed anymore.
285: this .properties.clear();
286: }
287:
288: /**
289: * Evaluate properties for the the given class instance.
290: *
291: * @param objectClass the class object representing the given class
292: * @param objectInstance the class instance whose methods will be invoked
293: * @param properties list of property setting commands
294: */
295: protected void evaluateProperties(Class objectClass,
296: Object objectInstance, List properties) {
297:
298: // Holds the single parameter passed to the class instance methods.
299: Object[] parameters = new Object[1];
300:
301: // Specifies the class of the single method parameter.
302: Class[] parameterTypes = new Class[1];
303:
304: // All property methods use Object[] as their single parameter, which
305: // happens to be the same type as the parameters variable above.
306: parameterTypes[0] = parameters.getClass();
307:
308: // Loop through all property commands and invoke the appropriate
309: // method for each one. Property commands are of the form:
310: // ({configClass}Property {instanceName} {methodName} {arg} ...)
311: for (int i = 0; i < properties.size(); i++) {
312: ConfigCommand cmd = (ConfigCommand) properties.get(i);
313: String methodName = (String) cmd.argv[2];
314: Object[] argv = new Object[cmd.argc - 3];
315:
316: for (int a = 0; a < argv.length; a++) {
317: argv[a] = cmd.argv[a + 3];
318: if (argv[a] instanceof ConfigCommand) {
319: // Evaluate a delayed built-in command.
320: ConfigCommand bcmd = (ConfigCommand) argv[a];
321: argv[a] = configContainer.evaluateBuiltIn(bcmd);
322: }
323: }
324:
325: parameters[0] = argv;
326:
327: try {
328: Method objectMethod = objectClass.getMethod(methodName,
329: parameterTypes);
330:
331: objectMethod.invoke(objectInstance, parameters);
332: } catch (NoSuchMethodException e) {
333: throw new IllegalArgumentException(errorMessage(cmd,
334: "Unknown property \"" + methodName + "\""));
335: } catch (IllegalAccessException e) {
336: throw new IllegalArgumentException(errorMessage(cmd,
337: "Illegal access to \"" + methodName + "\""));
338: } catch (InvocationTargetException e) {
339: throw new IllegalArgumentException(errorMessage(cmd, e
340: .getTargetException().getMessage()));
341: }
342: }
343: }
344:
345: /**
346: * Throws an IllegalArgumentException with the specified description.
347: * This is caught by the parser which prints out error diagnostics and
348: * continues parsing if it can.
349: *
350: * @param s string describing the syntax error
351: */
352: protected void syntaxError(String s) {
353: throw new IllegalArgumentException(s);
354: }
355:
356: /**
357: * Constructs an error message from the given string and file information
358: * from the given command.
359: */
360: static String errorMessage(ConfigCommand cmd, String s) {
361: return s + "\nat line " + cmd.lineNumber + " in "
362: + cmd.fileName + "\n" + cmd;
363: }
364:
365: /**
366: * Check if the argument is a name string.
367: *
368: * @param o the object to be checked
369: * @return true if the object is an instance of String
370: */
371: protected boolean isName(Object o) {
372: return (o instanceof String);
373: }
374: }
|