package net.xoetrope.registry;
import net.xoetrope.xui.XComponentConstructor;
import net.xoetrope.xui.XComponentFactory;
import java.awt.Component;
import java.io.Reader;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.net.URL;
import java.util.Enumeration;
import net.xoetrope.debug.DebugLogger;
import net.xoetrope.xml.XmlElement;
import net.xoetrope.xml.XmlSource;
import net.xoetrope.xui.build.BuildProperties;
import java.util.Vector;
import java.util.Hashtable;
import net.xoetrope.xui.XProject;
/**
* <p>A component factory that builds components based upon some XML configuration
* files. This factory is designed to work with the KalIDEoscope which includes a
* graphical utility for selection and configuration of components. The XML
* configuration file may also be manipulated outside of the editor.</p>
* <p>The factory works by reading a file specifying where a component can be
* loaded from and the methods within that component that can be used to get and
* set properties. The getter and setter methods do not need to follow the bean
* specification and can include various arguments and they can also use different
* naming schemes.
* </p>
* <p> Copyright (c) Xoetrope Ltd., 2002-2004</p>
* <p> $Revision: 1.21 $</p>
* <p> License: see License.txt</p>
*/
public class XRegisteredComponentFactory implements XComponentConstructor
{
/**
* The register of component adapters for each component type
*/
protected Hashtable propertiesRegister;
/**
* The config files used to build the set of component adapters
*/
protected static Hashtable configFiles;
/**
* A counter for changes to the registry. Used to indicate if the registry
* needs to be rebuilt
*/
protected static int changeCounter;
/**
* Used for tracking changes to the registry spec.
*/
protected int localChangeCounter = -1;
/**
* Location/spec of the jar file from which the resource are loaded. A temporary object
*/
protected String urlBase;
protected XProject currentProject;
/**
* Create the new component factory
* @param project the owner project
*/
public XRegisteredComponentFactory( XProject project )
{
propertiesRegister = new Hashtable();
if ( BuildProperties.DEBUG )
DebugLogger.trace( "Setting up XRegisteredComponentFactory" );
addConfigFile( "XUI", "net/xoetrope/xui/resources/components.xml", false );
currentProject = project;
String projectRegistryFile = currentProject.getStartupParam( "ComponentRegistry" );
if ( ( projectRegistryFile == null ) || ( projectRegistryFile.length() == 0 ) )
projectRegistryFile = "components";
if ( projectRegistryFile.indexOf( ".xml" ) < 0 )
projectRegistryFile += ".xml";
URL url = currentProject.findResource( projectRegistryFile );
if ( url != null )
addConfigFile( "Project", url, false );
}
/**
* Add a configuration file.
* If the files have already been loaded then the new file will be loaded
* @param key the name by which the configuration file is referenced
* @param resource the name/path of the configuration file or the URL for the file
* @param overwrite true to overwrite and existing entry matching the specified key
*/
public static void addConfigFile( String key, Object resource, boolean overwrite )
{
if ( resource == null )
return;
if ( configFiles == null )
configFiles = new Hashtable();
Object resourceObj = configFiles.get( key );
// If the same file name is already used then ignore it.
if (( resourceObj != null ) && resource.equals( resourceObj ))
return;
changeCounter++;
if (( resourceObj != null ) || overwrite )
configFiles.remove( key );
configFiles.put( key, resource );
}
/**
* Signal that the configuration has been updated.
*/
public static void updateConfig()
{
changeCounter++;
}
/**
* A generic factory for adding XComponents. The component is constructed, positioned and
* added to the parent panel if one exists. The component is named with a counter value
* to uniquely identify the control.
* When a ScrollPane is addd it becomes the parent.
* @param cf the calling component factory
* @param type a name identifying the type of component to be created
* @param content the component text/content
* @return the new component
*/
public Object constructComponent( XComponentFactory cf, String type, String content )
{
Component comp = null;
checkRegistration();
ComponentAdapter componentDescription = (ComponentAdapter)propertiesRegister.get( type );
if ( componentDescription != null ) {
try {
comp = (Component)componentDescription.clazz.newInstance();
componentDescription.setProperty( comp, "content", content, "String" );
}
catch ( IllegalAccessException ex ) {
ex.printStackTrace();
}
catch ( InstantiationException ex ) {
ex.printStackTrace();
}
}
return comp;
}
/**
* A generic factory method for adding non component elements. The method can
* be useful in conjunction with the 'repeat' and 'include' keywords where
* a component may not have a one to one relationship with the XML element.
* @param cf the calling component factory
* @param type the object type
* @param name a name identifying the element to be created
* @param content the component text/content
* @param attribs the element attributes if any
* @return the new component
*/
public Object addElement( XComponentFactory cf, String type, String name, String content, Hashtable attribs )
{
if ( BuildProperties.DEBUG )
DebugLogger.logWarning( "Unsupported Operation - XEditorRegisteredComponentFactory.addElement(...)" );
return null;
}
/**
* Notify the component factories that some of their settings may have changed
*/
public void update()
{
if ( BuildProperties.DEBUG )
DebugLogger.logWarning( "Unsupported Operation - XEditorRegisteredComponentFactory.update()" );
}
/**
* Set the package name for the factory's widgets. This factory ignores the
* default package name as the classes/componentes being instantiated can come
* from different packages and their package must be fully specified.
* @param defPackage the default package name
*/
public void setPackageName( String defPackage )
{
if ( BuildProperties.DEBUG )
DebugLogger.logWarning( "Unsupported Operation - XEditorRegisteredComponentFactory.setPackageName(...)" );
}
/**
* Get the adapter for a particular component type. The adapter can be used
* to get and set properties of the component.
* @param type the name by which the component is specified and referenced
* @return the adapter
*/
public ComponentAdapter getComponentAdapter( String type )
{
return (ComponentAdapter)propertiesRegister.get( type );
}
/**
* Read the component registry. The format is described in the components.xsd
* schema.
*/
protected void read()
{
propertiesRegister.clear();
Enumeration enumeration = configFiles.keys();
while ( enumeration.hasMoreElements() ) {
String key = ( String ) enumeration.nextElement();
if ( !key.equals( "Project" ) )
doRead( key, configFiles.get( key ) );
}
doRead( "Project", configFiles.get( "Project" ) );
}
/**
* Read the component registry. The format is described in the components.xsd
* schema. The config file is also registered.
* @param configFile the name of the configuration file
* @param key the name by which the configuration file is referenced
*/
protected void read( String key, String configFile )
{
addConfigFile( key, configFile, true );
doRead( key, configFile );
}
/**
* Read the component registry. The format is described in the components.xsd
* schema.
* @param configFile the name of the configuration file
* @param key the name by which the configuration file is referenced
*/
protected void doRead( String key, Object configFile )
{
if ( configFile == null )
return;
else if ( configFile instanceof URL )
doRead( key, (URL)configFile );
else
doRead( key, (String)configFile );
}
/**
* Read the component registry. The format is described in the components.xsd
* schema.
* @param configFile the name of the configuration file
* @param key the name by which the configuration file is referenced
*/
protected void doRead( String key, String configFile )
{
if ( BuildProperties.DEBUG )
DebugLogger.trace( "XRegisteredComponentFactory reading config file: " + configFile );
try {
urlBase = "";
String fileName = configFile;
if ( fileName.indexOf( ".xml" ) < 0 )
fileName += ".xml";
Reader r = currentProject.getBufferedReader( fileName, null );
if ( r != null )
read( key, r );
}
catch ( Exception ex ) {
ex.printStackTrace();
}
}
/**
* Read the component registry. The format is described in the components.xsd
* schema.
* @param configFileURL the URL of the configuration file
* @param key the name by which the configuration file is referenced
*/
protected void doRead( String key, URL configFileURL )
{
if ( BuildProperties.DEBUG )
DebugLogger.trace( "XRegisteredComponentFactory reading config file: " + configFileURL.toString() );
try {
String file = configFileURL.getFile();
urlBase = file.substring( 0, file.indexOf( '!' ) + 2 );
Reader r = new BufferedReader( new InputStreamReader( configFileURL.openStream() ));
if ( r != null )
read( key, r );
}
catch ( Exception ex ) {
ex.printStackTrace();
}
}
/**
* Read the component registry. The format is described in the components.xsd
* schema.
* @param key the name by which the configuration file is referenced
* @param reader the reader from which to read the file
*/
public void read( String key, Reader reader )
{
try {
XmlElement regRoot = XmlSource.read( reader );
Vector componentNodes = regRoot.getChildren();
int numComponents = componentNodes.size();
for ( int i = 0; i < numComponents; i++ ) {
XmlElement componentNode = ( XmlElement )componentNodes.elementAt( i );
try {
String componentName = componentNode.getAttribute( "name" );
String iconName = componentNode.getAttribute( "icon" );
String reflect = componentNode.getAttribute( "reflect" );
String tag = componentNode.getName().toLowerCase();
if ( !tag.equals( "component" ))
continue;
ComponentAdapter oldCa = (ComponentAdapter)propertiesRegister.get( componentName );
ComponentAdapter ca = oldCa;
if ( oldCa == null ) {
if (( reflect != null ) && reflect.equals( "true" )) {
ca = addReflectionAdapter( key, componentName, iconName, componentNode.getAttribute( "class" ), componentNode.getAttribute( "ui" ));
// Make sure there aren't duplicates of this property.
propertiesRegister.remove( componentName );
propertiesRegister.put( componentName, ca );
continue;
}
ca = addComponentAdapter( key, componentName, iconName, componentNode.getAttribute( "class" ), componentNode.getAttribute( "ui" ));
}
Vector propertyNodes = componentNode.getChildren();
int numProperties = propertyNodes.size();
for ( int j = 0; j < numProperties; j++ ) {
XmlElement propertyNode = componentNode.elementAt( j );
String type = propertyNode.getAttribute( "type" );
String name = propertyNode.getAttribute( "name" );
String method = propertyNode.getAttribute( "method" );
String attribStr = propertyNode.getAttribute( "attrib" );
boolean attributed = (( attribStr != null ) && ( attribStr.equals( "true" )));
String paramType = "String";
if ( type == null )
type = "both";
if(( name != null ) && ( method != null )){
if ( type.equals( "get" ) || type.equals( "both" )) {
String getMethod = method;
if (( getMethod == null ) || ( getMethod.length() == 0 ))
getMethod = "get" + name.substring( 0, 1 ).toUpperCase() + name.substring( 1 );
else if ( type.equals( "both" ))
getMethod = "get" + method.substring( 0, 1 ).toUpperCase() + method.substring( 1 );
Vector params = propertyNode.getChildren();
if ( params.size() > 0 )
paramType = ((XmlElement)params.elementAt( 0 )).getAttribute( "type" );
ca.addProperty( "get", name, getMethod, paramType, attributed, true, null );
}
if ( type.equals( "set" ) || type.equals( "both" )) {
String setMethod = method;
if (( setMethod == null ) || ( setMethod.length() == 0 ))
setMethod = "set" + name.substring( 0, 1 ).toUpperCase() + name.substring( 1 );
else if ( type.equals( "both" ))
setMethod = "set" + method.substring( 0, 1 ).toUpperCase() + method.substring( 1 );
Vector params = propertyNode.getChildren();
if ( params.size() > 0 )
paramType = ((XmlElement)params.elementAt( 0 )).getAttribute( "type" );
ca.addProperty( "set", name, setMethod, paramType, attributed, true, null );
}
}
}
// Make sure there aren't duplicates of this property.
propertiesRegister.remove( componentName );
propertiesRegister.put( componentName, ca );
}
catch ( Exception ex ) {
ex.printStackTrace();
}
}
}
catch ( Exception ex ) {
if ( BuildProperties.DEBUG )
DebugLogger.logError( "Unable to setup the component registry" );
}
}
/**
* Create a new component adapter
* @param owner the name ofthe owning module
* @param componentName the component name
* @param iconName the icon file
* @param clazz the class file name
* @param ui the name of the ui
* @throws java.lang.ClassNotFoundException problems finding the class
* @return the new component adapter
*/
public ComponentAdapter addComponentAdapter( String owner, String componentName, String iconName, String clazz, String ui ) throws ClassNotFoundException
{
return new ComponentAdapter( clazz, ui );
}
/**
* Create a new reflection component adapter, that is an adapter that uses
* reflection to find a suitable getter or setter.
* @param owner the name ofthe owning module
* @param componentName the component name
* @param iconName the icon file
* @param clazz the class file name
* @param ui the name of the ui
* @throws java.lang.ClassNotFoundException problems finding the class
* @return the new component adapter
*/
public ComponentAdapter addReflectionAdapter( String owner, String componentName, String iconName, String clazz, String ui ) throws ClassNotFoundException
{
return new ReflectionAdapter( clazz, ui );
}
/**
* Check that all the registered components are loaded
*/
public void checkRegistration()
{
if ( localChangeCounter != changeCounter ) {
read();
localChangeCounter = changeCounter;
}
}
}