/*
* Rapid Beans Framework: RapidBeansTypeLoader.java
*
* Copyright (C) 2009 Martin Bluemel
*
* Creation Date: 11/04/2005
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation;
* either version 3 of the License, or (at your option) any later version.
* This program 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 Lesser General Public License for more details.
* You should have received a copies of the GNU Lesser General Public License and the
* GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
package org.rapidbeans.core.type;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import org.rapidbeans.core.exception.RapidBeansRuntimeException;
import org.rapidbeans.core.exception.TypeNotFoundException;
import org.rapidbeans.core.util.StringHelper;
/**
* the type loader.
*
* @author Martin Bluemel
*/
public final class RapidBeansTypeLoader {
/**
* the single type loader instance.
*/
private static RapidBeansTypeLoader singleInstance = null;
/**
* the factory method for the singleton.
*
* @return the one and only TypeLoader instance.
*/
public static RapidBeansTypeLoader getInstance() {
if (singleInstance == null) {
singleInstance = new RapidBeansTypeLoader();
}
return singleInstance;
}
/**
* the type map: maps a type name to a RapidBeans type.
*/
private HashMap<String, RapidBeansType> typeMap = new HashMap<String, RapidBeansType>();
/**
* the XML root element binding map: maps an XML root element name to a
* RapidBeans type.
*/
private HashMap<String, TypeRapidBean> xmlRootElementMap = new HashMap<String, TypeRapidBean>();
/**
* Find an XML root element binding.
*
* @param elementName
* the XML root element name.
*
* @return the binding or null if no binding is registered
*/
public TypeRapidBean getXmlRootElementBinding(final String elementName) {
return this.xmlRootElementMap.get(elementName);
}
/**
* Add a new XML root element binding.
*
* @param elementName
* the XML element name
* @param typeName
* the RapidBean type name
*/
public void addXmlRootElementBinding(final String elementName, final String typeName) {
this.addXmlRootElementBinding(elementName, typeName, false);
}
/**
* Add a new XML root element binding.
*
* @param elementName
* the XML element name
* @param typeName
* the RapidBean type name
* @param override
* if overriding the bind is allowed or not Handle overriding
* with care
*/
public void addXmlRootElementBinding(final String elementName, final String typeName, final boolean override) {
if ((!override) && this.xmlRootElementMap.get(elementName) != null) {
throw new RapidBeansRuntimeException("An XML root element binding" + " for XML element name \""
+ elementName + "\" has already been defined.");
}
final TypeRapidBean type = TypeRapidBean.forName(typeName);
this.xmlRootElementMap.put(elementName, type);
}
/**
* default constructor only to be used privately.
*/
private RapidBeansTypeLoader() {
}
/**
* @param typename
* the type name.
* @param suffix
* the suffix of the declaration file
*
* @return the stream
*/
protected static InputStream findDeclaration(final String typename, final String suffix) {
InputStream is = null;
String typeInfoDescrFileName = null;
try {
// this looks a bit awkward but seems to be a way to load the
// description
// also in an Applet environment
final Class<?> typeclass = Class.forName(typename);
if (suffix == null) {
typeInfoDescrFileName = StringHelper.splitLast(typename, ".") + ".xml";
} else {
typeInfoDescrFileName = StringHelper.splitLast(typename, ".") + suffix + ".xml";
}
is = typeclass.getResourceAsStream(typeInfoDescrFileName);
} catch (ClassNotFoundException e) {
// for generic types we take the straight forward method which
// unfortunately
// won't run with Applets
if (suffix == null) {
typeInfoDescrFileName = typename.replace('.', '/') + ".xml";
} else {
typeInfoDescrFileName = typename.replace('.', '/') + suffix + ".xml";
}
is = ClassLoader.getSystemResourceAsStream(typeInfoDescrFileName);
}
return is;
}
/**
* registers a Rapid Beans type.
*
* @param typename
* the type's name
* @param type
* the type to register
*/
public void registerType(final RapidBeansType type) {
if (this.typeMap.get(type.getName()) != null) {
throw new RapidBeansRuntimeException("bean type \"" + type.getName() + "\" already registered");
}
this.typeMap.put(type.getName(), type);
}
/**
* registers an Easybiz type if the same instance is not already registered.
*
* @param typename
* the type's name
* @param type
* the type to register
*/
public void registerTypeIfNotRegistered(final String typename, final RapidBeansType type) {
if (this.typeMap.get(typename) != null) {
return;
}
this.typeMap.put(typename, type);
}
/**
* removes a Rapid Beans type registration. Caution: for test purposes
* only!!!
*
* @param typename
* the type's name
*/
public void unregisterType(final String typename) {
if (this.typeMap.get(typename) == null) {
throw new RapidBeansRuntimeException("bean type \"" + typename + "\" not registered");
}
this.typeMap.remove(typename);
}
/**
* looks up an Rapid Beans type.
*
* @param typename
* the type's name
*
* @return the registered type
*/
public RapidBeansType lookupType(final String typename) {
return this.typeMap.get(typename);
}
/**
* load a type specified by it's name.
* <p>
* <b>throws TypeNotFoundException:</b><br/>
* if the specified RapidEnum type does not exist<br/>
* </p>
*
* @param typeclass
* the type's class { TypeRapidEnum, TypeRapidQuantity,
* TypeRapidBean }
* @param typename
* the types full qualified name
*
* @return the loaded type
*/
protected RapidBeansType loadType(final Class<?> typeclass, final String typename) {
if (typeclass != TypeRapidEnum.class && typeclass != TypeRapidQuantity.class
&& typeclass != TypeRapidBean.class) {
throw new RapidBeansRuntimeException("RapidBeansType class \"" + typeclass.getName() + "\" not supported.");
}
RapidBeansType type = this.typeMap.get(typename);
Class<?> clazz = null;
if (type == null) {
try {
// the typeinfo implicitly is registered while the class is
// loaded
// (static initializer of all concrete enum classes)
clazz = Class.forName(typename);
// if we got no ClassNotFoundException
// we can get the typeinfo now
type = this.typeMap.get(typename);
} catch (ClassNotFoundException e) {
// do nothing (type stays null)
type = null;
}
if (type == null) {
if (typeclass == TypeRapidBean.class) {
type = new TypeRapidBean(clazz, RapidBeansType.loadDescription(typename),
RapidBeansType.loadDescriptionXmlBinding(typename), true);
} else if (typeclass == TypeRapidEnum.class) {
type = new TypeRapidEnum(typename);
this.registerType(type);
} else if (typeclass == TypeRapidQuantity.class) {
type = TypeRapidQuantity.createInstance(RapidBeansType.loadDescription(typename));
}
}
if (type == null) {
if (typeclass == TypeRapidEnum.class) {
throw new TypeNotFoundException("Enum type \"" + typename + "\"");
} else if (typeclass == TypeRapidQuantity.class) {
throw new TypeNotFoundException("Quantity type \"" + typename + "\"");
} else if (typeclass == TypeRapidBean.class) {
throw new TypeNotFoundException("Bean type \"" + typename + "\"");
}
}
} // if (type == null)
return type;
}
/**
* CAUTION: this methods clears the single typeMap. Just use this for unit
* tests or if you exactly know what you're doing.
*/
public static void typeMapClear() {
singleInstance.typeMap.clear();
}
/**
* CAUTION: this methods clears all generic bean types out of the single
* type map. Just use this for unit tests or if you exactly know what you're
* doing.
*/
public static void typeMapClearBeanGeneric() {
RapidBeansType bbType = null;
ArrayList<String> sa = new ArrayList<String>();
if (singleInstance != null) {
for (Object o : singleInstance.typeMap.values()) {
bbType = (RapidBeansType) o;
if (bbType.getImplementingClass() == null) {
sa.add(bbType.getName());
}
}
}
for (String s : sa) {
singleInstance.unregisterType(s);
}
}
/**
* CAUTION: this methods clears all root element bindings. Just use this for
* unit tests or if you exactly know what you're doing.
*/
public static void typeMapClearRootElementBindingsGeneric() {
final ArrayList<String> elementNames = new ArrayList<String>();
if (singleInstance != null) {
for (final Object o : singleInstance.xmlRootElementMap.keySet()) {
final String elementName = (String) o;
final TypeRapidBean beantype = singleInstance.xmlRootElementMap.get(elementName);
if (beantype.getImplementingClass() == null) {
elementNames.add(elementName);
}
}
for (final String elementName : elementNames) {
singleInstance.xmlRootElementMap.remove(elementName);
}
}
}
}