/*
* Rapid Beans Framework: TypeProperty.java
*
* Copyright (C) 2009 Martin Bluemel
*
* Creation Date: 11/26/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.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.rapidbeans.core.exception.ModelValidationException;
import org.rapidbeans.core.exception.RapidBeansRuntimeException;
import org.rapidbeans.core.exception.TypeNotFoundException;
import org.rapidbeans.core.util.StringHelper;
import org.rapidbeans.core.util.XmlNode;
/**
* the parent class for every property type.
*
* @author Martin Bluemel
*/
public abstract class TypeProperty {
/**
* @return the property type enumeration
*/
public abstract PropertyType getProptype();
/**
* @return the property's value type
*/
public abstract Class<?> getValuetype();
/**
* the property's name.
*/
private String propName = null;
/**
* @return the property's name.
*/
public final String getPropName() {
return this.propName;
}
/**
* the property's implementing class.
*/
private Class<?> propClass = null;
/**
* @return the property's class.
*/
public final Class<?> getPropClass() {
return this.propClass;
}
/**
* the parent bean type.
*/
private TypeRapidBean parentBeanType;
/**
* @return the parent bean type
*/
protected TypeRapidBean getParentBeanType() {
return parentBeanType;
}
/**
* the property's default value.
*/
private Object defaultValue = null;
/**
* @return the property's default value.
*/
public final Object getDefaultValue() {
return this.defaultValue;
}
/**
* internal setter for the default value.
*
* @param argValue
* the default value instance
*/
protected final void setDefaultValue(final Object argValue) {
this.defaultValue = argValue;
}
/**
* flag for key (candidate, component) attribute.
*/
private boolean key = false;
/**
* @return if the property is a key candidate
*/
public boolean isKeyCandidate() {
return this.key;
}
/**
* Indicates a read only property. Use for constants or derived properties
* (properties that may be only set by the program.
*/
private boolean readonly = false;
/**
* @return if this property may be written or not
*/
public boolean getReadonly() {
return this.readonly;
}
/**
* flag for mandatory attribute.
*/
private boolean mandatory = false;
/**
* @return if the property must be defined (= may not be null) or not
*/
public boolean getMandatory() {
return this.mandatory;
}
/**
* the final property flag.
*/
private boolean isFinal = false;
/**
* @return if this property is final or not.
*/
public boolean getFinal() {
return this.isFinal;
}
/**
* the transient flag.
*/
private boolean transientProp = false;
/**
* @return the transient flag.
*/
public final boolean isTransient() {
return this.transientProp;
}
/**
* The dependencies
*/
private List<TypeProperty> dependentFromProps = new ArrayList<TypeProperty>();
/**
* @return the dependentFromProps
*/
public List<TypeProperty> getDependentFromProps() {
return dependentFromProps;
}
public boolean equals(final Object other) {
if (!(other instanceof TypeProperty)) {
return false;
}
final TypeProperty otherPropType = (TypeProperty) other;
final String thisSignature = this.parentBeanType.getName() + "." + this.propName;
final String otherSignature = otherPropType.parentBeanType.getName() + "." + otherPropType.propName;
return thisSignature.equals(otherSignature);
}
public int hashCode() {
return (this.parentBeanType.getName() + "." + this.propName).hashCode();
}
private Map<String, TypeRapidBean> xmlBindXmlToType = null;
public TypeRapidBean mapXmlElementToType(final String xmlElement) {
if (this.xmlBindXmlToType != null) {
return this.xmlBindXmlToType.get(xmlElement);
}
return null;
}
private Map<TypeRapidBean, String> xmlBindTypeToXml = null;
public String mapTypeToXmlElement(final TypeRapidBean type) {
if (this.xmlBindTypeToXml != null) {
return this.xmlBindTypeToXml.get(type);
}
return null;
}
/**
* Define attribute or element binding.
*/
private PropertyXmlBindingType xmlBindingType = PropertyXmlBindingType.attribute;
/**
* @return the xmlBindingType
*/
public PropertyXmlBindingType getXmlBindingType() {
return this.xmlBindingType;
}
private String description = null;
/**
* @return the type description
*/
public String getDescription() {
return this.description;
}
/**
* creates a property type instance out of it XML description.
*
* @param propertyNode
* the XML DOM node with the property's type description
* @param propertyNode
* the XML DOM node with the property's XML binding
* @param bizBeanType
* the parent bizBeanType
*
* @return the new property type instance
*/
@SuppressWarnings("unchecked")
public static TypeProperty createInstance(final XmlNode propertyNode, final XmlNode propertyNodeXmlBinding,
final TypeRapidBean bizBeanType) {
TypeProperty type = null;
String propTypename = propertyNode.getAttributeValue("@type", "string");
switch (propTypename.charAt(0)) {
case 'a':
if (propTypename.equals("association") || propTypename.equals("associationend")) {
propTypename = "collection";
}
break;
case 'b':
// we need "bool" here because of enum PropertyType.
// boolean is a keyword.
if (propTypename.equals("boolean")) {
propTypename = "bool";
}
break;
default:
break;
}
final PropertyType proptype = PropertyType.valueOf(propTypename);
String classname = null;
if (proptype == PropertyType.bool) {
classname = "org.rapidbeans.core.type.TypePropertyBoolean";
} else {
classname = "org.rapidbeans.core.type.TypeProperty" + StringHelper.upperFirstCharacter(proptype.name());
}
Class<?> clazz;
try {
clazz = Class.forName(classname);
} catch (ClassNotFoundException e) {
throw new RapidBeansRuntimeException("class " + classname + " not found", e);
}
try {
Constructor<TypeProperty> constr = (Constructor<TypeProperty>) clazz
.getConstructor(BBPROPTYPE_CONSTRUCTOR_TYPES);
Object[] oa = { new XmlNode[] { propertyNode, propertyNodeXmlBinding }, bizBeanType };
type = (TypeProperty) constr.newInstance(oa);
} catch (NoSuchMethodException e) {
throw new RapidBeansRuntimeException("failed to initialize BBProp of Class \"" + clazz.getName() + "\".\n"
+ "Constructor (XmlNode) not found.");
} catch (IllegalAccessException e) {
throw new RapidBeansRuntimeException("failed to initialize BBProp of Class \"" + clazz.getName() + "\".\n"
+ "IllegalAccessException while calling the constructor.");
} catch (InstantiationException e) {
throw new RapidBeansRuntimeException("failed to initialize BBProp of Class \"" + clazz.getName() + "\".\n"
+ "InstatiationException while calling the constructor.");
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof ExceptionInInitializerError) {
t = ((ExceptionInInitializerError) t).getException();
}
if (t instanceof RuntimeException) {
throw ((RuntimeException) t);
}
throw new RapidBeansRuntimeException("failed to initialize BBProp of Class \"" + clazz.getName() + "\".\n"
+ "InvocationTargetException caused by " + t.getClass().getName() + " \"" + t.getMessage() + "\""
+ " while calling the constructor.");
}
return type;
}
/**
* constant for constructor arguments of class TypeProperty.
*/
private static final Class<?>[] BBPROPTYPE_CONSTRUCTOR_TYPES = { XmlNode[].class, TypeRapidBean.class };
/**
* constructor.
*
* @param typeId
* the property's type id { string, choice, ... }
* @param propertyNodes
* the XML nodes describing the property.
* @param parentType
* the parent bean type
*/
protected TypeProperty(final String typeId, final XmlNode[] propertyNodes, final TypeRapidBean parentType) {
final XmlNode propertyNode = propertyNodes[0];
this.propName = propertyNode.getAttributeValue("@name");
if (this.propName == null) {
throw new RapidBeansRuntimeException("Property defined without name");
}
this.parentBeanType = parentType;
String s = propertyNode.getAttributeValue("@depends");
if (s != null) {
for (final String sdep : StringHelper.split(s, ",")) {
this.dependentFromProps.add(this.getParentBeanType().getPropertyType(sdep));
}
if (this.dependentFromProps.size() > 0) {
this.transientProp = true;
this.readonly = true;
}
}
// make the first character upper case
String classname;
s = propertyNode.getAttributeValue("@propclass");
if (getDependentFromProps().size() > 0) {
classname = "org.rapidbeans.core.basic.PropertyReflectImpl";
} else if (s == null) {
classname = "org.rapidbeans.core.basic.Property" + typeId;
} else {
classname = s;
if (!s.contains(".") && this.getParentBeanType() != null) {
final String packageName = this.getParentBeanType().getPackageName();
if (packageName != null) {
classname = packageName + "." + s;
}
}
}
try {
this.propClass = Class.forName(classname);
} catch (ClassNotFoundException e) {
throw new RapidBeansRuntimeException("class \"" + classname + "\"not found");
}
s = propertyNode.getAttributeValue("@key");
if (s != null) {
this.key = Boolean.parseBoolean(s);
if (this.key) {
this.mandatory = true;
}
}
s = propertyNode.getAttributeValue("@readonly");
if (s != null) {
this.readonly = Boolean.parseBoolean(s);
}
s = propertyNode.getAttributeValue("@mandatory");
if (s != null) {
this.mandatory = Boolean.parseBoolean(s);
}
s = propertyNode.getAttributeValue("@final");
if (s != null) {
this.isFinal = Boolean.parseBoolean(s);
}
s = propertyNode.getAttributeValue("@transient");
if (s != null) {
this.transientProp = Boolean.parseBoolean(s);
}
this.description = RapidBeansType.readDescription(propertyNode);
if (propertyNodes.length > 1 && propertyNodes[1] != null) {
this.evalXmlBinding(propertyNodes[1]);
}
}
/**
* Evaluate the property type's XML binding description.
*
* @param xmlBindingDescr
* the XML Node with the binding description
*/
private void evalXmlBinding(final XmlNode propertyNodeXmlBinding) {
final String s = propertyNodeXmlBinding.getAttributeValue("@bindingtype");
if (s != null) {
this.xmlBindingType = PropertyXmlBindingType.valueOf(s);
}
final Collection<XmlNode> subnodes = propertyNodeXmlBinding.getSubnodes("beantype");
if (subnodes == null) {
return;
}
for (final XmlNode currentNode : subnodes) {
final String typename = currentNode.getAttributeValue("@name");
if (typename == null || typename.length() == 0) {
throw new RapidBeansRuntimeException("wrong XML binding, beantype name missing");
}
TypeRapidBean type = null;
try {
type = TypeRapidBean.forName(typename);
} catch (TypeNotFoundException e) {
throw new RapidBeansRuntimeException("Invalid XML binding, beantype name \"" + typename + "\" unknown",
e);
}
final String xmlelement = currentNode.getAttributeValue("@xmlelement");
if (this.xmlBindTypeToXml == null) {
this.xmlBindTypeToXml = new HashMap<TypeRapidBean, String>();
this.xmlBindXmlToType = new HashMap<String, TypeRapidBean>();
}
this.xmlBindTypeToXml.put(type, xmlelement);
this.xmlBindXmlToType.put(xmlelement, type);
}
}
protected void evalXmlBinding(final TypeRapidBean beantype, final XmlNode propNode) {
}
/**
* default parsing method for default values.
*
* @param xmlNode
* the XML DOM node with the property type description.
*/
protected void parseDefaultValue(final XmlNode xmlNode) {
final String defaultValueString = xmlNode.getAttributeValue("@default");
if (defaultValueString != null) {
this.setDefaultValue(defaultValueString);
}
}
protected void throwModelValidationException(final String message) {
String completeMessage;
if (this.getParentBeanType() != null) {
completeMessage = "Error in specification of bean type" + "\"" + this.getParentBeanType().getName()
+ "\", property " + "\"" + this.getPropName() + ":\n" + message;
} else {
completeMessage = "Error in specification of property " + "\"" + this.getPropName() + ":\n" + message;
}
throw new ModelValidationException(completeMessage);
}
}