/* * Rapid Beans Framework: RapidBeanImplStrict.java * * Copyright (C) 2012 Martin Bluemel * * Creation Date: 06/20/2012 * * 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.basic; 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 org.rapidbeans.core.exception.RapidBeansRuntimeException; import org.rapidbeans.core.exception.ValidationException; import org.rapidbeans.core.exception.ValidationInstanceAssocTwiceException; import org.rapidbeans.core.type.TypeProperty; import org.rapidbeans.core.type.TypePropertyCollection; import org.rapidbeans.core.type.TypeRapidBean; import org.rapidbeans.core.util.ClassHelper; import org.rapidbeans.core.util.StringHelper; /** * The "classical" bean implementation of the RapidBeans framework. * * * one extra property instance per attribute + quite save against illegal * modifications * properties are absolutely immutable * setting properties to * illegal values requires extra effort to switch of validation + potentially * higher dynamics possible (morphic objects not yet implemented) - not so * memory friendly - harder to debug * * @author Martin Bluemel */ public abstract class RapidBeanImplStrict extends RapidBeanImplParent { /** * empty string array. */ private static final String[] EMPTY_STRING_ARRAY = new String[0]; /** * parameter types for constructor RapidBean(String). */ private static final Class<?>[] CONSTR_PARAMTYPES_STRING_ARRAY = { String[].class }; /** * Creates a new bean instance from a special type. * * @param typename * the name of the bean's type * * @return the new bean instance */ public static RapidBeanImplStrict createInstance(final String typename) { return createInstance(TypeRapidBean.forName(typename)); } /** * Creates a new bean instance from a special type. * * @param type * the bean's type * * @return the new bean instance */ @SuppressWarnings("unchecked") public static RapidBeanImplStrict createInstance(final TypeRapidBean type) { RapidBeanImplStrict bean = null; Class<?> clazz = type.getImplementingClass(); if (clazz == null) { bean = new GenericBean(type); } else { try { Constructor<RapidBeanImplStrict> constr = (Constructor<RapidBeanImplStrict>) clazz .getConstructor(CONSTR_PARAMTYPES_STRING_ARRAY); Object[] initargs = new Object[1]; initargs[0] = EMPTY_STRING_ARRAY; bean = (RapidBeanImplStrict) constr.newInstance(initargs); } catch (NoSuchMethodException e) { throw new RapidBeansRuntimeException(e); } catch (IllegalArgumentException e) { throw new RapidBeansRuntimeException(e); } catch (InstantiationException e) { throw new RapidBeansRuntimeException(e); } catch (IllegalAccessException e) { throw new RapidBeansRuntimeException(e); } catch (InvocationTargetException e) { throw new RapidBeansRuntimeException(e); } } return bean; } /** * constructor without initial values. */ public RapidBeanImplStrict() { this(EMPTY_STRING_ARRAY, null); } /** * constructor for generic beans without initial values and type. * * @param type * the type */ protected RapidBeanImplStrict(final TypeRapidBean type) { this(EMPTY_STRING_ARRAY, type); } /** * constructor with initial values as one String. * * @param initvals * String with initial values braced with quotes separated by a * blank; */ public RapidBeanImplStrict(final String initvals) { this(initvals, null); } /** * constructor with initial values as one String and type. * * @param initvals * String with initial values braced with quotes separated by a * blank; * @param type * the type */ protected RapidBeanImplStrict(final String initvals, final TypeRapidBean type) { this(StringHelper.splitQuoted(initvals), type); } /** * constructor with initial values as String array. * * @param initvals * String array with initial values. */ public RapidBeanImplStrict(final String[] initvals) { this(initvals, null); } /** * constructor with initial values as String array. * * @param initvals * String array with initial values. * @param argType * possibility to give the type in advance */ protected RapidBeanImplStrict(final String[] initvals, final TypeRapidBean argType) { final RapidBeanState stateBefore = getBeanState(); try { setBeanState(RapidBeanState.initializing); // initialize this bean's properties TypeRapidBean type; if (argType != null) { type = argType; } else { type = this.getType(); } final Collection<TypeProperty> proptypes = type.getPropertyTypes(); final int propcount = proptypes.size(); Property prop; this.properties = new Property[propcount]; int i = 0; for (TypeProperty proptype : proptypes) { prop = Property.createInstance(proptype, this); this.properties[i] = prop; i++; } this.initProperties(); final int lenInitvals = initvals.length; for (int j = 0; j < this.properties.length; j++) { if (j < lenInitvals) { if (!this.properties[j].isDependent()) { try { this.properties[j].setValue(initvals[j]); } catch (ValidationException e) { throw e; } } } } // automatically create child instances for composite children bean // classes // with minimal multiplicity defined for (final PropertyCollection colProp : this.getColPropertiesComposition()) { final TypePropertyCollection colPropType = (TypePropertyCollection) colProp.getType(); final int minmult = colPropType.getMinmult(); if (minmult > 0) { final TypeRapidBean targetType = colPropType.getTargetType(); for (int j = 0; j < minmult; j++) { colProp.addLink(RapidBeanImplStrict.createInstance(targetType)); } } } setBeanState(RapidBeanState.initialized); } catch (RuntimeException e) { setBeanState(stateBefore); throw e; } } // private static final Logger log = Logger.getLogger( // RapidBeanImplStrict.class.getName()); /** * The property container. Implements the composition. */ private Property[] properties = null; /** * The map just optimizes finding by name. */ private HashMap<String, Property> propmap = null; /** * lazy initialization of the propmap. */ public void initPropmap() { this.propmap = new HashMap<String, Property>(); for (int i = 0; i < this.properties.length; i++) { this.propmap.put(this.properties[i].getType().getPropName(), this.properties[i]); } } /** * @return a List with all the bean's Properties */ public final List<Property> getPropertyList() { final List<Property> proplist = new ArrayList<Property>(); for (int i = 0; i < this.properties.length; i++) { proplist.add(this.properties[i]); } return proplist; } /** * empty key property array to avoid unnecessary instantiations. */ public static final Property[] EMPTY_KEYPROP_ARRAY = new Property[0]; /** * @return a list with all the bean's collection properties */ public final List<PropertyCollection> getColProperties() { final List<PropertyCollection> colproplist = new ArrayList<PropertyCollection>(); for (int i = 0; i < this.properties.length; i++) { if (ClassHelper.classOf(TypePropertyCollection.class, this.properties[i].getType().getClass())) { colproplist.add((PropertyCollection) this.properties[i]); } } return colproplist; } /** * @return a list with all the bean's composition collection properties */ public final List<PropertyCollection> getColPropertiesComposition() { final List<PropertyCollection> colproplist = new ArrayList<PropertyCollection>(); for (int i = 0; i < this.properties.length; i++) { if (ClassHelper.classOf(TypePropertyCollection.class, this.properties[i].getType().getClass()) && ((TypePropertyCollection) this.properties[i].getType()).isComposition()) { colproplist.add((PropertyCollection) this.properties[i]); } } return colproplist; } /** * @param name * the Property's name. * * @return the bean's Property with the specified name. */ public final Property getProperty(final String name) { if (this.propmap == null) { this.initPropmap(); } return this.propmap.get(name); } /** * Convenience getter for a property's value. Encourages not to hold the * bean's property reference for a long time. * * @param name * the Property's name * @return the Property's value */ public final Object getPropValue(final String name) { if (this.propmap == null) { this.initPropmap(); } Property prop = this.propmap.get(name); if (prop == null) { throw new ValidationException("invalid.prop.name", this, "unknown property \"" + name + "\" for bean type \"" + this.getType().getName() + "\"."); } return prop.getValue(); } /** * Convenience setter for a property's value. Encourages not to hold the * bean's property reference for a long time. * * @param name * the Property's name * @param value * the Property's value to set */ public final void setPropValue(final String name, final Object value) { if (this.propmap == null) { this.initPropmap(); } Property prop = this.propmap.get(name); if (prop == null) { throw new ValidationException("invalid.prop.name", this, "unknown property \"" + name + "\" for bean type \"" + this.getType().getName() + "\"."); } prop.setValue(value); } /** * @return a clone of this bean. */ public RapidBean clone() { RapidBeanImplStrict bClone = RapidBeanImplStrict.createInstance(this.getType()); for (int i = 0; i < this.properties.length; i++) { bClone.properties[i] = this.properties[i].clone(bClone); } bClone.initPropmap(); bClone.initProperties(); bClone.setContainer(this.getContainer()); return bClone; } /** * @param cloneContainer * the container for the cloned bean * * @return a clone of this bean including the whole hierarchy. The container * in set to null. Non compositions links are frozen. */ public RapidBean cloneExternal(final Container cloneContainer) { RapidBean bClone = RapidBeanImplStrict.createInstance(this.getType()); bClone.setId(Id.createInstance(bClone, this.getIdString())); if (cloneContainer != null && (!cloneContainer.contains(bClone))) { ((ContainerImpl) cloneContainer).insert(bClone, true); } bClone.setContainer(cloneContainer); for (int i = 0; i < this.properties.length; i++) { this.properties[i].cloneValue(bClone.getPropertyList().get(i), cloneContainer); } return bClone; } /** * clones a property. * * @param parentBean * the parent bean. * * @return the cloned property */ public static Property cloneProperty(final Property property, final RapidBean parentBean) { Property pClone = null; try { pClone = Property.createInstance(property.getType(), parentBean); ThreadLocalValidationSettings.validationOff(); if (property instanceof PropertyCollection) { ((PropertyCollection) pClone).setValue(property.getValue(), false, true); } else { if (!property.isDependent()) { pClone.setValue(property.getValue()); } } } catch (ValidationInstanceAssocTwiceException e) { // do not do anything else } finally { ThreadLocalValidationSettings.remove(); } return pClone; } /** * validate the whole bean. */ public void validate() { for (Property prop : this.properties) { ThreadLocalValidationSettings.readonlyOff(); prop.validate(prop.getValue()); } } }