001: /*
002: * Copyright (C) 2004 NNL Technology AB
003: * Visit www.infonode.net for information about InfoNode(R)
004: * products and how to contact NNL Technology AB.
005: *
006: * This program is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU General Public License
008: * as published by the Free Software Foundation; either version 2
009: * of the License, or (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place - Suite 330, Boston,
019: * MA 02111-1307, USA.
020: */
021:
022: // $Id: PropertyMapManager.java,v 1.16 2005/12/04 13:46:06 jesper Exp $
023: package net.infonode.properties.propertymap;
024:
025: import net.infonode.properties.propertymap.value.PropertyValue;
026: import net.infonode.util.Utils;
027: import net.infonode.util.ValueChange;
028: import net.infonode.util.collection.map.base.ConstMap;
029: import net.infonode.util.collection.map.base.ConstMapIterator;
030:
031: import java.util.Collections;
032: import java.util.HashMap;
033: import java.util.Iterator;
034: import java.util.Map;
035:
036: /**
037: * Utility class for performing multiple modifications to {@link PropertyMap}'s and merging change notifications to
038: * optimize performance.
039: *
040: * @author $Author: jesper $
041: * @version $Revision: 1.16 $
042: */
043: public class PropertyMapManager {
044: private static final PropertyMapManager INSTANCE = new PropertyMapManager();
045:
046: private HashMap changes;
047: private int batchCounter;
048:
049: /**
050: * Returns the only instance of this class.
051: *
052: * @return the only instance of this class
053: */
054: public static PropertyMapManager getInstance() {
055: return INSTANCE;
056: }
057:
058: void addMapChanges(PropertyMapImpl propertyMap, ConstMap mapChanges) {
059: HashMap map = (HashMap) changes.get(propertyMap);
060:
061: if (map == null) {
062: map = new HashMap();
063: changes.put(propertyMap, map);
064: }
065:
066: for (ConstMapIterator iterator = mapChanges.constIterator(); iterator
067: .atEntry(); iterator.next()) {
068: ValueChange vc = (ValueChange) iterator.getValue();
069: Object key = iterator.getKey();
070: Object newValue = vc.getNewValue() == null ? null
071: : ((PropertyValue) vc.getNewValue())
072: .getWithDefault(propertyMap);
073: Object value = map.get(key);
074: Object oldValue = value == null ? vc.getOldValue() == null ? null
075: : ((PropertyValue) vc.getOldValue())
076: .getWithDefault(propertyMap)
077: : ((ValueChange) value).getOldValue();
078:
079: if (!Utils.equals(oldValue, newValue))
080: map.put(iterator.getKey(), new ValueChange(oldValue,
081: newValue));
082: else if (value != null)
083: map.remove(key);
084: }
085: }
086:
087: /**
088: * Executes a method inside a {@link #beginBatch()} - {@link #endBatch()} pair. See {@link #beginBatch()} for
089: * more information. It's safe to call other batch methods from inside {@link Runnable#run}.
090: *
091: * @param runnable the runnable to invoke
092: */
093: public static void runBatch(Runnable runnable) {
094: getInstance().beginBatch();
095:
096: try {
097: runnable.run();
098: } finally {
099: getInstance().endBatch();
100: }
101: }
102:
103: /**
104: * Begins a batch operation. This stores and merges all change notifications occuring in all property maps until
105: * {@link #endBatch} is called. Each call to this method MUST be followed by a call to {@link #endBatch}.
106: * This method can be called an unlimited number of times without calling {@link #endBatch} in between, but each
107: * call must have a corresponding call to {@link #endBatch}. Only when exiting from the
108: * outermost {@link #endBatch()} the changes be propagated to the listeners.
109: */
110: public void beginBatch() {
111: if (batchCounter++ == 0)
112: changes = new HashMap();
113: }
114:
115: private void addTreeChanges(PropertyMapImpl map,
116: PropertyMapImpl modifiedMap, HashMap changes,
117: HashMap treeChanges) {
118: HashMap changeMap = (HashMap) treeChanges.get(map);
119:
120: if (changeMap == null) {
121: changeMap = new HashMap();
122: treeChanges.put(map, changeMap);
123: }
124:
125: changeMap.put(modifiedMap, changes);
126:
127: if (map.getParent() != null)
128: addTreeChanges(map.getParent(), modifiedMap, changes,
129: treeChanges);
130: }
131:
132: /**
133: * Ends a batch operation. See {@link #beginBatch()} for more information.
134: */
135: public void endBatch() {
136: if (--batchCounter == 0) {
137: HashMap treeChanges = new HashMap();
138: HashMap localChanges = changes;
139: changes = null;
140:
141: for (Iterator iterator = localChanges.entrySet().iterator(); iterator
142: .hasNext();) {
143: Map.Entry entry = (Map.Entry) iterator.next();
144: PropertyMapImpl object = (PropertyMapImpl) entry
145: .getKey();
146: HashMap objectChanges = (HashMap) entry.getValue();
147:
148: if (!objectChanges.isEmpty()) {
149: object.firePropertyValuesChanged(Collections
150: .unmodifiableMap(objectChanges));
151: addTreeChanges(object, object, objectChanges,
152: treeChanges);
153: }
154: }
155:
156: for (Iterator iterator = treeChanges.entrySet().iterator(); iterator
157: .hasNext();) {
158: Map.Entry entry = (Map.Entry) iterator.next();
159: PropertyMapImpl object = (PropertyMapImpl) entry
160: .getKey();
161: HashMap objectChanges = (HashMap) entry.getValue();
162:
163: if (!objectChanges.isEmpty())
164: object.firePropertyTreeValuesChanged(Collections
165: .unmodifiableMap(objectChanges));
166: }
167: }
168: }
169: }
|