| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.openjpa.kernel; |
| |
| import java.io.IOException; |
| import java.io.ObjectOutput; |
| import java.lang.reflect.Array; |
| import java.util.ArrayList; |
| import java.util.BitSet; |
| import java.util.Calendar; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.Date; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.commons.collections.map.IdentityMap; |
| import org.apache.openjpa.conf.Compatibility; |
| import org.apache.openjpa.conf.DetachOptions; |
| import org.apache.openjpa.enhance.PersistenceCapable; |
| import org.apache.openjpa.event.CallbackModes; |
| import org.apache.openjpa.event.LifecycleEvent; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.meta.ClassMetaData; |
| import org.apache.openjpa.meta.FieldMetaData; |
| import org.apache.openjpa.meta.JavaTypes; |
| import org.apache.openjpa.util.CallbackException; |
| import org.apache.openjpa.util.ObjectNotFoundException; |
| import org.apache.openjpa.util.Proxy; |
| import org.apache.openjpa.util.ProxyManager; |
| import org.apache.openjpa.util.UserException; |
| |
| /** |
| * Handles detaching instances. |
| * |
| * @author Marc Prud'hommeaux |
| * @nojavadoc |
| */ |
| public class DetachManager |
| implements DetachState { |
| |
| private static Localizer _loc = Localizer.forPackage(DetachManager.class); |
| |
| private final BrokerImpl _broker; |
| private final boolean _copy; |
| private final boolean _full; |
| private final ProxyManager _proxy; |
| private final DetachOptions _opts; |
| private final OpCallbacks _call; |
| private final boolean _failFast; |
| private boolean _flushed = false; |
| private boolean _flushBeforeDetach; |
| private boolean _reloadOnDetach; |
| |
| // if we're not detaching full, we need to track all detached objects; |
| // if we are, then we use a special field manager for more efficient |
| // detachment than the standard one |
| private final IdentityMap _detached; |
| private final DetachFieldManager _fullFM; |
| |
| /** |
| * Used to prepare a detachable instance that does not externalize |
| * detached state. |
| */ |
| static boolean preSerialize(StateManagerImpl sm) { |
| if (!sm.isPersistent()) |
| return false; |
| |
| if (sm.getBroker().getConfiguration().getCompatibilityInstance() |
| .getFlushBeforeDetach()) { |
| flushDirty(sm); |
| } |
| |
| ClassMetaData meta = sm.getMetaData(); |
| boolean setState = meta.getDetachedState() != null |
| && !ClassMetaData.SYNTHETIC.equals(meta.getDetachedState()); |
| BitSet idxs = (setState) ? new BitSet(meta.getFields().length) : null; |
| preDetach(sm.getBroker(), sm, idxs, false, true); |
| |
| if (setState) { |
| sm.getPersistenceCapable().pcSetDetachedState(getDetachedState |
| (sm, idxs)); |
| return false; // don't null state |
| } |
| return true; |
| } |
| |
| /** |
| * Used by classes that externalize detached state. |
| * |
| * @return whether to use a detached state manager |
| */ |
| static boolean writeDetachedState(StateManagerImpl sm, ObjectOutput out, |
| BitSet idxs) |
| throws IOException { |
| if (!sm.isPersistent()) { |
| out.writeObject(null); // state |
| out.writeObject(null); // sm |
| return false; |
| } |
| |
| // dirty state causes flush |
| flushDirty(sm); |
| |
| Broker broker = sm.getBroker(); |
| preDetach(broker, sm, idxs, false, true); |
| |
| // write detached state object and state manager |
| DetachOptions opts = broker.getConfiguration(). |
| getDetachStateInstance(); |
| if (!opts.getDetachedStateManager() |
| || !useDetachedStateManager(sm, opts)) { |
| out.writeObject(getDetachedState(sm, idxs)); |
| out.writeObject(null); |
| return false; |
| } |
| out.writeObject(null); |
| out.writeObject(new DetachedStateManager(sm.getPersistenceCapable(), |
| sm, idxs, opts.getAccessUnloaded(), broker.getMultithreaded())); |
| return true; |
| } |
| |
| /** |
| * Ready the object for detachment, including loading the fields to be |
| * detached and updating version information. |
| * |
| * @param idxs the indexes of fields to detach will be set as a side |
| * effect of this method |
| */ |
| private static void preDetach(Broker broker, StateManagerImpl sm, |
| BitSet idxs, boolean full, |
| boolean reloadOnDetach) { |
| // make sure the existing object has the right fields fetched; call |
| // even if using currently-loaded fields for detach to make sure |
| // version is set |
| int detachMode = broker.getDetachState(); |
| int loadMode = StateManagerImpl.LOAD_FGS; |
| BitSet exclude = null; |
| if (detachMode == DETACH_LOADED) |
| exclude = StoreContext.EXCLUDE_ALL; |
| else if (detachMode == DETACH_ALL) |
| loadMode = StateManagerImpl.LOAD_ALL; |
| try { |
| if (detachMode != DETACH_LOADED || |
| reloadOnDetach || |
| (!reloadOnDetach && !full)) { |
| sm.load(broker.getFetchConfiguration(), loadMode, exclude, |
| null, false); |
| } |
| } catch (ObjectNotFoundException onfe) { |
| // consume the exception |
| } |
| |
| // create bitset of fields to detach; if mode is all we can use |
| // currently loaded bitset clone, since we know all fields are loaded |
| if (idxs != null) { |
| if (detachMode == DETACH_FETCH_GROUPS) |
| setFetchGroupFields(broker, sm, idxs); |
| else |
| idxs.or(sm.getLoaded()); |
| |
| // clear lrs fields |
| FieldMetaData[] fmds = sm.getMetaData().getFields(); |
| for (int i = 0; i < fmds.length; i++) |
| if (fmds[i].isLRS()) |
| idxs.clear(i); |
| } |
| } |
| |
| /** |
| * Generate the detached state for the given instance. |
| */ |
| private static Object getDetachedState(StateManagerImpl sm, BitSet fields) { |
| // if datastore, store id in first element |
| int offset = (sm.getMetaData().getIdentityType() == |
| ClassMetaData.ID_DATASTORE) ? 1 : 0; |
| |
| // make version state array one larger for new instances; marks new |
| // instances without affecting serialization size much |
| Object[] state; |
| if (sm.isNew()) |
| state = new Object[3 + offset]; |
| else |
| state = new Object[2 + offset]; |
| |
| if (offset > 0) { |
| Object id; |
| if (sm.isEmbedded() || sm.getObjectId() == null) |
| id = sm.getId(); |
| else |
| id = sm.getObjectId(); |
| state[0] = id.toString(); |
| } |
| state[offset] = sm.getVersion(); |
| state[offset + 1] = fields; |
| return state; |
| } |
| |
| /** |
| * Flush or invoke pre-store callbacks on the given broker if |
| * needed. Return true if flushed/stored, false otherwise. |
| */ |
| private static boolean flushDirty(StateManagerImpl sm) { |
| if (!sm.isDirty() || !sm.getBroker().isActive()) |
| return false; |
| |
| // only flush if there are actually any dirty non-flushed fields |
| BitSet dirtyFields = sm.getDirty(); |
| BitSet flushedFields = sm.getFlushed(); |
| for (int i = 0; i < dirtyFields.size(); i++) { |
| if (dirtyFields.get(i) && !flushedFields.get(i)) { |
| if (sm.getBroker().getRollbackOnly()) |
| sm.getBroker().preFlush(); |
| else |
| sm.getBroker().flush(); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Create a bit set for the fields in the current fetch groups. |
| */ |
| private static void setFetchGroupFields(Broker broker, |
| StateManagerImpl sm, BitSet idxs) { |
| FetchConfiguration fetch = broker.getFetchConfiguration(); |
| FieldMetaData[] fmds = sm.getMetaData().getFields(); |
| for (int i = 0; i < fmds.length; i++) { |
| if (fmds[i].isPrimaryKey() || fetch.requiresFetch(fmds[i]) |
| != FetchConfiguration.FETCH_NONE) |
| idxs.set(i); |
| } |
| } |
| |
| /** |
| * Constructor. |
| * |
| * @param broker owning broker |
| * @param full whether the entire broker cache is being detached; if |
| * this is the case, we assume the broker has already |
| * flushed if needed, and that we're detaching in-place |
| */ |
| public DetachManager(BrokerImpl broker, boolean full, OpCallbacks call) { |
| _broker = broker; |
| _proxy = broker.getConfiguration().getProxyManagerInstance(); |
| _opts = broker.getConfiguration().getDetachStateInstance(); |
| _copy = !full; |
| _flushed = full; |
| _call = call; |
| _failFast = (broker.getConfiguration().getMetaDataRepositoryInstance(). |
| getMetaDataFactory().getDefaults().getCallbackMode() |
| & CallbackModes.CALLBACK_FAIL_FAST) != 0; |
| |
| // we can only rely on our "all" shortcuts if we know we won't be |
| // loading any more data |
| _full = full && broker.getDetachState() == DetachState.DETACH_LOADED; |
| if (_full) { |
| _detached = null; |
| _fullFM = new DetachFieldManager(); |
| } else { |
| _detached = new IdentityMap(); |
| _fullFM = null; |
| } |
| Compatibility compatibility = |
| broker.getConfiguration().getCompatibilityInstance(); |
| _flushBeforeDetach = compatibility.getFlushBeforeDetach(); |
| _reloadOnDetach = compatibility.getReloadOnDetach(); |
| } |
| |
| /** |
| * Return a detached version of the given instance. |
| */ |
| public Object detach(Object toDetach) { |
| List exceps = null; |
| try { |
| return detachInternal(toDetach); |
| } catch (CallbackException ce) { |
| exceps = new ArrayList(1); |
| exceps.add(ce); |
| return null; // won't be reached as exception will be rethrown |
| } finally { |
| if (exceps == null || !_failFast) |
| exceps = invokeAfterDetach(Collections.singleton(toDetach), |
| exceps); |
| if (_detached != null) |
| _detached.clear(); |
| throwExceptions(exceps); |
| } |
| } |
| |
| /** |
| * Return detached versions of all the given instances. If not copying, |
| * null will be returned. |
| */ |
| public Object[] detachAll(Collection instances) { |
| List exceps = null; |
| List detached = null; |
| if (_copy) |
| detached = new ArrayList(instances.size()); |
| |
| boolean failFast = false; |
| try { |
| Object detach; |
| for (Iterator itr = instances.iterator(); itr.hasNext();) { |
| detach = detachInternal(itr.next()); |
| if (_copy) |
| detached.add(detach); |
| } |
| } |
| catch (RuntimeException re) { |
| if (re instanceof CallbackException && _failFast) |
| failFast = true; |
| exceps = add(exceps, re); |
| } finally { |
| if (!failFast) |
| exceps = invokeAfterDetach(instances, exceps); |
| if (_detached != null) |
| _detached.clear(); |
| } |
| throwExceptions(exceps); |
| |
| if (_copy) |
| return detached.toArray(); |
| return null; |
| } |
| |
| /** |
| * Invoke postDetach() on any detached instances that implement |
| * PostDetachCallback. This will be done after the entire graph has |
| * been detached. This method has the side-effect of also clearing |
| * out the map of all detached instances. |
| */ |
| private List invokeAfterDetach(Collection objs, List exceps) { |
| Iterator itr = (_full) ? objs.iterator() |
| : _detached.entrySet().iterator(); |
| |
| Object orig, detached; |
| Map.Entry entry; |
| while (itr.hasNext()) { |
| if (_full) { |
| orig = itr.next(); |
| detached = orig; |
| } else { |
| entry = (Map.Entry) itr.next(); |
| orig = entry.getKey(); |
| detached = entry.getValue(); |
| } |
| |
| StateManagerImpl sm = _broker.getStateManagerImpl(orig, true); |
| try { |
| if (sm != null) |
| _broker.fireLifecycleEvent(detached, orig, |
| sm.getMetaData(), LifecycleEvent.AFTER_DETACH); |
| } catch (CallbackException ce) { |
| exceps = add(exceps, ce); |
| if (_failFast) |
| break; // don't continue processing |
| } |
| } |
| return exceps; |
| } |
| |
| /** |
| * Add an exception to the list. |
| */ |
| private List add(List exceps, RuntimeException re) { |
| if (exceps == null) |
| exceps = new LinkedList(); |
| exceps.add(re); |
| return exceps; |
| } |
| |
| /** |
| * Throw all gathered exceptions. |
| */ |
| private void throwExceptions(List exceps) { |
| if (exceps == null) |
| return; |
| |
| if (exceps.size() == 1) |
| throw (RuntimeException) exceps.get(0); |
| throw new UserException(_loc.get("nested-exceps")). |
| setNestedThrowables((Throwable[]) exceps.toArray |
| (new Throwable[exceps.size()])); |
| } |
| |
| /** |
| * Detach. |
| */ |
| private Object detachInternal(Object toDetach) { |
| if (toDetach == null) |
| return null; |
| |
| // already detached? |
| if (_detached != null) { |
| Object detached = _detached.get(toDetach); |
| if (detached != null) |
| return detached; |
| } |
| |
| StateManagerImpl sm = _broker.getStateManagerImpl(toDetach, true); |
| if (_call != null && (_call.processArgument(OpCallbacks.OP_DETACH, |
| toDetach, sm) & OpCallbacks.ACT_RUN) == 0) |
| return toDetach; |
| if (sm == null) |
| return toDetach; |
| |
| // Call PreDetach first as we can't tell if the new system |
| // fired an event or just did not fail. |
| _broker.fireLifecycleEvent(toDetach, null, sm.getMetaData(), |
| LifecycleEvent.BEFORE_DETACH); |
| |
| if(! _flushed) { |
| if(_flushBeforeDetach) { |
| // any dirty instances cause a flush to occur |
| flushDirty(sm); |
| } |
| _flushed = true; |
| } |
| |
| BitSet fields = new BitSet(); |
| preDetach(_broker, sm, fields, _full, |
| _reloadOnDetach); |
| |
| // create and store new object before copy to avoid endless recursion |
| PersistenceCapable pc = sm.getPersistenceCapable(); |
| PersistenceCapable detachedPC; |
| if (_copy) |
| detachedPC = pc.pcNewInstance(null, true); |
| else |
| detachedPC = pc; |
| if (_detached != null) |
| _detached.put(toDetach, detachedPC); |
| |
| // detach fields and set detached variables |
| DetachedStateManager detSM = null; |
| if (_opts.getDetachedStateManager() |
| && useDetachedStateManager(sm, _opts)) |
| detSM = new DetachedStateManager(detachedPC, sm, fields, |
| _opts.getAccessUnloaded(), _broker.getMultithreaded()); |
| if (_full) { |
| _fullFM.setStateManager(sm); |
| if (_copy || _reloadOnDetach) { |
| _fullFM.detachVersion(); |
| } |
| _fullFM.reproxy(detSM); |
| _fullFM.setStateManager(null); |
| } else { |
| InstanceDetachFieldManager fm = new InstanceDetachFieldManager |
| (detachedPC, detSM); |
| fm.setStateManager(sm); |
| fm.detachFields(fields); |
| } |
| |
| if (!Boolean.FALSE.equals(sm.getMetaData().usesDetachedState())) |
| detachedPC.pcSetDetachedState(getDetachedState(sm, fields)); |
| if (!_copy) |
| sm.release(false, !_copy); |
| if (detSM != null) |
| detachedPC.pcReplaceStateManager(detSM); |
| return detachedPC; |
| } |
| |
| private static boolean useDetachedStateManager(StateManagerImpl sm, |
| DetachOptions opts) { |
| ClassMetaData meta = sm.getMetaData(); |
| return !Boolean.FALSE.equals(meta.usesDetachedState()) && |
| ClassMetaData.SYNTHETIC.equals(meta.getDetachedState()) && |
| opts.getDetachedStateManager(); |
| } |
| |
| /** |
| * Base detach field manager. |
| */ |
| private static class DetachFieldManager |
| extends TransferFieldManager { |
| |
| protected StateManagerImpl sm; |
| |
| /** |
| * Set the source state manager. |
| */ |
| public void setStateManager(StateManagerImpl sm) { |
| this.sm = sm; |
| } |
| |
| /** |
| * Transfer the current version object from the state manager to the |
| * detached instance. |
| */ |
| public void detachVersion() { |
| FieldMetaData fmd = sm.getMetaData().getVersionField(); |
| if (fmd == null) |
| return; |
| |
| Object val = JavaTypes.convert(sm.getVersion(), |
| fmd.getTypeCode()); |
| val = fmd.getFieldValue(val, sm.getBroker()); |
| switch (fmd.getDeclaredTypeCode()) { |
| case JavaTypes.LONG: |
| case JavaTypes.SHORT: |
| case JavaTypes.INT: |
| case JavaTypes.BYTE: |
| longval = (val == null) ? 0L : ((Number) val).longValue(); |
| break; |
| case JavaTypes.DOUBLE: |
| case JavaTypes.FLOAT: |
| dblval = (val == null) ? 0D : ((Number) val).doubleValue(); |
| break; |
| default: |
| objval = val; |
| } |
| sm.replaceField(getDetachedPersistenceCapable(), this, |
| fmd.getIndex()); |
| } |
| |
| /** |
| * Unproxies second class object fields. |
| */ |
| public void reproxy(DetachedStateManager dsm) { |
| FieldMetaData[] fmds = sm.getMetaData().getFields(); |
| for (int i = 0; i < fmds.length; i++) { |
| switch (fmds[i].getDeclaredTypeCode()) { |
| case JavaTypes.COLLECTION: |
| case JavaTypes.MAP: |
| // lrs proxies not detached |
| if (fmds[i].isLRS()) { |
| objval = null; |
| sm.replaceField(getDetachedPersistenceCapable(), |
| this, i); |
| break; |
| } |
| // no break |
| case JavaTypes.CALENDAR: |
| case JavaTypes.DATE: |
| case JavaTypes.OBJECT: |
| sm.provideField(getDetachedPersistenceCapable(), this, i); |
| if (objval instanceof Proxy) { |
| Proxy proxy = (Proxy) objval; |
| if (proxy.getChangeTracker() != null) |
| proxy.getChangeTracker().stopTracking(); |
| proxy.setOwner(dsm, (dsm == null) ? -1 : i); |
| } |
| } |
| } |
| clear(); |
| } |
| |
| /** |
| * Return the instance being detached. |
| */ |
| protected PersistenceCapable getDetachedPersistenceCapable() { |
| return sm.getPersistenceCapable(); |
| } |
| } |
| |
| /** |
| * FieldManager that can copy all the fields from one |
| * PersistenceCapable instance to another. One of the |
| * instances must be managed by a StateManager, and the |
| * other must be unmanaged. |
| * |
| * @author Marc Prud'hommeaux |
| */ |
| private class InstanceDetachFieldManager |
| extends DetachFieldManager { |
| |
| private final PersistenceCapable _to; |
| private final DetachedStateManager _detSM; |
| |
| /** |
| * Constructor. Supply instance to to copy to. |
| */ |
| public InstanceDetachFieldManager(PersistenceCapable to, |
| DetachedStateManager detSM) { |
| _to = to; |
| _detSM = detSM; |
| } |
| |
| protected PersistenceCapable getDetachedPersistenceCapable() { |
| return _to; |
| } |
| |
| /** |
| * Detach the fields of the state manager given on construction to |
| * the persistence capable given on construction. |
| * Only the fields in the given bit set will be copied. |
| */ |
| public void detachFields(BitSet fgfields) { |
| PersistenceCapable from = sm.getPersistenceCapable(); |
| FieldMetaData[] pks = sm.getMetaData().getPrimaryKeyFields(); |
| FieldMetaData[] fmds = sm.getMetaData().getFields(); |
| |
| if (_copy) |
| _to.pcReplaceStateManager(sm); |
| try { |
| // we start with pk fields: objects might rely on pk fields for |
| // equals and hashCode methods, and this ensures that pk fields |
| // are set properly if we return any partially-detached objects |
| // due to reentrant calls when traversing relations |
| for (int i = 0; i < pks.length; i++) |
| detachField(from, pks[i].getIndex(), true); |
| detachVersion(); |
| for (int i = 0; i < fmds.length; i++) |
| if (!fmds[i].isPrimaryKey() && !fmds[i].isVersion()) |
| detachField(from, i, fgfields.get(i)); |
| } finally { |
| // clear the StateManager from the target object |
| if (_copy) |
| _to.pcReplaceStateManager(null); |
| } |
| } |
| |
| /** |
| * Detach (or clear) the given field index. |
| */ |
| private void detachField(PersistenceCapable from, int i, boolean fg) { |
| // tell the state manager to provide the fields from the source to |
| // this field manager, which will then replace the field with a |
| // detached version |
| if (fg) |
| sm.provideField(from, this, i); |
| else if (!_copy) { |
| // if not copying and field should not be detached, clear it |
| clear(); |
| sm.replaceField(_to, this, i); |
| } |
| } |
| |
| public void storeBooleanField(int field, boolean curVal) { |
| super.storeBooleanField(field, curVal); |
| sm.replaceField(_to, this, field); |
| } |
| |
| public void storeByteField(int field, byte curVal) { |
| super.storeByteField(field, curVal); |
| sm.replaceField(_to, this, field); |
| } |
| |
| public void storeCharField(int field, char curVal) { |
| super.storeCharField(field, curVal); |
| sm.replaceField(_to, this, field); |
| } |
| |
| public void storeDoubleField(int field, double curVal) { |
| super.storeDoubleField(field, curVal); |
| sm.replaceField(_to, this, field); |
| } |
| |
| public void storeFloatField(int field, float curVal) { |
| super.storeFloatField(field, curVal); |
| sm.replaceField(_to, this, field); |
| } |
| |
| public void storeIntField(int field, int curVal) { |
| super.storeIntField(field, curVal); |
| sm.replaceField(_to, this, field); |
| } |
| |
| public void storeLongField(int field, long curVal) { |
| super.storeLongField(field, curVal); |
| sm.replaceField(_to, this, field); |
| } |
| |
| public void storeShortField(int field, short curVal) { |
| super.storeShortField(field, curVal); |
| sm.replaceField(_to, this, field); |
| } |
| |
| public void storeStringField(int field, String curVal) { |
| super.storeStringField(field, curVal); |
| sm.replaceField(_to, this, field); |
| } |
| |
| public void storeObjectField(int field, Object curVal) { |
| super.storeObjectField(field, detachField(curVal, field)); |
| sm.replaceField(_to, this, field); |
| } |
| |
| /** |
| * Set the owner of the field's proxy to the detached state manager. |
| */ |
| private Object reproxy(Object obj, int field) { |
| if (obj != null && _detSM != null && obj instanceof Proxy) |
| ((Proxy) obj).setOwner(_detSM, field); |
| return obj; |
| } |
| |
| /** |
| * Detach the given value if needed. |
| */ |
| private Object detachField(Object curVal, int field) { |
| if (curVal == null) |
| return null; |
| |
| FieldMetaData fmd = sm.getMetaData().getField(field); |
| Object newVal = null; |
| switch (fmd.getDeclaredTypeCode()) { |
| case JavaTypes.ARRAY: |
| if (_copy) |
| newVal = _proxy.copyArray(curVal); |
| else |
| newVal = curVal; |
| detachArray(newVal, fmd); |
| return newVal; |
| case JavaTypes.COLLECTION: |
| if (_copy) { |
| if (_detSM != null) { |
| newVal = _proxy.newCollectionProxy(fmd.getProxyType(), |
| fmd.getElement().getDeclaredType(), |
| fmd.getInitializer() instanceof Comparator ? |
| (Comparator) fmd.getInitializer() : null); |
| ((Collection) newVal).addAll((Collection) curVal); |
| } else |
| newVal = _proxy.copyCollection((Collection) curVal); |
| } else |
| newVal = curVal; |
| detachCollection((Collection) newVal, (Collection) curVal, fmd); |
| return reproxy(newVal, field); |
| case JavaTypes.MAP: |
| if (_copy) { |
| if (_detSM != null) { |
| newVal = _proxy.newMapProxy(fmd.getProxyType(), |
| fmd.getKey().getDeclaredType(), |
| fmd.getElement().getDeclaredType(), |
| fmd.getInitializer() instanceof Comparator ? |
| (Comparator) fmd.getInitializer() : null); |
| ((Map) newVal).putAll((Map) curVal); |
| } else |
| newVal = _proxy.copyMap((Map) curVal); |
| } else |
| newVal = curVal; |
| detachMap((Map) newVal, (Map) curVal, fmd); |
| return reproxy(newVal, field); |
| case JavaTypes.CALENDAR: |
| newVal = (_copy) ? _proxy.copyCalendar((Calendar) curVal) : |
| curVal; |
| return reproxy(newVal, field); |
| case JavaTypes.DATE: |
| newVal = (_copy) ? _proxy.copyDate((Date) curVal) : curVal; |
| return reproxy(newVal, field); |
| case JavaTypes.OBJECT: |
| if (_copy) |
| newVal = _proxy.copyCustom(curVal); |
| return reproxy((newVal == null) ? curVal : newVal, field); |
| case JavaTypes.PC: |
| case JavaTypes.PC_UNTYPED: |
| return detachInternal(curVal); |
| default: |
| return curVal; |
| } |
| } |
| |
| /** |
| * Make sure all the values in the given array are detached. |
| */ |
| private void detachArray(Object array, FieldMetaData fmd) { |
| if (!fmd.getElement().isDeclaredTypePC()) |
| return; |
| |
| int len = Array.getLength(array); |
| for (int i = 0; i < len; i++) |
| Array.set(array, i, detachInternal(Array.get(array, i))); |
| } |
| |
| /** |
| * Make sure all the values in the given collection are detached. |
| */ |
| private void detachCollection(Collection coll, Collection orig, |
| FieldMetaData fmd) { |
| // coll can be null if not copyable |
| if (_copy && coll == null) |
| throw new UserException(_loc.get("not-copyable", fmd)); |
| if (!fmd.getElement().isDeclaredTypePC()) |
| return; |
| |
| // unfortunately we have to clear the original and re-add to copy |
| if (_copy) |
| coll.clear(); |
| Object detached; |
| for (Iterator itr = orig.iterator(); itr.hasNext();) { |
| detached = detachInternal(itr.next()); |
| if (_copy) |
| coll.add(detached); |
| } |
| } |
| |
| /** |
| * Make sure all the values in the given map are detached. |
| */ |
| private void detachMap(Map map, Map orig, FieldMetaData fmd) { |
| // map can be null if not copyable |
| if (_copy && map == null) |
| throw new UserException(_loc.get("not-copyable", fmd)); |
| boolean keyPC = fmd.getKey().isDeclaredTypePC(); |
| boolean valPC = fmd.getElement().isDeclaredTypePC(); |
| if (!keyPC && !valPC) |
| return; |
| |
| // if we have to copy keys, just clear and re-add; otherwise |
| // we can use the entry set to reset the values only |
| Map.Entry entry; |
| if (!_copy || keyPC) { |
| if (_copy) |
| map.clear(); |
| Object key, val; |
| for (Iterator itr = orig.entrySet().iterator(); itr.hasNext();){ |
| entry = (Map.Entry) itr.next(); |
| key = entry.getKey(); |
| if (keyPC) |
| key = detachInternal(key); |
| val = entry.getValue(); |
| if (valPC) |
| val = detachInternal(val); |
| if (_copy) |
| map.put(key, val); |
| } |
| } else { |
| for (Iterator itr = map.entrySet().iterator(); itr.hasNext();) { |
| entry = (Map.Entry) itr.next (); |
| entry.setValue (detachInternal (entry.getValue ())); |
| } |
| } |
| } |
| } |
| } |