blob: 5b639c017c606136c8ab2bcacb36fb2d86b454ea [file] [log] [blame]
/*
* 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.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
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.meta.ValueMetaData;
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
*/
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 _cascadeWithDetach;
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 IdentityHashMap _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();
_flushed = full;
_call = call;
_failFast = (broker.getConfiguration().getMetaDataRepositoryInstance().
getMetaDataFactory().getDefaults().getCallbackMode()
& CallbackModes.CALLBACK_FAIL_FAST) != 0;
// we can only rely on our "full" 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 IdentityHashMap();
_fullFM = null;
}
Compatibility compatibility =
broker.getConfiguration().getCompatibilityInstance();
_flushBeforeDetach = compatibility.getFlushBeforeDetach();
_reloadOnDetach = compatibility.getReloadOnDetach();
_cascadeWithDetach = compatibility.getCascadeWithDetach();
if (full) {
_copy = false;
}
else {
_copy = compatibility.getCopyOnDetach();
}
}
/**
* 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 (Object instance : instances) {
detach = detachInternal(instance);
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)
&& !(sm.isNew() && !sm.isDeleted() && !sm.isFlushed()))
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, true);
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) {
for (FieldMetaData fmd : sm.getMetaData().getProxyFields()) {
switch (fmd.getDeclaredTypeCode()) {
case JavaTypes.COLLECTION:
case JavaTypes.MAP:
// lrs proxies not detached
if (fmd.isLRS()) {
objval = null;
sm.replaceField(getDetachedPersistenceCapable(), this, fmd.getIndex());
break;
}
// no break
case JavaTypes.CALENDAR:
case JavaTypes.DATE:
case JavaTypes.OBJECT:
sm.provideField(getDetachedPersistenceCapable(), this, fmd.getIndex());
if (objval instanceof Proxy) {
Proxy proxy = (Proxy) objval;
if (proxy.getChangeTracker() != null)
proxy.getChangeTracker().stopTracking();
proxy.setOwner(dsm, (dsm == null) ? -1 : fmd.getIndex());
}
}
}
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;
}
@Override
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 (FieldMetaData pk : pks) {
detachField(from, pk.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);
}
}
@Override
public void storeBooleanField(int field, boolean curVal) {
super.storeBooleanField(field, curVal);
sm.replaceField(_to, this, field);
}
@Override
public void storeByteField(int field, byte curVal) {
super.storeByteField(field, curVal);
sm.replaceField(_to, this, field);
}
@Override
public void storeCharField(int field, char curVal) {
super.storeCharField(field, curVal);
sm.replaceField(_to, this, field);
}
@Override
public void storeDoubleField(int field, double curVal) {
super.storeDoubleField(field, curVal);
sm.replaceField(_to, this, field);
}
@Override
public void storeFloatField(int field, float curVal) {
super.storeFloatField(field, curVal);
sm.replaceField(_to, this, field);
}
@Override
public void storeIntField(int field, int curVal) {
super.storeIntField(field, curVal);
sm.replaceField(_to, this, field);
}
@Override
public void storeLongField(int field, long curVal) {
super.storeLongField(field, curVal);
sm.replaceField(_to, this, field);
}
@Override
public void storeShortField(int field, short curVal) {
super.storeShortField(field, curVal);
sm.replaceField(_to, this, field);
}
@Override
public void storeStringField(int field, String curVal) {
super.storeStringField(field, curVal);
sm.replaceField(_to, this, field);
}
@Override
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);
boolean cascade = false;
if(_cascadeWithDetach
|| fmd.getCascadeDetach() ==
ValueMetaData.CASCADE_IMMEDIATE
|| fmd.getKey().getCascadeDetach() ==
ValueMetaData.CASCADE_IMMEDIATE
|| fmd.getElement().getCascadeDetach() ==
ValueMetaData.CASCADE_IMMEDIATE) {
cascade = true;
}
Object newVal = null;
switch (fmd.getDeclaredTypeCode()) {
case JavaTypes.ARRAY:
if (_copy)
newVal = _proxy.copyArray(curVal);
else
newVal = curVal;
if (cascade) {
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,
sm.getBroker().getConfiguration().
getCompatibilityInstance().getAutoOff());
((Collection) newVal).addAll((Collection) curVal);
} else
newVal = _proxy.copyCollection((Collection) curVal);
} else
newVal = curVal;
if (cascade) {
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,
sm.getBroker().getConfiguration().
getCompatibilityInstance().getAutoOff());
((Map) newVal).putAll((Map) curVal);
} else
newVal = _proxy.copyMap((Map) curVal);
} else
newVal = curVal;
if (cascade) {
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:
if (cascade) {
return detachInternal(curVal);
}
return 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 (Object o : orig) {
detached = detachInternal(o);
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 (Object o : orig.entrySet()) {
entry = (Map.Entry) o;
key = entry.getKey();
if (keyPC)
key = detachInternal(key);
val = entry.getValue();
if (valPC)
val = detachInternal(val);
if (_copy)
map.put(key, val);
}
} else {
for (Object o : map.entrySet()) {
entry = (Map.Entry) o;
entry.setValue(detachInternal(entry.getValue()));
}
}
}
}
}