Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Fluegge2009-11-08 17:46:51 +0000
committerMartin Fluegge2009-11-08 17:46:51 +0000
commitda573ece58cf4bbf26d42ddda80332a568f0cf7b (patch)
treee2956a38f3096a2559e20caedaaabeb1ee02dd1d /plugins/org.eclipse.emf.cdo
parentbec457a7ab122fc44d23d9eedd4c08f881b73010 (diff)
downloadcdo-da573ece58cf4bbf26d42ddda80332a568f0cf7b.tar.gz
cdo-da573ece58cf4bbf26d42ddda80332a568f0cf7b.tar.xz
cdo-da573ece58cf4bbf26d42ddda80332a568f0cf7b.zip
[247226] Transparently support legacy models
https://bugs.eclipse.org/bugs/show_bug.cgi?id=247226
Diffstat (limited to 'plugins/org.eclipse.emf.cdo')
-rw-r--r--plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOLegacyAdapter.java89
-rw-r--r--plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOLegacyWrapper.java353
2 files changed, 343 insertions, 99 deletions
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOLegacyAdapter.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOLegacyAdapter.java
index b91c510eb7..22e5d2f03a 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOLegacyAdapter.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOLegacyAdapter.java
@@ -7,9 +7,12 @@
*
* Contributors:
* Eike Stepper - initial API and implementation
+ * Martin Fluegge - bug 247226: Transparently support legacy models
*/
package org.eclipse.emf.internal.cdo;
+import org.eclipse.emf.cdo.common.model.EMFUtil;
+
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.Notifier;
@@ -56,60 +59,60 @@ public class CDOLegacyAdapter extends CDOLegacyWrapper implements Adapter.Intern
{
CDOStore store = view.getStore();
EStructuralFeature feature = (EStructuralFeature)msg.getFeature();
- switch (msg.getEventType())
+
+ if (EMFUtil.isPersistent(feature))
{
- case Notification.SET:
- store.set(instance, feature, msg.getPosition(), msg.getNewValue());
- break;
+ switch (msg.getEventType())
+ {
+ case Notification.SET:
+ store.set(instance, feature, msg.getPosition(), msg.getNewValue());
+ break;
- case Notification.UNSET:
- store.unset(instance, feature);
- break;
+ case Notification.UNSET:
+ store.unset(instance, feature);
+ break;
- case Notification.MOVE:
- // TODO Is that correct?
- store.move(instance, feature, msg.getPosition(), (Integer)msg.getOldValue());
- break;
+ case Notification.MOVE:
+ // TODO Is that correct?
+ store.move(instance, feature, msg.getPosition(), (Integer)msg.getOldValue());
+ break;
- case Notification.ADD:
- store.add(instance, feature, msg.getPosition(), msg.getNewValue());
- break;
+ case Notification.ADD:
+ store.add(instance, feature, msg.getPosition(), msg.getNewValue());
+ break;
- case Notification.ADD_MANY:
- {
- int pos = msg.getPosition();
- @SuppressWarnings("unchecked")
- List<Object> list = (List<Object>)msg.getNewValue();
- for (Object object : list)
+ case Notification.ADD_MANY:
{
- // TODO Is that correct?
- store.add(instance, feature, pos++, object);
+ int pos = msg.getPosition();
+ @SuppressWarnings("unchecked")
+ List<Object> list = (List<Object>)msg.getNewValue();
+ for (Object object : list)
+ {
+ store.add(instance, feature, pos++, object);
+ }
+
+ break;
}
- }
-
- break;
-
- case Notification.REMOVE:
- store.remove(instance, feature, msg.getPosition());
- break;
- case Notification.REMOVE_MANY:
- {
- int pos = msg.getPosition();
- @SuppressWarnings("unchecked")
- List<Object> list = (List<Object>)msg.getOldValue();
- for (int i = 0; i < list.size(); i++)
+ case Notification.REMOVE:
{
- store.remove(instance, feature, pos);
+ store.remove(instance, feature, msg.getPosition());
+ break;
}
- }
- break;
+ case Notification.REMOVE_MANY:
+ {
+ int pos = msg.getPosition();
+ @SuppressWarnings("unchecked")
+ List<Object> list = (List<Object>)msg.getOldValue();
+ for (int i = 0; i < list.size(); i++)
+ {
+ store.remove(instance, feature, pos);
+ }
+
+ break;
+ }
+ }
}
}
-
- @Override
- public void cdoInternalPostInvalidate()
- {
- }
}
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
index 45e666f1b6..6387226e10 100644
--- 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
@@ -7,6 +7,7 @@
*
* Contributors:
* Eike Stepper - initial API and implementation
+ * Martin Fluegge - bug 247226: Transparently support legacy models
*/
package org.eclipse.emf.internal.cdo;
@@ -15,6 +16,7 @@ import org.eclipse.emf.cdo.CDOState;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.common.model.CDOModelUtil;
import org.eclipse.emf.cdo.common.model.CDOPackageRegistry;
+import org.eclipse.emf.cdo.common.model.CDOType;
import org.eclipse.emf.cdo.common.protocol.CDOProtocolConstants;
import org.eclipse.emf.cdo.common.revision.CDORevision;
import org.eclipse.emf.cdo.eresource.CDOResource;
@@ -28,9 +30,9 @@ 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.Notification;
import org.eclipse.emf.common.notify.impl.NotifyingListImpl;
import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
@@ -46,6 +48,8 @@ import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
+import java.util.HashMap;
+import java.util.Map;
/**
* @author Eike Stepper
@@ -59,6 +63,38 @@ public abstract class CDOLegacyWrapper extends CDOObjectWrapper
protected InternalCDORevision revision;
+ /**
+ * @since 3.0
+ * @description 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.
+ */
+ private boolean underConstruction;
+
+ /**
+ * This local ThreadMap stores all pre-registered objects. This avoids a neverending loop when setting the container
+ * for the object.
+ */
+ private static ThreadLocal<Map<CDOID, EObject>> preRegisteredObjects = new InheritableThreadLocal<Map<CDOID, EObject>>()
+ {
+ @Override
+ protected Map<CDOID, EObject> initialValue()
+ {
+ return new HashMap<CDOID, EObject>();
+ }
+ };
+
+ private static ThreadLocal<Counter> recursionCounter = new InheritableThreadLocal<Counter>()
+ {
+ @Override
+ protected Counter initialValue()
+ {
+ return new Counter();
+ }
+ };
+
public CDOLegacyWrapper(InternalEObject instance)
{
this.instance = instance;
@@ -86,7 +122,7 @@ public abstract class CDOLegacyWrapper extends CDOObjectWrapper
{
if (TRACER.isEnabled())
{
- TRACER.format("Setting state {0} for {1}", state, this); //$NON-NLS-1$
+ TRACER.format("Setting state {0} for {1}", state, this);
}
CDOState oldState = this.state;
@@ -107,7 +143,7 @@ public abstract class CDOLegacyWrapper extends CDOObjectWrapper
{
if (TRACER.isEnabled())
{
- TRACER.format("Setting revision: {0}", revision); //$NON-NLS-1$
+ TRACER.format(("Setting revision: " + revision + ""));
}
this.revision = (InternalCDORevision)revision;
@@ -118,7 +154,8 @@ public abstract class CDOLegacyWrapper extends CDOObjectWrapper
instanceToRevision();
// TODO Avoid if no adapters in list (eBasicAdapters?)
- // TODO LEGACY Clarify how to intercept adapter addition in the legacy instance
+ // TODO LEGACY Clarify how to intercept adapter addition in the legacy
+ // instance
for (Adapter adapter : eAdapters())
{
view.subscribe(this, adapter);
@@ -127,22 +164,10 @@ public abstract class CDOLegacyWrapper extends CDOObjectWrapper
public void cdoInternalPostDetach(boolean remote)
{
- // Do nothing
}
public void cdoInternalPreCommit()
{
- // instanceToRevision();
- // if (cdoState() == CDOState.DIRTY) // NEW is handled in PrepareTransition
- // {
- // CDORevisionManagerImpl revisionManager = (CDORevisionManagerImpl)cdoView().getSession().getRevisionManager();
- // 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 cdoInternalPreLoad()
@@ -151,12 +176,17 @@ public abstract class CDOLegacyWrapper extends CDOObjectWrapper
public void cdoInternalPostLoad()
{
- // TODO Consider not remembering the revisin after copying it to the instance (spare 1/2 of the space)
+ // TODO Consider not remembering the revisin after copying it to the
+ // instance (spare 1/2 of the space)
revisionToInstance();
}
public void cdoInternalPostInvalidate()
{
+ InternalCDORevision revision = cdoView().getRevision(cdoID(), true);
+ cdoInternalSetRevision(revision);
+ revisionToInstance();
+ cdoInternalSetState(CDOState.CLEAN);
}
public void cdoInternalCleanup()
@@ -183,14 +213,14 @@ public abstract class CDOLegacyWrapper extends CDOObjectWrapper
@Override
public String toString()
{
- return "CDOLegacyWrapper[" + id + "]"; //$NON-NLS-1$ //$NON-NLS-2$
+ return "CDOLegacyWrapper[" + id + "]";
}
protected void instanceToRevision()
{
if (TRACER.isEnabled())
{
- TRACER.format("Transfering instance to revision: {0} --> {1}", instance, revision); //$NON-NLS-1$
+ TRACER.format("Transfering instance to revision: {0} --> {1}", instance, revision);
}
// Handle containment
@@ -230,14 +260,19 @@ public abstract class CDOLegacyWrapper extends CDOObjectWrapper
CDOObjectImpl.instanceToRevisionFeature(view, this, feature, instanceValue);
}
- /**
- * TODO Simon: Fix this whole mess ;-)
- */
protected void revisionToInstance()
{
+ if (underConstruction)
+ {
+ // return if revisionToInstance was called before to avoid doubled calls
+ return;
+ }
+
+ underConstruction = true;
+
if (TRACER.isEnabled())
{
- TRACER.format("Transfering revision to instance: {0} --> {1}", revision, instance); //$NON-NLS-1$
+ TRACER.format("Transfering revision to instance: {0} --> {1}", revision, instance);
}
boolean deliver = instance.eDeliver();
@@ -246,28 +281,52 @@ public abstract class CDOLegacyWrapper extends CDOObjectWrapper
instance.eSetDeliver(false);
}
+ Counter counter = recursionCounter.get();
try
{
- // Handle containment
+ preRegisterObject(this);
+ counter.increment();
+
revisionToInstanceContainment();
- // Handle values
- CDOPackageRegistry packageRegistry = cdoView().getSession().getPackageRegistry();
- EClass eClass = revision.getEClass();
- for (EStructuralFeature feature : CDOModelUtil.getAllPersistentFeatures(eClass))
+ for (EStructuralFeature feature : CDOModelUtil.getAllPersistentFeatures(revision.getEClass()))
{
- revisionToInstanceFeature(feature, packageRegistry);
+ revisionToInstanceFeature(feature);
}
}
+ catch (RuntimeException ex)
+ {
+ OM.LOG.error(ex);
+ throw ex;
+ }
finally
{
if (deliver)
{
instance.eSetDeliver(true);
}
+
+ int newThreadCount = counter.decrement();
+
+ if (newThreadCount == 0 && getPreRegisteredObjects() != null)
+ {
+ // localThread.remove(); // TODO Martin: check why new
+ // objects will be created if this list is cleared
+ }
+
+ underConstruction = false;
}
}
+ /**
+ * adds an object to the pre-registered objects list which hold all created objects even if they are not registered in
+ * the view
+ */
+ private void preRegisterObject(CDOLegacyWrapper wrapper)
+ {
+ getPreRegisteredObjects().put(wrapper.cdoID(), wrapper);
+ }
+
protected void revisionToInstanceContainment()
{
CDOID resourceID = revision.getResourceID();
@@ -279,11 +338,141 @@ public abstract class CDOLegacyWrapper extends CDOObjectWrapper
setInstanceContainer(container, revision.getContainingFeatureID());
}
- protected void revisionToInstanceFeature(EStructuralFeature feature, CDOPackageRegistry packageRegistry)
+ private Map<CDOID, EObject> getPreRegisteredObjects()
+ {
+ return preRegisteredObjects.get();
+ }
+
+ /**
+ * @since 3.0
+ */
+
+ protected void revisionToInstanceFeature(EStructuralFeature feature)
+ {
+ if (feature.isMany())
+ {
+ if (TRACER.isEnabled())
+ {
+ TRACER.format("State of Object is : " + state);
+ }
+
+ // if (state != CDOState.CONFLICT) // do not do anything with the
+ if (state == CDOState.CLEAN || state == CDOState.PROXY) // do not do anything with the
+ // list if state is conflict
+ {
+ // int size = view.getStore().size(instance, feature);
+ int size = revision.size(feature);
+
+ @SuppressWarnings("unchecked")
+ InternalEList<Object> list = (InternalEList<Object>)instance.eGet(feature);
+
+ clearList(feature, 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));
+ }
+
+ // instance.eInverseAdd((InternalEObject)object, instance.eClass().getFeatureID(feature), object.getClass(),
+ // null);
+ list.basicAdd(object, null);
+ }
+ }
+ }
+ else
+ { // ! 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));
+ }
+
+ eSet(feature, object);
+ }
+ else
+ { // EReferences
+ if (TRACER.isEnabled())
+ {
+ TRACER.format(("Adding object " + object + " to feature " + feature + " in instance " + instance));
+ }
+
+ int featureID = instance.eClass().getFeatureID(feature);
+ Class<? extends Object> baseClass = object == null ? null : object.getClass();
+ instance.eInverseAdd((InternalEObject)object, featureID, baseClass, null);
+
+ if (TRACER.isEnabled())
+ {
+ TRACER.format(("Added object " + object + " to feature " + feature + " in instance " + instance));
+ }
+ }
+ }
+ }
+
+ private void clearList(EStructuralFeature feature, InternalEList<Object> list)
{
- // Attempt 4
- Object value = revision.getValue(feature);
- view.getStore().set(instance, feature, Notification.NO_INDEX, value);
+ // attempt 2
+ int featureID = feature.getFeatureID();
+ for (int i = list.size() - 1; i >= 0; --i)
+ {
+ InternalEObject obj = (InternalEObject)list.get(i);
+ // TODO clarify obj.getClass()/baseclass
+ instance.eInverseRemove(obj, featureID, obj.getClass(), null);
+ }
+ }
+
+ /**
+ * 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 retireive 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)
+ {
+ Object object = revision.get(feature, index);
+
+ if (object == null)
+ {
+ return null;
+ }
+
+ CDOType type = CDOModelUtil.getType(feature.getEType());
+ object = type.convertToEMF(feature.getEType(), object);
+
+ if (type == CDOType.OBJECT)
+ {
+ CDOID id = (CDOID)object;
+ if (id.isNull())
+ {
+ return null;
+ }
+
+ object = getPreRegisteredObjects().get(id);
+
+ if (object != null)
+ {
+ System.out.println(((CDOLegacyWrapper)object).cdoInternalInstance());
+ return ((CDOLegacyWrapper)object).cdoInternalInstance();
+ }
+
+ // return view.getStore().get(instance, feature, index);
+ object = view.getObject(id);
+
+ if (object instanceof CDOObjectWrapper)
+ {
+ return ((CDOObjectWrapper)object).cdoInternalInstance();
+ }
+ }
+
+ return object;
}
protected Resource.Internal getInstanceResource(InternalEObject instance)
@@ -315,6 +504,7 @@ public abstract class CDOLegacyWrapper extends CDOObjectWrapper
protected void setInstanceContainer(InternalEObject container, int containerFeatureID)
{
+ // TODO change to direct call of eBasicSetContainer
Method method = ReflectUtil.getMethod(instance.getClass(), "eBasicSetContainer", InternalEObject.class, int.class); //$NON-NLS-1$
ReflectUtil.invokeMethod(method, instance, container, containerFeatureID);
}
@@ -333,25 +523,37 @@ public abstract class CDOLegacyWrapper extends CDOObjectWrapper
protected InternalEObject getEObjectFromPotentialID(InternalCDOView view, EStructuralFeature feature,
Object potentialID)
{
- if (potentialID instanceof CDOID)
+ if (getPreRegisteredObjects().get(potentialID) != null)
{
- CDOID id = (CDOID)potentialID;
- if (id.isNull())
- {
- return null;
- }
+ potentialID = ((CDOLegacyWrapper)getPreRegisteredObjects().get(potentialID)).instance;
- boolean loadOnDemand = feature == null;
- potentialID = view.getObject(id, loadOnDemand);
- if (potentialID == null && !loadOnDemand)
+ if (TRACER.isEnabled())
{
- return createProxy(view, feature, id);
+ TRACER.format(("getting Object (" + potentialID + ") from localThread instead of the view"));
}
}
-
- if (potentialID instanceof InternalCDOObject)
+ else
{
- return ((InternalCDOObject)potentialID).cdoInternalInstance();
+ 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;
@@ -441,9 +643,11 @@ public abstract class CDOLegacyWrapper extends CDOObjectWrapper
// TODO LEGACY
// // TODO Is InternalEList.basicSet() needed???
- // if (list instanceof org.eclipse.emf.ecore.util.DelegatingInternalEList)
+ // if (list instanceof
+ // org.eclipse.emf.ecore.util.DelegatingInternalEList)
// {
- // list = ((org.eclipse.emf.ecore.util.DelegatingInternalEList)list).getDelegateInternalEList();
+ // list =
+ // ((org.eclipse.emf.ecore.util.DelegatingInternalEList)list).getDelegateInternalEList();
// }
if (list instanceof NotifyingListImpl)
@@ -482,7 +686,7 @@ public abstract class CDOLegacyWrapper extends CDOObjectWrapper
URI uri = URI.createURI(CDOProtocolConstants.PROTOCOL_NAME + ":proxy#" + id); //$NON-NLS-1$
if (TRACER.isEnabled())
{
- TRACER.format("Setting proxyURI {0} for {1}", uri, instance); //$NON-NLS-1$
+ TRACER.format("Setting proxyURI {0} for {1}", uri, instance);
}
instance.eSetProxyURI(uri);
@@ -494,7 +698,7 @@ public abstract class CDOLegacyWrapper extends CDOObjectWrapper
{
if (TRACER.isEnabled())
{
- TRACER.format("Unsetting proxyURI for {0}", instance); //$NON-NLS-1$
+ TRACER.format("Unsetting proxyURI for {0}", instance);
}
instance.eSetProxyURI(null);
@@ -538,11 +742,11 @@ public abstract class CDOLegacyWrapper extends CDOObjectWrapper
*/
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 getIDMethod = ReflectUtil.getMethod(LegacyProxy.class, "getID");
- private static final Method eIsProxyMethod = ReflectUtil.getMethod(EObject.class, "eIsProxy"); //$NON-NLS-1$
+ private static final Method eIsProxyMethod = ReflectUtil.getMethod(EObject.class, "eIsProxy");
- private static final Method eProxyURIMethod = ReflectUtil.getMethod(InternalEObject.class, "eProxyURI"); //$NON-NLS-1$
+ private static final Method eProxyURIMethod = ReflectUtil.getMethod(InternalEObject.class, "eProxyURI");
private CDOLegacyWrapper wrapper;
@@ -573,16 +777,53 @@ public abstract class CDOLegacyWrapper extends CDOObjectWrapper
if (method.equals(eProxyURIMethod))
{
- // Use the resource of the container because it's guaranteed to be in the same CDOView as the resource
+ // 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(...)
+ // 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!
+ // A client must have invoked the proxy while being told not to do
+ // so!
throw new UnsupportedOperationException(method.getName());
}
}
+
+ /**
+ * @since 3.0
+ */
+ public void setUnderConstruction(boolean underConstruction)
+ {
+ this.underConstruction = underConstruction;
+ }
+
+ /**
+ * @since 3.0
+ */
+ public boolean isUnderConstruction()
+ {
+ return underConstruction;
+ }
+
+ /**
+ * @author Martin Fluegge
+ */
+ private static final class Counter
+ {
+ private int value;
+
+ public void increment()
+ {
+ ++value;
+ }
+
+ public int decrement()
+ {
+ return --value;
+ }
+ }
}

Back to the top