/** * <copyright> * * Copyright (c) 2005, 2006, 2007, 2008 Springsite BV (The Netherlands) and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Martin Taal - Initial API and implementation * * </copyright> * * $Id: FeatureMapEntry.java,v 1.7 2008/04/11 23:43:43 mtaal Exp $ */ package org.eclipse.emf.teneo.type; import java.io.IOException; import java.io.Serializable; import org.eclipse.emf.common.notify.NotificationChain; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.InternalEObject; import org.eclipse.emf.ecore.util.ExtendedMetaData; import org.eclipse.emf.ecore.util.FeatureMap; import org.eclipse.emf.teneo.EContainerRepairControl; import org.eclipse.emf.teneo.util.StoreUtil; /** * Is used to replace the EMF feature map entry with an entry which can be handled by the or layer. * * The FeatureMap.Entry.Internal methods are handled through a delegate. Based on the efeature the * correct delegate is created. * * @author <a href="mailto:mtaal@elver.org">Martin Taal</a> * @version $Revision: 1.7 $ */ public abstract class FeatureMapEntry implements FeatureMap.Entry.Internal, Serializable { private static final long serialVersionUID = 1L; /** The structural feature which defines which element this is */ private EStructuralFeature eStructuralFeature; /** Path to the efeature for serialization support */ private String eFeaturePath; /** And its value */ private Object value; /** Keeps track if the class was initialized */ private boolean initialized = false; /** The delegate which implements the inverse action */ private InverseAction inverseAction; /** * The featuremap to which we are connected. Is used to determine if entries have been added to * another featuremap. This happens in copy actions. */ private FeatureMap.Internal owningMap; /** Constructor called by the storage layer, fields need to be set by calls to subclass */ public FeatureMapEntry() { } /** Constructor called by the storage layer, fields need to be set by calls to subclass */ public FeatureMapEntry(EStructuralFeature feature, Object val) { eStructuralFeature = feature; value = val; initialized = true; initializeSpecificImplementation(); setInverseAction(); } /** Takes care of serializing the efeature */ private void writeObject(java.io.ObjectOutputStream out) throws IOException { eFeaturePath = StoreUtil.structuralFeatureToString(eStructuralFeature); eStructuralFeature = null; out.defaultWriteObject(); } /** Takes care of deserializing the efeature */ private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); eStructuralFeature = StoreUtil.stringToStructureFeature(eFeaturePath); } /** Set the inverseaction delegate, must be called after the efeature is set */ private void setInverseAction() { if (eStructuralFeature instanceof EReference) { final EReference eref = (EReference) eStructuralFeature; if (eref.getEOpposite() != null) { inverseAction = new BidirectionalInverseAction(); } else if (eref.isContainment()) { inverseAction = new ContainmentInverseAction(); } else { inverseAction = new InverseAction(); } } else { inverseAction = new InverseAction(); } } /** Sets the featuremap, is done when an entry is added to the featuremap */ public void setFeatureMap(FeatureMap.Internal featureMap) { owningMap = featureMap; /* * if (value != null && value instanceof InternalEObject && eStructuralFeature instanceof * EReference && ((EReference)eStructuralFeature).isContainment()) { * ((InternalEObject)value).eSetResource((Resource.Internal)owningMap.getEObject().eResource(), * null); } */ } /** Unsets the featuremap, is done when an entry is removed */ public void unsetFeatureMap() { owningMap = null; } /** Is true if this featureMap already belongs to a Map */ public boolean isFeatureMapSet() { return owningMap != null; } /** Is true if this featureMap already belongs to the passed map */ public boolean belongsToFeatureMap(FeatureMap.Internal fm) { return owningMap == fm; // object equality! } /** Set the value from a previous entry */ public void setEntry(FeatureMap.Entry entry) { eStructuralFeature = entry.getEStructuralFeature(); value = entry.getValue(); initialized = true; // needs to be set before the call to the subclass, otherwise infinite // looping initializeSpecificImplementation(); setInverseAction(); } /** Initializes this class from the values in the subclass */ public void initialize() { eStructuralFeature = retrieveStructuralFeature(getStructuralFeatureDBID()); value = getValueFromSpecificImplementation(eStructuralFeature); initialized = true; setInverseAction(); } /** * Needs to be implemented by the subclass, returns the value based on one of the fields set * through the db */ protected abstract Object getValueFromSpecificImplementation(EStructuralFeature eFeature); /** Needs to be implemented by the subclass, returns the database id of the structural feature */ protected abstract String getStructuralFeatureDBID(); /** * Is called by the super class to notify the subclass that it needs to set its fields based on * the structural feature */ protected abstract void initializeSpecificImplementation(); /** Method which needs to be called by the subclass to set the superclass members */ public void setFields(EStructuralFeature structuralFeature, Object structuralValue) { eStructuralFeature = structuralFeature; value = structuralValue; initialized = true; // do this before the call to the subclass // initialize the subclass so that the fields are stored in the db initializeSpecificImplementation(); setInverseAction(); } /** Returns structural feature */ public EStructuralFeature getEStructuralFeature() { if (!initialized) { initialize(); } return eStructuralFeature; } /** Returns the value */ public Object getValue() { if (!initialized) { initialize(); } return value; } /** * Returns the string which is used to store the unique identification of this structuralfeature * in the db */ protected String createStructuralFeatureDBID() { return StoreUtil.structuralFeatureToString(getEStructuralFeature()); } /** Gets a structuralfeature on the basis of the passed id */ protected EStructuralFeature retrieveStructuralFeature(String dbid) { return StoreUtil.stringToStructureFeature(dbid); } /** * Checks if a certain feature has a certain name or that its group (if present) has this name, * in which case it is also set to true. */ protected boolean featureForField(String name) { if (eStructuralFeature.getName().compareTo(name) == 0) { return true; } // check the group feature final EStructuralFeature groupFeature = ExtendedMetaData.INSTANCE.getGroup(eStructuralFeature); if (groupFeature != null && groupFeature.getName().compareTo(name) == 0) { return true; } final EStructuralFeature affiliatedFeature = ExtendedMetaData.INSTANCE.getAffiliation(eStructuralFeature); if (affiliatedFeature != null && affiliatedFeature.getName().compareTo(name) == 0) { return true; } return false; } /** * Sets the container property of the value if the value is an EObject and the feature is a * containment feature. */ public void setContainer(InternalEObject owner) { if (!initialized) { initialize(); } if (value != null && value instanceof InternalEObject && eStructuralFeature instanceof EReference && ((EReference) eStructuralFeature).isContainment()) { EContainerRepairControl.setContainer(owner, (InternalEObject) value, eStructuralFeature); } } /** Code copied from FeatureMapUtil.EntryImpl */ @Override public boolean equals(Object that) { if (!initialized) { initialize(); } if (this == that) { return true; } else if (!(that instanceof FeatureMap.Entry)) { return false; } else { FeatureMap.Entry entry = (FeatureMap.Entry) that; return entry.getEStructuralFeature() == eStructuralFeature && (value == null ? entry.getValue() == null : value.equals(entry.getValue())); } } /** Code copied from FeatureMapUtil.EntryImpl */ @Override public int hashCode() { /* * Used to create a hashcode which maps all instances of one class to the same hashcode Is * required because the normal hashcode method (see commented out part below) resulted in * null-pointer exceptions in hibernate because the content of the entry was used for * determining the hashcode while the object was not initialized from the db */ return this.getClass().hashCode(); /* * if (!initialized) initialize(); * * return eStructuralFeature.hashCode() ^ (value == null ? 0 : value.hashCode()); */ } /** Code copied from FeatureMapUtil.EntryImpl */ @Override public String toString() { if (!initialized) { initialize(); } String prefix = eStructuralFeature.getEContainingClass().getEPackage().getNsPrefix(); eStructuralFeature.getName(); return (prefix != null && prefix.length() != 0 ? prefix + ":" + eStructuralFeature.getName() : eStructuralFeature.getName()) + "=" + value; } /** Create copy with same feature and different value */ public Internal createEntry(InternalEObject value) { return createEntry((Object) value); } /** Create copy with same feature and different value */ public abstract Internal createEntry(Object value); /** Do inverse action */ public NotificationChain inverseAdd(InternalEObject owner, int featureID, NotificationChain notifications) { return inverseAction.inverseAdd(owner, featureID, notifications); } /** Do inverse action */ public NotificationChain inverseAdd(InternalEObject owner, Object otherEnd, int featureID, NotificationChain notifications) { return inverseAction.inverseAdd(owner, otherEnd, featureID, notifications); } /** Do inverse action */ public NotificationChain inverseRemove(InternalEObject owner, int featureID, NotificationChain notifications) { return inverseAction.inverseRemove(owner, featureID, notifications); } /** Do inverse action */ public NotificationChain inverseRemove(InternalEObject owner, Object otherEnd, int featureID, NotificationChain notifications) { return inverseAction.inverseRemove(owner, otherEnd, featureID, notifications); } /** Validate type of object against the type of the efeature */ public void validate(Object value) { if (value != null && !eStructuralFeature.getEType().isInstance(value)) { String valueClass = value instanceof EObject ? ((EObject) value).eClass().getName() : value.getClass().getName(); throw new ClassCastException("The feature '" + eStructuralFeature.getName() + "'s type '" + eStructuralFeature.getEType().getName() + "' does not permit a value of type '" + valueClass + "'"); } } /** Internal class to handle inverse actions */ private class InverseAction { /** Handles inverse action, differs on the basis of the feature type */ public NotificationChain inverseAdd(InternalEObject owner, int featureID, NotificationChain notifications) { return inverseAdd(owner, value, featureID, notifications); } /** Handles inverse action, differs on the basis of the feature type */ public NotificationChain inverseRemove(InternalEObject owner, int featureID, NotificationChain notifications) { return inverseRemove(owner, value, featureID, notifications); } /** Handles inverse action, differs on the basis of the feature type */ public NotificationChain inverseAdd(InternalEObject owner, Object otherEnd, int featureID, NotificationChain notifications) { return notifications; } /** Handles inverse action, differs on the basis of the feature type */ public NotificationChain inverseRemove(InternalEObject owner, Object otherEnd, int featureID, NotificationChain notifications) { return notifications; } /** validate the type of the value with the type expected by the efeature */ public void validate(Object value) { if (value != null && !eStructuralFeature.getEType().isInstance(value)) { String valueClass = value instanceof EObject ? ((EObject) value).eClass().getName() : value.getClass().getName(); throw new ClassCastException("The feature '" + eStructuralFeature.getName() + "'s type '" + eStructuralFeature.getEType().getName() + "' does not permit a value of type '" + valueClass + "'"); } } } /** Containment Inverse Action */ private class ContainmentInverseAction extends InverseAction { /** Handles inverse action, differs on the basis of the feature type */ @Override public NotificationChain inverseAdd(InternalEObject owner, Object otherEnd, int featureID, NotificationChain notifications) { return inverseAdd(owner, (InternalEObject) value, featureID, notifications); } /** Handles inverse action, differs on the basis of the feature type */ @Override public NotificationChain inverseRemove(InternalEObject owner, Object otherEnd, int featureID, NotificationChain notifications) { return inverseRemove(owner, (InternalEObject) value, featureID, notifications); } /** Does inverse action on other end */ private NotificationChain inverseAdd(InternalEObject owner, InternalEObject otherEnd, int featureID, NotificationChain notifications) { if (otherEnd != null) { int containmentFeatureID = owner.eClass().getFeatureID(eStructuralFeature); notifications = otherEnd.eInverseAdd(owner, InternalEObject.EOPPOSITE_FEATURE_BASE - (containmentFeatureID == -1 ? featureID : containmentFeatureID), null, notifications); } return notifications; } /** Does inverse action on other end */ private NotificationChain inverseRemove(InternalEObject owner, InternalEObject otherEnd, int featureID, NotificationChain notifications) { if (otherEnd != null) { int containmentFeatureID = owner.eClass().getFeatureID(eStructuralFeature); notifications = otherEnd.eInverseRemove(owner, InternalEObject.EOPPOSITE_FEATURE_BASE - (containmentFeatureID == -1 ? featureID : containmentFeatureID), null, notifications); } return notifications; } } /** Bidirectional feature value */ private class BidirectionalInverseAction extends InverseAction { /** Handles inverse action, differs on the basis of the feature type */ @Override public NotificationChain inverseAdd(InternalEObject owner, Object otherEnd, int featureID, NotificationChain notifications) { return inverseAdd(owner, (InternalEObject) value, featureID, notifications); } /** Handles inverse action, differs on the basis of the feature type */ @Override public NotificationChain inverseRemove(InternalEObject owner, Object otherEnd, int featureID, NotificationChain notifications) { return inverseRemove(owner, (InternalEObject) value, featureID, notifications); } /** Does inverse action on other end */ private final NotificationChain inverseAdd(InternalEObject owner, InternalEObject otherEnd, int featureID, NotificationChain notifications) { if (otherEnd != null) { notifications = otherEnd.eInverseAdd(owner, otherEnd.eClass().getFeatureID( ((EReference) eStructuralFeature).getEOpposite()), null, notifications); } return notifications; } /** Does inverse action on other end */ private final NotificationChain inverseRemove(InternalEObject owner, InternalEObject otherEnd, int featureID, NotificationChain notifications) { if (otherEnd != null) { notifications = otherEnd.eInverseRemove(owner, otherEnd.eClass().getFeatureID( ((EReference) eStructuralFeature).getEOpposite()), null, notifications); } return notifications; } } }