/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenFlexo 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.foundation.dm; import java.util.Collection; import java.util.Enumeration; import java.util.HashSet; import java.util.StringTokenizer; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import javax.naming.InvalidNameException; import org.openflexo.foundation.DataFlexoObserver; import org.openflexo.foundation.DataModification; import org.openflexo.foundation.DeletableObject; import org.openflexo.foundation.FlexoException; import org.openflexo.foundation.FlexoObservable; import org.openflexo.foundation.RepresentableFlexoModelObject; import org.openflexo.foundation.dm.dm.ChildrenReordered; import org.openflexo.foundation.dm.eo.DMEOModel; import org.openflexo.foundation.dm.eo.DMEOObject; import org.openflexo.foundation.rm.FlexoProject; import org.openflexo.foundation.rm.XMLStorageResourceData; import org.openflexo.foundation.validation.FixProposal; import org.openflexo.foundation.validation.ParameteredFixProposal; import org.openflexo.foundation.validation.Validable; import org.openflexo.foundation.validation.ValidationError; import org.openflexo.foundation.validation.ValidationIssue; import org.openflexo.foundation.validation.ValidationModel; import org.openflexo.foundation.validation.ValidationReport; import org.openflexo.foundation.validation.ValidationRule; import org.openflexo.inspector.InspectableObject; import org.openflexo.toolbox.EmptyVector; import org.openflexo.toolbox.JavaUtils; import org.openflexo.toolbox.ReservedKeyword; import org.openflexo.toolbox.StringUtils; import org.openflexo.toolbox.ToolBox; /** * Abstract representation of an object defined in the data model * * @author sguerin * */ public abstract class DMObject extends RepresentableFlexoModelObject implements InspectableObject, DeletableObject, Validable, DataFlexoObserver { private static final Logger logger = Logger.getLogger(DMObject.class.getPackage().getName()); protected static final Vector<DMObject> EMPTY_VECTOR = EmptyVector.EMPTY_VECTOR(DMObject.class); // ========================================================================== // ============================= Variables // ================================== // ========================================================================== protected DMModel _dmModel; // ========================================================================== // ============================= Constructor // ================================ // ========================================================================== public DMObject(DMModel dmModel) { this(dmModel.getProject()); _dmModel = dmModel; } public DMObject(FlexoProject project) { super(project); } private DMGenericDeclaration _previousDMGenericDeclaration = null; @Override public void initializeDeserialization(Object builder) { super.initializeDeserialization(builder); if (this instanceof DMGenericDeclaration) { _previousDMGenericDeclaration = getDMModel().getDmTypeConverter().getConverterTypeVariableContext(); getDMModel().getDmTypeConverter().setConverterTypeVariableContext((DMGenericDeclaration) this); } } @Override public void finalizeDeserialization(Object builder) { super.finalizeDeserialization(builder); if (this instanceof DMGenericDeclaration) { getDMModel().getDmTypeConverter().setConverterTypeVariableContext(_previousDMGenericDeclaration); } } // ========================================================================== // ========================== Instance methods // ============================== // ========================================================================== /** * Returns reference to the main object in which this XML-serializable object is contained relating to storing scheme: here it's the * data model itself * * @return the data model this object is relating to */ @Override public XMLStorageResourceData getXMLResourceData() { return getDMModel(); } public DMModel getDMModel() { return _dmModel; } @Override public abstract String getName(); @Override public abstract void setName(String aName) throws InvalidNameException, FlexoException; /** * used by velocity (dynamic invacation: DON'T delete !) * * @return Name */ public String getCapitalizedName() { return getCapitalizedName(getName()); } public static String getCapitalizedName(String aName) { return aName.substring(0, 1).toUpperCase() + aName.substring(1); } public String getNameAsMethodArgument() { return getNameAsMethodArgument(getName()); } public static String getNameAsMethodArgument(String aName) { String cap = getCapitalizedName(aName); if (cap.startsWith("A") || cap.startsWith("E") || cap.startsWith("I") || cap.startsWith("O") || cap.startsWith("U") || cap.startsWith("Y")) { return "an" + cap; } return "a" + cap; } // ========================================================================== // ============================ FlexoObserver // =============================== // ========================================================================== @Override public void update(FlexoObservable observable, DataModification dataModification) { // Not implemented } // ========================================================================== // ======================== Embedding Management // ============================ // ========================================================================== private void processToAdditionOfEmbedded(DMObject object, Collection<DMObject> queue) { if (!queue.contains(object)) { queue.add(object); } if (object.getEmbeddedDMObjects() != null) { Enumeration<? extends DMObject> en = object.getEmbeddedDMObjects().elements(); DMObject candidate = null; while (en.hasMoreElements()) { candidate = en.nextElement(); if (!queue.contains(candidate)) { queue.add(candidate); processToAdditionOfEmbedded(candidate, queue); } } } } /** * Return a Vector of all embedded IEObjects: recursive method (Note must include itself in this vector) * * @return a Vector of IEObject instances */ public Vector<DMObject> getAllEmbeddedDMObjects() { HashSet<DMObject> returned = new HashSet<DMObject>(); processToAdditionOfEmbedded(this, returned); return new Vector<DMObject>(returned); } /** * Return a Vector of embedded DMObjects at this level. NOTE that this is NOT a recursive method * * @return a Vector of IEObject instances */ public abstract Vector<? extends DMObject> getEmbeddedDMObjects(); @Override public void setChanged() { super.setChanged(propagateModified); if (propagateModified) { if (!isDeserializing() && !isSerializing()) { if (this instanceof DMEOObject) { DMEOModel dmEOModel = ((DMEOObject) this).getDMEOModel(); if (dmEOModel != null && dmEOModel.getEOModelResourceData() != null) { dmEOModel.getEOModelResourceData().setIsModified(); } } } } } private boolean propagateModified = true; public void preventFromModifiedPropagation() { propagateModified = false; } public void allowModifiedPropagation() { propagateModified = true; } // ========================================================================== // ========================= Validable interface // ============================ // ========================================================================== /** * Return default validation model for this object * * @return */ @Override public ValidationModel getDefaultValidationModel() { if (getDMModel() != null) { if (getDMModel().getProject() != null) { return getDMModel().getProject().getDMValidationModel(); } else { if (logger.isLoggable(Level.WARNING)) { logger.warning("Could not access to project !"); } } } else { if (logger.isLoggable(Level.WARNING)) { logger.warning("Could not access to process !"); } } return null; } /** * Returns a flag indicating if this object is valid according to default validation model * * @return boolean */ @Override public boolean isValid() { return isValid(getDefaultValidationModel()); } /** * Returns a flag indicating if this object is valid according to specified validation model * * @return boolean */ @Override public boolean isValid(ValidationModel validationModel) { return validationModel.isValid(this); } /** * Validates this object by building new ValidationReport object Default validation model is used to perform this validation. */ @Override public ValidationReport validate() { return validate(getDefaultValidationModel()); } /** * Validates this object by building new ValidationReport object Supplied validation model is used to perform this validation. */ @Override public ValidationReport validate(ValidationModel validationModel) { return validationModel.validate(this); } /** * Validates this object by appending eventual issues to supplied ValidationReport. Default validation model is used to perform this * validation. * * @param report * , a ValidationReport object on which found issues are appened */ @Override public void validate(ValidationReport report) { validate(report, getDefaultValidationModel()); } /** * Validates this object by appending eventual issues to supplied ValidationReport. Supplied validation model is used to perform this * validation. * * @param report * , a ValidationReport object on which found issues are appened */ @Override public void validate(ValidationReport report, ValidationModel validationModel) { validationModel.validate(this, report); } /** * Return a vector of all embedded objects on which the validation will be performed * * @return a Vector of Validable objects */ @Override public Vector<Validable> getAllEmbeddedValidableObjects() { return new Vector<Validable>(getAllEmbeddedDMObjects()); } // ========================================================================== // ======================== Deletion implementation // ========================= // ========================================================================== /** * Build and return a vector of all the objects that will be deleted during this deletion * * @param aVector * of DeletableObject */ @Override public Vector<DMObject> getAllEmbeddedDeleted() { return getAllEmbeddedDMObjects(); } public abstract boolean isDeletable(); public abstract Vector<? extends DMObject> getOrderedChildren(); public abstract DMObject getParent(); @Override public String toString() { return getFullyQualifiedName(); } public String getLocalizedName() { return getName(); } public void notifyReordering(DMObject cause) { setChanged(); notifyObservers(new ChildrenReordered(cause)); } public boolean isNameValid() { return getName() != null && getName().matches(JavaUtils.JAVA_CLASS_NAME_REGEXP); } public static String getTagAndParamRepresentation(String tag, String param, String tagValue) { if (tagValue == null || tagValue.trim().equals("")) { return " * @" + tag + " " + param + StringUtils.LINE_SEPARATOR; } StringBuffer returned = new StringBuffer(); int indentLength = (" @" + tag + " " + param).length(); String indent = null; StringTokenizer st2 = new StringTokenizer(tagValue, StringUtils.LINE_SEPARATOR); boolean isFirst = true; while (st2.hasMoreTokens()) { if (isFirst) { returned.append(" * @" + tag + " " + param + " " + st2.nextToken() + StringUtils.LINE_SEPARATOR); } else { if (indent == null) { indent = StringUtils.buildWhiteSpaceIndentation(indentLength); } returned.append(" *" + indent + " " + st2.nextToken() + StringUtils.LINE_SEPARATOR); } isFirst = false; } return returned.toString(); } public static class DataModelObjectNameMustBeValid extends ValidationRule<DataModelObjectNameMustBeValid, DMObject> { /** * @param objectType * @param ruleName */ public DataModelObjectNameMustBeValid() { super(DMObject.class, "data_model_objects_name_must_be_valid"); } /** * Overrides applyValidation * * @see org.openflexo.foundation.validation.ValidationRule#applyValidation(org.openflexo.foundation.validation.Validable) */ @Override public ValidationIssue<DataModelObjectNameMustBeValid, DMObject> applyValidation(DMObject o) { if (!o.isNameValid()) { ValidationError<DataModelObjectNameMustBeValid, DMObject> err = new ValidationError<DataModelObjectNameMustBeValid, DMObject>( this, o, "data_model_objects_can_contain_only_alphanumeric_characters_or_underscore_and_must_start_with_a_letter"); if (o.getName() != null) { err.addToFixProposals(new CleanDataModelObjectName(ToolBox.cleanStringForJava(o.getName()))); } err.addToFixProposals(new SetCustomName()); return err; } return null; } public static class CleanDataModelObjectName extends FixProposal<DataModelObjectNameMustBeValid, DMObject> { private String newName; /** * @param aMessage */ public CleanDataModelObjectName(String newName) { super("set_name_to_($newName)"); this.newName = newName; } /** * Overrides fixAction * * @see org.openflexo.foundation.validation.FixProposal#fixAction() */ @Override protected void fixAction() { int i = 0; boolean ok = false; String attempt = newName; while (!ok) { try { if (ReservedKeyword.contains(attempt)) { throw new InvalidNameException(); } getObject().setName(attempt); ok = true; } catch (Exception e) { e.printStackTrace(); attempt = newName + "_" + i++; if (i > 100) { ok = true; } } } } public String getNewName() { return newName; } } public static class SetCustomName extends ParameteredFixProposal<DataModelObjectNameMustBeValid, DMObject> { /** * @param aMessage */ public SetCustomName() { super("set_custom_name", "customName", "customName", ""); } /** * Overrides fixAction * * @see org.openflexo.foundation.validation.FixProposal#fixAction() */ @Override protected void fixAction() { String s = (String) getValueForParameter("customName"); try { if (ReservedKeyword.contains(s)) { throw new InvalidNameException(); } getObject().setName(s); } catch (Exception e) { e.printStackTrace(); } } } } }