blob: d5dc92396eb88407394e699fa9f4eff60e3370fa [file] [log] [blame]
/*
* Copyright (c) 2011-2015 Eike Stepper (Berlin, Germany) 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:
* Eike Stepper - initial API and implementation
* Martin Fluegge - bug 247226: Transparently support legacy models
* Christian W. Damus (CEA) - isLoading() support for CDOResource
*/
package org.eclipse.emf.internal.cdo.object;
import org.eclipse.emf.cdo.CDOObject;
import org.eclipse.emf.cdo.CDOObjectHistory;
import org.eclipse.emf.cdo.CDOState;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.common.id.CDOIDUtil;
import org.eclipse.emf.cdo.common.id.CDOIdentifiable;
import org.eclipse.emf.cdo.common.model.CDOModelUtil;
import org.eclipse.emf.cdo.common.model.CDOType;
import org.eclipse.emf.cdo.common.protocol.CDOProtocolConstants;
import org.eclipse.emf.cdo.common.revision.CDOElementProxy;
import org.eclipse.emf.cdo.common.revision.CDORevision;
import org.eclipse.emf.cdo.common.revision.CDORevisionData;
import org.eclipse.emf.cdo.common.security.CDOPermission;
import org.eclipse.emf.cdo.common.util.CDOException;
import org.eclipse.emf.cdo.eresource.CDOResource;
import org.eclipse.emf.cdo.eresource.impl.CDOResourceImpl;
import org.eclipse.emf.cdo.spi.common.model.InternalCDOClassInfo;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision;
import org.eclipse.emf.cdo.util.CDOUtil;
import org.eclipse.emf.cdo.view.CDOView;
import org.eclipse.emf.internal.cdo.CDOObjectImpl;
import org.eclipse.emf.internal.cdo.bundle.OM;
import org.eclipse.emf.internal.cdo.view.CDOStateMachine;
import org.eclipse.net4j.util.ReflectUtil;
import org.eclipse.net4j.util.WrappedException;
import org.eclipse.net4j.util.om.trace.ContextTracer;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClassifier;
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.resource.Resource;
import org.eclipse.emf.ecore.util.InternalEList;
import org.eclipse.emf.spi.cdo.CDOStore;
import org.eclipse.emf.spi.cdo.FSMUtil;
import org.eclipse.emf.spi.cdo.InternalCDOObject;
import org.eclipse.emf.spi.cdo.InternalCDOResource;
import org.eclipse.emf.spi.cdo.InternalCDOView;
import org.eclipse.emf.spi.cdo.InternalCDOView.ViewAndState;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
/**
* @author Eike Stepper
* @author Martin Fluegge
* @since 2.0
*/
public abstract class CDOLegacyWrapper extends CDOObjectWrapper
{
private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG_OBJECT, CDOLegacyWrapper.class);
private final InternalCDOClassInfo classInfo;
/**
* This ThreadLocal map stores all pre-registered objects. This avoids a never-ending loop when setting the container
* of an object.
*/
private static ThreadLocal<Map<CDOID, CDOLegacyWrapper>> wrapperRegistry = new InheritableThreadLocal<Map<CDOID, CDOLegacyWrapper>>();
private static ThreadLocal<Counter> recursionCounter = new InheritableThreadLocal<Counter>();
protected ViewAndState viewAndState;
protected Object idOrRevision;
/**
* It could happen that while <i>revisionToInstance()</i> is executed externally the <i>internalPostLoad()</i> method
* will be called. This happens for example if <i>internalPostInvalidate()</i> is called. The leads to another
* <i>revisionToInstance()</i> call while the first call has not finished. This is certainly not so cool. That's why
* <b>underConstruction</b> will flag that <i>revisionToInstance()</i> is still running and avoid the second call.
*
* @since 3.0
*/
private boolean underConstruction;
public CDOLegacyWrapper(InternalEObject instance)
{
this.instance = instance;
classInfo = (InternalCDOClassInfo)CDOModelUtil.getClassInfo(instance.eClass());
viewAndState = ViewAndState.TRANSIENT;
}
public InternalCDOClassInfo cdoClassInfo()
{
return classInfo;
}
public CDOID cdoID()
{
if (idOrRevision == null)
{
return null;
}
if (idOrRevision instanceof CDOID)
{
return (CDOID)idOrRevision;
}
return ((InternalCDORevision)idOrRevision).getID();
}
public InternalCDOView cdoView()
{
return viewAndState.view;
}
public void cdoInternalSetID(CDOID id)
{
if (TRACER.isEnabled())
{
TRACER.format("Setting ID: {0} for {1}", id, instance); //$NON-NLS-1$
}
if (idOrRevision == null || id == null)
{
idOrRevision = id;
}
}
public void cdoInternalSetView(CDOView view)
{
if (TRACER.isEnabled())
{
TRACER.format("Setting view: {0} for {1}", view, instance); //$NON-NLS-1$
}
InternalCDOView newView = (InternalCDOView)view;
if (newView != null)
{
viewAndState = newView.getViewAndState(viewAndState.state);
}
else
{
viewAndState = ViewAndState.TRANSIENT.getViewAndState(viewAndState.state);
}
}
public CDOState cdoState()
{
return viewAndState.state;
}
public InternalCDORevision cdoRevision()
{
if (idOrRevision instanceof InternalCDORevision)
{
return (InternalCDORevision)idOrRevision;
}
return null;
}
public InternalCDORevision cdoRevision(boolean loadOnDemand)
{
if (loadOnDemand)
{
CDOStateMachine.INSTANCE.read(this);
}
return cdoRevision();
}
public CDOPermission cdoPermission()
{
InternalCDORevision revision = cdoRevision(true);
if (revision == null)
{
return CDOPermission.WRITE;
}
return revision.getPermission();
}
@Override
public CDOResourceImpl cdoResource()
{
revisionToInstanceResource();
return super.cdoResource();
}
@Deprecated
public void cdoReload()
{
CDOStateMachine.INSTANCE.reload(this);
}
public CDOObjectHistory cdoHistory()
{
return viewAndState.view.getHistory(this);
}
public CDOState cdoInternalSetState(CDOState state)
{
CDOState oldState = viewAndState.state;
if (oldState != state)
{
if (TRACER.isEnabled())
{
TRACER.format("Setting state {0} for {1}", state, this); //$NON-NLS-1$
}
viewAndState = viewAndState.getViewAndState(state);
adjustEProxy();
if (viewAndState.view != null)
{
viewAndState.view.handleObjectStateChanged(this, oldState, state);
}
return oldState;
}
return null;
}
public void cdoInternalSetRevision(CDORevision revision)
{
if (TRACER.isEnabled())
{
TRACER.trace("Setting revision: " + revision); //$NON-NLS-1$
}
if (revision == null)
{
idOrRevision = cdoID();
}
else
{
idOrRevision = revision;
}
}
public void cdoInternalPostAttach()
{
instanceToRevision();
for (Adapter adapter : eAdapters())
{
if (!(adapter instanceof CDOObjectWrapper))
{
viewAndState.view.handleAddAdapter(this, adapter);
}
}
}
public void cdoInternalPostDetach(boolean remote)
{
if (remote)
{
setInstanceContainer(null, eContainerFeatureID());
setInstanceResource(null);
return;
}
// This loop adjusts the opposite wrapper objects to support dangling references. See Bugzilla_251263_Test
InternalCDORevision revision = cdoRevision();
for (EReference reference : classInfo.getAllPersistentReferences())
{
if (!reference.isContainer() && classInfo.hasPersistentOpposite(reference))
{
if (reference.isMany())
{
EReference oppositeReference = reference.getEOpposite();
int size = revision.size(reference);
for (int i = 0; i < size; i++)
{
EObject object = (EObject)getValueFromRevision(reference, i);
adjustPersistentOppositeReference(this, object, oppositeReference);
}
}
else
{
EObject oppositeObject = (EObject)instance.eGet(reference);
if (oppositeObject != null)
{
EReference oppositeReference = reference.getEOpposite();
adjustPersistentOppositeReference(this, oppositeObject, oppositeReference);
}
}
}
}
}
/**
* @since 3.0
*/
public void cdoInternalPostRollback()
{
CDOStateMachine.INSTANCE.read(this);
}
/**
* CDO persists the isUnset state of an eObject in the database. The indicator for this is that the feature is null in
* the revision (see CDOStore.isSet()). When committing a legacy object all values in the instance for native
* attributes are set with the java default values. So, these values will be stored in the revision and CDO cannot
* distinguish whether the feature is set or not. This method must ensure that the value will be set to null if the
* feature is not set.
*/
public void cdoInternalPreCommit()
{
// We have to set this here because the CDOLegacyAdapter will not be notified when the instance is the target of a
// single-directional containment reference.
// If the container is not an legacy Object the system will get no information
instanceToRevisionContainment();
InternalCDORevision revision = cdoRevision();
for (EStructuralFeature feature : classInfo.getAllPersistentFeatures())
{
if (feature.isUnsettable())
{
if (!isSetInstanceValue(instance, feature))
{
if (feature.isMany())
{
@SuppressWarnings("unchecked")
InternalEList<Object> list = (InternalEList<Object>)instance.eGet(feature);
clearEList(list);
}
else
{
revision.set(feature, EStore.NO_INDEX, null);
}
}
else if (instance.eGet(feature) == null)
{
// Must be single-valued!
revision.set(feature, EStore.NO_INDEX, CDORevisionData.NIL);
}
}
}
}
public void cdoInternalPreLoad()
{
// Do nothing
}
public void cdoInternalPostLoad()
{
// TODO Consider not remembering the revision after copying it to the instance (spare 1/2 of the space)
revisionToInstance();
}
public void cdoInternalPostInvalidate()
{
if (cdoState() != CDOState.PROXY)
{
throw new IllegalStateException();
}
InternalCDORevision revision = cdoView().getRevision(cdoID(), true);
if (revision == null)
{
cdoInternalPostDetach(true);
}
else
{
cdoInternalSetRevision(revision);
revisionToInstance();
cdoInternalSetState(CDOState.CLEAN);
}
}
protected void instanceToRevision()
{
InternalCDORevision revision = cdoRevision();
if (TRACER.isEnabled())
{
TRACER.format("Transfering instance to revision: {0} --> {1}", instance, revision); //$NON-NLS-1$
}
// Handle containment
instanceToRevisionContainment();
// Handle values
for (EStructuralFeature feature : classInfo.getAllPersistentFeatures())
{
instanceToRevisionFeature(feature);
}
revision.setUnchunked();
}
protected void instanceToRevisionContainment()
{
InternalCDORevision revision = cdoRevision();
CDOResource resource = (CDOResource)getInstanceResource(instance);
revision.setResourceID(resource == null ? CDOID.NULL : resource.cdoID());
InternalEObject eContainer = getInstanceContainer(instance);
if (eContainer == null)
{
revision.setContainerID(CDOID.NULL);
revision.setContainingFeatureID(0);
}
else
{
CDOObject cdoContainer = FSMUtil.adapt(eContainer, viewAndState.view);
revision.setContainerID(cdoContainer);
revision.setContainingFeatureID(getInstanceContainerFeatureID(instance));
}
}
protected void instanceToRevisionFeature(EStructuralFeature feature)
{
if (isSetInstanceValue(instance, feature))
{
Object instanceValue = getInstanceValue(instance, feature);
CDOObjectImpl.instanceToRevisionFeature(viewAndState.view, this, feature, instanceValue);
}
}
protected void revisionToInstance()
{
synchronized (recursionCounter)
{
if (underConstruction)
{
// Return if revisionToInstance was called before to avoid doubled calls
return;
}
underConstruction = true;
InternalCDORevision revision = cdoRevision();
if (TRACER.isEnabled())
{
TRACER.format("Transfering revision to instance: {0} --> {1}", revision, instance); //$NON-NLS-1$
}
boolean deliver = instance.eDeliver();
if (deliver)
{
instance.eSetDeliver(false);
}
Counter counter = recursionCounter.get();
if (counter == null)
{
counter = new Counter();
recursionCounter.set(counter);
}
InternalCDOResource resource = null;
boolean bypassPermissionChecks = revision.bypassPermissionChecks(true);
try
{
registerWrapper(this);
counter.increment();
viewAndState.view.registerObject(this);
revisionToInstanceResource();
revisionToInstanceContainer();
Resource eResource = instance.eResource();
if (eResource instanceof InternalCDOResource)
{
resource = (InternalCDOResource)eResource;
resource.cdoInternalLoading(instance);
}
for (EStructuralFeature feature : classInfo.getAllPersistentFeatures())
{
revisionToInstanceFeature(feature);
}
}
catch (RuntimeException ex)
{
OM.LOG.error(ex);
throw ex;
}
catch (Exception ex)
{
OM.LOG.error(ex);
throw new CDOException(ex);
}
finally
{
try
{
revision.bypassPermissionChecks(bypassPermissionChecks);
if (resource != null)
{
resource.cdoInternalLoadingDone(instance);
}
if (deliver)
{
instance.eSetDeliver(true);
}
}
finally
{
if (counter.decrement() == 0)
{
recursionCounter.remove();
}
unregisterWrapper(this);
underConstruction = false;
}
}
}
}
/**
* @since 4.0
*/
protected void revisionToInstanceContainer()
{
InternalCDORevision revision = cdoRevision();
CDOPermission permission = revision.getPermission();
if (permission != CDOPermission.WRITE)
{
revision.setPermission(CDOPermission.WRITE);
}
try
{
Object containerID = revision.getContainerID();
InternalEObject container = getEObjectFromPotentialID(viewAndState.view, null, containerID);
EObject oldContainer = instance.eContainer();
if (oldContainer != container)
{
setInstanceContainer(container, revision.getContainingFeatureID());
}
}
finally
{
if (permission != CDOPermission.WRITE)
{
revision.setPermission(permission);
}
}
}
/**
* @since 4.0
*/
protected void revisionToInstanceResource()
{
InternalCDORevision revision = cdoRevision();
if (revision != null)
{
CDOID resourceID = revision.getResourceID();
if (!CDOIDUtil.isNull(resourceID))
{
InternalEObject resource = getEObjectFromPotentialID(viewAndState.view, null, resourceID);
setInstanceResource((Resource.Internal)resource);
if (resource != null)
{
viewAndState.view.registerObject((InternalCDOObject)resource);
}
}
}
}
/**
* @since 3.0
*/
protected void revisionToInstanceFeature(EStructuralFeature feature)
{
if (feature.isUnsettable() && !viewAndState.view.getStore().isSet(this, feature))
{
// Clarify if this is sufficient for bidirectional references
instance.eUnset(feature);
return;
}
if (feature.isMany())
{
if (TRACER.isEnabled())
{
TRACER.format("State of Object (" + this + "/" + instance + ") is : " + viewAndState.state); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
if (viewAndState.state == CDOState.CLEAN || viewAndState.state == CDOState.PROXY
|| viewAndState.state == CDOState.NEW || viewAndState.state == CDOState.DIRTY)
{
InternalCDORevision revision = cdoRevision();
int size = revision.size(feature);
@SuppressWarnings("unchecked")
InternalEList<Object> list = (InternalEList<Object>)instance.eGet(feature);
clearEList(list);
for (int i = 0; i < size; i++)
{
Object object = getValueFromRevision(feature, i);
if (TRACER.isEnabled())
{
TRACER.format("Adding " + object + " to feature " + feature + "in instance " + instance); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
// Disable notifications from value during the
// invalidation in case of
// eInverseAdd/eInverseRemove
boolean eDeliver = false;
if (object instanceof Notifier)
{
Notifier notifier = (Notifier)object;
eDeliver = notifier.eDeliver();
if (eDeliver)
{
notifier.eSetDeliver(false);
}
}
list.basicAdd(object, null);
if (object instanceof Notifier && eDeliver)
{
Notifier notifier = (Notifier)object;
notifier.eSetDeliver(eDeliver);
}
}
}
}
else
{
// !feature.isMany()
Object object = getValueFromRevision(feature, 0);
if (feature instanceof EAttribute)
{
if (TRACER.isEnabled())
{
TRACER.format("Setting attribute value " + object + " to feature " + feature + " in instance " + instance); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
// Just fake it for the store :(
if (feature.isUnsettable() && object.equals(CDORevisionData.NIL))
{
eSet(feature, null);
}
else
{
if (object != null)
{
eSet(feature, object);
}
else
{
// TODO Unset for features with non-null default values would not lead to null values.
// Probably CDORevisionData.NIL has to be used, but that impacts all IStores. Deferred ;-(
eUnset(feature);
}
}
}
else
{
// EReferences
if (TRACER.isEnabled())
{
TRACER.format("Adding object " + object + " to feature " + feature + " in instance " + instance); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
// Disable notifications from value during the
// invalidation in case of
// eInverseAdd/eInverseRemove
boolean eDeliver = false;
if (object instanceof Notifier)
{
Notifier notifier = (Notifier)object;
eDeliver = notifier.eDeliver();
if (eDeliver)
{
notifier.eSetDeliver(false);
}
}
EObject oldContainerOfValue = null;
boolean eDeliverForOldContainerOfValue = false;
if (object instanceof InternalEObject)
{
InternalEObject eObject = (InternalEObject)object;
oldContainerOfValue = eObject.eInternalContainer();
if (oldContainerOfValue != null)
{
eDeliverForOldContainerOfValue = oldContainerOfValue.eDeliver();
if (eDeliverForOldContainerOfValue)
{
oldContainerOfValue.eSetDeliver(false);
}
}
}
int featureID = instance.eClass().getFeatureID(feature);
Class<? extends Object> baseClass = object == null ? null : object.getClass();
EStructuralFeature.Internal internalFeature = (EStructuralFeature.Internal)feature;
EReference oppositeReference = internalFeature.getEOpposite();
if (oppositeReference != null)
{
if (object != null && object != instance.eGet(feature))
{
// If you have a containment reference but the container is not set, but the object is attached to a
// resource
// do not set the feature to null. Otherwise the object will be removed from the container which is the
// resource instead of the original container. As a result the object will be detached. See
// MapTest.testEObjectToEObjectValueContainedMap for more information
if (object != instance.eContainer() || !oppositeReference.isContainment())
{
instance.eInverseAdd((InternalEObject)object, featureID, baseClass, null);
}
if (!classInfo.hasPersistentOpposite(internalFeature))
{
adjustTransientOppositeReference(instance, (InternalEObject)object, oppositeReference);
}
}
}
else
{
if (object != CDORevisionData.NIL)
{
EReference reference = (EReference)feature;
if (reference.isContainment())
{
if (object != null)
{
// Calling eSet it not the optimal approach, but currently there is no other way to set the value here.
// To avoid attaching already processed (clean) objects a check was introduced to
// CDOResourceImpl.attached(EObject).
// If we find a way to avoid the call of eSet and if we are able to only set the feature value directly
// this check can be removed from CDOResourceImpl. See also Bug 352204.
instance.eSet(feature, object);
}
else
{
instance.eSet(feature, null);
}
}
else
{
instance.eSet(feature, object);
}
}
else
{
instance.eSet(feature, null);
}
}
if (object instanceof Notifier && eDeliver)
{
Notifier notifier = (Notifier)object;
notifier.eSetDeliver(eDeliver);
}
if (oldContainerOfValue != null && eDeliverForOldContainerOfValue)
{
oldContainerOfValue.eSetDeliver(eDeliverForOldContainerOfValue);
}
if (TRACER.isEnabled())
{
TRACER.format("Added object " + object + " to feature " + feature + " in instance " + instance); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
}
}
/**
* This method retrieves the value from the feature at the given index. It retrieves the value either from the views's
* store or the internal pre-registration Map.
*
* @param feature
* the feature to retrieve the value from
* @param index
* the given index of the object in the feature
* @return the value from the feature at the given index
*/
private Object getValueFromRevision(EStructuralFeature feature, int index)
{
InternalCDORevision revision = cdoRevision();
Object object = revision.get(feature, index);
if (object == null)
{
return null;
}
if (object instanceof CDOElementProxy)
{
// Resolve proxy
CDOElementProxy proxy = (CDOElementProxy)object;
object = viewAndState.view.getSession().resolveElementProxy(revision, feature, index, proxy.getIndex());
}
if (object instanceof CDOLegacyWrapper)
{
return ((CDOLegacyWrapper)object).cdoInternalInstance();
}
CDOType type = CDOModelUtil.getType(feature.getEType());
object = viewAndState.view.getStore().convertToEMF(instance, revision, feature, index, object);
if (type == CDOType.OBJECT)
{
if (object instanceof CDOID)
{
CDOID id = (CDOID)object;
if (id.isNull())
{
return null;
}
object = getRegisteredWrapper(id);
if (object != null)
{
return ((CDOLegacyWrapper)object).cdoInternalInstance();
}
if (id.isExternal())
{
object = viewAndState.view.getResourceSet().getEObject(URI.createURI(id.toURIFragment()), true);
}
else
{
object = viewAndState.view.getObject(id);
}
if (object instanceof CDOObjectWrapper)
{
return ((CDOObjectWrapper)object).cdoInternalInstance();
}
}
}
return object;
}
/**
* @param feature
* in case that a proxy has to be created the feature that will determine the interface type of the proxy and
* that will be used later to resolve the proxy. <code>null</code> indicates that proxy creation will be
* avoided!
*/
protected InternalEObject getEObjectFromPotentialID(InternalCDOView view, EStructuralFeature feature,
Object potentialID)
{
CDOLegacyWrapper wrapper;
if (potentialID instanceof CDOID && (wrapper = getRegisteredWrapper((CDOID)potentialID)) != null)
{
potentialID = wrapper.instance;
if (TRACER.isEnabled())
{
TRACER.format("Getting Object (" + potentialID + ") from localThread instead of the view"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
else
{
if (potentialID instanceof CDOID)
{
CDOID id = (CDOID)potentialID;
if (id.isNull())
{
return null;
}
if (id.isExternal())
{
URI uri = URI.createURI(id.toURIFragment());
InternalEObject eObject = (InternalEObject)viewAndState.view.getResourceSet().getEObject(uri, true);
return eObject;
}
boolean loadOnDemand = feature == null;
potentialID = viewAndState.view.getObject(id, loadOnDemand);
if (potentialID == null && !loadOnDemand)
{
return createProxy(view, feature, id);
}
}
if (potentialID instanceof InternalCDOObject)
{
return ((InternalCDOObject)potentialID).cdoInternalInstance();
}
}
return (InternalEObject)potentialID;
}
/**
* Creates and returns a <em>proxy</em> object. The usage of a proxy object is strongly limited. The only guarantee
* that can be made is that the following methods are callable and will behave in the expected way:
* <ul>
* <li>{@link CDOObject#cdoID()} will return the {@link CDOID} of the target object
* <li>{@link CDOObject#cdoState()} will return {@link CDOState#PROXY PROXY}
* <li>{@link InternalEObject#eIsProxy()} will return <code>true</code>
* <li>{@link InternalEObject#eProxyURI()} will return the EMF proxy URI of the target object
* </ul>
* Calling any other method on the proxy object will result in an {@link UnsupportedOperationException} being thrown
* at runtime. Note also that the proxy object might even not be cast to the concrete type of the target object. The
* proxy can only guaranteed to be of <em>any</em> concrete subtype of the declared type of the given feature.
* <p>
* TODO {@link InternalEObject#eResolveProxy(InternalEObject)}
*/
protected InternalEObject createProxy(InternalCDOView view, EStructuralFeature feature, CDOID id)
{
EClassifier eType = feature.getEType();
Class<?> instanceClass = eType.getInstanceClass();
Class<?>[] interfaces = { instanceClass, InternalEObject.class, LegacyProxy.class };
ClassLoader classLoader = CDOLegacyWrapper.class.getClassLoader();
LegacyProxyInvocationHandler handler = new LegacyProxyInvocationHandler(this, id);
return (InternalEObject)Proxy.newProxyInstance(classLoader, interfaces, handler);
}
protected void clearEList(InternalEList<?> list)
{
for (int i = list.size() - 1; i >= 0; --i)
{
Object obj = list.get(i);
// Disable notifications from value during the
// invalidation in case of
// eInverseAdd/eInverseRemove
boolean eDeliver = false;
if (obj instanceof Notifier)
{
Notifier notifier = (Notifier)obj;
eDeliver = notifier.eDeliver();
if (eDeliver)
{
notifier.eSetDeliver(false);
}
}
list.basicRemove(obj, null);
if (obj instanceof Notifier && eDeliver)
{
Notifier notifier = (Notifier)obj;
notifier.eSetDeliver(eDeliver);
}
}
}
/**
* TODO Consider using only EMF concepts for resolving proxies!
*/
protected void resolveAllProxies()
{
for (EStructuralFeature feature : classInfo.getAllPersistentFeatures())
{
if (feature instanceof EReference)
{
resolveProxies(feature);
}
}
}
/**
* IMPORTANT: Compile errors in this method might indicate an old version of EMF. Legacy support is only enabled for
* EMF with fixed bug #247130. These compile errors do not affect native models!
*/
protected void resolveProxies(EStructuralFeature feature)
{
Object value = getInstanceValue(instance, feature);
if (value != null)
{
if (feature.isMany())
{
@SuppressWarnings("unchecked")
InternalEList<Object> list = (InternalEList<Object>)value;
int size = list.size();
boolean deliver = instance.eDeliver();
if (deliver)
{
instance.eSetDeliver(false);
}
for (int i = 0; i < size; i++)
{
Object element = list.get(i);
if (element instanceof LegacyProxy)
{
CDOID id = ((LegacyProxy)element).getID();
InternalCDOObject resolved = (InternalCDOObject)viewAndState.view.getObject(id);
InternalEObject instance = resolved.cdoInternalInstance();
// TODO LEGACY
// // TODO Is InternalEList.basicSet() needed???
// if (list instanceof
// org.eclipse.emf.ecore.util.DelegatingInternalEList)
// {
// list =
// ((org.eclipse.emf.ecore.util.DelegatingInternalEList)list).getDelegateInternalEList();
// }
// if (list instanceof NotifyingListImpl<?>)
// {
// ((NotifyingListImpl<Object>)list).basicSet(i, instance, null);
// }
// else
// {
list.set(i, instance);
// }
}
}
if (deliver)
{
instance.eSetDeliver(true);
}
}
else
{
if (value instanceof LegacyProxy)
{
CDOID id = ((LegacyProxy)value).getID();
InternalCDOObject resolved = (InternalCDOObject)viewAndState.view.getObject(id);
InternalEObject instance = resolved.cdoInternalInstance();
setInstanceValue(instance, feature, instance);
}
}
}
}
protected void adjustEProxy()
{
// Setting eProxyURI is necessary to prevent content adapters from
// loading the whole content tree.
// TODO Does not have the desired effect ;-( see CDOEditor.createModel()
if (viewAndState.state == CDOState.PROXY)
{
if (!instance.eIsProxy())
{
URI uri = URI.createURI(CDOProtocolConstants.PROTOCOL_NAME + ":proxy#" + cdoID()); //$NON-NLS-1$
if (TRACER.isEnabled())
{
TRACER.format("Setting proxyURI {0} for {1}", uri, instance); //$NON-NLS-1$
}
instance.eSetProxyURI(uri);
}
}
else
{
if (instance.eIsProxy())
{
if (TRACER.isEnabled())
{
TRACER.format("Unsetting proxyURI for {0}", instance); //$NON-NLS-1$
}
instance.eSetProxyURI(null);
}
}
}
@Override
public synchronized EList<Adapter> eAdapters()
{
EList<Adapter> adapters = super.eAdapters();
if (!FSMUtil.isTransient(this))
{
InternalCDOView view = cdoView();
for (Adapter adapter : adapters)
{
if (!(adapter instanceof CDOLegacyWrapper))
{
view.handleAddAdapter(this, adapter);
}
}
}
return adapters;
}
public static boolean isLegacyProxy(Object object)
{
return object instanceof LegacyProxy;
}
protected static int getEFlagMask(Class<?> instanceClass, String flagName)
{
Field field = ReflectUtil.getField(instanceClass, flagName);
if (!field.isAccessible())
{
field.setAccessible(true);
}
try
{
return (Integer)field.get(null);
}
catch (IllegalAccessException ex)
{
throw WrappedException.wrap(ex);
}
}
/**
* @since 3.0
*/
private static CDOLegacyWrapper getRegisteredWrapper(CDOID id)
{
Map<CDOID, CDOLegacyWrapper> map = wrapperRegistry.get();
if (map == null)
{
return null;
}
return map.get(id);
}
/**
* Adds an object to the pre-registered objects list which hold all created objects even if they are not registered in
* the view
*
* @since 3.0
*/
private static void registerWrapper(CDOLegacyWrapper wrapper)
{
Map<CDOID, CDOLegacyWrapper> map = wrapperRegistry.get();
if (map == null)
{
map = CDOIDUtil.createMap();
wrapperRegistry.set(map);
}
map.put(wrapper.cdoID(), wrapper);
}
/**
* @since 3.0
*/
private static void unregisterWrapper(CDOLegacyWrapper wrapper)
{
Map<CDOID, CDOLegacyWrapper> map = wrapperRegistry.get();
if (map == null)
{
return;
}
CDOID id = wrapper.cdoID();
if (map.remove(id) != null)
{
if (map.isEmpty())
{
wrapperRegistry.remove();
}
}
}
private void adjustPersistentOppositeReference(InternalCDOObject cdoObject, EObject oppositeObject,
EReference oppositeReference)
{
InternalCDOObject oppositeCDOObject = (InternalCDOObject)CDOUtil.getCDOObject(oppositeObject);
if (oppositeCDOObject != null)
{
InternalCDOView view = oppositeCDOObject.cdoView();
if (view != null)
{
CDOStore store = viewAndState.view.getStore();
if (store != null)
{
if (oppositeReference.isMany())
{
EObject eObject = oppositeCDOObject.cdoInternalInstance();
@SuppressWarnings("unchecked")
EList<Object> list = (EList<Object>)eObject.eGet(oppositeReference);
int index = list.indexOf(instance);
if (index != EStore.NO_INDEX && !store.isEmpty(oppositeCDOObject, oppositeReference))
{
store.set(oppositeCDOObject, oppositeReference, index, cdoObject);
}
}
else
{
store.set(oppositeCDOObject, oppositeReference, 0, cdoObject);
}
}
}
}
}
private void adjustTransientOppositeReference(InternalEObject instance, InternalEObject object,
EReference oppositeReference)
{
boolean wasDeliver = object.eDeliver(); // Disable notifications
if (wasDeliver)
{
object.eSetDeliver(false);
}
try
{
if (oppositeReference.isMany())
{
@SuppressWarnings("unchecked")
InternalEList<Object> list = (InternalEList<Object>)object.eGet(oppositeReference);
list.basicAdd(instance, null);
}
else
{
if (object.eGet(oppositeReference) != instance)
{
object.eInverseAdd(instance, oppositeReference.getFeatureID(), ((EObject)instance).getClass(), null);
}
}
}
finally
{
if (wasDeliver)
{
object.eSetDeliver(true);
}
}
}
/**
* @author Eike Stepper
*/
private static interface LegacyProxy extends CDOIdentifiable
{
}
/**
* @author Eike Stepper
*/
private static final class LegacyProxyInvocationHandler implements InvocationHandler, LegacyProxy
{
private static final Method getIDMethod = ReflectUtil.getMethod(LegacyProxy.class, "getID"); //$NON-NLS-1$
private static final Method eIsProxyMethod = ReflectUtil.getMethod(EObject.class, "eIsProxy"); //$NON-NLS-1$
private static final Method eProxyURIMethod = ReflectUtil.getMethod(InternalEObject.class, "eProxyURI"); //$NON-NLS-1$
private CDOLegacyWrapper wrapper;
private CDOID id;
public LegacyProxyInvocationHandler(CDOLegacyWrapper wrapper, CDOID id)
{
this.wrapper = wrapper;
this.id = id;
}
public CDOID getID()
{
return id;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
if (method.equals(getIDMethod))
{
return id;
}
if (method.equals(eIsProxyMethod))
{
return true;
}
if (method.equals(eProxyURIMethod))
{
// Use container's resource because it's guaranteed to be in the same CDOView as the resource of the target!
Resource resource = wrapper.eResource();
// TODO Consider using a "fake" Resource implementation. See Resource.getEObject(...)
return resource.getURI().appendFragment(id.toURIFragment());
}
// A client must have invoked the proxy while being told not to do so!
throw new UnsupportedOperationException(method.getName());
}
}
/**
* @author Martin Fluegge
*/
private static final class Counter
{
private int value;
public Counter()
{
}
public void increment()
{
++value;
}
public int decrement()
{
return --value;
}
}
}