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