001: /**********************************************************************
002: Copyright (c) 2007 Andy Jefferson and others. All rights reserved.
003: Licensed under the Apache License, Version 2.0 (the "License");
004: you may not use this file except in compliance with the License.
005: You may obtain a copy of the License at
006:
007: http://www.apache.org/licenses/LICENSE-2.0
008:
009: Unless required by applicable law or agreed to in writing, software
010: distributed under the License is distributed on an "AS IS" BASIS,
011: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: See the License for the specific language governing permissions and
013: limitations under the License.
014:
015: Contributors:
016: ...
017: **********************************************************************/package org.jpox.sco.simple;
018:
019: import java.io.ObjectStreamException;
020: import java.util.Collection;
021:
022: import org.jpox.StateManager;
023: import org.jpox.metadata.AbstractMemberMetaData;
024: import org.jpox.sco.SCOMap;
025: import org.jpox.sco.SCOUtils;
026: import org.jpox.sco.exceptions.NullsNotAllowedException;
027: import org.jpox.state.FetchPlanState;
028: import org.jpox.util.JPOXLogger;
029: import org.jpox.util.Localiser;
030: import org.jpox.util.StringUtils;
031:
032: /**
033: * A mutable second-class LinkedHashMap object.
034: * This is the simplified form that intercepts mutators and marks the field as dirty.
035: * Only available for JRE 1.4 or above.
036: *
037: * @version $Revision: 1.2 $
038: */
039: public class LinkedHashMap extends java.util.LinkedHashMap implements
040: SCOMap, Cloneable {
041: protected static final Localiser LOCALISER = Localiser
042: .getInstance("org.jpox.Localisation");
043:
044: protected transient Object owner;
045: protected transient StateManager ownerSM;
046: protected transient String fieldName;
047: protected transient int fieldNumber;
048: protected transient boolean allowNulls;
049:
050: /** The internal "delegate". */
051: protected java.util.LinkedHashMap delegate;
052:
053: /**
054: * Constructor
055: * @param ownerSM the owner of this Map
056: * @param fieldName the declared field name
057: */
058: public LinkedHashMap(StateManager ownerSM, String fieldName) {
059: this .ownerSM = ownerSM;
060: this .fieldName = fieldName;
061: this .allowNulls = false;
062:
063: if (ownerSM != null) {
064: AbstractMemberMetaData fmd = ownerSM.getClassMetaData()
065: .getMetaDataForMember(fieldName);
066: owner = ownerSM.getObject();
067: fieldNumber = fmd.getAbsoluteFieldNumber();
068: allowNulls = SCOUtils
069: .allowNullsInContainer(allowNulls, fmd);
070: }
071: }
072:
073: /**
074: * Method to initialise the SCO from an existing value.
075: * @param o Object to set value using.
076: * @param forInsert Whether the object needs inserting in the datastore with this value
077: * @param forUpdate Whether to update the datastore with this value
078: */
079: public void initialise(Object o, boolean forInsert,
080: boolean forUpdate) {
081: java.util.Map m = (java.util.Map) o;
082: if (m != null) {
083: delegate = new java.util.LinkedHashMap(m); // Make copy of container rather than using same memory
084: } else {
085: delegate = new java.util.LinkedHashMap();
086: }
087: if (JPOXLogger.PERSISTENCE.isDebugEnabled()) {
088: JPOXLogger.PERSISTENCE.debug(LOCALISER.msg("023003",
089: StringUtils.toJVMIDString(ownerSM.getObject()),
090: fieldName, "" + size(), SCOUtils
091: .getSCOWrapperOptionsMessage(true, false,
092: allowNulls, false)));
093: }
094: }
095:
096: /**
097: * Method to initialise the SCO for use.
098: */
099: public void initialise() {
100: delegate = new java.util.LinkedHashMap();
101: if (JPOXLogger.PERSISTENCE.isDebugEnabled()) {
102: JPOXLogger.PERSISTENCE.debug(LOCALISER.msg("023003",
103: StringUtils.toJVMIDString(ownerSM.getObject()),
104: fieldName, "" + size(), SCOUtils
105: .getSCOWrapperOptionsMessage(true, false,
106: allowNulls, false)));
107: }
108: }
109:
110: /**
111: * Accessor for the unwrapped value that we are wrapping.
112: * @return The unwrapped value
113: */
114: public Object getValue() {
115: return delegate;
116: }
117:
118: /**
119: * Method to effect the load of the data in the SCO.
120: * Used when the SCO supports lazy-loading to tell it to load all now.
121: */
122: public void load() {
123: // Always loaded
124: }
125:
126: /**
127: * Method to flush the changes to the datastore when operating in queued mode.
128: * Does nothing in "direct" mode.
129: */
130: public void flush() {
131: // Never queued
132: }
133:
134: /**
135: * Method to update an embedded key in this map.
136: * @param key The key
137: * @param fieldNumber Number of field in the key
138: * @param newValue New value for this field
139: */
140: public void updateEmbeddedKey(Object key, int fieldNumber,
141: Object newValue) {
142: // Embedded not supported so do nothing
143: }
144:
145: /**
146: * Method to update an embedded value in this map.
147: * @param value The value
148: * @param fieldNumber Number of field in the value
149: * @param newValue New value for this field
150: */
151: public void updateEmbeddedValue(Object value, int fieldNumber,
152: Object newValue) {
153: // Embedded not supported so do nothing
154: }
155:
156: /**
157: * Accessor for the field name that this LinkedHashMap relates to.
158: * @return The field name
159: **/
160: public String getFieldName() {
161: return fieldName;
162: }
163:
164: /**
165: * Accessor for the owner that this LinkedHashMap relates to.
166: * @return The owner
167: **/
168: public Object getOwner() {
169: return owner;
170: }
171:
172: /**
173: * Method to unset the owner and field details.
174: **/
175: public synchronized void unsetOwner() {
176: if (ownerSM != null) {
177: owner = null;
178: ownerSM = null;
179: fieldName = null;
180: }
181: }
182:
183: /**
184: * Utility to mark the object as dirty
185: **/
186: public void makeDirty() {
187: // Although we are never really "dirty", the owning object must be
188: // marked dirty so that the proper state change occurs and its
189: // jdoPreStore() gets called properly.
190: if (ownerSM != null) {
191: ownerSM.makeDirty(fieldNumber);
192: }
193: }
194:
195: /**
196: * Method to return a detached copy of the container.
197: * Recurse sthrough the keys/values so that they are likewise detached.
198: * @param state State for detachment process
199: * @return The detached container
200: */
201: public Object detachCopy(FetchPlanState state) {
202: java.util.Map detached = new java.util.LinkedHashMap();
203: SCOUtils.detachCopyForMap(ownerSM, entrySet(), state, detached);
204: return detached;
205: }
206:
207: /**
208: * Method to return an attached copy of the passed (detached) value. The returned attached copy
209: * is a SCO wrapper. Goes through the existing keys/values in the store for this owner field and
210: * removes ones no longer present, and adds new keys/values. All keys/values in the (detached)
211: * value are attached.
212: * @param value The new (map) value
213: */
214: public void attachCopy(Object value) {
215: java.util.Map m = (java.util.Map) value;
216:
217: // Attach all of the keys/values in the new map
218: AbstractMemberMetaData fmd = ownerSM.getClassMetaData()
219: .getMetaDataForMember(fieldName);
220: boolean keysWithoutIdentity = SCOUtils
221: .mapHasKeysWithoutIdentity(fmd);
222: boolean valuesWithoutIdentity = SCOUtils
223: .mapHasValuesWithoutIdentity(fmd);
224:
225: java.util.Map attachedKeysValues = new java.util.HashMap(m
226: .size());
227: SCOUtils.attachCopyForMap(ownerSM, m.entrySet(),
228: attachedKeysValues, keysWithoutIdentity,
229: valuesWithoutIdentity);
230:
231: // Update the attached map with the detached elements
232: SCOUtils.updateMapWithMapKeysValues(ownerSM.getObjectManager()
233: .getApiAdapter(), this , attachedKeysValues);
234: }
235:
236: // ------------------ Implementation of LinkedHashMap methods --------------------
237:
238: /**
239: * Creates and returns a copy of this object.
240: *
241: * <P>Mutable second-class Objects are required to provide a public
242: * clone method in order to allow for copying PersistenceCapable
243: * objects. In contrast to Object.clone(), this method must not throw a
244: * CloneNotSupportedException.
245: * @return The cloned object
246: */
247: public Object clone() {
248: return delegate.clone();
249: }
250:
251: /**
252: * Method to return if the map contains this key
253: * @param key The key
254: * @return Whether it is contained
255: **/
256: public boolean containsKey(Object key) {
257: return delegate.containsKey(key);
258: }
259:
260: /**
261: * Method to return if the map contains this value.
262: * @param value The value
263: * @return Whether it is contained
264: **/
265: public boolean containsValue(Object value) {
266: return delegate.containsValue(value);
267: }
268:
269: /**
270: * Accessor for the set of entries in the Map.
271: * @return Set of entries
272: **/
273: public java.util.Set entrySet() {
274: return delegate.entrySet();
275: }
276:
277: /**
278: * Method to check the equality of this map, and another.
279: * @param o The map to compare against.
280: * @return Whether they are equal.
281: **/
282: public synchronized boolean equals(Object o) {
283: return delegate.equals(o);
284: }
285:
286: /**
287: * Accessor for the value stored against a key.
288: * @param key The key
289: * @return The value.
290: **/
291: public Object get(Object key) {
292: return delegate.get(key);
293: }
294:
295: /**
296: * Method to generate a hashcode for this Map.
297: * @return The hashcode.
298: **/
299: public synchronized int hashCode() {
300: return delegate.hashCode();
301: }
302:
303: /**
304: * Method to return if the Map is empty.
305: * @return Whether it is empty.
306: **/
307: public boolean isEmpty() {
308: return delegate.isEmpty();
309: }
310:
311: /**
312: * Accessor for the set of keys in the Map.
313: * @return Set of keys.
314: **/
315: public java.util.Set keySet() {
316: return delegate.keySet();
317: }
318:
319: /**
320: * Method to return the size of the Map.
321: * @return The size
322: **/
323: public int size() {
324: return delegate.size();
325: }
326:
327: /**
328: * Accessor for the set of values in the Map.
329: * @return Set of values.
330: **/
331: public Collection values() {
332: return delegate.values();
333: }
334:
335: /**
336: * Method to clear the LinkedHashMap.
337: **/
338: public void clear() {
339: delegate.clear();
340: makeDirty();
341: }
342:
343: /**
344: * Method to add a value against a key to the LinkedHashMap.
345: * @param key The key
346: * @param value The value
347: * @return The previous value for the specified key.
348: **/
349: public Object put(Object key, Object value) {
350: // Reject inappropriate elements
351: if (value == null && !allowNulls) {
352: throw new NullsNotAllowedException(ownerSM, fieldName);
353: }
354:
355: Object oldValue = delegate.put(key, value);
356: makeDirty();
357: return oldValue;
358: }
359:
360: /**
361: * Method to add the specified Map's values under their keys here.
362: * @param m The map
363: **/
364: public void putAll(java.util.Map m) {
365: delegate.putAll(m);
366: makeDirty();
367: }
368:
369: /**
370: * Method to remove the value for a key from the LinkedHashMap.
371: * @param key The key to remove
372: * @return The value that was removed from this key.
373: **/
374: public Object remove(Object key) {
375: Object value = delegate.remove(key);
376: makeDirty();
377: return value;
378: }
379:
380: /**
381: * The writeReplace method is called when ObjectOutputStream is preparing
382: * to write the object to the stream. The ObjectOutputStream checks whether
383: * the class defines the writeReplace method. If the method is defined, the
384: * writeReplace method is called to allow the object to designate its
385: * replacement in the stream. The object returned should be either of the
386: * same type as the object passed in or an object that when read and
387: * resolved will result in an object of a type that is compatible with all
388: * references to the object.
389: *
390: * @return the replaced object
391: * @throws ObjectStreamException
392: */
393: protected Object writeReplace() throws ObjectStreamException {
394: return new java.util.LinkedHashMap(delegate);
395: }
396: }
|