1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.openjpa.kernel;
20
21 import java.io.IOException;
22 import java.io.ObjectOutput;
23 import java.lang.reflect.Array;
24 import java.util.ArrayList;
25 import java.util.BitSet;
26 import java.util.Calendar;
27 import java.util.Collection;
28 import java.util.Collections;
29 import java.util.Comparator;
30 import java.util.Date;
31 import java.util.Iterator;
32 import java.util.LinkedList;
33 import java.util.List;
34 import java.util.Map;
35
36 import org.apache.commons.collections.map.IdentityMap;
37 import org.apache.openjpa.conf.DetachOptions;
38 import org.apache.openjpa.enhance.PersistenceCapable;
39 import org.apache.openjpa.event.CallbackModes;
40 import org.apache.openjpa.event.LifecycleEvent;
41 import org.apache.openjpa.lib.util.Localizer;
42 import org.apache.openjpa.meta.ClassMetaData;
43 import org.apache.openjpa.meta.FieldMetaData;
44 import org.apache.openjpa.meta.JavaTypes;
45 import org.apache.openjpa.util.CallbackException;
46 import org.apache.openjpa.util.ObjectNotFoundException;
47 import org.apache.openjpa.util.Proxy;
48 import org.apache.openjpa.util.ProxyManager;
49 import org.apache.openjpa.util.UserException;
50
51 /**
52 * Handles detaching instances.
53 *
54 * @author Marc Prud'hommeaux
55 * @nojavadoc
56 */
57 public class DetachManager
58 implements DetachState {
59
60 private static Localizer _loc = Localizer.forPackage(DetachManager.class);
61
62 private final BrokerImpl _broker;
63 private final boolean _copy;
64 private final boolean _full;
65 private final ProxyManager _proxy;
66 private final DetachOptions _opts;
67 private final OpCallbacks _call;
68 private final boolean _failFast;
69 private boolean _flushed = false;
70 private boolean _flushBeforeDetach;
71
72 // if we're not detaching full, we need to track all detached objects;
73 // if we are, then we use a special field manager for more efficient
74 // detachment than the standard one
75 private final IdentityMap _detached;
76 private final DetachFieldManager _fullFM;
77
78 /**
79 * Used to prepare a detachable instance that does not externalize
80 * detached state.
81 */
82 static boolean preSerialize(StateManagerImpl sm) {
83 if (!sm.isPersistent())
84 return false;
85
86 if (sm.getBroker().getConfiguration().getCompatibilityInstance()
87 .getFlushBeforeDetach()) {
88 flushDirty(sm);
89 }
90
91 ClassMetaData meta = sm.getMetaData();
92 boolean setState = meta.getDetachedState() != null
93 && !ClassMetaData.SYNTHETIC.equals(meta.getDetachedState());
94 BitSet idxs = (setState) ? new BitSet(meta.getFields().length) : null;
95 preDetach(sm.getBroker(), sm, idxs);
96
97 if (setState) {
98 sm.getPersistenceCapable().pcSetDetachedState(getDetachedState
99 (sm, idxs));
100 return false; // don't null state
101 }
102 return true;
103 }
104
105 /**
106 * Used by classes that externalize detached state.
107 *
108 * @return whether to use a detached state manager
109 */
110 static boolean writeDetachedState(StateManagerImpl sm, ObjectOutput out,
111 BitSet idxs)
112 throws IOException {
113 if (!sm.isPersistent()) {
114 out.writeObject(null); // state
115 out.writeObject(null); // sm
116 return false;
117 }
118
119 // dirty state causes flush
120 flushDirty(sm);
121
122 Broker broker = sm.getBroker();
123 preDetach(broker, sm, idxs);
124
125 // write detached state object and state manager
126 DetachOptions opts = broker.getConfiguration().
127 getDetachStateInstance();
128 if (!opts.getDetachedStateManager()
129 || !useDetachedStateManager(sm, opts)) {
130 out.writeObject(getDetachedState(sm, idxs));
131 out.writeObject(null);
132 return false;
133 }
134 out.writeObject(null);
135 out.writeObject(new DetachedStateManager(sm.getPersistenceCapable(),
136 sm, idxs, opts.getAccessUnloaded(), broker.getMultithreaded()));
137 return true;
138 }
139
140 /**
141 * Ready the object for detachment, including loading the fields to be
142 * detached and updating version information.
143 *
144 * @param idxs the indexes of fields to detach will be set as a side
145 * effect of this method
146 */
147 private static void preDetach(Broker broker, StateManagerImpl sm,
148 BitSet idxs) {
149 // make sure the existing object has the right fields fetched; call
150 // even if using currently-loaded fields for detach to make sure
151 // version is set
152 int detachMode = broker.getDetachState();
153 int loadMode = StateManagerImpl.LOAD_FGS;
154 BitSet exclude = null;
155 if (detachMode == DETACH_LOADED)
156 exclude = StoreContext.EXCLUDE_ALL;
157 else if (detachMode == DETACH_ALL)
158 loadMode = StateManagerImpl.LOAD_ALL;
159 try {
160 sm.load(broker.getFetchConfiguration(), loadMode, exclude, null,
161 false);
162 } catch (ObjectNotFoundException onfe) {
163 // consume the exception
164 }
165
166 // create bitset of fields to detach; if mode is all we can use
167 // currently loaded bitset clone, since we know all fields are loaded
168 if (idxs != null) {
169 if (detachMode == DETACH_FETCH_GROUPS)
170 setFetchGroupFields(broker, sm, idxs);
171 else
172 idxs.or(sm.getLoaded());
173
174 // clear lrs fields
175 FieldMetaData[] fmds = sm.getMetaData().getFields();
176 for (int i = 0; i < fmds.length; i++)
177 if (fmds[i].isLRS())
178 idxs.clear(i);
179 }
180 }
181
182 /**
183 * Generate the detached state for the given instance.
184 */
185 private static Object getDetachedState(StateManagerImpl sm, BitSet fields) {
186 // if datastore, store id in first element
187 int offset = (sm.getMetaData().getIdentityType() ==
188 ClassMetaData.ID_DATASTORE) ? 1 : 0;
189
190 // make version state array one larger for new instances; marks new
191 // instances without affecting serialization size much
192 Object[] state;
193 if (sm.isNew())
194 state = new Object[3 + offset];
195 else
196 state = new Object[2 + offset];
197
198 if (offset > 0) {
199 Object id;
200 if (sm.isEmbedded() || sm.getObjectId() == null)
201 id = sm.getId();
202 else
203 id = sm.getObjectId();
204 state[0] = id.toString();
205 }
206 state[offset] = sm.getVersion();
207 state[offset + 1] = fields;
208 return state;
209 }
210
211 /**
212 * Flush or invoke pre-store callbacks on the given broker if
213 * needed. Return true if flushed/stored, false otherwise.
214 */
215 private static boolean flushDirty(StateManagerImpl sm) {
216 if (!sm.isDirty() || !sm.getBroker().isActive())
217 return false;
218
219 // only flush if there are actually any dirty non-flushed fields
220 BitSet dirtyFields = sm.getDirty();
221 BitSet flushedFields = sm.getFlushed();
222 for (int i = 0; i < dirtyFields.size(); i++) {
223 if (dirtyFields.get(i) && !flushedFields.get(i)) {
224 if (sm.getBroker().getRollbackOnly())
225 sm.getBroker().preFlush();
226 else
227 sm.getBroker().flush();
228 return true;
229 }
230 }
231 return false;
232 }
233
234 /**
235 * Create a bit set for the fields in the current fetch groups.
236 */
237 private static void setFetchGroupFields(Broker broker,
238 StateManagerImpl sm, BitSet idxs) {
239 FetchConfiguration fetch = broker.getFetchConfiguration();
240 FieldMetaData[] fmds = sm.getMetaData().getFields();
241 for (int i = 0; i < fmds.length; i++) {
242 if (fmds[i].isPrimaryKey() || fetch.requiresFetch(fmds[i])
243 != FetchConfiguration.FETCH_NONE)
244 idxs.set(i);
245 }
246 }
247
248 /**
249 * Constructor.
250 *
251 * @param broker owning broker
252 * @param full whether the entire broker cache is being detached; if
253 * this is the case, we assume the broker has already
254 * flushed if needed, and that we're detaching in-place
255 */
256 public DetachManager(BrokerImpl broker, boolean full, OpCallbacks call) {
257 _broker = broker;
258 _proxy = broker.getConfiguration().getProxyManagerInstance();
259 _opts = broker.getConfiguration().getDetachStateInstance();
260 _copy = !full;
261 _flushed = full;
262 _call = call;
263 _failFast = (broker.getConfiguration().getMetaDataRepositoryInstance().
264 getMetaDataFactory().getDefaults().getCallbackMode()
265 & CallbackModes.CALLBACK_FAIL_FAST) != 0;
266
267 // we can only rely on our "all" shortcuts if we know we won't be
268 // loading any more data
269 _full = full && broker.getDetachState() == DetachState.DETACH_LOADED;
270 if (_full) {
271 _detached = null;
272 _fullFM = new DetachFieldManager();
273 } else {
274 _detached = new IdentityMap();
275 _fullFM = null;
276 }
277 _flushBeforeDetach =
278 broker.getConfiguration().getCompatibilityInstance()
279 .getFlushBeforeDetach();
280 }
281
282 /**
283 * Return a detached version of the given instance.
284 */
285 public Object detach(Object toDetach) {
286 List exceps = null;
287 try {
288 return detachInternal(toDetach);
289 } catch (CallbackException ce) {
290 exceps = new ArrayList(1);
291 exceps.add(ce);
292 return null; // won't be reached as exception will be rethrown
293 } finally {
294 if (exceps == null || !_failFast)
295 exceps = invokeAfterDetach(Collections.singleton(toDetach),
296 exceps);
297 if (_detached != null)
298 _detached.clear();
299 throwExceptions(exceps);
300 }
301 }
302
303 /**
304 * Return detached versions of all the given instances. If not copying,
305 * null will be returned.
306 */
307 public Object[] detachAll(Collection instances) {
308 List exceps = null;
309 List detached = null;
310 if (_copy)
311 detached = new ArrayList(instances.size());
312
313 boolean failFast = false;
314 try {
315 Object detach;
316 for (Iterator itr = instances.iterator(); itr.hasNext();) {
317 detach = detachInternal(itr.next());
318 if (_copy)
319 detached.add(detach);
320 }
321 }
322 catch (RuntimeException re) {
323 if (re instanceof CallbackException && _failFast)
324 failFast = true;
325 exceps = add(exceps, re);
326 } finally {
327 if (!failFast)
328 exceps = invokeAfterDetach(instances, exceps);
329 if (_detached != null)
330 _detached.clear();
331 }
332 throwExceptions(exceps);
333
334 if (_copy)
335 return detached.toArray();
336 return null;
337 }
338
339 /**
340 * Invoke postDetach() on any detached instances that implement
341 * PostDetachCallback. This will be done after the entire graph has
342 * been detached. This method has the side-effect of also clearing
343 * out the map of all detached instances.
344 */
345 private List invokeAfterDetach(Collection objs, List exceps) {
346 Iterator itr = (_full) ? objs.iterator()
347 : _detached.entrySet().iterator();
348
349 Object orig, detached;
350 Map.Entry entry;
351 while (itr.hasNext()) {
352 if (_full) {
353 orig = itr.next();
354 detached = orig;
355 } else {
356 entry = (Map.Entry) itr.next();
357 orig = entry.getKey();
358 detached = entry.getValue();
359 }
360
361 StateManagerImpl sm = _broker.getStateManagerImpl(orig, true);
362 try {
363 if (sm != null)
364 _broker.fireLifecycleEvent(detached, orig,
365 sm.getMetaData(), LifecycleEvent.AFTER_DETACH);
366 } catch (CallbackException ce) {
367 exceps = add(exceps, ce);
368 if (_failFast)
369 break; // don't continue processing
370 }
371 }
372 return exceps;
373 }
374
375 /**
376 * Add an exception to the list.
377 */
378 private List add(List exceps, RuntimeException re) {
379 if (exceps == null)
380 exceps = new LinkedList();
381 exceps.add(re);
382 return exceps;
383 }
384
385 /**
386 * Throw all gathered exceptions.
387 */
388 private void throwExceptions(List exceps) {
389 if (exceps == null)
390 return;
391
392 if (exceps.size() == 1)
393 throw (RuntimeException) exceps.get(0);
394 throw new UserException(_loc.get("nested-exceps")).
395 setNestedThrowables((Throwable[]) exceps.toArray
396 (new Throwable[exceps.size()]));
397 }
398
399 /**
400 * Detach.
401 */
402 private Object detachInternal(Object toDetach) {
403 if (toDetach == null)
404 return null;
405
406 // already detached?
407 if (_detached != null) {
408 Object detached = _detached.get(toDetach);
409 if (detached != null)
410 return detached;
411 }
412
413 StateManagerImpl sm = _broker.getStateManagerImpl(toDetach, true);
414 if (_call != null && (_call.processArgument(OpCallbacks.OP_DETACH,
415 toDetach, sm) & OpCallbacks.ACT_RUN) == 0)
416 return toDetach;
417 if (sm == null)
418 return toDetach;
419
420 // Call PreDetach first as we can't tell if the new system
421 // fired an event or just did not fail.
422 _broker.fireLifecycleEvent(toDetach, null, sm.getMetaData(),
423 LifecycleEvent.BEFORE_DETACH);
424
425 if(! _flushed) {
426 if(_flushBeforeDetach) {
427 // any dirty instances cause a flush to occur
428 flushDirty(sm);
429 }
430 _flushed = true;
431 }
432
433 BitSet fields = new BitSet();
434 preDetach(_broker, sm, fields);
435
436 // create and store new object before copy to avoid endless recursion
437 PersistenceCapable pc = sm.getPersistenceCapable();
438 PersistenceCapable detachedPC;
439 if (_copy)
440 detachedPC = pc.pcNewInstance(null, true);
441 else
442 detachedPC = pc;
443 if (_detached != null)
444 _detached.put(toDetach, detachedPC);
445
446 // detach fields and set detached variables
447 DetachedStateManager detSM = null;
448 if (_opts.getDetachedStateManager()
449 && useDetachedStateManager(sm, _opts))
450 detSM = new DetachedStateManager(detachedPC, sm, fields,
451 _opts.getAccessUnloaded(), _broker.getMultithreaded());
452 if (_full) {
453 _fullFM.setStateManager(sm);
454 _fullFM.detachVersion();
455 _fullFM.reproxy(detSM);
456 _fullFM.setStateManager(null);
457 } else {
458 InstanceDetachFieldManager fm = new InstanceDetachFieldManager
459 (detachedPC, detSM);
460 fm.setStateManager(sm);
461 fm.detachFields(fields);
462 }
463
464 if (!Boolean.FALSE.equals(sm.getMetaData().usesDetachedState()))
465 detachedPC.pcSetDetachedState(getDetachedState(sm, fields));
466 if (!_copy)
467 sm.release(false, !_copy);
468 if (detSM != null)
469 detachedPC.pcReplaceStateManager(detSM);
470 return detachedPC;
471 }
472
473 private static boolean useDetachedStateManager(StateManagerImpl sm,
474 DetachOptions opts) {
475 ClassMetaData meta = sm.getMetaData();
476 return !Boolean.FALSE.equals(meta.usesDetachedState()) &&
477 ClassMetaData.SYNTHETIC.equals(meta.getDetachedState()) &&
478 opts.getDetachedStateManager();
479 }
480
481 /**
482 * Base detach field manager.
483 */
484 private static class DetachFieldManager
485 extends TransferFieldManager {
486
487 protected StateManagerImpl sm;
488
489 /**
490 * Set the source state manager.
491 */
492 public void setStateManager(StateManagerImpl sm) {
493 this.sm = sm;
494 }
495
496 /**
497 * Transfer the current version object from the state manager to the
498 * detached instance.
499 */
500 public void detachVersion() {
501 FieldMetaData fmd = sm.getMetaData().getVersionField();
502 if (fmd == null)
503 return;
504
505 Object val = JavaTypes.convert(sm.getVersion(),
506 fmd.getTypeCode());
507 val = fmd.getFieldValue(val, sm.getBroker());
508 switch (fmd.getDeclaredTypeCode()) {
509 case JavaTypes.LONG:
510 case JavaTypes.SHORT:
511 case JavaTypes.INT:
512 case JavaTypes.BYTE:
513 longval = (val == null) ? 0L : ((Number) val).longValue();
514 break;
515 case JavaTypes.DOUBLE:
516 case JavaTypes.FLOAT:
517 dblval = (val == null) ? 0D : ((Number) val).doubleValue();
518 break;
519 default:
520 objval = val;
521 }
522 sm.replaceField(getDetachedPersistenceCapable(), this,
523 fmd.getIndex());
524 }
525
526 /**
527 * Unproxies second class object fields.
528 */
529 public void reproxy(DetachedStateManager dsm) {
530 FieldMetaData[] fmds = sm.getMetaData().getFields();
531 for (int i = 0; i < fmds.length; i++) {
532 switch (fmds[i].getDeclaredTypeCode()) {
533 case JavaTypes.COLLECTION:
534 case JavaTypes.MAP:
535 // lrs proxies not detached
536 if (fmds[i].isLRS()) {
537 objval = null;
538 sm.replaceField(getDetachedPersistenceCapable(),
539 this, i);
540 break;
541 }
542 // no break
543 case JavaTypes.CALENDAR:
544 case JavaTypes.DATE:
545 case JavaTypes.OBJECT:
546 sm.provideField(getDetachedPersistenceCapable(), this, i);
547 if (objval instanceof Proxy) {
548 Proxy proxy = (Proxy) objval;
549 if (proxy.getChangeTracker() != null)
550 proxy.getChangeTracker().stopTracking();
551 proxy.setOwner(dsm, (dsm == null) ? -1 : i);
552 }
553 }
554 }
555 clear();
556 }
557
558 /**
559 * Return the instance being detached.
560 */
561 protected PersistenceCapable getDetachedPersistenceCapable() {
562 return sm.getPersistenceCapable();
563 }
564 }
565
566 /**
567 * FieldManager that can copy all the fields from one
568 * PersistenceCapable instance to another. One of the
569 * instances must be managed by a StateManager, and the
570 * other must be unmanaged.
571 *
572 * @author Marc Prud'hommeaux
573 */
574 private class InstanceDetachFieldManager
575 extends DetachFieldManager {
576
577 private final PersistenceCapable _to;
578 private final DetachedStateManager _detSM;
579
580 /**
581 * Constructor. Supply instance to to copy to.
582 */
583 public InstanceDetachFieldManager(PersistenceCapable to,
584 DetachedStateManager detSM) {
585 _to = to;
586 _detSM = detSM;
587 }
588
589 protected PersistenceCapable getDetachedPersistenceCapable() {
590 return _to;
591 }
592
593 /**
594 * Detach the fields of the state manager given on construction to
595 * the persistence capable given on construction.
596 * Only the fields in the given bit set will be copied.
597 */
598 public void detachFields(BitSet fgfields) {
599 PersistenceCapable from = sm.getPersistenceCapable();
600 FieldMetaData[] pks = sm.getMetaData().getPrimaryKeyFields();
601 FieldMetaData[] fmds = sm.getMetaData().getFields();
602
603 if (_copy)
604 _to.pcReplaceStateManager(sm);
605 try {
606 // we start with pk fields: objects might rely on pk fields for
607 // equals and hashCode methods, and this ensures that pk fields
608 // are set properly if we return any partially-detached objects
609 // due to reentrant calls when traversing relations
610 for (int i = 0; i < pks.length; i++)
611 detachField(from, pks[i].getIndex(), true);
612 detachVersion();
613 for (int i = 0; i < fmds.length; i++)
614 if (!fmds[i].isPrimaryKey() && !fmds[i].isVersion())
615 detachField(from, i, fgfields.get(i));
616 } finally {
617 // clear the StateManager from the target object
618 if (_copy)
619 _to.pcReplaceStateManager(null);
620 }
621 }
622
623 /**
624 * Detach (or clear) the given field index.
625 */
626 private void detachField(PersistenceCapable from, int i, boolean fg) {
627 // tell the state manager to provide the fields from the source to
628 // this field manager, which will then replace the field with a
629 // detached version
630 if (fg)
631 sm.provideField(from, this, i);
632 else if (!_copy) {
633 // if not copying and field should not be detached, clear it
634 clear();
635 sm.replaceField(_to, this, i);
636 }
637 }
638
639 public void storeBooleanField(int field, boolean curVal) {
640 super.storeBooleanField(field, curVal);
641 sm.replaceField(_to, this, field);
642 }
643
644 public void storeByteField(int field, byte curVal) {
645 super.storeByteField(field, curVal);
646 sm.replaceField(_to, this, field);
647 }
648
649 public void storeCharField(int field, char curVal) {
650 super.storeCharField(field, curVal);
651 sm.replaceField(_to, this, field);
652 }
653
654 public void storeDoubleField(int field, double curVal) {
655 super.storeDoubleField(field, curVal);
656 sm.replaceField(_to, this, field);
657 }
658
659 public void storeFloatField(int field, float curVal) {
660 super.storeFloatField(field, curVal);
661 sm.replaceField(_to, this, field);
662 }
663
664 public void storeIntField(int field, int curVal) {
665 super.storeIntField(field, curVal);
666 sm.replaceField(_to, this, field);
667 }
668
669 public void storeLongField(int field, long curVal) {
670 super.storeLongField(field, curVal);
671 sm.replaceField(_to, this, field);
672 }
673
674 public void storeShortField(int field, short curVal) {
675 super.storeShortField(field, curVal);
676 sm.replaceField(_to, this, field);
677 }
678
679 public void storeStringField(int field, String curVal) {
680 super.storeStringField(field, curVal);
681 sm.replaceField(_to, this, field);
682 }
683
684 public void storeObjectField(int field, Object curVal) {
685 super.storeObjectField(field, detachField(curVal, field));
686 sm.replaceField(_to, this, field);
687 }
688
689 /**
690 * Set the owner of the field's proxy to the detached state manager.
691 */
692 private Object reproxy(Object obj, int field) {
693 if (obj != null && _detSM != null && obj instanceof Proxy)
694 ((Proxy) obj).setOwner(_detSM, field);
695 return obj;
696 }
697
698 /**
699 * Detach the given value if needed.
700 */
701 private Object detachField(Object curVal, int field) {
702 if (curVal == null)
703 return null;
704
705 FieldMetaData fmd = sm.getMetaData().getField(field);
706 Object newVal = null;
707 switch (fmd.getDeclaredTypeCode()) {
708 case JavaTypes.ARRAY:
709 if (_copy)
710 newVal = _proxy.copyArray(curVal);
711 else
712 newVal = curVal;
713 detachArray(newVal, fmd);
714 return newVal;
715 case JavaTypes.COLLECTION:
716 if (_copy) {
717 if (_detSM != null) {
718 newVal = _proxy.newCollectionProxy(fmd.getProxyType(),
719 fmd.getElement().getDeclaredType(),
720 fmd.getInitializer() instanceof Comparator ?
721 (Comparator) fmd.getInitializer() : null);
722 ((Collection) newVal).addAll((Collection) curVal);
723 } else
724 newVal = _proxy.copyCollection((Collection) curVal);
725 } else
726 newVal = curVal;
727 detachCollection((Collection) newVal, (Collection) curVal, fmd);
728 return reproxy(newVal, field);
729 case JavaTypes.MAP:
730 if (_copy) {
731 if (_detSM != null) {
732 newVal = _proxy.newMapProxy(fmd.getProxyType(),
733 fmd.getKey().getDeclaredType(),
734 fmd.getElement().getDeclaredType(),
735 fmd.getInitializer() instanceof Comparator ?
736 (Comparator) fmd.getInitializer() : null);
737 ((Map) newVal).putAll((Map) curVal);
738 } else
739 newVal = _proxy.copyMap((Map) curVal);
740 } else
741 newVal = curVal;
742 detachMap((Map) newVal, (Map) curVal, fmd);
743 return reproxy(newVal, field);
744 case JavaTypes.CALENDAR:
745 newVal = (_copy) ? _proxy.copyCalendar((Calendar) curVal) :
746 curVal;
747 return reproxy(newVal, field);
748 case JavaTypes.DATE:
749 newVal = (_copy) ? _proxy.copyDate((Date) curVal) : curVal;
750 return reproxy(newVal, field);
751 case JavaTypes.OBJECT:
752 if (_copy)
753 newVal = _proxy.copyCustom(curVal);
754 return reproxy((newVal == null) ? curVal : newVal, field);
755 case JavaTypes.PC:
756 case JavaTypes.PC_UNTYPED:
757 return detachInternal(curVal);
758 default:
759 return curVal;
760 }
761 }
762
763 /**
764 * Make sure all the values in the given array are detached.
765 */
766 private void detachArray(Object array, FieldMetaData fmd) {
767 if (!fmd.getElement().isDeclaredTypePC())
768 return;
769
770 int len = Array.getLength(array);
771 for (int i = 0; i < len; i++)
772 Array.set(array, i, detachInternal(Array.get(array, i)));
773 }
774
775 /**
776 * Make sure all the values in the given collection are detached.
777 */
778 private void detachCollection(Collection coll, Collection orig,
779 FieldMetaData fmd) {
780 // coll can be null if not copyable
781 if (_copy && coll == null)
782 throw new UserException(_loc.get("not-copyable", fmd));
783 if (!fmd.getElement().isDeclaredTypePC())
784 return;
785
786 // unfortunately we have to clear the original and re-add to copy
787 if (_copy)
788 coll.clear();
789 Object detached;
790 for (Iterator itr = orig.iterator(); itr.hasNext();) {
791 detached = detachInternal(itr.next());
792 if (_copy)
793 coll.add(detached);
794 }
795 }
796
797 /**
798 * Make sure all the values in the given map are detached.
799 */
800 private void detachMap(Map map, Map orig, FieldMetaData fmd) {
801 // map can be null if not copyable
802 if (_copy && map == null)
803 throw new UserException(_loc.get("not-copyable", fmd));
804 boolean keyPC = fmd.getKey().isDeclaredTypePC();
805 boolean valPC = fmd.getElement().isDeclaredTypePC();
806 if (!keyPC && !valPC)
807 return;
808
809 // if we have to copy keys, just clear and re-add; otherwise
810 // we can use the entry set to reset the values only
811 Map.Entry entry;
812 if (!_copy || keyPC) {
813 if (_copy)
814 map.clear();
815 Object key, val;
816 for (Iterator itr = orig.entrySet().iterator(); itr.hasNext();){
817 entry = (Map.Entry) itr.next();
818 key = entry.getKey();
819 if (keyPC)
820 key = detachInternal(key);
821 val = entry.getValue();
822 if (valPC)
823 val = detachInternal(val);
824 if (_copy)
825 map.put(key, val);
826 }
827 } else {
828 for (Iterator itr = map.entrySet().iterator(); itr.hasNext();) {
829 entry = (Map.Entry) itr.next ();
830 entry.setValue (detachInternal (entry.getValue ()));
831 }
832 }
833 }
834 }
835 }