package net.xoetrope.xui.data;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import net.xoetrope.debug.DebugLogger;
import net.xoetrope.xml.XmlElement;
import net.xoetrope.xml.XmlSource;
import net.xoetrope.xui.PageSupport;
import net.xoetrope.xui.XProject;
import net.xoetrope.xui.build.BuildProperties;
/**
* A data binding factory that loads a specification of data bindings from an
* XML configuration file
* <p> Copyright (c) Xoetrope Ltd., 2002-2006. See license.txt for licensing
* details</p>
* @author luano
*/
public class XRegisteredDataBindingFactory extends XDataBindingFactory
{
private static XRegisteredDataBindingFactory instance = null;
/**
* Match bindings based on the target component class name
*/
public static final int CLASS_MATCH = 0;
/**
* Match bindings based on implementation of a particular interface
*/
public static final int INTERFACE_MATCH = 1;
/**
* Match bindings based on inheritance from a particular class
*/
public static final int INSTANCE_MATCH = 2;
/**
* Match bindings based on the results of a user defined comparison
*/
public static final int INSPECTOR_MATCH = 3;
private static final int NUM_MATCH_MODES = 4;
/**
* The register of binding adapters for each component type
*/
protected ArrayList[] bindingRegisters;
/**
* The config files used to build the set of component adapters. Stores the
* file names
*/
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;
private XProject currentProject;
/**
* Create the new component factory
* @param project the owner project
*/
private XRegisteredDataBindingFactory( XProject project )
{
super( project );
if ( BuildProperties.DEBUG )
DebugLogger.trace( "Setting up XRegisteredDataBindingFactory" );
addConfigFile( "XUI", "net/xoetrope/xui/data/bindings.xml", false );
currentProject = project;
String projectRegistryFile = currentProject.getStartupParam( "BindingsRegistry" );
if ( ( projectRegistryFile == null ) || ( projectRegistryFile.length() == 0 ) )
projectRegistryFile = "bindings";
if ( projectRegistryFile.indexOf( ".xml" ) < 0 )
projectRegistryFile += ".xml";
URL url = currentProject.findResource( projectRegistryFile );
if ( url != null )
addConfigFile( "Project", url, false );
}
/**
* Register an instance of this binding factory.
* @param currentProject the owner project
*/
public static void register( XProject currentProject )
{
if ( instance == null )
instance = new XRegisteredDataBindingFactory( currentProject );
currentProject.registerBindingFactory( instance );
}
/**
* Try to get a binding factory to construct the binding
* @param page the page that will own the binding
* @param comp the target component
* @param instanceConfig the attributes of the binding instance
* @return the new binding if one could be constructed
*/
public XDataBinding getBinding( PageSupport page, Object comp, Hashtable instanceConfig )
{
checkRegistration();
String className = comp.getClass().getName();
String type = (String)instanceConfig.get( "type" );
Hashtable bindingConfig = new Hashtable();
for ( int mode = 0; mode < NUM_MATCH_MODES; mode++ ) {
int numRegistrations = bindingRegisters[ mode ].size();
for ( int i = 0; i < numRegistrations; i++ ) {
XBindingMatch match = (XBindingMatch)bindingRegisters[ mode ].get( i );
if ( match.matchMode == mode ) {
if ( match.matches( mode, comp, className, type )) {
try {
XDataBinding db = (XDataBinding)Class.forName( match.className.trim()).newInstance();
db.setup( currentProject, comp, bindingConfig, instanceConfig );
return db;
}
catch ( Exception ex )
{
ex.printStackTrace();
}
}
}
}
}
return null;
}
/**
* 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 void updateConfig()
{
changeCounter++;
}
/**
* Read the component registry. The format is described in the components.xsd
* schema.
*/
protected void read()
{
bindingRegisters = new ArrayList[ NUM_MATCH_MODES ];
for ( int i = 0; i < NUM_MATCH_MODES; i++ )
bindingRegisters[ i ] = new ArrayList();
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( "XRegisteredDataBindingFactory reading config file: " + configFile );
try {
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( "XRegisteredDataBindingFactory reading config file: " + configFileURL.toString() );
try {
// String file = configFileURL.getFile();
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 registrationNodes = regRoot.getChildren();
int numElements = registrationNodes.size();
for ( int i = 0; i < numElements; i++ ) {
XmlElement regElement = (XmlElement)registrationNodes.elementAt( i );
String tag = regElement.getName();
int mode = ( tag.equals( "InspectorBindings" ) ? INSPECTOR_MATCH :
tag.equals( "ClassBindings" ) ? CLASS_MATCH :
tag.equals( "InterfaceBindings" ) ? INTERFACE_MATCH :
INSTANCE_MATCH
);
addBindingTypes( regElement, mode );
}
}
catch ( Exception ex ) {
if ( BuildProperties.DEBUG )
DebugLogger.logError( "Unable to setup the component registry" );
}
}
/**
* Read the component registry. The format is described in the components.xsd
* schema.
* @param regRoot the name by which the configuration file is referenced
* @param mode the match mode for the new binding types
*/
protected void addBindingTypes( XmlElement regRoot, int mode )
{
try {
Vector registrationNodes = regRoot.getChildren();
int numElements = registrationNodes.size();
for ( int i = 0; i < numElements; i++ ) {
XmlElement regElement = (XmlElement)registrationNodes.elementAt( i );
XBindingMatch registration = new XBindingMatch();
registration.matchMode = mode;
registration.target = regElement.getAttribute( "target" );
registration.className = regElement.getAttribute( "class" );
registration.type = regElement.getAttribute( "type" );
/** @todo store the extra attributes */
bindingRegisters[ mode ].add( registration );
}
}
catch ( Exception ex ) {
if ( BuildProperties.DEBUG )
DebugLogger.logError( "Unable to setup the component registry" );
}
}
/**
* Check that all the registered components are loaded
*/
public void checkRegistration()
{
if ( localChangeCounter != changeCounter ) {
read();
localChangeCounter = changeCounter;
}
}
class XBindingMatch
{
public int matchMode;
public String target;
public String className;
public String type;
private Class targetClass;
public boolean matches( int mode, Object comp, String className, String instanceType )
{
if (( instanceType != null ) && ( instanceType.length() > 0 ) && !instanceType.equals( type ))
return false;
if ( mode == CLASS_MATCH ) {
if ( target.equals( className ))
return true;
int pos = target.indexOf( '*' );
if ( pos > 0 ) {
if ( className.startsWith( target.substring( 0, pos )) && className.endsWith( target.substring( pos + 1 )))
return true;
}
}
else if ( mode == INTERFACE_MATCH ) {
try {
if ( targetClass == null )
targetClass = Class.forName( target.trim());
Class sc = comp.getClass();
while ( sc != Object.class ) {
Class[] interfaces = sc.getInterfaces();
for ( int i = 0; i < interfaces.length; i++ ) {
if ( interfaces[ i ] == targetClass )
return true;
}
sc = sc.getSuperclass();
}
}
catch ( ClassNotFoundException ex )
{
ex.printStackTrace();
}
}
else if ( mode == INSTANCE_MATCH ) {
try {
if ( targetClass == null )
targetClass = Class.forName( target.trim());
Class sc = comp.getClass();
while ( sc != Object.class ) {
if ( sc == targetClass )
return true;
else
sc = sc.getSuperclass();
}
}
catch ( ClassNotFoundException ex )
{
ex.printStackTrace();
}
}
else if ( mode == INSPECTOR_MATCH )
return false;
return false;
}
}
}