org.scijava.java3d.utils.universe.ConfigContainer Maven / Gradle / Ivy
Show all versions of j3dutils Show documentation
/*
* Copyright (c) 2007 Sun Microsystems, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistribution of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistribution in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* Neither the name of Sun Microsystems, Inc. or the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any
* kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
* WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
* EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
* NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
* USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
* ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
* CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
* REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
* INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or
* intended for use in the design, construction, operation or
* maintenance of any nuclear facility.
*
*/
package org.scijava.java3d.utils.universe ;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* Loads a Java 3D configuration file and creates a container of named objects
* that will effect the viewing configuration specified in the file. These
* can include Viewers, ViewingPlatforms, ViewPlatformBehaviors, InputDevices,
* Sensors, and other objects.
*
* Clients can construct the view side of a scene graph by retrieving these
* objects using the accessor methods provided by this class. This could
* involve as little as just attaching ViewingPlatforms to a Locale, depending
* upon how completely the viewing configuration is specified in the file.
* The ConfiguredUniverse class is an example of a ConfigContainer client and
* how it can be used.
*
* ConfigContainer can be useful for clients other than ConfiguredUniverse.
* InputDevice and ViewPlatformBehavior configuration is fully supported, so a
* given Java 3D installation can provide configuration files to an
* application that will allow it to fully utilize whatever site-specific
* devices and behaviors are available. The configuration mechanism can be
* extended for any target object through the use of the
* NewObject
and ObjectProperty
configuration
* commands.
*
* @see ConfiguredUniverse
* @see
* The Java 3D Configuration File
* @see
* Example Configuration Files
*
* @since Java 3D 1.3.1
*/
public class ConfigContainer {
//
// The configuration object database is implemented with a HashMap which
// maps their class names to ArrayList objects which contain the actual
// instances. The latter are used since the instances of a given class
// must be evaluated in the order in which they were created.
// LinkedHashMap is available in JDK 1.4 but currently this code must run
// under JDK 1.3.1 as well.
//
private Map baseNameMap = new HashMap() ;
// Map containing named canvases for each view.
private Map viewCanvasMap = new HashMap() ;
// Read-only Maps for the public interface to the configuration database.
private ReadOnlyMap bodyMap = null ;
private ReadOnlyMap environmentMap = null ;
private ReadOnlyMap viewerMap = null ;
private ReadOnlyMap deviceMap = null ;
private ReadOnlyMap sensorMap = null ;
private ReadOnlyMap behaviorMap = null ;
private ReadOnlyMap platformMap = null ;
private ReadOnlyMap genericObjectMap = null ;
// Read-only Sets for the public interface to the configuration database.
private ReadOnlySet bodies = null ;
private ReadOnlySet environments = null ;
private ReadOnlySet viewers = null ;
private ReadOnlySet devices = null ;
private ReadOnlySet sensors = null ;
private ReadOnlySet behaviors = null ;
private ReadOnlySet platforms = null ;
private ReadOnlySet genericObjects = null ;
// The number of TransformGroups to include in ViewingPlatforms.
private int transformCount = 1 ;
// The visibility status of Viewer AWT components.
private boolean setVisible = false ;
private ClassLoader classLoader = ClassLoader.getSystemClassLoader();
/**
* The name of the file this ConfigContainer is currently loading.
*/
String currentFileName = null ;
/**
* Creates a new ConfigContainer and loads the configuration file at the
* specified URL. All ViewingPlatform instances are created with a single
* TransformGroup and all Viewer components are initially invisible.
*
* @param userConfig URL of the configuration file to load
*/
public ConfigContainer(URL userConfig) {
this(userConfig, false, 1, true) ;
}
/**
* Creates a new ConfigContainer and loads the configuration file at the
* specified URL. All ViewingPlatform instances are created with a single
* TransformGroup and all Viewer components are initially invisible.
*
* @param userConfig URL of the configuration file to load
* @param classLoader the class loader to use to load classes specified
* in the config file.
*/
public ConfigContainer(URL userConfig, ClassLoader classLoader) {
this(userConfig, false, 1, true, classLoader) ;
}
/**
* Creates a new ConfigContainer and loads the configuration file at the
* specified URL. Any ViewingPlatform instantiated by the configuration
* file will be created with the specified number of transforms. Viewer
* components may be set initially visible or invisible with the
* setVisible
flag.
*
* @param userConfig URL of the configuration file to load
* @param setVisible if true, setVisible(true)
is called on
* all Viewers
* @param transformCount number of transforms to be included in any
* ViewingPlatform created; must be greater than 0
*/
public ConfigContainer(URL userConfig,
boolean setVisible, int transformCount) {
this(userConfig, setVisible, transformCount, true) ;
}
/**
* Creates a new ConfigContainer and loads the configuration file at the
* specified URL. Any ViewingPlatform instantiated by the configuration
* file will be created with the specified number of transforms. Viewer
* components may be set initially visible or invisible with the
* setVisible
flag.
*
* @param userConfig URL of the configuration file to load
* @param setVisible if true, setVisible(true)
is called on
* all Viewers
* @param transformCount number of transforms to be included in any
* ViewingPlatform created; must be greater than 0
* @param classLoader the class loader to use to load classes specified
* in the config file.
*/
public ConfigContainer(URL userConfig,
boolean setVisible, int transformCount,
ClassLoader classLoader) {
this(userConfig, setVisible, transformCount, true, classLoader) ;
}
/**
* Package-scoped constructor for ConfigContainer. This provides an
* additional flag, attachBehaviors
, which indicates whether
* or not ViewPlatformBehaviors should be attached to the ViewingPlatforms
* specified for them.
*
* Normally the flag should be true. However, when instantiated by
* ConfiguredUniverse, this flag is set false so that ConfiguredUniverse
* can set a reference to itself in the ViewingPlatform before attaching
* the behavior. This provides backwards compatibility to behaviors that
* access the ConfiguredUniverse instance from a call to
* setViewingPlatform
in order to look up the actual Sensor,
* Viewer, Behavior, etc., instances associated with the names provided
* them from the configuration file.
*
* The preferred methods to retrieve instances of specific objects defined
* in the configuration file are to either 1) get the ConfiguredUniverse
* instance when the behavior's initialize
method is called,
* or to 2) define properties that accept object instances directly, and
* then use the newer Device, Sensor, ViewPlatform, etc., built-in
* commands in the configuration file. These built-ins will return an
* object instance from a name.
*
* @param userConfig URL of the configuration file to load
* @param setVisible if true, setVisible(true)
is called on
* all Viewers
* @param transformCount number of transforms to be included in any
* ViewingPlatform created; must be greater than 0
* @param attachBehaviors if true, attach ViewPlatformBehaviors to the
* appropriate ViewingPlatforms
*/
ConfigContainer(URL userConfig, boolean setVisible,
int transformCount, boolean attachBehaviors) {
if (transformCount < 1)
throw new IllegalArgumentException
("transformCount must be greater than 0") ;
loadConfig(userConfig) ;
processConfig(setVisible, transformCount, attachBehaviors) ;
}
/**
* Package scoped constructor that adds the ability to set the ClassLoader
* which will be used to load any app specific classes specified in the
* configuration file. By default SystemClassLoader is used.
*/
ConfigContainer(URL userConfig, boolean setVisible,
int transformCount, boolean attachBehaviors,
ClassLoader classLoader) {
this(userConfig, setVisible, transformCount, attachBehaviors);
this.classLoader = classLoader;
}
/**
* Open, parse, and load the contents of a configuration file.
*
* @param userConfig location of the configuration file
*/
private void loadConfig(URL userConfig) {
InputStream inputStream = null ;
StreamTokenizer streamTokenizer = null ;
String lastFileName = currentFileName ;
currentFileName = userConfig.toString() ;
try {
inputStream = userConfig.openStream() ;
Reader r = new BufferedReader(new InputStreamReader(inputStream)) ;
streamTokenizer = new StreamTokenizer(r) ;
}
catch (IOException e) {
throw new IllegalArgumentException(
e + "\nUnable to open " + currentFileName) ;
}
//
// Set up syntax tables for the tokenizer.
//
// It would be nice to allow '/' as a word constituent for URL strings
// and Unix paths, but then the scanner won't ignore "//" and "/* */"
// comment style syntax. Treating '/' as an ordinary character will
// allow comments to work, but then '/' becomes a single token which
// has to be concatenated with subsequent tokens to reconstruct the
// original word string.
//
// It is cleaner to just require quoting for forward slashes. '/'
// should still be treated as an ordinary character however, so that a
// non-quoted URL string or Unix path will be treated as a syntax
// error instead of a comment.
//
streamTokenizer.ordinaryChar('/') ;
streamTokenizer.wordChars('_', '_') ;
streamTokenizer.wordChars('$', '$') ; // for ${...} Java property
streamTokenizer.wordChars('{', '}') ; // substitution in word tokens
streamTokenizer.slashSlashComments(true) ;
streamTokenizer.slashStarComments(true) ;
// Create an s-expression parser to use for all top-level (0) commands.
ConfigSexpression sexp = new ConfigSexpression() ;
// Loop through all top-level commands. Boolean.FALSE is returned
// after the last one is evaluated.
while (sexp.parseAndEval(this, streamTokenizer, 0) != Boolean.FALSE) ;
// Close the input stream.
try {
inputStream.close() ;
}
catch (IOException e) {
throw new IllegalArgumentException(
e + "\nUnable to close " + currentFileName) ;
}
// Restore current file name.
currentFileName = lastFileName ;
}
/**
* This method gets called from the s-expression parser to process a
* configuration command.
*
* @param elements tokenized list of sexp elements
* @param lineNumber command line number
*/
void evaluateCommand(ArrayList elements, int lineNumber) {
ConfigObject co ;
ConfigCommand cmd ;
// Create a command object.
cmd = new ConfigCommand(elements, currentFileName, lineNumber) ;
// Process the command according to its type.
switch (cmd.type) {
case ConfigCommand.CREATE:
co = createConfigObject(cmd) ;
addConfigObject(co) ;
break ;
case ConfigCommand.ALIAS:
co = createConfigAlias(cmd) ;
addConfigObject(co) ;
break ;
case ConfigCommand.PROPERTY:
co = findConfigObject(cmd.baseName, cmd.instanceName) ;
co.setProperty(cmd) ;
break ;
case ConfigCommand.INCLUDE:
if (! (cmd.argv[1] instanceof String)) {
throw new IllegalArgumentException
("Include file must be a URL string") ;
}
URL url = null ;
String urlString = (String)cmd.argv[1] ;
try {
url = new URL(urlString) ;
}
catch (MalformedURLException e) {
throw new IllegalArgumentException(e.toString()) ;
}
loadConfig(url) ;
break ;
case ConfigCommand.IGNORE:
break ;
default:
throw new IllegalArgumentException
("Unknown command \"" + cmd.commandName + "\"") ;
}
}
/**
* Instantiates and initializes an object that extends the ConfigObject
* base class. The class name of the object is derived from the
* command, which is of the following form:
*
* (New{baseName} {instanceName} ... [Alias {aliasName}])
*
* The first two command elements and the optional trailing Alias syntax
* are processed here, at which point the subclass implementation of
* initialize() is called. Subclasses must override initialize() if they
* need to process more than what is processed by default here.
*
* @param cmd configuration command that creates a new ConfigObject
*/
private ConfigObject createConfigObject(ConfigCommand cmd) {
Class objectClass = null ;
ConfigObject configObject = null ;
// Instantatiate the ConfigObject if possible. This is not the target
// object, but an object that will gather configuration properties,
// instantiate the target object, and then apply the configuration
// properties to it.
try {
objectClass = Class.forName("org.scijava.java3d.utils.universe.Config" +
cmd.baseName) ;
}
catch (ClassNotFoundException e) {
throw new IllegalArgumentException
("\"" + cmd.baseName + "\"" +
" is not a configurable object; ignoring command") ;
}
try {
configObject = (ConfigObject)(objectClass.newInstance()) ;
}
catch (IllegalAccessException e) {
System.out.println(e) ;
throw new IllegalArgumentException("Ignoring command") ;
}
catch (InstantiationException e) {
System.out.println(e) ;
throw new IllegalArgumentException("Ignoring command") ;
}
// Process an Alias keyword if present. This option is available for
// all New commands so it is processed here. The Alias keyword must
// be the penultimate command element, followed by a String.
for (int i = 2 ; i < cmd.argc ; i++) {
if (cmd.argv[i] instanceof String &&
((String)cmd.argv[i]).equals("Alias")) {
if (i == (cmd.argc - 2) && cmd.argv[i+1] instanceof String) {
addConfigObject(new ConfigAlias(cmd.baseName,
(String)cmd.argv[i+1],
configObject)) ;
cmd.argc -= 2 ;
}
else {
throw new IllegalArgumentException
("The alias name must be a string and " +
"must be the last command argument") ;
}
}
}
// Initialize common fields.
configObject.baseName = cmd.baseName ;
configObject.instanceName = cmd.instanceName ;
configObject.creatingCommand = cmd ;
configObject.configContainer = this ;
// Initialize specific fields and return the ConfigObject.
configObject.setClassLoader(classLoader);
configObject.initialize(cmd) ;
return configObject ;
}
/**
* Instantiate and initialize a ConfigObject base class containing alias
* information. The command is of the form:
*
* ({baseName}Alias {aliasName} {originalName})
*
* @param cmd configuration command that creates a new alias
* @return the new ConfigObject with alias information
*/
private ConfigObject createConfigAlias(ConfigCommand cmd) {
ConfigObject original ;
if (cmd.argc != 3 || ! (cmd.argv[2] instanceof String))
throw new IllegalArgumentException
("Command \"" + cmd.commandName +
"\" requires an instance name as second argument") ;
original = findConfigObject(cmd.baseName, (String)cmd.argv[2]) ;
return new ConfigAlias(cmd.baseName, cmd.instanceName, original) ;
}
/**
* A class that does nothing but reference another ConfigObject. Once
* created, the alias name can be used in all commands that would accept
* the original name. A lookup of the alias name will always return the
* original instance.
*/
private static class ConfigAlias extends ConfigObject {
ConfigAlias(String baseName, String instanceName, ConfigObject targ) {
this.baseName = baseName ;
this.instanceName = instanceName ;
this.isAlias = true ;
this.original = targ ;
targ.aliases.add(instanceName) ;
}
}
/**
* Adds the specified ConfigObject instance into this container using the
* given ConfigCommand's base name and instance name.
*
* @param object the ConfigObject instance to add into the database
*/
private void addConfigObject(ConfigObject object) {
ArrayList instances ;
instances = (ArrayList)baseNameMap.get(object.baseName) ;
if (instances == null) {
instances = new ArrayList() ;
baseNameMap.put(object.baseName, instances) ;
}
// Disallow duplicate instance names.
for (int i = 0 ; i < instances.size() ; i++) {
ConfigObject co = (ConfigObject)instances.get(i) ;
if (co.instanceName.equals(object.instanceName)) {
// Don't confuse anybody using Window.
String base = object.baseName ;
if (base.equals("Screen")) base = "Screen or Window" ;
throw new IllegalArgumentException
("Duplicate " + base + " instance name \"" +
object.instanceName + "\" ignored") ;
}
}
instances.add(object) ;
}
/**
* Finds a config object matching the given base name and the instance
* name. If an alias is found, then its original is returned. If the
* object is not found, an IllegalArgumentException is thrown.
*
* @param basename base name of the config object
* @param instanceName name associated with this config object instance
* @return the found ConfigObject
*/
ConfigObject findConfigObject(String baseName, String instanceName) {
ArrayList instances ;
ConfigObject configObject ;
instances = (ArrayList)baseNameMap.get(baseName) ;
if (instances != null) {
for (int i = 0 ; i < instances.size() ; i++) {
configObject = (ConfigObject)instances.get(i) ;
if (configObject.instanceName.equals(instanceName)) {
if (configObject.isAlias)
return configObject.original ;
else
return configObject ;
}
}
}
// Throw an error, but don't confuse anybody using Window.
if (baseName.equals("Screen")) baseName = "Screen or Window" ;
throw new IllegalArgumentException
(baseName + " \"" + instanceName + "\" not found") ;
}
/**
* Find instances of config objects with the given base name.
* This is the same as findConfigObjects(baseName, true)
.
* Aliases are filtered out so that all returned instances are unique.
*
* @param baseName base name of desired config object class
* @return ArrayList of config object instances of the desired base
* class, or null if instances of the base class don't exist
*/
Collection findConfigObjects(String baseName) {
return findConfigObjects(baseName, true) ;
}
/**
* Find instances of config objects with the given base name.
*
* @param baseName base name of desired config object class
* @param filterAlias if true, aliases are filtered out so that all
* returned instances are unique
* @return ArrayList of config object instances of the desired base
* class, or null if instances of the base class don't exist
*/
Collection findConfigObjects(String baseName, boolean filterAlias) {
ArrayList instances ;
instances = (ArrayList)baseNameMap.get(baseName) ;
if (instances == null || instances.size() == 0) {
return null ; // This is not an error.
}
if (filterAlias) {
ArrayList output = new ArrayList() ;
for (int i = 0 ; i < instances.size() ; i++) {
ConfigObject configObject = (ConfigObject)instances.get(i) ;
if (! configObject.isAlias) {
output.add(configObject) ;
}
}
return output ;
}
else {
return instances ;
}
}
/**
* Returns the ConfigObject associated with the name in the given
* ConfigCommand. This is used for evaluating retained built-in commands
* after the config file has already been parsed. The parser won't catch
* any of the exceptions generated by this method, so the error messages
* are wrapped accordingly.
*
* @param basename base name of the config object
* @param cmd command containing the name in argv[1]
* @return the found ConfigObject
*/
private ConfigObject findConfigObject(String baseName, ConfigCommand cmd) {
if (cmd.argc != 2 || !(cmd.argv[1] instanceof String))
throw new IllegalArgumentException
(ConfigObject.errorMessage
(cmd, "Parameter must be a single string")) ;
try {
return findConfigObject(baseName, (String)cmd.argv[1]) ;
}
catch (IllegalArgumentException e) {
throw new IllegalArgumentException
(ConfigObject.errorMessage(cmd, e.getMessage())) ;
}
}
/**
* This method gets called from a ConfigObject to evaluate a retained
* built-in command nested within a property command. These are commands
* that can't be evaluated until the entire config file is parsed.
*
* @param cmd the built-in command
* @return object representing result of evaluation
*/
Object evaluateBuiltIn(ConfigCommand cmd) {
int argc = cmd.argc ;
Object[] argv = cmd.argv ;
if (cmd.commandName.equals("ConfigContainer")) {
// return a reference to this ConfigContainer
return this ;
}
else if (cmd.commandName.equals("Canvas3D")) {
// Look for canvases in the screen database.
return ((ConfigScreen)findConfigObject("Screen", cmd)).j3dCanvas ;
}
else if (baseNameMap.get(cmd.commandName) != null) {
// Handle commands of the form ({objectType} name) that return the
// object associated with the name.
return findConfigObject(cmd.commandName, cmd).targetObject ;
}
else {
// So far no other retained built-in commands.
throw new IllegalArgumentException
(ConfigObject.errorMessage(cmd, "Unknown built-in command \"" +
cmd.commandName + "\"")) ;
}
}
/**
* Process the configuration after parsing the configuration file.
* Note: the processing order of the various config objects is
* significant.
*
* @param setVisible true if Viewer components should be visible
* @param transformCount number of TransformGroups with which
* ViewingPlatforms should be created
* @param attachBehaviors true if behaviors should be attached to
* ViewingPlatforms
*/
private void processConfig(boolean setVisible,
int transformCount, boolean attachBehaviors) {
Collection c, s, pe, vp ;
this.setVisible = setVisible ;
this.transformCount = transformCount ;
c = findConfigObjects("PhysicalBody") ;
if (c != null) {
processPhysicalBodies(c) ;
}
pe = findConfigObjects("PhysicalEnvironment") ;
if (pe != null) {
processPhysicalEnvironments(pe) ;
}
c = findConfigObjects("View") ;
if (c != null) {
processViews(c, setVisible) ;
}
c = findConfigObjects("Device") ;
s = findConfigObjects("Sensor") ;
if (c != null) {
processDevices(c, s, pe) ;
}
vp = findConfigObjects("ViewPlatform") ;
if (vp != null) {
processViewPlatforms(vp, transformCount) ;
}
c = findConfigObjects("ViewPlatformBehavior") ;
if (c != null) {
processViewPlatformBehaviors(c, vp, attachBehaviors) ;
}
c = findConfigObjects("Object") ;
if (c != null) {
processGenericObjects(c) ;
}
}
// Process config physical environments into Java 3D physical
// environments.
private void processPhysicalEnvironments(Collection c) {
Iterator i = c.iterator() ;
while (i.hasNext()) {
ConfigPhysicalEnvironment e = (ConfigPhysicalEnvironment)i.next() ;
e.targetObject = e.createJ3dPhysicalEnvironment() ;
}
}
// Process config physical bodys into Java 3D physical bodies.
private void processPhysicalBodies(Collection c) {
Iterator i = c.iterator() ;
while (i.hasNext()) {
ConfigPhysicalBody b = (ConfigPhysicalBody)i.next() ;
b.targetObject = b.createJ3dPhysicalBody() ;
}
}
// Process config views into Java 3D Views and then create Viewer objects
// for them. This should only be called after all physical bodies and
// physical environments have been processed.
private void processViews(Collection c, boolean setVisible) {
Iterator i = c.iterator() ;
while (i.hasNext()) {
ConfigView v = (ConfigView)i.next() ;
v.targetObject = v.createViewer(setVisible) ;
}
}
// Process config devices into Java 3D input devices. This should be done
// only after all views have been processed, as some InputDevice
// implementations require the AWT components associated with a view.
private void processDevices(Collection c, Collection s, Collection p) {
ConfigDevice cd = null ;
Iterator i = c.iterator() ;
while (i.hasNext()) {
cd = (ConfigDevice)i.next() ;
cd.targetObject = cd.createInputDevice() ;
}
// Process device properties only after all InputDevices have been
// instantiated. Some InputDevice properties require references
// to other InputDevice implementations.
i = c.iterator() ;
while (i.hasNext()) ((ConfigDevice)i.next()).processProperties() ;
// Initialize the devices only after all have been instantiated, as
// some InputDevices implementations are slaved to the first one
// created and will not initialize otherwise (e.g. LogitechTracker).
i = c.iterator() ;
while (i.hasNext()) {
cd = (ConfigDevice)i.next() ;
if (! cd.j3dInputDevice.initialize())
throw new RuntimeException
(cd.errorMessage(cd.creatingCommand,
"could not initialize device \"" +
cd.instanceName + "\"")) ;
}
// An InputDevice implementation will have created all its Sensors by
// the time initialize() returns. Retrieve and configure them here.
if (s != null) {
i = s.iterator() ;
while (i.hasNext()) {
ConfigSensor cs = (ConfigSensor)i.next() ;
cs.configureSensor() ;
cs.targetObject = cs.j3dSensor ;
}
}
// Iterate through the PhysicalEnvironments and process the devices.
if (p != null) {
i = p.iterator() ;
while (i.hasNext())
((ConfigPhysicalEnvironment)i.next()).processDevices() ;
}
}
// Process config view platforms into Java 3D viewing platforms.
private void processViewPlatforms(Collection c, int numTransforms) {
Iterator i = c.iterator() ;
while (i.hasNext()) {
ConfigViewPlatform cvp = (ConfigViewPlatform)i.next() ;
cvp.targetObject = cvp.createViewingPlatform(numTransforms) ;
}
}
// Process the configured view platform behaviors.
private void processViewPlatformBehaviors(Collection behaviors,
Collection viewPlatforms,
boolean attach) {
Iterator i = behaviors.iterator() ;
while (i.hasNext()) {
ConfigViewPlatformBehavior b =
(ConfigViewPlatformBehavior)i.next() ;
b.targetObject = b.createViewPlatformBehavior() ;
}
// Process properties only after all behaviors are instantiated.
i = behaviors.iterator() ;
while (i.hasNext())
((ConfigViewPlatformBehavior)i.next()).processProperties() ;
// Attach behaviors to platforms after properties processed.
if (attach && viewPlatforms != null) {
i = viewPlatforms.iterator() ;
while (i.hasNext())
((ConfigViewPlatform)i.next()).processBehavior() ;
}
}
// Process generic objects.
private void processGenericObjects(Collection objects) {
Iterator i = objects.iterator() ;
while (i.hasNext()) {
ConfigObject o = (ConfigObject)i.next() ;
o.targetObject = o.createTargetObject() ;
}
// Process properties only after all target objects are instantiated.
i = objects.iterator() ;
while (i.hasNext()) ((ConfigObject)i.next()).processProperties() ;
}
// Returns a read-only Set containing all unique Java 3D objects of the
// specified base class.
private ReadOnlySet createSet(String baseName) {
Collection c = findConfigObjects(baseName, true) ;
if (c == null || c.size() == 0)
return null ;
Iterator i = c.iterator() ;
ArrayList l = new ArrayList() ;
while (i.hasNext()) l.add(((ConfigObject)i.next()).targetObject) ;
return new ReadOnlySet(l) ;
}
// Returns a read-only Map that maps all names in the specified base
// class, including aliases, to their corresponding Java 3D objects.
private ReadOnlyMap createMap(String baseName) {
Collection c = findConfigObjects(baseName, false) ;
if (c == null || c.size() == 0)
return null ;
Iterator i = c.iterator() ;
HashMap m = new HashMap() ;
while (i.hasNext()) {
ConfigObject co = (ConfigObject)i.next() ;
if (co.isAlias)
m.put(co.instanceName, co.original.targetObject) ;
else
m.put(co.instanceName, co.targetObject) ;
}
return new ReadOnlyMap(m) ;
}
/**
* Returns a read-only Set of all configured PhysicalBody instances in the
* order they were defined in the configuration file.
*
* PhysicalBody instances are created with the following command:
*
* (NewPhysicalBody <instance name>
* [Alias <alias name>])
*
*
* The PhysicalBody is configured through the following command:
*
* (PhysicalBodyProperty <instance name>
* <property name> <property value>)
*
*
* @return read-only Set of all unique instances, or null
*/
public Set getPhysicalBodies() {
if (bodies != null) return bodies ;
bodies = createSet("PhysicalBody") ;
return bodies ;
}
/**
* Returns a read-only Map that maps PhysicalBody names to instances.
* Names may be aliases and if so will map to the original instances.
*
* @return read-only Map from names to PhysicalBody instances, or null if
* no instances
*/
public Map getNamedPhysicalBodies() {
if (bodyMap != null) return bodyMap ;
bodyMap = createMap("PhysicalBody") ;
return bodyMap ;
}
/**
* Returns a read-only Set of all configured PhysicalEnvironment instances
* in the order they were defined in the configuration file.
*
* PhysicalEnvironment instances are created with the following command:
*
* (NewPhysicalEnvironment <instance name>
* [Alias <alias name>])
*
*
* The PhysicalEnvironment is configured through the following command:
*
* (PhysicalEnvironmentProperty <instance name>
* <property name> <property value>)
*
*
* @return read-only Set of all unique instances, or null
*/
public Set getPhysicalEnvironments() {
if (environments != null) return environments ;
environments = createSet("PhysicalEnvironment") ;
return environments ;
}
/**
* Returns a read-only Map that maps PhysicalEnvironment names to
* instances. Names may be aliases and if so will map to the original
* instances.
*
* @return read-only Map from names to PhysicalEnvironment instances, or
* null if no instances
*/
public Map getNamedPhysicalEnvironments() {
if (environmentMap != null) return environmentMap ;
environmentMap = createMap("PhysicalEnvironment") ;
return environmentMap ;
}
/**
* Returns a read-only Set of all configured Viewer instances in the order
* they were defined in the configuration file. The Viewers will have
* incorporated any PhysicalEnvironment and PhysicalBody objects specfied
* for them in the configuration file, and will be attached to any
* ViewingPlatforms specified for them.
*
* Viewer instances are created with the following command:
*
* (NewView <instance name> [Alias <alias name>])
*
*
* The Viewer is configured through the following command:
*
* (ViewProperty <instance name>
* <property name> <property value>)
*
*
* @return read-only Set of all unique instances, or null
*/
public Set getViewers() {
if (viewers != null) return viewers ;
viewers = createSet("View") ;
return viewers ;
}
/**
* Returns a read-only Map that maps Viewer names to instances.
* Names may be aliases and if so will map to the original instances.
* The Viewers will have incorporated any PhysicalEnvironment and
* PhysicalBody objects specfied for them in the configuration file, and
* will be attached to any ViewingPlatforms specified for them.
*
* @return read-only Map from names to Viewer instances, or
* null if no instances
*/
public Map getNamedViewers() {
if (viewerMap != null) return viewerMap ;
viewerMap = createMap("View") ;
return viewerMap ;
}
/**
* Returns a read-only Set of all configured InputDevice instances in the
* order they were defined in the configuration file. All InputDevice
* instances in the set are initialized and registered with any
* PhysicalEnvironments that reference them.
*
* InputDevice instances are created with the following command:
*
* (NewDevice <instanceName> <className>
* [Alias <alias name>])
*
*
* className must be the fully-qualified name of a class that
* implements the InputDevice interface. The implementation
* must provide a parameterless constructor.
*
* The InputDevice is configured through the DeviceProperty command:
*
* (DeviceProperty <instanceName> <propertyName>
* <arg0> ... <argn>)
*
*
* propertyName must be the name of a input device method that
* takes an array of Objects as its only parameter; the array is populated
* with the values of arg0 through argn when the method is
* invoked to set the property. These additional requirements for
* configurable input devices can usually be fulfilled by extending or
* wrapping available InputDevice implementations.
*
* @return read-only Set of all unique instances, or null
*/
public Set getInputDevices() {
if (devices != null) return devices ;
devices = createSet("Device") ;
return devices ;
}
/**
* Returns a read-only Map that maps InputDevice names to instances.
* Names may be aliases and if so will map to the original instances. All
* InputDevice instances in the map are initialized and registered with
* any PhysicalEnvironments that reference them.
*
* @return read-only Map from names to InputDevice instances, or
* null if no instances
* @see #getInputDevices
*/
public Map getNamedInputDevices() {
if (deviceMap != null) return deviceMap ;
deviceMap = createMap("Device") ;
return deviceMap ;
}
/**
* Returns a read-only Set of all configured Sensor instances in the order
* they were defined in the configuration file. The associated
* InputDevices are all initialized and registered with any
* PhysicalEnvironments that reference them.
*
* Sensor instances are named with the following command:
*
* (NewSensor <instance name> <device name>
* <sensor index> [Alias <alias name>])
*
*
* device name is the instance name of a previously defined
* InputDevice, and sensor index is the index of the Sensor to be
* bound to instance name. The InputDevice implementation is
* responsible for creating its own Sensor objects, so this command does
* not create any new instances.
*
* The Sensor is configured through the SensorProperty command:
*
* (SensorProperty <instance name> <property name>
* <property value>)
*
*
* With the sole exception of the Sensor assigned to the head tracker,
* none of the Sensors defined in the configuration file are placed into
* the Sensor array maintained by a PhysicalEnvironment.
*
* @return read-only Set of all unique instances, or null
*/
public Set getSensors() {
if (sensors != null) return sensors ;
sensors = createSet("Sensor") ;
return sensors ;
}
/**
* Returns a read-only Map that maps Sensor names to instances. Names may
* be aliases and if so will map to the original instances. The
* associated InputDevices are all initialized and registered with any
* PhysicalEnvironments that reference them.
*
* With the sole exception of the Sensor assigned to the head tracker,
* none of the Sensors defined in the configuration file are placed into
* the Sensor array maintained by a PhysicalEnvironment.
*
* @return read-only Map from names to Sensor instances, or
* null if no instances
*/
public Map getNamedSensors() {
if (sensorMap != null) return sensorMap ;
sensorMap = createMap("Sensor") ;
return sensorMap ;
}
/**
* Returns a read-only Set of all configured ViewingPlatform instances in
* the order they were defined in the configuration file. The
* ConfigContainer class itself does not attach the ViewingPlatform
* instances to any scengraph components or universe Locales; they are not
* "live" until made so by a separate client such as ConfiguredUniverse.
*
* ViewingPlatform instances are created with the following command:
*
* (NewViewPlatform <instance name>
* [Alias <alias name>])
*
*
* The ViewingPlatform is configured through the following command:
*
* (ViewPlatformProperty <instance name> <property name>
* <property value>)
*
*
* @return read-only Set of all unique instances, or null
*/
public Set getViewingPlatforms() {
if (platforms != null) return platforms ;
platforms = createSet("ViewPlatform") ;
return platforms ;
}
/**
* Returns a read-only Map that maps ViewingPlatform names to instances.
* Names may be aliases and if so will map to the original instances. The
* ConfigContainer class itself does not attach the ViewingPlatform
* instances to any scengraph components or universe Locales; they are not
* "live" until made so by a separate client such as ConfiguredUniverse.
*
* @return read-only Map from names to ViewingPlatform instances, or
* null if no instances
*/
public Map getNamedViewingPlatforms() {
if (platformMap != null) return platformMap ;
platformMap = createMap("ViewPlatform") ;
return platformMap ;
}
/**
* Returns a read-only Set of all configured ViewPlatformBehavior
* instances in the order they were defined in the configuration file.
*
* The behaviors are attached to any ViewingPlatforms that specified them;
* that is, the setViewPlatformBehavior
and
* setViewingPlatform
methods of ViewingPlatform and
* ViewPlatformBehavior have been called if appropriate. However, a
* behavior's initialize
method is not called until the
* ViewingPlatform to which it is attached is made live.
*
* ViewPlatformBehavior instances are created by the following command:
*
* (NewViewPlatformBehavior <instanceName> <className>)
*
*
* className must be the fully qualified name of a concrete class
* that extends the abstract ViewPlatformBehavior class. The
* implementation must provide a parameterless constructor.
*
* The behavior is configured using ViewPlatformBehaviorProperty:
*
* (ViewPlatformBehaviorProperty <instanceName>
* <propertyName> <arg0> ... <argn>)
*
*
* ViewPlatformBehavior subclasses inherit a number of pre-defined
* properties that can be directly specified with the propertyName
* string; see the configuration file documentation for details.
*
* Concrete ViewPlatformBehavior instances can also define their own
* unique properties. In those cases, propertyName must be the
* name of a behavior method that takes an array of Objects as its only
* parameter; the array is populated with the values of arg0
* through argn when the method is invoked to set the property.
* These additional requirements for configurable behaviors can usually be
* fulfilled by extending or wrapping available ViewPlatformBehavior
* subclasses.
*
* @return read-only Set of all unique instances, or null
*/
public Set getViewPlatformBehaviors() {
if (behaviors != null) return behaviors ;
behaviors = createSet("ViewPlatformBehavior") ;
return behaviors ;
}
/**
* Returns a read-only Map that maps ViewPlatformBehavior names to
* instances. Names may be aliases and if so will map to the original
* instances.
*
* The behaviors are attached to any ViewingPlatforms that specified them;
* that is, the setViewPlatformBehavior
and
* setViewingPlatform
methods of ViewingPlatform and
* ViewPlatformBehavior have been called if appropriate. However, a
* behavior's initialize
method is not called until the
* ViewingPlatform to which it is attached is made live.
*
* @return read-only Map from names to ViewPlatformBehavior instances, or
* null if no instances
* @see #getViewPlatformBehaviors
*/
public Map getNamedViewPlatformBehaviors() {
if (behaviorMap != null) return behaviorMap ;
behaviorMap = createMap("ViewPlatformBehavior") ;
return behaviorMap ;
}
/**
* Returns a read-only Map containing the named Canvas3D instances used by
* the specified Viewer. Names may be aliases and if so will map to the
* original instances. The set of unique Canvas3D instances used by a
* Viewer may be obtained by calling the Viewer's accessor methods
* directly.
*
* A named Canvas3D is created and added to a Viewer whenever any of the
* following configuration commands are used:
*
* (ViewProperty <view> Screen <screenName>)
* (ViewProperty <view> Window <windowName>)
*
*
* view is the name of a Viewer created with the NewView command.
* The screenName and windowName parameters of the above
* commands are the keys to use when looking up the associated Canvas3D
* instances in the Map returned by this method. Note: the
* NewScreen and NewWindow commands do not create Canvas3D
* instances themselves; they are created only by the above configuration
* commands.
*
* @param viewName the name of the Viewer
* @return read-only Map containing the Viewer's named Canvas3D instances
*/
public Map getNamedCanvases(String viewName) {
Map m = (Map)viewCanvasMap.get(viewName) ;
if (m != null) return m ;
m = new HashMap() ;
ConfigView cv = (ConfigView)findConfigObject("View", viewName) ;
Iterator i = cv.screens.iterator() ;
while (i.hasNext()) {
ConfigScreen cs = (ConfigScreen)i.next() ;
m.put(cs.instanceName, cs.j3dCanvas) ;
// The aliases list contains all alias strings for the canvas.
Iterator j = cs.aliases.iterator() ;
while (j.hasNext()) m.put(j.next(), cs.j3dCanvas) ;
}
m = new ReadOnlyMap(m) ;
viewCanvasMap.put(viewName, m) ;
return m ;
}
/**
* Returns a read-only Set of all generic configuration object
* instances in the order they were defined in the configuration file.
*
* Generic object instances are created with the following command:
*
* (NewObject <instanceName> <className>)
*
*
* className must be the fully-qualified name of a class that
* provides a parameterless constructor.
*
* The object is configured through the ObjectProperty command:
*
* (ObjectProperty <instanceName> <propertyName>
* <arg0> ... <argn>)
*
*
* propertyName must be the name of a method provided by object
* instanceName. It must take an array of Objects as its only
* parameter; the array is populated with the values of arg0
* through argn when the method is invoked to set the property.
* These additional requirements for configurable objects can usually be
* fulfilled by extending or wrapping available object classes.
*
* @return read-only Set of all unique instances, or null
*/
public Set getGenericObjects() {
if (genericObjects != null) return genericObjects ;
genericObjects = createSet("Object") ;
return genericObjects ;
}
/**
* Returns a read-only Map that maps generic object names to
* instances. Names may be aliases and if so will map to the original
* instances.
*
* @return read-only Map from names to generic object instances, or
* null if no instances
* @see #getGenericObjects
*/
public Map getNamedGenericObjects() {
if (genericObjectMap != null) return genericObjectMap ;
genericObjectMap = createMap("Object") ;
return genericObjectMap ;
}
/**
* Returns the number of TransformGroups with which ViewingPlatforms
* should be created. This is useful for clients that wish to provide a
* default ViewingPlatform if the configuration file doesn't specify one.
*
* @return the number of TransformGroups
*/
public int getViewPlatformTransformCount() {
return transformCount ;
}
/**
* Returns whether Viewers should be created with their AWT components
* initially visible or invisible. This is useful for clients that wish
* to provide a default Viewer if the configuration file doesn't specify
* one.
*
* @return true if Viewer components should be initially visible; false
* otherwise
*/
public boolean getViewerVisibility() {
return setVisible ;
}
/**
* Release memory references used by this ConfigContainer. All Sets and
* Maps obtained from this ConfigContainer are cleared.
*/
public void clear() {
// Clear baseNameList.
Iterator i = baseNameMap.values().iterator() ;
while (i.hasNext()) ((Collection)i.next()).clear() ;
baseNameMap.clear() ;
// Clear viewCanvasMap.
i = viewCanvasMap.values().iterator() ;
while (i.hasNext()) ((ReadOnlyMap)i.next()).map.clear() ;
viewCanvasMap.clear() ;
// Release reference to file name.
currentFileName = null ;
// Clear and release sets.
if (bodies != null) {
bodies.collection.clear() ;
bodies = null ;
}
if (environments != null) {
environments.collection.clear() ;
environments = null ;
}
if (devices != null) {
devices.collection.clear() ;
devices = null ;
}
if (sensors != null) {
sensors.collection.clear() ;
sensors = null ;
}
if (behaviors != null) {
behaviors.collection.clear() ;
behaviors = null ;
}
if (platforms != null) {
platforms.collection.clear() ;
platforms = null ;
}
if (viewers != null) {
viewers.collection.clear() ;
viewers = null ;
}
if (genericObjects != null) {
genericObjects.collection.clear() ;
genericObjects = null ;
}
// Clear and release maps.
if (bodyMap != null) {
bodyMap.map.clear() ;
bodyMap = null ;
}
if (environmentMap != null) {
environmentMap.map.clear() ;
environmentMap = null ;
}
if (deviceMap != null) {
deviceMap.map.clear() ;
deviceMap = null ;
}
if (sensorMap != null) {
sensorMap.map.clear() ;
sensorMap = null ;
}
if (behaviorMap != null) {
behaviorMap.map.clear() ;
behaviorMap = null ;
}
if (platformMap != null) {
platformMap.map.clear() ;
platformMap = null ;
}
if (viewerMap != null) {
viewerMap.map.clear() ;
viewerMap = null ;
}
if (genericObjectMap != null) {
genericObjectMap.map.clear() ;
genericObjectMap = null ;
}
}
/**
* Returns the config file URL based on system properties. The current
* implementation of this method parses the j3d.configURL property as a
* URL string. For example, the following command line would specify that
* the config file is taken from the file "j3dconfig" in the current
* directory:
*
* java -Dj3d.configURL=file:j3dconfig ...
*
*
* @return the URL of the config file; null is returned if no valid
* URL is defined by the system properties
*/
public static URL getConfigURL() {
return getConfigURL(null) ;
}
/**
* Returns the config file URL based on system properties. The current
* implementation of this method parses the j3d.configURL property as a
* URL string. For example, the following command line would specify that
* the config file is taken from the file "j3dconfig" in the current
* directory:
*
* java -Dj3d.configURL=file:j3dconfig ...
*
*
* @param defaultURLString the default string used to construct
* the URL if the appropriate system properties are not defined
* @return the URL of the config file; null is returned if no
* valid URL is defined either by the system properties or the
* default URL string
*/
public static URL getConfigURL(String defaultURLString) {
URL url = null ;
String urlString = null ;
final String defProp = defaultURLString ;
urlString = (String)java.security.AccessController.doPrivileged
(new java.security.PrivilegedAction() {
@Override
public Object run() {
return System.getProperty("j3d.configURL", defProp) ;
}
}) ;
if (urlString == null) {
return null ;
}
try {
url = new URL(urlString) ;
}
catch(MalformedURLException e) {
System.out.println(e) ;
return null ;
}
return url ;
}
// A general purpose read-only Map backed by a HashMap.
private static class ReadOnlyMap extends AbstractMap {
HashMap map ;
private Set entrySet = null ;
ReadOnlyMap(Map map) {
this.map = new HashMap(map) ;
}
// overridden for efficiency
@Override
public Object get(Object key) {
return map.get(key) ;
}
// overridden for efficiency
@Override
public boolean containsKey(Object key) {
return map.containsKey(key) ;
}
// overridden for efficiency
@Override
public boolean containsValue(Object value) {
return map.containsValue(value) ;
}
@Override
public Set entrySet() {
if (entrySet == null)
entrySet = new ReadOnlySet(map.entrySet()) ;
return entrySet ;
}
}
// A general purpose read-only Set backed by a Collection containing
// unique objects.
private static class ReadOnlySet extends AbstractSet {
Collection collection = null ;
ReadOnlySet(Collection c) {
this.collection = c ;
}
@Override
public int size() {
return collection.size() ;
}
@Override
public Iterator iterator() {
return new ReadOnlyIterator(collection.iterator()) ;
}
}
// A general purpose read-only Iterator backed by another Iterator.
private static class ReadOnlyIterator implements Iterator {
private Iterator i ;
ReadOnlyIterator(Iterator i) {
this.i = i ;
}
@Override
public boolean hasNext() {
return i.hasNext() ;
}
@Override
public Object next() {
return i.next() ;
}
@Override
public void remove() {
throw new UnsupportedOperationException() ;
}
}
}