001: /*
002: *
003: * JMoney - A Personal Finance Manager
004: * Copyright (c) 2004 Nigel Westbury <westbury@users.sourceforge.net>
005: *
006: *
007: * This program is free software; you can redistribute it and/or modify
008: * it under the terms of the GNU General Public License as published by
009: * the Free Software Foundation; either version 2 of the License, or
010: * (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
020: *
021: */
022:
023: package net.sf.jmoney.model2;
024:
025: import java.util.Collection;
026: import java.util.Vector;
027:
028: /**
029: * Keeps track of changes made to the model. This is done to enable the
030: * undo/redo feature.
031: *
032: * As changes are undone and redone, the id of each object may change.
033: * For example, in the serializeddatastore plug-in, the id of each object
034: * is a reference to the object itself, i.e. the java identity. Unless
035: * we keep a reference to these objects, which we don't, the identity of
036: * objects will not be the same when the object is re-created. (Even if
037: * we kept a reference to an object, it is not possible to insert that
038: * object back into the object store for various technical reasons).
039: * If the datastore is a database, for example in the jdbcdatastore plug-in,
040: * the id is automatically generated as a value of a unique column.
041: * The database may decide to re-use the id of a delete row.
042: * Therefore, this class never stores ids of objects that have been
043: * deleted. When an object is deleted, all old values that reference
044: * the object are replaced with references to the delete entry.
045: * This allows the data to be re-created correctly by the undo method.
046: *
047: * @author Nigel Westbury
048: */
049: public class ChangeManager {
050:
051: /**
052: * When we delete an object, we know that nothing in the
053: * datastore references it. However, there may be old
054: * values that referenced it. It is important that these
055: * old values are updated to reference this deleted object.
056: * Otherwise, if the object is re-created with a different
057: * id then those old values cannot be restored correctly.
058: */
059: private class KeyProxy {
060: IObjectKey key;
061:
062: KeyProxy(IObjectKey key) {
063: this .key = key;
064: }
065: }
066:
067: /*
068: * If there are no references to the KeyProxy then this means there are no changes that
069: * need the key proxy to undo the change. The entry can be removed from the map.
070: * Thus we use a map with weak value references.
071: */
072: private WeakValuedMap<IObjectKey, KeyProxy> keyProxyMap = new WeakValuedMap<IObjectKey, KeyProxy>();
073:
074: private UndoableChange currentUndoableChange = null;
075:
076: public class UndoableChange {
077: /**
078: * Vector of ChangeEntry objects. Changes are added to
079: * this vector in order. If changes are undone, they must
080: * be undone in reverse order, starting at the end of this
081: * vector.
082: */
083: private Vector<ChangeEntry> changes = new Vector<ChangeEntry>();
084:
085: /**
086: * Submit a series of updates, which have been stored,
087: * to the datastore. These updates carry out the reverse
088: * of the updates stored.
089: */
090: public void undoChanges() {
091: // Undo the changes in reverse order.
092: for (int i = changes.size() - 1; i >= 0; i--) {
093: ChangeEntry changeEntry = changes.get(i);
094: changeEntry.undo();
095: }
096: }
097:
098: /**
099: * @param newChangeEntry
100: */
101: void addChange(ChangeEntry newChangeEntry) {
102: changes.add(newChangeEntry);
103: }
104: }
105:
106: /**
107: * Base class for all objects that represent a component of
108: * a change. Derived classes represent property changes,
109: * insertion of new objects, and deletion of objects.
110: *
111: * These objects have only a constructor and the <code>undo</code> method.
112: * Once the <code>undo</code> is called the object is dead.
113: */
114: abstract class ChangeEntry {
115: abstract void undo();
116: }
117:
118: /**
119: * A ChangeEntry object for an update to a scalar property (excluding
120: * scalar properties that are references to extendable objects).
121: *
122: * @param <V>
123: */
124: class ChangeEntry_UpdateScalar<V> extends ChangeEntry {
125: private KeyProxy objectKeyProxy;
126: private ScalarPropertyAccessor<V> propertyAccessor;
127: private V oldValue = null;
128:
129: ChangeEntry_UpdateScalar(KeyProxy objectKeyProxy,
130: ScalarPropertyAccessor<V> propertyAccessor, V oldValue) {
131: this .objectKeyProxy = objectKeyProxy;
132: this .propertyAccessor = propertyAccessor;
133: this .oldValue = oldValue;
134: }
135:
136: @Override
137: void undo() {
138: ExtendableObject object = objectKeyProxy.key.getObject(); // efficient???
139: object.setPropertyValue(propertyAccessor, oldValue);
140: }
141: }
142:
143: /**
144: * A ChangeEntry object for an update to a scalar property that is a
145: * reference to an extendable object.
146: *
147: * @param <E>
148: */
149: // TODO: E should be bounded to classes that extend ExtendableObject.
150: // However, this method does not currently make use of such bounding,
151: // and to do that we would have to push back seperate methods for
152: // reference properties and other scalar properties.
153: class ChangeEntry_UpdateReference<E> extends ChangeEntry {
154: private KeyProxy objectKeyProxy;
155: private ScalarPropertyAccessor<E> propertyAccessor;
156: private KeyProxy oldValueProxy = null;
157:
158: ChangeEntry_UpdateReference(KeyProxy objectKeyProxy,
159: ScalarPropertyAccessor<E> propertyAccessor,
160: KeyProxy oldValueProxy) {
161: this .objectKeyProxy = objectKeyProxy;
162: this .propertyAccessor = propertyAccessor;
163: this .oldValueProxy = oldValueProxy;
164: }
165:
166: @Override
167: void undo() {
168: ExtendableObject object = objectKeyProxy.key.getObject(); // efficient???
169: // If IObjectKey had a type parameter, we would not need
170: // this cast.
171: object.setPropertyValue(propertyAccessor, propertyAccessor
172: .getClassOfValueObject().cast(
173: oldValueProxy.key.getObject()));
174: }
175: }
176:
177: class ChangeEntry_Insert extends ChangeEntry {
178: private KeyProxy parentKeyProxy;
179: private ListPropertyAccessor<?> owningListProperty;
180: private KeyProxy objectKeyProxy;
181:
182: ChangeEntry_Insert(KeyProxy parentKeyProxy,
183: ListPropertyAccessor<?> owningListProperty,
184: KeyProxy objectKeyProxy) {
185: this .parentKeyProxy = parentKeyProxy;
186: this .owningListProperty = owningListProperty;
187: this .objectKeyProxy = objectKeyProxy;
188: }
189:
190: @Override
191: void undo() {
192: // Delete the object.
193: ExtendableObject object = objectKeyProxy.key.getObject(); // efficient???
194: ExtendableObject parent = parentKeyProxy.key.getObject();
195:
196: // Delete the object from the datastore.
197: parent.getListPropertyValue(owningListProperty).remove(
198: object);
199: }
200: }
201:
202: /**
203: * @param <E> the type of the object being deleted
204: */
205: class ChangeEntry_Delete<E extends ExtendableObject> extends
206: ChangeEntry {
207: private Object[] oldValues;
208: private Collection<ExtensionPropertySet<?>> nonDefaultExtensions;
209:
210: private KeyProxy parentKeyProxy;
211: private ListPropertyAccessor<E> owningListProperty;
212: private KeyProxy objectKeyProxy;
213: private ExtendablePropertySet<? extends E> actualPropertySet;
214:
215: ChangeEntry_Delete(KeyProxy parentKeyProxy,
216: ListPropertyAccessor<E> owningListProperty, E oldObject) {
217: this .parentKeyProxy = parentKeyProxy;
218: this .owningListProperty = owningListProperty;
219:
220: this .objectKeyProxy = getKeyProxy(oldObject.getObjectKey());
221: this .actualPropertySet = owningListProperty
222: .getElementPropertySet().getActualPropertySet(
223: (Class<? extends E>) oldObject.getClass());
224:
225: /*
226: * Save all the property values from the deleted object. We need
227: * these to re-create the object if this change is undone.
228: */
229: nonDefaultExtensions = oldObject.getExtensions();
230:
231: int count = actualPropertySet.getScalarProperties3().size();
232: oldValues = new Object[count];
233: int index = 0;
234: for (ScalarPropertyAccessor<?> propertyAccessor : actualPropertySet
235: .getScalarProperties3()) {
236: if (index != propertyAccessor
237: .getIndexIntoScalarProperties()) {
238: throw new RuntimeException("index mismatch");
239: }
240:
241: Object value = oldObject
242: .getPropertyValue(propertyAccessor);
243: if (value instanceof ExtendableObject) {
244: /*
245: * We can't store extendable objects or even the object keys
246: * because those may not remain valid (the referenced object may
247: * be deleted). We store instead a KeyProxy. If the referenced
248: * object is later deleted, then un-deleted using an undo
249: * operation, then this change is also undone, the key proxy
250: * will give us the new object key for the referenced object.
251: */
252: IObjectKey objectKey = ((ExtendableObject) value)
253: .getObjectKey();
254: oldValues[index++] = getKeyProxy(objectKey);
255: } else {
256: oldValues[index++] = value;
257: }
258: }
259: }
260:
261: @Override
262: void undo() {
263: /* Create the object in the datastore.
264: * However, we must first convert the key proxies back to keys before passing
265: * on to the constructor.
266: */
267: IValues oldValues2 = new IValues() {
268:
269: public <V> V getScalarValue(
270: ScalarPropertyAccessor<V> propertyAccessor) {
271: return propertyAccessor
272: .getClassOfValueObject()
273: .cast(
274: oldValues[propertyAccessor
275: .getIndexIntoScalarProperties()]);
276: }
277:
278: public IObjectKey getReferencedObjectKey(
279: ReferencePropertyAccessor<? extends ExtendableObject> propertyAccessor) {
280: KeyProxy keyProxy = (KeyProxy) oldValues[propertyAccessor
281: .getIndexIntoScalarProperties()];
282: return keyProxy == null ? null : keyProxy.key;
283: }
284:
285: public <E2 extends ExtendableObject> IListManager<E2> getListManager(
286: IObjectKey listOwnerKey,
287: ListPropertyAccessor<E2> listAccessor) {
288: return listOwnerKey
289: .constructListManager(listAccessor);
290: }
291:
292: public Collection<ExtensionPropertySet<?>> getNonDefaultExtensions() {
293: return nonDefaultExtensions;
294: }
295: };
296:
297: ExtendableObject parent = parentKeyProxy.key.getObject();
298: ExtendableObject object = parent.getListPropertyValue(
299: owningListProperty).createNewElement(
300: actualPropertySet, oldValues2, false);
301:
302: /*
303: * Set the new object key back into the proxy. This ensures that
304: * earlier changes to this object will be undone in this object. We
305: * must also add to our map so that if further changes are made that
306: * reference this object key, they will be using the same proxy.
307: */
308: if (objectKeyProxy.key != null) {
309: throw new RuntimeException(
310: "internal error - key proxy error");
311: }
312: objectKeyProxy.key = object.getObjectKey();
313: keyProxyMap.put(objectKeyProxy.key, objectKeyProxy);
314: }
315: }
316:
317: /**
318: * A ChangeEntry object for a move of an object from one list to another.
319: */
320: class ChangeEntry_Move<E extends ExtendableObject> extends
321: ChangeEntry {
322: private E movedObject;
323: private KeyProxy originalParentKeyProxy;
324: private ListPropertyAccessor<? super E> originalListProperty;
325:
326: ChangeEntry_Move(E movedObject,
327: KeyProxy originalParentKeyProxy,
328: ListPropertyAccessor<? super E> originalListProperty) {
329: this .movedObject = movedObject;
330: this .originalParentKeyProxy = originalParentKeyProxy;
331: this .originalListProperty = originalListProperty;
332: }
333:
334: @Override
335: void undo() {
336: ExtendableObject originalParent = originalParentKeyProxy.key
337: .getObject(); // efficient???
338: originalParent.getListPropertyValue(originalListProperty)
339: .moveElement(movedObject);
340: }
341: }
342:
343: private KeyProxy getKeyProxy(IObjectKey objectKey) {
344: if (objectKey != null) {
345: KeyProxy keyProxy = keyProxyMap.get(objectKey);
346: if (keyProxy == null) {
347: keyProxy = new KeyProxy(objectKey);
348: keyProxyMap.put(objectKey, keyProxy);
349: }
350: return keyProxy;
351: } else {
352: return null;
353: }
354: }
355:
356: private void addUndoableChangeEntry(ChangeEntry changeEntry) {
357: if (currentUndoableChange != null) {
358: currentUndoableChange.addChange(changeEntry);
359: }
360:
361: /*
362: * If changes are made while currentUndoableChange is set to null then
363: * the changes are not undoable. This is supported but is not common. It
364: * is typically used for very large transactions such as imports of
365: * entire databases by the copier plug-in.
366: */
367: // TODO: We should really clear out the change history as
368: // prior changes are not likely to be undoable after this
369: // change has been applied.
370: }
371:
372: /**
373: * The property may be any property in the passed object.
374: * The property may be defined in the actual class or
375: * any super classes which the class extends. The property
376: * may also be a property in any extension class which extends
377: * the class of this object or which extends any super class
378: * of the class of this object.
379: */
380: public <V> void processPropertyUpdate(ExtendableObject object,
381: ScalarPropertyAccessor<V> propertyAccessor, V oldValue,
382: V newValue) {
383:
384: // Replace any keys with proxy keys
385: if (propertyAccessor.getClassOfValueObject().isAssignableFrom(
386: ExtendableObject.class)) {
387: ChangeEntry newChangeEntry = new ChangeEntry_UpdateReference<V>(
388: getKeyProxy(object.getObjectKey()),
389: propertyAccessor,
390: getKeyProxy((IObjectKey) oldValue));
391:
392: addUndoableChangeEntry(newChangeEntry);
393: } else {
394: ChangeEntry newChangeEntry = new ChangeEntry_UpdateScalar<V>(
395: getKeyProxy(object.getObjectKey()),
396: propertyAccessor, oldValue);
397:
398: addUndoableChangeEntry(newChangeEntry);
399: }
400: }
401:
402: public void processObjectCreation(ListKey<?> owningListKey,
403: ExtendableObject newObject) {
404:
405: ChangeEntry newChangeEntry = new ChangeEntry_Insert(
406: getKeyProxy(owningListKey.getParentKey()),
407: owningListKey.getListPropertyAccessor(),
408: getKeyProxy(newObject.getObjectKey()));
409:
410: addUndoableChangeEntry(newChangeEntry);
411: }
412:
413: /**
414: * Processes the deletion of an object. This involves adding the property
415: * values to the change list so that the deletion can be undone.
416: * <P>
417: * Also we must call this method recursively on any objects contained in any
418: * list properties in the object. This is because this object 'owns' such
419: * objects, and so those objects will also be deleted and must be restored
420: * if this operation is undone.
421: *
422: * @param <E>
423: * @param parent
424: * @param owningListProperty
425: * @param oldObject
426: */
427: public <E extends ExtendableObject> void processObjectDeletion(
428: ExtendableObject parent,
429: ListPropertyAccessor<E> owningListProperty, E oldObject) {
430:
431: /*
432: * We must also process objects owned by this object in a recursive
433: * manner. Otherwise, undoing the deletion of an object will not restore
434: * any objects owned by that object.
435: */
436: for (ListPropertyAccessor<?> subList : PropertySet
437: .getPropertySet(oldObject.getClass())
438: .getListProperties3()) {
439: processObjectListDeletion(oldObject, subList);
440: }
441:
442: ChangeEntry_Delete<E> newChangeEntry = new ChangeEntry_Delete<E>(
443: getKeyProxy(parent.getObjectKey()), owningListProperty,
444: oldObject);
445:
446: /*
447: * The actual key is no longer valid, so we remove the proxy from the
448: * map that maps object keys to proxies. For safety we also set this to
449: * null.
450: *
451: * Note that the proxy itself still exists. If this deletion is later
452: * undone then the object is re-inserted and will be given a new object
453: * key by the underlying datastore. That new object key will then be set in
454: * the proxy and the proxy will be added back to the map with the new
455: * object key.
456: */
457:
458: // Remove from the map.
459: keyProxyMap.remove(newChangeEntry.objectKeyProxy.key);
460:
461: // This line may not be needed, as the key should never
462: // be accessed if the proxy represents a key that currently
463: // does not exist in the datastore. This line is here for
464: // safety only.
465: newChangeEntry.objectKeyProxy.key = null;
466:
467: addUndoableChangeEntry(newChangeEntry);
468: }
469:
470: /**
471: * Processes the deletion of an object. This involves adding the property
472: * values to the change list so that the deletion can be undone.
473: * <P>
474: * Also we must call this method recursively on any objects contained in any
475: * list properties in the object. This is because this object 'owns' such
476: * objects, and so those objects will also be deleted and must be restored
477: * if this operation is undone.
478: *
479: * @param <E>
480: * @param parent
481: * @param owningListProperty
482: * @param oldObject
483: */
484: public <E extends ExtendableObject> void processObjectMove(
485: E movedObject, ListKey<? super E> originalParentListKey) {
486:
487: ChangeEntry_Move<E> newChangeEntry = new ChangeEntry_Move<E>(
488: movedObject, getKeyProxy(originalParentListKey
489: .getParentKey()), originalParentListKey
490: .getListPropertyAccessor());
491:
492: addUndoableChangeEntry(newChangeEntry);
493: }
494:
495: /**
496: * Helper function to process the deletion of all objects in a list
497: * property.
498: *
499: * @param <E>
500: * @param parent the object containing the list
501: * @param listProperty the property accessor for the list
502: */
503: private <E extends ExtendableObject> void processObjectListDeletion(
504: ExtendableObject parent,
505: ListPropertyAccessor<E> listProperty) {
506: for (E childObject : parent.getListPropertyValue(listProperty)) {
507: processObjectDeletion(parent, listProperty, childObject);
508: }
509: }
510:
511: public void setUndoableChange() {
512: currentUndoableChange = new UndoableChange();
513: }
514:
515: public UndoableChange takeUndoableChange() {
516: UndoableChange result = currentUndoableChange;
517: currentUndoableChange = null;
518: return result;
519: }
520: }
|