/*
* Rapid Beans Framework: TypeRapidBean.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.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import org.rapidbeans.core.basic.IdGenerator;
import org.rapidbeans.core.basic.IdType;
import org.rapidbeans.core.common.RapidBeansLocale;
import org.rapidbeans.core.exception.ModelValidationException;
import org.rapidbeans.core.exception.RapidBeansRuntimeException;
import org.rapidbeans.core.util.ClassHelper;
import org.rapidbeans.core.util.XmlNode;
/**
* the type class with meta information of Rapid Beans.
*
* @author Martin Bluemel
*/
public final class TypeRapidBean extends RapidBeansType {
/**
* abstract type flag.
*/
private boolean isAbstract = false;
/**
* @return if this type is abstract or not.
*/
public boolean getAbstract() {
return this.isAbstract;
}
/**
* the final type flag.
*/
private boolean isFinal = false;
/**
* @return if this type is final or not.
*/
public boolean getFinal() {
return this.isFinal;
}
/**
* the collection of property types.
*/
private List<TypeProperty> propertyTypes = new ArrayList<TypeProperty>();
/**
* @return the collection of the bean's property types.
*/
public List<TypeProperty> getPropertyTypes() {
return this.propertyTypes;
}
/**
* @return a list with all the bean's collection properties
*/
public List<TypePropertyCollection> getColPropertyTypes() {
final List<TypePropertyCollection> colproptypelist = new ArrayList<TypePropertyCollection>();
for (final TypeProperty proptype : this.propertyTypes) {
if (ClassHelper.classOf(TypePropertyCollection.class, proptype.getClass())) {
colproptypelist.add((TypePropertyCollection) proptype);
}
}
return colproptypelist;
}
/**
* the hash map for property types.
*/
private HashMap<String, TypeProperty> propTypeMap = new HashMap<String, TypeProperty>();
/**
* retrieve a property type by a property name.
*
* @param propname
* the property name
*
* @return the property type
*/
public TypeProperty getPropertyType(final String propname) {
final TypeProperty proptype = this.propTypeMap.get(propname);
if (proptype != null) {
return this.propTypeMap.get(propname);
} else if (this.getSupertype() != null) {
return this.getSupertype().getPropertyType(propname);
} else {
return null;
}
}
/**
* Find's a bean type instance out of it's name and loads it if it's not
* already loaded.
*
* @param typename
* the Bizbean's type name
*
* @return the type instance if found
*/
public static TypeRapidBean forName(final String typename) {
return (TypeRapidBean) RapidBeansTypeLoader.getInstance().loadType(TypeRapidBean.class, typename);
}
/**
* the type's super type.
*/
private TypeRapidBean supertype = null;
/**
* @return the type's super type
*/
public TypeRapidBean getSupertype() {
return this.supertype;
}
/**
* the type's sub types.
*/
private Collection<TypeRapidBean> subtypes = new ArrayList<TypeRapidBean>();
/**
* @return the type's super type
*/
public Collection<TypeRapidBean> getSubtypes() {
return this.subtypes;
}
/**
* @param subtype
* the subtype to add
*/
public void addSubtype(final TypeRapidBean subtype) {
this.subtypes.add(subtype);
}
/**
* the bean's id type.
*/
private IdType idtype = null;
/**
* @return the bean's idtype
* @return
*/
public IdType getIdtype() {
return this.idtype;
}
/**
* the depth of parents in the hierarchy.
*/
private int idtypeParentScopeDepth = 0;
/**
* the depth of parents in the hierarchy is only valid for idtype =
* "keypropswithparentscope".
*
* @return the depth of parents in the hierarchy.
*/
public int getIdtypeParentScopeDepth() {
return this.idtypeParentScopeDepth;
}
/**
* the Id Generator.
*/
private IdGenerator idGenerator = null;
/**
* @return the ID genrator instance
*/
public IdGenerator getIdGenerator() {
return this.idGenerator;
}
/**
* inject the id generator.
*
* @param generator
* the generator instance
*/
public void setIdGenerator(final IdGenerator generator) {
this.idGenerator = generator;
}
/**
* Mapping of this RapidBean type to an XML element. Specifies the name of
* the root element of an XML document if a bean of this type is root of a
* document.
*/
private String xmlRootElement = null;
/**
* Maps this RapidBean type to an XML element. This mapping specifies the
* name of the root element of an XML document if this bean is root of a
* document.
*
* @return the XML element name of this RapidBean type
*/
public String getXmlRootElement() {
if (this.xmlRootElement != null) {
return this.xmlRootElement;
}
TypeRapidBean type = this;
while (type != null) {
if (type.xmlRootElement != null) {
return type.xmlRootElement;
}
type = type.getSupertype();
}
return null;
}
/**
* Mapping of XML element to a collection property type
*/
private Map<String, TypeProperty> xmlElements = null;
/**
* @return the xmlElements
*/
public Map<String, TypeProperty> getXmlElements() {
return this.xmlElements;
}
/**
* Mapping of XML element to a bean type
*/
private Map<String, TypeRapidBean> xmlElementsTypeMap = null;
/**
* @return the xmlElements type map
*/
public Map<String, TypeRapidBean> getXmlElementsTypeMap() {
return this.xmlElementsTypeMap;
}
/**
* Maps a given XML element name to a name of a property of this type if the
* appropriate XML binding is defined.
*
* @param xmlRootElement
* the XML element name
*
* @return the mapped property name if defined, otherwise "null"
*/
public String mapXmlElementToPropName(final String xmlElement) {
String propName = null;
if (this.xmlElements != null) {
final TypeProperty propType = this.xmlElements.get(xmlElement);
if (propType != null) {
propName = propType.getPropName();
}
}
return propName;
}
/**
* Constructor.
*
* @param clazz
* the bean's class
*/
protected TypeRapidBean(final Class<?> clazz) {
this(clazz, loadDescription(clazz.getName()), loadDescriptionXmlBinding(clazz.getName()), false);
}
/**
* Constructor.
*
* @param clazz
* the bean's class
* @param typeDescr
* the RapidBean type description's top level node
* @param xmlBindingDescr
* the XML binding description's top level node
* @param register
* specifies if the type should be registered
*/
public TypeRapidBean(final Class<?> clazz, final XmlNode typeDescr, final XmlNode xmlBindingDescr,
final boolean register) {
try {
ThreadLocalBeanInitDepth.increment(this);
// System.out.println("@@@ INIT["
// + ThreadLocalBeanInitDepth.getDepth()
// + "]: " + typeDescr.getAttributeValue("@name"));
if (clazz == null) {
// implementingClass stays null
this.setName(typeDescr.getAttributeValue("@name"));
} else {
this.setName(clazz.getName());
this.setImplementingClass(clazz);
}
if (register) {
if (clazz == null) {
RapidBeansTypeLoader.getInstance().registerTypeIfNotRegistered(this.getName(), this);
} else {
RapidBeansTypeLoader.getInstance().registerTypeIfNotRegistered(clazz.getName(), this);
}
}
this.isAbstract = Boolean.parseBoolean(typeDescr.getAttributeValue("@abstract", "false"));
this.isFinal = Boolean.parseBoolean(typeDescr.getAttributeValue("@final", "false"));
final String superTypeName = typeDescr.getAttributeValue("@extends");
if (superTypeName != null) {
this.supertype = TypeRapidBean.forName(superTypeName);
}
if ((this.supertype != null) && (typeDescr.getAttributeValue("@idtype") == null)) {
this.idtype = this.supertype.getIdtype();
} else {
this.idtype = IdType.valueOf(typeDescr.getAttributeValue("@idtype", "transientid"));
}
if (this.idtype == IdType.keypropswithparentscope) {
final String s = typeDescr.getAttributeValue("@idtypeparentscopedepth", "0");
this.idtypeParentScopeDepth = Integer.parseInt(s);
}
final HashMap<String, TypeProperty> propMap = new HashMap<String, TypeProperty>();
if (this.supertype != null) {
this.supertype = TypeRapidBean.forName(superTypeName);
for (TypeProperty superPropType : this.supertype.getPropertyTypes()) {
this.propertyTypes.add(superPropType);
this.propTypeMap.put(superTypeName, superPropType);
propMap.put(superPropType.getPropName(), superPropType);
}
this.supertype.addSubtype(this);
}
for (XmlNode propertyNode : typeDescr.getSubnodes("property")) {
XmlNode propertyNodeXmlBinding = null;
if (xmlBindingDescr != null) {
propertyNodeXmlBinding = retrievePropertyNodeXmlBinding(propertyNode, xmlBindingDescr);
} else {
for (XmlNode xmlBindingNode : typeDescr.getSubnodes("xmlbinding")) {
propertyNodeXmlBinding = retrievePropertyNodeXmlBinding(propertyNode, xmlBindingNode);
}
}
final TypeProperty propertyType = TypeProperty.createInstance(propertyNode, propertyNodeXmlBinding,
this);
final TypeProperty superPropType = propMap.get(propertyType.getPropName());
if (superPropType != null) {
if (this.isFinal || propertyType.getFinal()) {
throw new RapidBeansRuntimeException("Error while initializing" + " bean type \""
+ this.getName() + "\": final property \"" + propertyType.getPropName()
+ "\" may not be overriden.");
}
this.propertyTypes.set(getIndexInPropertyTypes(superPropType), propertyType);
} else {
this.propertyTypes.add(propertyType);
}
this.propTypeMap.put(propertyType.getPropName(), propertyType);
}
if (ThreadLocalBeanInitDepth.getDepth() == 1) {
for (final TypeRapidBean typeInited : ThreadLocalBeanInitDepth.getTypesInitialilized()) {
for (final TypePropertyCollection colPropType : typeInited.getColPropertyTypes()) {
if (colPropType.getInverse() != null
&& colPropType.getTargetType().getPropertyType(colPropType.getInverse()) == null) {
final String msg = "Problem with property \"" + typeInited.getName() + "::"
+ colPropType.getPropName() + "\":\n" + "inverse property \""
+ colPropType.getTargetType().getName() + "::" + colPropType.getInverse()
+ "\" is not defined.";
throw new ModelValidationException(msg);
}
}
}
}
if (this.supertype != null
&& (this.idtype == IdType.keyprops || this.idtype == IdType.keypropswithparentscope)) {
checkKeypropsSorting();
}
setDescription(RapidBeansType.readDescription(typeDescr));
// evaluate XML binding description
if (xmlBindingDescr != null) {
evalXmlBinding(xmlBindingDescr);
} else {
for (XmlNode xmlBindingNode : typeDescr.getSubnodes("xmlbinding")) {
evalXmlBinding(xmlBindingNode);
}
}
} finally {
ThreadLocalBeanInitDepth.decrement();
}
}
private int getIndexInPropertyTypes(final TypeProperty propType) {
int i = 0;
for (final TypeProperty currentPropType : this.propertyTypes) {
if (propType.getPropName().equals(currentPropType.getPropName())) {
return i;
}
i++;
}
return -1;
}
private XmlNode retrievePropertyNodeXmlBinding(final XmlNode propertyNode, final XmlNode xmlBindingDescr) {
final String propName = propertyNode.getAttributeValue("@name");
XmlNode propertyNodeXmlBinding = null;
for (XmlNode currentNode : xmlBindingDescr.getSubnodes("property")) {
if (currentNode.getAttributeValue("@name").equals(propName)) {
propertyNodeXmlBinding = currentNode;
break;
}
}
return propertyNodeXmlBinding;
}
/**
* Evaluate the type's XML binding description.
*
* @param xmlBindingDescr
* the XML Node with the binding description
*/
private void evalXmlBinding(final XmlNode xmlBindingDescr) {
final List<TypeRapidBean> supertypes = new ArrayList<TypeRapidBean>();
TypeRapidBean supertype = this.getSupertype();
while (supertype != null) {
supertypes.add(supertype);
supertype = supertype.getSupertype();
}
final int size = supertypes.size();
for (int i = size - 1; i > 0; i--) {
final TypeRapidBean st = supertypes.get(i);
if (st.getXmlRootElement() != null) {
this.xmlRootElement = st.getXmlRootElement();
}
if (st.getXmlElements() != null) {
for (final String typename : st.getXmlElements().keySet()) {
// lazy initialization
if (this.xmlElements == null) {
this.xmlElements = new HashMap<String, TypeProperty>();
}
this.xmlElements.put(typename, st.getXmlElements().get(typename));
}
}
}
if (xmlBindingDescr.getAttributeValue("@xmlrootelement") != null) {
this.xmlRootElement = xmlBindingDescr.getAttributeValue("@xmlrootelement");
}
if (this.xmlRootElement != null && this.xmlRootElement.length() > 0) {
final RapidBeansTypeLoader typeLoader = RapidBeansTypeLoader.getInstance();
if (typeLoader.getXmlRootElementBinding(this.xmlRootElement) == null) {
typeLoader.addXmlRootElementBinding(this.xmlRootElement, this.getName());
} else {
if (!typeLoader.getXmlRootElementBinding(this.xmlRootElement).getName().equals(this.getName())) {
throw new RapidBeansRuntimeException("An XML root element binding" + " for XML element name "
+ this.xmlRootElement + " has already been defined differently\n" + "Current definition: "
+ typeLoader.getXmlRootElementBinding(this.xmlRootElement).getName());
}
}
}
for (final XmlNode propNode : xmlBindingDescr.getSubnodes("property")) {
final String propName = propNode.getAttributeValue("@name");
if (propName == null || propName.length() == 0) {
throw new RapidBeansRuntimeException("Invalid XML Binding for type \"" + this.getName() + "\":\n"
+ "\"property\" element without or with empty attribute \"name\"");
}
TypeProperty proptype = this.propTypeMap.get(propName);
if (proptype == null) {
TypeRapidBean st = this.getSupertype();
while (st != null && proptype == null) {
proptype = st.propTypeMap.get(propName);
st = st.getSupertype();
}
}
if (proptype == null) {
throw new RapidBeansRuntimeException("Invalid XML Binding for type \"" + this.getName() + "\":\n"
+ "property \"" + proptype + "\" does not exist.");
}
proptype.evalXmlBinding(this, propNode);
for (final XmlNode beantypeNode : propNode.getSubnodes("beantype")) {
final String mappedTypeName = beantypeNode.getAttributeValue("@name");
if (mappedTypeName == null || mappedTypeName.length() == 0) {
throw new RapidBeansRuntimeException("Invalid XML Binding for type \"" + this.getName() + "\":\n"
+ "\"beanype\" element without or with empty attribute \"xmlelement\"");
}
final String xmlPropElement = beantypeNode.getAttributeValue("@xmlelement");
if (xmlPropElement == null || xmlPropElement.length() == 0) {
throw new RapidBeansRuntimeException("Invalid XML Binding for type \"" + this.getName() + "\":\n"
+ "\"beanype\" element without or with empty attribute \"xmlelement\"");
}
// lazy initialization
if (this.xmlElements == null) {
this.xmlElements = new HashMap<String, TypeProperty>();
}
this.xmlElements.put(xmlPropElement, proptype);
if (this.xmlElementsTypeMap == null) {
this.xmlElementsTypeMap = new HashMap<String, TypeRapidBean>();
}
this.xmlElementsTypeMap.put(xmlPropElement, TypeRapidBean.forName(mappedTypeName));
}
}
}
/**
* factory method for the type instance.
*
* @param clazz
* the bean's class
*
* @return the new type instance
*/
public static TypeRapidBean createInstance(final Class<?> clazz) {
TypeRapidBean type = new TypeRapidBean(clazz, loadDescription(clazz.getName()),
loadDescriptionXmlBinding(clazz.getName()), true);
return type;
}
/**
* behaves like "instance of" but with bean types.
*
* @param supertype
* the same or super type.
* @param comptype
* the type to test if same or sub type
*
* @return if the type to test is same or sub type of the super type
*/
public static boolean isSameOrSubtype(final TypeRapidBean supertype, final TypeRapidBean comptype) {
boolean b = false;
TypeRapidBean typeToCompare = comptype;
while (!b && typeToCompare != null) {
if (typeToCompare == supertype) {
b = true;
}
typeToCompare = typeToCompare.getSupertype();
}
return b;
}
/**
* check and sort key properties together if necessary.
*/
private void checkKeypropsSorting() {
int size = this.propertyTypes.size();
int firstNonKeypropIndex = -1;
for (int i = 0; i < size; i++) {
if (firstNonKeypropIndex == -1) {
if (!this.propertyTypes.get(i).isKeyCandidate()) {
firstNonKeypropIndex = i;
}
} else {
if (this.propertyTypes.get(i).isKeyCandidate()) {
final TypeProperty swaptype = this.propertyTypes.get(i);
for (int j = i; j > firstNonKeypropIndex; j--) {
this.propertyTypes.set(j, this.propertyTypes.get(j - 1));
}
this.propertyTypes.set(firstNonKeypropIndex, swaptype);
firstNonKeypropIndex++;
}
}
}
}
/**
* Retrieve all concrete sub beantypes from the inheritance tree.
*
* @return a list of bean types
*/
public List<TypeRapidBean> getConcreteSubtypes() {
final List<TypeRapidBean> concreteSubtpyes = new ArrayList<TypeRapidBean>();
for (final TypeRapidBean subtype : this.getSubtypes()) {
subtype.collectConcreteSubtypes(concreteSubtpyes);
}
return concreteSubtpyes;
}
/**
* computes a localized String to present this type in a UI.
*
* @param locale
* the Locale
* @param plural
* determines if the plural is required
* @param defaultTypename
* if null the type's short name is returned
*
* @return the localized String for this Bean
*/
public String toStringGui(final RapidBeansLocale locale, final boolean plural, final String defaultTypename) {
String uistring = null;
if (locale != null) {
try {
String pattern = "bean." + getName().toLowerCase();
if (plural) {
pattern += ".plural";
}
uistring = locale.getStringGui(pattern);
} catch (MissingResourceException e) {
uistring = null;
}
}
if (uistring == null) {
if (defaultTypename == null) {
if (plural) {
uistring = getNameShort() + 's';
} else {
uistring = getNameShort();
}
} else {
uistring = defaultTypename;
}
}
return uistring;
}
/**
* Recursive collection of sub beantypes.
*
* @param concreteSubtpyes
* the list into which we collect
*/
private void collectConcreteSubtypes(List<TypeRapidBean> concreteSubtpyes) {
if (!this.getAbstract()) {
concreteSubtpyes.add(this);
}
for (final TypeRapidBean subtype : this.getSubtypes()) {
subtype.collectConcreteSubtypes(concreteSubtpyes);
}
}
/**
* @return if the bean type has a dependend key property
*/
public boolean hasDependendKeyProp() {
boolean hasDependendKeyProp = false;
if (getIdtype() == IdType.keyprops || getIdtype() == IdType.keypropswithparentscope) {
for (final TypeProperty proptype : this.getPropertyTypes()) {
if (proptype.getDependentFromProps().size() > 0) {
hasDependendKeyProp = true;
break;
}
}
}
return hasDependendKeyProp;
}
}