/**
* <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
* </copyright>
*
* $Id: HibernateFeatureMapEntry.java,v 1.9 2009/03/15 08:09:22 mtaal Exp $
*/
package org.eclipse.emf.teneo.hibernate.mapping.elist;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
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.AssertUtil;
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 special feature of this entry is that all allowed features can be set,
* internally it keeps a list of feature value pairs. Only one of these pairs is
* the valid one, which one is determined by the value of the eStructuralFeature
* member.
*
* @author <a href="mailto:mtaal@elver.org">Martin Taal</a>
* @version $Revision: 1.9 $
*/
public class HibernateFeatureMapEntry implements FeatureMap.Entry.Internal,
Serializable {
/**
* Generated serial ID
*/
private static final long serialVersionUID = 3946138277481892125L;
/**
* Gets an 'normal' FeatureMap.Entry and if it is not a FeatureMapEntry
* replaces it with a specific implementation.
*/
public static HibernateFeatureMapEntry replaceEntry(Object obj,
FeatureMap.Internal owningMap) {
// do special check, in case the featuremap entry does not need to be
// changed
if (obj instanceof HibernateFeatureMapEntry) {
final HibernateFeatureMapEntry fmEntry = (HibernateFeatureMapEntry) obj;
// return the entry if it is not yet set, in this case it does not
// yet belong to a
// featuremap and can be used. This happens with a featuremap which
// has already been
// replaced.
if (!fmEntry.isFeatureMapSet()
|| fmEntry.belongsToFeatureMap(owningMap)) {
return fmEntry;
}
}
HibernateFeatureMapEntry entry = new HibernateFeatureMapEntry();
entry.setEntry((FeatureMap.Entry) obj, owningMap);
return entry;
}
/**
* Replaces standard FeatureMap.Entry with a FeatureMapEntry for a
* collection
*/
public static Collection<HibernateFeatureMapEntry> replaceEntryAll(
Collection<FeatureMap.Entry> c, Class<?> replaceByType,
FeatureMap.Internal owningMap) {
final ArrayList<HibernateFeatureMapEntry> newEntries = new ArrayList<HibernateFeatureMapEntry>();
final Iterator<FeatureMap.Entry> it = c.iterator();
while (it.hasNext()) {
newEntries.add(replaceEntry(it.next(), owningMap));
}
return newEntries;
}
/** Creates an entry with the correct type */
public static FeatureMap.Entry createEntry(EStructuralFeature feature,
Object value, FeatureMap.Internal owningMap) {
HibernateFeatureMapEntry entry = new HibernateFeatureMapEntry();
entry.setFeatureValue(feature, value, owningMap);
return entry;
}
/**
* Method which creates a list of entries based on one feature and multiple
* values
*/
public static Collection<FeatureMap.Entry> createEntryAll(
EStructuralFeature feature, Collection<?> values,
FeatureMap.Internal owningMap) {
final ArrayList<FeatureMap.Entry> entries = new ArrayList<FeatureMap.Entry>();
final Iterator<?> it = values.iterator();
while (it.hasNext()) {
entries.add(createEntry(feature, it.next(), owningMap));
}
return entries;
}
/** The structural feature which defines which element this is */
private EStructuralFeature eStructuralFeature;
/** To store the efeature during serialization */
private String eFeaturePath;
/**
* 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;
/** The feature value map */
private ArrayList<FeatureValue> featureValues = new ArrayList<FeatureValue>();
/**
* The entity name of this entry, is filled when the object is read from the
* db
*/
private String entityName = null;
/**
* Constructor called by the storage layer, fields need to be set by calls
* to subclass
*/
public HibernateFeatureMapEntry() {
}
/** Sets the featuremap, is done when an entry is added to the featuremap */
public void setFeatureMap(FeatureMap.Internal featureMap) {
owningMap = featureMap;
if (featureMap != null) {
entityName = StoreUtil.getEntityName(featureMap
.getEStructuralFeature());
}
}
/** 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!
}
/** 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 value from a previous entry */
public void setEntry(FeatureMap.Entry entry, FeatureMap.Internal owningMap) {
eStructuralFeature = entry.getEStructuralFeature();
featureValues.add(createFeatureValue(eStructuralFeature, entry
.getValue()));
setFeatureMap(owningMap);
}
/** Returns structural feature */
public EStructuralFeature getEStructuralFeature() {
return eStructuralFeature;
}
/**
* @param structuralFeature
* the eStructuralFeature to set
*/
private void setEStructuralFeature(EStructuralFeature structuralFeature) {
eStructuralFeature = structuralFeature;
}
/** Return the string repr. of the feature */
public String getFeatureURI() {
return StoreUtil.structuralFeatureToString(getEStructuralFeature());
}
/** Set the feature on the basis of the string */
public void setEStructuralFeature(String featureURI) {
setEStructuralFeature(StoreUtil.stringToStructureFeature(featureURI));
}
/** Returns the value */
public Object getValue() {
return getValue(getEStructuralFeature());
}
/**
* Add a feature value combination to the entry, only one of these values is
* the valid one but the other nullable values are stored in the db
*/
public void addFeatureValue(EStructuralFeature feature, Object value) {
featureValues.add(createFeatureValue(feature, value));
}
/** Sets the exact feature value for this entry */
public void setFeatureValue(EStructuralFeature feature, Object value,
FeatureMap.Internal owningMap) {
featureValues.add(createFeatureValue(feature, value));
setEStructuralFeature(feature);
setFeatureMap(owningMap);
}
/** Returns the value for a specific feature */
public Object getValue(EStructuralFeature feature) {
for (FeatureValue fv : featureValues) {
if (fv.matchesFeature(feature)) {
return fv.getValue();
}
}
return null; // TODO: maybe throw error?
}
/** get the real feature value */
private FeatureValue getFeatureValue() {
final EStructuralFeature feature = getEStructuralFeature();
AssertUtil.assertTrue("Feature is not set", feature != null);
for (FeatureValue fv : featureValues) {
if (fv.matchesFeature(feature)) {
return fv;
}
}
// can not get here, see assertion above
return null;
}
/**
* 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) {
final Object value = getValue();
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 (this == that) {
return true;
} else if (!(that instanceof FeatureMap.Entry)) {
return false;
} else {
final FeatureMap.Entry entry = (FeatureMap.Entry) that;
final Object value = getValue();
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
* hashcode impl in emf EntryImpl) 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();
}
/** Code copied from FeatureMapUtil.EntryImpl */
@Override
public String toString() {
String prefix = eStructuralFeature.getEContainingClass().getEPackage()
.getNsPrefix();
eStructuralFeature.getName();
return (prefix != null && prefix.length() != 0 ? prefix + ":"
+ eStructuralFeature.getName() : eStructuralFeature.getName())
+ "=" + getValue();
}
/**
* @return the entityName
*/
public String getEntityName() {
return entityName;
}
/**
* @param entityName
* the entityName to set
*/
public void setEntityName(String entityName) {
this.entityName = entityName;
}
/** Create the correct feature value type */
private FeatureValue createFeatureValue(EStructuralFeature feature,
Object value) {
if (feature instanceof EReference
&& ((EReference) feature).getEOpposite() != null) {
return new InverseFeatureValue(feature, value);
} else if (feature instanceof EReference
&& ((EReference) feature).isContainment()) {
return new ContainmentFeatureValue(feature, value);
} else {
return new FeatureValue(feature, value);
}
}
/** Create a copy of this entry with a different value */
public Internal createEntry(InternalEObject value) {
HibernateFeatureMapEntry hfm = new HibernateFeatureMapEntry();
hfm.setFeatureValue(getEStructuralFeature(), value, owningMap);
return hfm;
}
/**
* Create a copy of this entry with a different value, calls
* createEntry(InternalEObject value)
*/
public Internal createEntry(Object value) {
return createEntry((InternalEObject) value);
}
/** Does inverse action on other end */
public final NotificationChain inverseAdd(InternalEObject owner,
int featureID, NotificationChain notifications) {
return getFeatureValue().inverseAdd(owner, getValue(), featureID,
notifications);
}
/** Does inverse action on other end */
public final NotificationChain inverseRemove(InternalEObject owner,
int featureID, NotificationChain notifications) {
return getFeatureValue().inverseRemove(owner, getValue(), featureID,
notifications);
}
/** Does inverse action on other end */
public final NotificationChain inverseAdd(InternalEObject owner,
Object otherEnd, int featureID, NotificationChain notifications) {
return getFeatureValue().inverseAdd(owner, getValue(), featureID,
notifications);
}
/** Does inverse action on other end */
public NotificationChain inverseRemove(InternalEObject owner,
Object otherEnd, int featureID, NotificationChain notifications) {
return getFeatureValue().inverseRemove(owner, otherEnd, featureID,
notifications);
}
/** validate the type of the value with the type expected by the efeature */
public void validate(Object value) {
getFeatureValue().validate(value);
}
/** Class to store feature value pairs together with their validator */
private class FeatureValue implements Serializable {
/**
* Generated Serial Version ID
*/
private static final long serialVersionUID = 3665363921316852811L;
/** The feature */
protected EStructuralFeature feature;
/** The featurepath, is used during serialization */
private String featurePath;
/** Its value (can be null) */
protected final Object value;
/** Constructor */
private FeatureValue(EStructuralFeature feature, Object value) {
this.feature = feature;
this.value = value;
}
/** Takes care of serializing the efeature */
private void writeObject(java.io.ObjectOutputStream out)
throws IOException {
featurePath = StoreUtil.structuralFeatureToString(feature);
feature = null;
out.defaultWriteObject();
}
/** Takes care of deserializing the efeature */
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();
feature = StoreUtil.stringToStructureFeature(featurePath);
}
/**
* Returns true if this feature value corresponds to the passed feature
* (taking into account substitution groups
*/
private boolean matchesFeature(EStructuralFeature eFeature) {
if (feature.equals(eFeature)) {
return true;
}
// compare on the basis of the affiliates (substitutiongroup)
final EStructuralFeature aff1 = ExtendedMetaData.INSTANCE
.getAffiliation(owningMap.getEObject().eClass(), feature);
final EStructuralFeature aff2 = ExtendedMetaData.INSTANCE
.getAffiliation(owningMap.getEObject().eClass(), eFeature);
if (aff1 != null && aff2 != null && aff1 == aff2) {
return true;
}
return false;
}
/** Returns the value */
private Object getValue() {
return value;
}
/** 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 feature value */
private class ContainmentFeatureValue extends FeatureValue {
/**
* Generated serial id
*/
private static final long serialVersionUID = -5915172909939056481L;
/** Constructor */
private ContainmentFeatureValue(EStructuralFeature feature, Object value) {
super(feature, value);
}
/** 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 InverseFeatureValue extends FeatureValue {
/**
* Generated Serial Version ID
*/
private static final long serialVersionUID = 7207038502480577523L;
/** Constructor */
private InverseFeatureValue(EStructuralFeature feature, Object value) {
super(feature, value);
}
/** 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;
}
}
}