diff options
author | Eike Stepper | 2008-09-18 12:57:24 +0000 |
---|---|---|
committer | Eike Stepper | 2008-09-18 12:57:24 +0000 |
commit | 25059b9f4277a331568ced5198fc87177c97a0df (patch) | |
tree | 8b93331ef33c0e990b9d934aaa541544e6e78891 /plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOLegacyWrapper.java | |
parent | fc9b00dd988bddeb67ab89f6c077cb7eb8a5dcc6 (diff) | |
download | cdo-25059b9f4277a331568ced5198fc87177c97a0df.tar.gz cdo-25059b9f4277a331568ced5198fc87177c97a0df.tar.xz cdo-25059b9f4277a331568ced5198fc87177c97a0df.zip |
[247226] Transparently support legacy models
https://bugs.eclipse.org/bugs/show_bug.cgi?id=247226
Diffstat (limited to 'plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOLegacyWrapper.java')
-rw-r--r-- | plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOLegacyWrapper.java | 783 |
1 files changed, 783 insertions, 0 deletions
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOLegacyWrapper.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOLegacyWrapper.java new file mode 100644 index 0000000000..9815902d89 --- /dev/null +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOLegacyWrapper.java @@ -0,0 +1,783 @@ +/*************************************************************************** + * Copyright (c) 2004 - 2008 Eike Stepper, Germany. + * 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 + **************************************************************************/ +package org.eclipse.emf.internal.cdo; + +import org.eclipse.emf.cdo.CDOObject; +import org.eclipse.emf.cdo.CDOState; +import org.eclipse.emf.cdo.common.CDOProtocolConstants; +import org.eclipse.emf.cdo.common.id.CDOID; +import org.eclipse.emf.cdo.common.model.CDOClass; +import org.eclipse.emf.cdo.common.model.CDOFeature; +import org.eclipse.emf.cdo.common.model.CDOType; +import org.eclipse.emf.cdo.common.revision.CDORevision; +import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta; +import org.eclipse.emf.cdo.eresource.CDOResource; +import org.eclipse.emf.cdo.eresource.impl.CDOResourceImpl; +import org.eclipse.emf.cdo.spi.common.InternalCDORevision; +import org.eclipse.emf.cdo.util.CDOPackageRegistry; + +import org.eclipse.emf.internal.cdo.bundle.OM; +import org.eclipse.emf.internal.cdo.util.FSMUtil; +import org.eclipse.emf.internal.cdo.util.GenUtil; +import org.eclipse.emf.internal.cdo.util.ModelUtil; + +import org.eclipse.net4j.util.ImplementationError; +import org.eclipse.net4j.util.ReflectUtil; +import org.eclipse.net4j.util.om.trace.ContextTracer; + +import org.eclipse.emf.common.notify.Adapter; +import org.eclipse.emf.common.notify.impl.NotifyingListImpl; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.InternalEObject; +import org.eclipse.emf.ecore.impl.EAttributeImpl; +import org.eclipse.emf.ecore.impl.EClassImpl; +import org.eclipse.emf.ecore.impl.EDataTypeImpl; +import org.eclipse.emf.ecore.impl.EObjectImpl; +import org.eclipse.emf.ecore.impl.EReferenceImpl; +import org.eclipse.emf.ecore.impl.EStructuralFeatureImpl; +import org.eclipse.emf.ecore.impl.ETypedElementImpl; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.util.InternalEList; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Iterator; +import java.util.List; + +/** + * @author Eike Stepper + * @since 2.0 + */ +/* + * IMPORTANT: Compile errors in this class 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! + */ +public final class CDOLegacyWrapper extends CDOObjectWrapper implements InternalEObject.EReadListener, + InternalEObject.EWriteListener +{ + private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG_OBJECT, CDOLegacyWrapper.class); + + private static final Method eSetDirectResourceMethod = ReflectUtil.getMethod(EObjectImpl.class, "eSetDirectResource", + Resource.Internal.class); + + private static final Method eBasicSetContainerMethod = ReflectUtil.getMethod(EObjectImpl.class, "eBasicSetContainer", + InternalEObject.class, int.class); + + private CDOState state; + + private CDOResourceImpl resource; + + private InternalCDORevision revision; + + private boolean allProxiesResolved; + + private boolean handlingCallback; + + public CDOLegacyWrapper(InternalEObject instance) + { + this.instance = instance; + state = CDOState.TRANSIENT; + } + + public CDOClass cdoClass() + { + return CDOObjectImpl.getCDOClass(this); + } + + public CDOState cdoState() + { + return state; + } + + public InternalCDORevision cdoRevision() + { + return revision; + } + + public CDOResourceImpl cdoResource() + { + return resource; + } + + public void cdoReload() + { + CDOStateMachine.INSTANCE.reload(this); + } + + public CDOState cdoInternalSetState(CDOState state) + { + if (this.state != state) + { + if (TRACER.isEnabled()) + { + TRACER.format("Setting state {0} for {1}", state, this); + } + + CDOState tmp = this.state; + this.state = state; + adjustEProxy(); + return tmp; + } + + // TODO Detect duplicate cdoInternalSetState() calls + return null; + } + + public void cdoInternalSetRevision(CDORevision revision) + { + if (TRACER.isEnabled()) + { + TRACER.format("Setting revision: {0}", revision); + } + + this.revision = (InternalCDORevision)revision; + } + + public void cdoInternalSetResource(CDOResource resource) + { + if (TRACER.isEnabled()) + { + TRACER.format("Setting resource: {0}", resource); + } + + this.resource = (CDOResourceImpl)resource; + } + + public void cdoInternalPostAttach() + { + // TODO Avoid if no adapters in list (eBasicAdapters?) + // TODO LEGACY Clarify how to intercept adapter addition in the legacy instance + for (Adapter adapter : eAdapters()) + { + view.subscribe(this, adapter); + } + } + + public void cdoInternalPostDetach() + { + // Do nothing + } + + public void cdoInternalPreCommit() + { + instanceToRevision(); + if (cdoState() == CDOState.DIRTY) // NEW is handled in PrepareTransition + { + CDORevisionManagerImpl revisionManager = (CDORevisionManagerImpl)revision.getRevisionResolver(); + InternalCDORevision originRevision = revisionManager.getRevisionByVersion(revision.getID(), + CDORevision.UNCHUNKED, revision.getVersion() - 1, false); + CDORevisionDelta delta = revision.compare(originRevision); + + // TODO LEGACY Consider to gather the deltas on the fly with noremal EMF change notifications + cdoView().toTransaction().registerRevisionDelta(delta); + } + } + + public void cdoInternalPostLoad() + { + // TODO Consider not remembering the revisin after copying it to the instance (spare 1/2 of the space) + revisionToInstance(); + } + + public synchronized void handleRead(InternalEObject object, int featureID) + { + if (!handlingCallback) + { + try + { + handlingCallback = true; + CDOStateMachine.INSTANCE.read(this); + + // TODO Optimize this when the list position index is added to the new callbacks + resolveAllProxies(); + } + finally + { + handlingCallback = false; + } + } + } + + public synchronized void handleWrite(InternalEObject object, int featureID) + { + if (!handlingCallback) + { + try + { + handlingCallback = true; + CDOStateMachine.INSTANCE.write(this); + + // TODO Optimize this when the list position index is added to the new callbacks + resolveAllProxies(); + } + finally + { + handlingCallback = false; + } + } + } + + @Override + public boolean equals(Object obj) + { + return obj == this || obj == instance; + } + + @Override + public int hashCode() + { + if (instance != null) + { + return instance.hashCode(); + } + + return super.hashCode(); + } + + @Override + public String toString() + { + return "CDOLegacyWrapper[" + id + "]"; + } + + private void instanceToRevision() + { + if (TRACER.isEnabled()) + { + TRACER.format("Transfering instance to revision: {0} --> {1}", instance, revision); + } + + // Handle containment + instanceToRevisionContainment(); + + // Handle values + CDOPackageRegistry packageRegistry = cdoView().getSession().getPackageRegistry(); + CDOClass cdoClass = revision.getCDOClass(); + for (CDOFeature feature : cdoClass.getAllFeatures()) + { + instanceToRevisionFeature(feature, packageRegistry); + } + } + + private void instanceToRevisionContainment() + { + 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, view); + revision.setContainerID(cdoContainer.cdoID()); + revision.setContainingFeatureID(getInstanceContainerFeatureID(instance)); + } + } + + private void instanceToRevisionFeature(CDOFeature feature, CDOPackageRegistry packageRegistry) + { + Object instanceValue = getInstanceValue(instance, feature, packageRegistry); + if (feature.isMany()) + { + List<Object> revisionList = revision.getList(feature); // TODO lazy? + revisionList.clear(); + + if (instanceValue != null) + { + InternalEList<?> instanceList = (InternalEList<?>)instanceValue; + if (!instanceList.isEmpty()) + { + for (Iterator<?> it = instanceList.basicIterator(); it.hasNext();) + { + Object instanceElement = it.next(); + if (instanceElement != null && feature.isReference()) + { + instanceElement = view.convertObjectToID(instanceElement); + } + + revisionList.add(instanceElement); + } + } + } + } + else + { + if (instanceValue != null && feature.isReference()) + { + instanceValue = view.convertObjectToID(instanceValue); + } + + revision.setValue(feature, instanceValue); + } + } + + /** + * TODO Simon: Fix this whole mess ;-) + */ + private void revisionToInstance() + { + if (TRACER.isEnabled()) + { + TRACER.format("Transfering revision to instance: {0} --> {1}", revision, instance); + } + + boolean deliver = instance.eDeliver(); + if (deliver) + { + instance.eSetDeliver(false); + } + + try + { + // Handle containment + revisionToInstanceContainment(); + + // Handle values + CDOPackageRegistry packageRegistry = cdoView().getSession().getPackageRegistry(); + CDOClass cdoClass = revision.getCDOClass(); + for (CDOFeature feature : cdoClass.getAllFeatures()) + { + revisionToInstanceFeature(feature, packageRegistry); + } + } + finally + { + if (deliver) + { + instance.eSetDeliver(true); + } + } + } + + private void revisionToInstanceContainment() + { + CDOID resourceID = revision.getResourceID(); + InternalEObject resource = getEObjectFromPotentialID(view, null, resourceID); + setInstanceResource((Resource.Internal)resource); + + Object containerID = revision.getContainerID(); + InternalEObject container = getEObjectFromPotentialID(view, null, containerID); + setInstanceContainer(container, revision.getContainingFeatureID()); + } + + @SuppressWarnings("unchecked") + private void revisionToInstanceFeature(CDOFeature feature, CDOPackageRegistry packageRegistry) + { + Object value = revision.getValue(feature); + if (feature.isMany()) + { + InternalEList<Object> instanceList = (InternalEList<Object>)getInstanceValue(instance, feature, packageRegistry); + if (instanceList != null) + { + clearEList(instanceList); + if (value != null) + { + List<?> revisionList = (List<?>)value; + if (feature.isReference()) + { + for (Object element : revisionList) + { + element = getEObjectFromPotentialID(view, feature, element); + instanceList.basicAdd(element, null); + } + } + else + { + // TODO Is this only for multi-valued attributes?? + for (Object element : revisionList) + { + instanceList.basicAdd(element, null); + } + } + } + } + } + else + { + if (feature.isReference()) + { + value = getEObjectFromPotentialID(view, feature, value); + } + + setInstanceValue(instance, feature, value); + } + } + + private Resource.Internal getInstanceResource(InternalEObject instance) + { + return instance.eDirectResource(); + } + + private InternalEObject getInstanceContainer(InternalEObject instance) + { + return instance.eInternalContainer(); + } + + private int getInstanceContainerFeatureID(InternalEObject instance) + { + return instance.eContainerFeatureID(); + } + + private Object getInstanceValue(InternalEObject instance, CDOFeature feature, CDOPackageRegistry packageRegistry) + { + EStructuralFeature eFeature = ModelUtil.getEFeature(feature, packageRegistry); + return instance.eGet(eFeature); + } + + private void setInstanceResource(Resource.Internal resource) + { + ReflectUtil.invokeMethod(eSetDirectResourceMethod, instance, resource); + } + + private void setInstanceContainer(InternalEObject container, int containerFeatureID) + { + ReflectUtil.invokeMethod(eBasicSetContainerMethod, instance, container, containerFeatureID); + } + + /** + * TODO Ed: Help to fix whole mess (avoid inverse updates) + */ + private void setInstanceValue(InternalEObject instance, CDOFeature feature, Object value) + { + // TODO Consider EStoreEObjectImpl based objects as well! + // TODO Don't use Java reflection + Class<?> instanceClass = instance.getClass(); + String featureName = feature.getName(); + String fieldName = featureName;// TODO safeName() + Field field = ReflectUtil.getField(instanceClass, fieldName); + if (field == null && feature.getType() == CDOType.BOOLEAN) + { + if (instanceClass.isAssignableFrom(EAttributeImpl.class) || instanceClass.isAssignableFrom(EClassImpl.class) + || instanceClass.isAssignableFrom(EDataTypeImpl.class) + || instanceClass.isAssignableFrom(EReferenceImpl.class) + || instanceClass.isAssignableFrom(EStructuralFeatureImpl.class) + || instanceClass.isAssignableFrom(ETypedElementImpl.class)) + { + // ******************************************* + // ID_EFLAG = 1 << 15; + // ******************************************* + // ABSTRACT_EFLAG = 1 << 8; + // INTERFACE_EFLAG = 1 << 9; + // ******************************************* + // SERIALIZABLE_EFLAG = 1 << 8; + // ******************************************* + // CONTAINMENT_EFLAG = 1 << 15; + // RESOLVE_PROXIES_EFLAG = 1 << 16; + // ******************************************* + // CHANGEABLE_EFLAG = 1 << 10; + // VOLATILE_EFLAG = 1 << 11; + // TRANSIENT_EFLAG = 1 << 12; + // UNSETTABLE_EFLAG = 1 << 13; + // DERIVED_EFLAG = 1 << 14; + // ******************************************* + // ORDERED_EFLAG = 1 << 8; + // UNIQUE_EFLAG = 1 << 9; + // ******************************************* + + String flagName = GenUtil.getFeatureUpperName(featureName) + "_EFLAG"; + int flagsMask = getEFlagMask(instanceClass, flagName); + + field = ReflectUtil.getField(instanceClass, "eFlags"); + int flags = (Integer)ReflectUtil.getValue(field, instance); + boolean on = (Boolean)value; + if (on) + { + flags |= flagsMask; // Add EFlag + } + else + { + flags &= ~flagsMask; // Remove EFlag + } + + ReflectUtil.setValue(field, instance, flags); + return; + } + } + + if (field == null) + { + throw new ImplementationError("Field not found: " + fieldName); + } + + ReflectUtil.setValue(field, instance, value); + } + + /** + * @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! + */ + private InternalEObject getEObjectFromPotentialID(CDOViewImpl view, CDOFeature feature, Object potentialID) + { + if (potentialID instanceof CDOID) + { + CDOID id = (CDOID)potentialID; + if (id.isNull()) + { + return null; + } + + boolean loadOnDemand = feature == null; + potentialID = 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) + */ + private InternalEObject createProxy(CDOViewImpl view, CDOFeature feature, CDOID id) + { + CDOPackageRegistry packageRegistry = view.getSession().getPackageRegistry(); + EStructuralFeature eFeature = ModelUtil.getEFeature(feature, packageRegistry); + EClassifier eType = eFeature.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); + } + + /** + * TODO Consider using only EMF concepts for resolving proxies! + */ + private void resolveAllProxies() + { + // if (!allProxiesResolved) + { + CDOPackageRegistry packageRegistry = cdoView().getSession().getPackageRegistry(); + CDOClass cdoClass = revision.getCDOClass(); + for (CDOFeature feature : cdoClass.getAllFeatures()) + { + if (feature.isReference()) + { + resolveProxies(feature, packageRegistry); + } + } + + // allProxiesResolved = true; + } + } + + /* + * 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! + */ + @SuppressWarnings("unchecked") + private void resolveProxies(CDOFeature feature, CDOPackageRegistry packageRegistry) + { + Object value = getInstanceValue(instance, feature, packageRegistry); + if (value != null) + { + if (feature.isMany()) + { + InternalEList<Object> list = (InternalEList<Object>)value; + int size = list.size(); + for (int i = 0; i < size; i++) + { + Object element = list.get(i); + if (element instanceof LegacyProxy) + { + CDOID id = ((LegacyProxy)element).getID(); + InternalCDOObject resolved = view.getObject(id); + InternalEObject instance = resolved.cdoInternalInstance(); + + // 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)list).basicSet(i, instance, null); + } + else + { + list.set(i, instance); + } + } + } + } + else + { + if (value instanceof LegacyProxy) + { + CDOID id = ((LegacyProxy)value).getID(); + InternalCDOObject resolved = view.getObject(id); + InternalEObject instance = resolved.cdoInternalInstance(); + setInstanceValue(instance, feature, instance); + } + } + } + } + + private 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 (state == CDOState.PROXY) + { + if (!instance.eIsProxy()) + { + URI uri = URI.createURI(CDOProtocolConstants.PROTOCOL_NAME + ":proxy#" + id); + if (TRACER.isEnabled()) + { + TRACER.format("Setting proxyURI {0} for {1}", uri, instance); + } + + instance.eSetProxyURI(uri); + } + } + else + { + if (instance.eIsProxy()) + { + if (TRACER.isEnabled()) + { + TRACER.format("Unsetting proxyURI for {0}", instance); + } + + instance.eSetProxyURI(null); + } + } + } + + /** + * TODO Ed: Fix whole mess ;-) + */ + private void clearEList(InternalEList<Object> list) + { + while (!list.isEmpty()) + { + Object toBeRemoved = list.basicGet(0); + list.basicRemove(toBeRemoved, null); + } + } + + private 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 new ImplementationError(ex); + } + } + + public static boolean isLegacyProxy(Object object) + { + return object instanceof LegacyProxy; + } + + /** + * @author Eike Stepper + */ + private static interface LegacyProxy + { + public CDOID getID(); + } + + /** + * @author Eike Stepper + */ + private static final class LegacyProxyInvocationHandler implements InvocationHandler, LegacyProxy + { + private static final Method getIDMethod = ReflectUtil.getMethod(LegacyProxy.class, "getID"); + + private static final Method eIsProxyMethod = ReflectUtil.getMethod(EObject.class, "eIsProxy"); + + private static final Method eProxyURIMethod = ReflectUtil.getMethod(InternalEObject.class, "eProxyURI"); + + 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 the resource of the container 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()); + } + } +} |