001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
003: * notice. All rights reserved.
004: */
005: package com.tc.object.change;
006:
007: import com.tc.io.TCByteBufferOutputStream;
008: import com.tc.logging.TCLogger;
009: import com.tc.logging.TCLogging;
010: import com.tc.object.TCClass;
011: import com.tc.object.TCObject;
012: import com.tc.object.change.event.ArrayElementChangeEvent;
013: import com.tc.object.change.event.LiteralChangeEvent;
014: import com.tc.object.change.event.LogicalChangeEvent;
015: import com.tc.object.change.event.PhysicalChangeEvent;
016: import com.tc.object.dna.api.DNACursor;
017: import com.tc.object.dna.api.DNAWriter;
018: import com.tc.object.dna.api.DNAEncoding;
019: import com.tc.object.dna.api.LogicalAction;
020: import com.tc.object.dna.api.PhysicalAction;
021: import com.tc.object.dna.impl.DNAWriterImpl;
022: import com.tc.object.dna.impl.ObjectStringSerializer;
023: import com.tc.object.tx.optimistic.OptimisticTransactionManager;
024: import com.tc.util.Assert;
025: import com.tc.util.concurrent.SetOnceFlag;
026:
027: import java.util.Collection;
028: import java.util.HashMap;
029: import java.util.Iterator;
030: import java.util.LinkedHashMap;
031: import java.util.LinkedList;
032: import java.util.List;
033: import java.util.Map;
034:
035: /**
036: * @author orion
037: */
038: public class TCChangeBufferImpl implements TCChangeBuffer {
039: private static final TCLogger logger = TCLogging
040: .getLogger(TCChangeBuffer.class);
041:
042: private final SetOnceFlag dnaCreated = new SetOnceFlag();
043: private final TCObject tcObject;
044:
045: private final int type;
046: private final Map physicalEvents;
047: private final List logicalEvents;
048: private final Map arrayEvents;
049: private final List literalValueChangedEvents;
050:
051: public TCChangeBufferImpl(TCObject object) {
052: this .tcObject = object;
053:
054: // This stuff is slightly yucky, but the "null"-ness of these event collections is relevant for determining whether
055: // physical updates to logical classes should be ignore or not
056: TCClass clazz = tcObject.getTCClass();
057: if (clazz.isIndexed()) {
058: type = ARRAY;
059: physicalEvents = null;
060: literalValueChangedEvents = null;
061: logicalEvents = null;
062: arrayEvents = new LinkedHashMap();
063: } else if (clazz.isLogical()) {
064: type = LOGICAL;
065: physicalEvents = null;
066: literalValueChangedEvents = null;
067: logicalEvents = new LinkedList();
068: arrayEvents = null;
069: } else {
070: type = PHYSICAL;
071: physicalEvents = new HashMap();
072: literalValueChangedEvents = new LinkedList();
073: logicalEvents = null;
074: arrayEvents = null;
075: }
076: }
077:
078: public void writeTo(TCByteBufferOutputStream output,
079: ObjectStringSerializer serializer, DNAEncoding encoding) {
080: // NOTE: This method releases the change events to conserve memory
081:
082: if (dnaCreated.attemptSet()) {
083: TCClass tcClass = tcObject.getTCClass();
084: String className = tcClass.getExtendingClassName();
085: String loaderDesc = tcClass.getDefiningLoaderDescription();
086: DNAWriter writer = new DNAWriterImpl(output, tcObject
087: .getObjectID(), className, serializer, encoding,
088: loaderDesc);
089:
090: final boolean isDelta;
091:
092: if (tcObject.dehydrateIfNew(writer)) {
093: isDelta = false;
094: } else {
095: isDelta = true;
096: if (arrayEvents != null) {
097: writeEventsToDNA(arrayEvents.values(), writer);
098: }
099: if (physicalEvents != null) {
100: writeEventsToDNA(physicalEvents.values(), writer);
101: }
102: if (logicalEvents != null) {
103: writeEventsToDNA(logicalEvents, writer);
104: }
105: if (literalValueChangedEvents != null) {
106: writeEventsToDNA(literalValueChangedEvents, writer);
107: }
108: }
109:
110: writer.finalizeDNA(isDelta);
111: return;
112: }
113:
114: throw new IllegalStateException("DNA already created");
115: }
116:
117: private void writeEventsToDNA(Collection events, DNAWriter writer) {
118: if (events.size() > 0) {
119: for (Iterator iter = events.iterator(); iter.hasNext();) {
120: TCChangeBufferEvent event = (TCChangeBufferEvent) iter
121: .next();
122: event.write(writer);
123: }
124: }
125: }
126:
127: public void literalValueChanged(Object newValue) {
128: literalValueChangedEvents.add(new LiteralChangeEvent(newValue));
129: }
130:
131: public void fieldChanged(String classname, String fieldname,
132: Object newValue, int index) {
133: Assert.eval(newValue != null);
134:
135: if (index >= 0) {
136: // We could add some form of threshold for array change events where instead of encoding the individual updates,
137: // we could just send the data for the entire array (like we do when a managed array is brand new)
138:
139: // could use a better collection that maintain put order for repeated additions
140: Integer key = new Integer(index);
141: arrayEvents.remove(key);
142: arrayEvents.put(key, new ArrayElementChangeEvent(index,
143: newValue));
144: } else {
145: if (logicalEvents != null) {
146: // ignore physical updates to classes that are logically managed (This happens for things like THash which is a
147: // superclass of THashMap)
148: if (logger.isDebugEnabled()) {
149: logger.debug("Ignoring physical field change for "
150: + classname + "." + fieldname + " since "
151: + tcObject.getTCClass().getName()
152: + " is logically managed");
153: }
154: return;
155: }
156:
157: // XXX: only fully qualify fieldnames when necessary (ie. when a variable name is shadowed)
158: // XXX: When and if this change is made, you also want to look at GenericTCField
159: // fieldname = classname + "." + fieldname;
160:
161: // Assert.eval(fieldname.indexOf('.') >= 0);
162: // this will replace any existing event for this field. Last change made to any field within the same TXN wins
163: physicalEvents.put(fieldname, new PhysicalChangeEvent(
164: fieldname, newValue));
165: }
166: }
167:
168: public void arrayChanged(int startPos, Object array, int newLength) {
169: // could use a better collection that maintain put order for repeated additions
170: Integer key = new Integer(-startPos); // negative int is used for sub-arrays
171: ArrayElementChangeEvent oldEvent = (ArrayElementChangeEvent) arrayEvents
172: .remove(key);
173: if (oldEvent != null) {
174: Object oldArray = oldEvent.getValue();
175: int oldLength = oldEvent.getLength();
176: if (oldLength > newLength) {
177: System.arraycopy(array, 0, oldArray, 0, newLength);
178: array = oldArray;
179: }
180: }
181: arrayEvents.put(key, new ArrayElementChangeEvent(startPos,
182: array, newLength));
183: }
184:
185: public void logicalInvoke(int method, Object[] parameters) {
186: // TODO: It might be useful (if it doesn't take too much CPU) to collapse logical operations. For instance,
187: // if a put() is followed by a remove() on the same key we don't need to send anything. Or if multiple put()s are
188: // done, only the last one matters
189:
190: logicalEvents.add(new LogicalChangeEvent(method, parameters));
191: }
192:
193: public TCObject getTCObject() {
194: return tcObject;
195: }
196:
197: public int getTotalEventCount() {
198: int eventCount = 0;
199: if (physicalEvents != null) {
200: eventCount += physicalEvents.size();
201: }
202: if (literalValueChangedEvents != null) {
203: eventCount += literalValueChangedEvents.size();
204: }
205: if (logicalEvents != null) {
206: eventCount += logicalEvents.size();
207: }
208: if (arrayEvents != null) {
209: eventCount += arrayEvents.size();
210: }
211: return eventCount;
212: }
213:
214: public int getType() {
215: return type;
216: }
217:
218: public void accept(TCChangeBufferEventVisitor visitor) {
219: switch (type) {
220: case LOGICAL:
221: for (Iterator it = logicalEvents.iterator(); it.hasNext();) {
222: visitor.visitLogicalEvent((LogicalChangeEvent) it
223: .next());
224: }
225: break;
226:
227: case PHYSICAL:
228: if (literalValueChangedEvents != null
229: && literalValueChangedEvents.size() > 0) {
230: throw new AssertionError(
231: "Changes to literal roots are not supported in OptimisticTransaction.");
232: }
233: for (Iterator it = physicalEvents.values().iterator(); it
234: .hasNext();) {
235: visitor
236: .visitPhysicalChangeEvent((PhysicalChangeEvent) it
237: .next());
238: }
239: break;
240:
241: case ARRAY:
242: for (Iterator it = arrayEvents.values().iterator(); it
243: .hasNext();) {
244: visitor
245: .visitArrayElementChangeEvent((ArrayElementChangeEvent) it
246: .next());
247: }
248: break;
249:
250: default:
251: throw new AssertionError("Unknown event type " + type);
252: }
253: }
254:
255: public DNACursor getDNACursor(
256: OptimisticTransactionManager transactionManager) {
257: switch (type) {
258: case PHYSICAL:
259: if (literalValueChangedEvents != null
260: && literalValueChangedEvents.size() > 0) {
261: throw new AssertionError(
262: "Changes to literal roots are not supported in OptimisticTransaction.");
263: }
264: return new AbstractDNACursor(physicalEvents.values(),
265: transactionManager) {
266: Object createNextAction(Object object) {
267: PhysicalChangeEvent pe = (PhysicalChangeEvent) object;
268: return new PhysicalAction(pe.getFieldName(),
269: convertToParameter(pe.getNewValue()), pe
270: .isReference());
271: }
272: };
273:
274: case LOGICAL:
275: return new AbstractDNACursor(logicalEvents,
276: transactionManager) {
277: Object createNextAction(Object object) {
278: LogicalChangeEvent le = (LogicalChangeEvent) object;
279: Object[] p = new Object[le.getParameters().length];
280: for (int i = 0; i < le.getParameters().length; i++) {
281: p[i] = convertToParameter(le.getParameters()[i]);
282: }
283: return new LogicalAction(le.getMethodID(), p);
284: }
285: };
286:
287: case ARRAY:
288: return new AbstractDNACursor(arrayEvents.values(),
289: transactionManager) {
290: Object createNextAction(Object object) {
291: ArrayElementChangeEvent ae = (ArrayElementChangeEvent) object;
292: if (ae.isSubarray()) {
293: return new PhysicalAction(ae.getValue(), ae
294: .getIndex());
295: } else {
296: return new PhysicalAction(ae.getIndex(),
297: convertToParameter(ae.getValue()), ae
298: .isReference());
299: }
300: }
301: };
302:
303: default:
304: throw new AssertionError("Unknown event type " + type);
305: }
306: }
307:
308: private static abstract class AbstractDNACursor implements
309: DNACursor {
310: private final OptimisticTransactionManager transactionManager;
311: private final Iterator iterator;
312: private final int size;
313:
314: private Object currentAction = null;
315:
316: public AbstractDNACursor(Collection values,
317: OptimisticTransactionManager transactionManager) {
318: this .transactionManager = transactionManager;
319: this .iterator = values.iterator();
320: this .size = values.size();
321: }
322:
323: public boolean next() {
324: boolean hasNext = iterator.hasNext();
325: if (hasNext) {
326: this .currentAction = createNextAction(iterator.next());
327: }
328: return hasNext;
329: }
330:
331: abstract Object createNextAction(Object object);
332:
333: public boolean next(DNAEncoding encoding) {
334: return next();
335: }
336:
337: public int getActionCount() {
338: return size;
339: }
340:
341: public Object getAction() {
342: return currentAction;
343: }
344:
345: public LogicalAction getLogicalAction() {
346: return (LogicalAction) currentAction;
347: }
348:
349: public PhysicalAction getPhysicalAction() {
350: return (PhysicalAction) currentAction;
351: }
352:
353: public void reset() throws UnsupportedOperationException {
354: throw new UnsupportedOperationException(
355: "This operation is not supported by this class.");
356: }
357:
358: protected Object convertToParameter(Object object) {
359: return transactionManager.convertToParameter(object);
360: }
361: }
362:
363: }
|