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;
006:
007: import sun.misc.Unsafe;
008:
009: import com.tc.exception.TCRuntimeException;
010: import com.tc.logging.TCLogger;
011: import com.tc.logging.TCLogging;
012: import com.tc.object.applicator.ChangeApplicator;
013: import com.tc.object.dna.api.DNA;
014: import com.tc.object.dna.api.DNAWriter;
015: import com.tc.object.dna.impl.ProxyInstance;
016: import com.tc.object.field.TCField;
017: import com.tc.object.field.TCFieldFactory;
018: import com.tc.object.loaders.Namespace;
019: import com.tc.object.tx.optimistic.OptimisticTransactionManager;
020: import com.tc.util.Assert;
021: import com.tc.util.ClassUtils;
022: import com.tc.util.ReflectionUtil;
023: import com.tc.util.UnsafeUtil;
024:
025: import java.io.IOException;
026: import java.lang.reflect.Constructor;
027: import java.lang.reflect.Field;
028: import java.lang.reflect.Modifier;
029: import java.lang.reflect.Proxy;
030: import java.util.ConcurrentModificationException;
031: import java.util.HashMap;
032: import java.util.Iterator;
033: import java.util.LinkedList;
034: import java.util.Map;
035:
036: /**
037: * Peer of a Class under management.
038: * <p>
039: * This is used to cache the fields of each class by type.
040: *
041: * @author orion
042: */
043: public class TCClassImpl implements TCClass {
044: private final static TCLogger logger = TCLogging
045: .getLogger(TCClassImpl.class);
046: private final static Unsafe unsafe = UnsafeUtil.getUnsafe();
047:
048: /**
049: * Peer java class that this TCClass represents.
050: */
051: private final Class peer;
052:
053: private final TCClass super clazz;
054: private final TCClassFactory clazzFactory;
055: private final TCField[] portableFields;
056: private final boolean indexed;
057: private final boolean isNonStaticInner;
058: private final boolean isLogical;
059: private final boolean isCallConstructor;
060: private final String onLoadScript;
061: private final String onLoadMethod;
062: private final ChangeApplicator applicator;
063: private final String parentFieldName;
064: private final Map declaredTCFieldsByName = new HashMap();
065: private final Map tcFieldsByName = new HashMap();
066: private final String loaderDesc;
067: private Constructor constructor = null;
068: private final Field parentField;
069: private final static SerializationUtil SERIALIZATION_UTIL = new SerializationUtil();
070: private final boolean useNonDefaultConstructor;
071: private Map offsetToFields;
072: private final ClientObjectManager objectManager;
073: private final boolean isProxyClass;
074: private final boolean isEnum;
075:
076: private final String logicalExtendingClassName;
077: private final Class logicalSuperClass;
078:
079: TCClassImpl(TCFieldFactory factory, TCClassFactory clazzFactory,
080: ClientObjectManager objectManager, Class peer,
081: Class logicalSuperClass, String loaderDesc,
082: String logicalExtendingClassName, boolean isLogical,
083: boolean isCallConstructor, String onLoadScript,
084: String onLoadMethod, boolean useNonDefaultConstructor) {
085: this .clazzFactory = clazzFactory;
086: this .objectManager = objectManager;
087: this .peer = peer;
088: this .loaderDesc = loaderDesc;
089: this .indexed = peer.isArray();
090:
091: boolean isStatic = Modifier.isStatic(peer.getModifiers());
092: boolean mightBeInner = peer.getName().indexOf('$') != -1
093: && !isIndexed();
094: this .parentField = mightBeInner && !isStatic ? findParentField()
095: : null;
096: this .isNonStaticInner = parentField != null;
097: this .parentFieldName = parentField == null ? null : getName()
098: + '.' + parentField.getName();
099:
100: this .isLogical = isLogical;
101: this .isProxyClass = Proxy.isProxyClass(peer)
102: || ProxyInstance.class.getName().equals(peer.getName());
103: this .isCallConstructor = isCallConstructor;
104: this .onLoadScript = onLoadScript;
105: this .onLoadMethod = onLoadMethod;
106: this .super clazz = findSuperClass(peer);
107: this .isEnum = ClassUtils.isEnum(peer);
108: this .logicalExtendingClassName = logicalExtendingClassName;
109:
110: this .applicator = createApplicator();
111:
112: introspectFields(peer, factory);
113: this .portableFields = createPortableFields();
114: this .useNonDefaultConstructor = isProxyClass
115: || ClassUtils.isPortableReflectionClass(peer)
116: || useNonDefaultConstructor;
117: this .logicalSuperClass = logicalSuperClass;
118: }
119:
120: public Field getParentField() {
121: return parentField;
122: }
123:
124: public boolean isNonStaticInner() {
125: return this .isNonStaticInner;
126: }
127:
128: public Class getPeerClass() {
129: return this .peer;
130: }
131:
132: private Field findParentField() {
133: Field[] fields = peer.getDeclaredFields();
134: for (int i = 0; i < fields.length; i++) {
135: if (SERIALIZATION_UTIL.isParent(fields[i].getName()))
136: return fields[i];
137: }
138: return null;
139: }
140:
141: private TCClass findSuperClass(Class c) {
142: Class super class = c.getSuperclass();
143: if (super class != null) {
144: return clazzFactory.getOrCreate(super class, objectManager);
145: }
146: return null;
147: }
148:
149: private ChangeApplicator createApplicator() {
150: return clazzFactory.createApplicatorFor(this , indexed);
151: }
152:
153: public void hydrate(TCObject tcObject, DNA dna, Object pojo,
154: boolean force) throws IOException, ClassNotFoundException {
155: // Okay...long story here The application of the DNA used to be a synchronized(applicator) block. As best as Steve
156: // and I could tell, the synchronization was solely a memory boundary and not a mutual exlusion mechanism. For the
157: // time being, we have resolved that we need no synchronization here (either for memory, or exclusion). The memory
158: // barrier aspect isn't known to be a problem and the concurrency is handled by the server (ie. we won't get
159: // concurrent updates). At some point it would be a good idea to detect (and error out) when updates are received
160: // from L2 but local read/writes have been made on the target TCObject
161:
162: final long localVersion = tcObject.getVersion();
163: final long dnaVersion = dna.getVersion();
164:
165: if (force || (localVersion < dnaVersion)) {
166: tcObject.setVersion(dnaVersion);
167: applicator.hydrate(objectManager, tcObject, dna, pojo);
168: } else if (logger.isDebugEnabled()) {
169: logger.debug("IGNORING UPDATE, local object at version "
170: + localVersion + ", dna update is version "
171: + dnaVersion);
172: }
173:
174: }
175:
176: public void dehydrate(TCObject tcObject, DNAWriter writer,
177: Object pojo) {
178: try {
179: applicator.dehydrate(objectManager, tcObject, writer, pojo);
180: } catch (ConcurrentModificationException cme) {
181: // try to log some useful stuff about the pojo in question here.
182: // This indicates improper locking, but is certainly possible
183: String type = pojo == null ? "null" : pojo.getClass()
184: .getName();
185: String toString = String.valueOf(pojo);
186: int ihc = System.identityHashCode(pojo);
187: logger.error(
188: "Shared object (presumably new) modified during dehydrate (type "
189: + type + ", ihc " + ihc + "): " + toString,
190: cme);
191: throw cme;
192: }
193: }
194:
195: public Class getComponentType() {
196: return peer.getComponentType();
197: }
198:
199: public boolean isEnum() {
200: return isEnum;
201: }
202:
203: public String getName() {
204: if (isProxyClass) {
205: return ProxyInstance.class.getName();
206: }
207: if (isEnum) {
208: return LiteralValues.ENUM_CLASS_DOTS;
209: }
210: return peer.getName();
211: }
212:
213: public String getExtendingClassName() {
214: String className = getName();
215: if (this .logicalExtendingClassName != null) {
216: className = Namespace.createLogicalExtendingClassName(
217: className, logicalExtendingClassName);
218: }
219: return className;
220: }
221:
222: public TCClass getSuperclass() {
223: return super clazz;
224: }
225:
226: public synchronized Constructor getConstructor() {
227: if (constructor == null) {
228: // As best as I can tell, the reason for the lazy initialization here is that we don't actually need the cstr
229: // looked up for all of the TCClass instances we cook up. Additionally, the assertions in findConstructor will go
230: // off for a fair number of abstract base classes (eg. java.util.AbstractMap, java.util.Dictionary, etc)
231: constructor = findConstructor();
232: }
233: return constructor;
234: }
235:
236: public boolean hasOnLoadExecuteScript() {
237: return onLoadScript != null;
238: }
239:
240: public String getOnLoadExecuteScript() {
241: Assert.eval(hasOnLoadExecuteScript());
242: return onLoadScript;
243: }
244:
245: public String getOnLoadMethod() {
246: Assert.eval(hasOnLoadMethod());
247: return onLoadMethod;
248: }
249:
250: private Constructor findConstructor() {
251: Constructor rv = null;
252:
253: if (isCallConstructor || isLogical) {
254: Constructor[] cons = peer.getDeclaredConstructors();
255: for (int i = 0; i < cons.length; i++) {
256: Class[] types = cons[i].getParameterTypes();
257: if (types.length == 0) {
258: rv = cons[i];
259: rv.setAccessible(true);
260: return rv;
261: }
262: }
263: }
264:
265: if (rv == null) {
266: rv = ReflectionUtil.newConstructor(peer, logicalSuperClass);
267: rv.setAccessible(true);
268: }
269: return rv;
270: }
271:
272: public String getParentFieldName() {
273: return parentFieldName;
274: }
275:
276: private void introspectFields(Class clazz,
277: TCFieldFactory fieldFactory) {
278: // Note: this gets us all of the fields declared in the class, static
279: // as well as instance fields.
280: Field[] fields = clazz.equals(Object.class) ? new Field[0]
281: : clazz.getDeclaredFields();
282:
283: Field field;
284: TCField tcField;
285: for (int i = 0; i < fields.length; i++) {
286: field = fields[i];
287: // The factory does a bunch of callbacks based on the field type.
288: tcField = fieldFactory.getInstance(this , field);
289: declaredTCFieldsByName.put(field.getName(), tcField);
290: tcFieldsByName.put(tcField.getName(), tcField);
291: }
292: }
293:
294: public String toString() {
295: return peer.getName();
296: }
297:
298: /**
299: * Expects the field name in the format <classname>. <fieldname>(e.g. com.foo.Bar.baz)
300: */
301: public TCField getField(String name) {
302: TCField rv = (TCField) tcFieldsByName.get(name);
303: if (rv == null && super clazz != null) {
304: rv = super clazz.getField(name);
305: }
306: return rv;
307: }
308:
309: public TCField[] getPortableFields() {
310: return portableFields;
311: }
312:
313: public TraversedReferences getPortableObjects(Object pojo,
314: TraversedReferences addTo) {
315: return applicator.getPortableObjects(pojo, addTo);
316: }
317:
318: private TCField[] createPortableFields() {
319: if (isLogical || !objectManager.isPortableClass(this .peer)) {
320: return new TCField[0];
321: }
322: LinkedList l = new LinkedList();
323: for (Iterator i = declaredTCFieldsByName.values().iterator(); i
324: .hasNext();) {
325:
326: TCField f = (TCField) i.next();
327: if (f.isPortable()) {
328: l.add(f);
329: }
330: }
331: return (TCField[]) l.toArray(new TCField[l.size()]);
332: }
333:
334: public Map connectedCopy(Object source, Object dest, Map visited,
335: OptimisticTransactionManager txManager) {
336: return this .applicator.connectedCopy(source, dest, visited,
337: objectManager, txManager);
338: }
339:
340: public boolean isIndexed() {
341: return indexed;
342: }
343:
344: public String getDefiningLoaderDescription() {
345: return loaderDesc;
346: }
347:
348: public boolean isLogical() {
349: return isLogical;
350: }
351:
352: public ClientObjectManager getObjectManager() {
353: return objectManager;
354: }
355:
356: public TCObject createTCObject(ObjectID id, Object pojo) {
357: if (isLogical) {
358: return new TCObjectLogical(objectManager
359: .getReferenceQueue(), id, pojo, this );
360: } else {
361: return new TCObjectPhysical(objectManager
362: .getReferenceQueue(), id, pojo, this );
363: }
364: }
365:
366: public boolean hasOnLoadMethod() {
367: return onLoadMethod != null;
368: }
369:
370: public boolean isUseNonDefaultConstructor() {
371: return useNonDefaultConstructor;
372: }
373:
374: public Object getNewInstanceFromNonDefaultConstructor(DNA dna)
375: throws IOException, ClassNotFoundException {
376: Object o = applicator.getNewInstance(objectManager, dna);
377:
378: if (o == null) {
379: throw new AssertionError(
380: "Can't find suitable constructor for class: "
381: + getName() + ".");
382: }
383: return o;
384: }
385:
386: private synchronized void setupFieldOffsetIfNecessary() {
387: if (offsetToFields != null) {
388: return;
389: }
390:
391: offsetToFields = new HashMap();
392: if (unsafe != null) {
393: try {
394: Field[] fields = peer.equals(Object.class) ? new Field[0]
395: : peer.getDeclaredFields();
396: // System.err.println("Thread " + Thread.currentThread().getName() + ", class: " + getName() + ", # of field: "
397: // + fields.length);
398: for (int i = 0; i < fields.length; i++) {
399: try {
400: if (!Modifier
401: .isStatic(fields[i].getModifiers())) {
402: fields[i].setAccessible(true);
403: offsetToFields.put(new Long(unsafe
404: .objectFieldOffset(fields[i])),
405: fields[i]);
406: // System.err.println("Thread " + Thread.currentThread().getName() + ", class: " + getName() + ", field: "
407: // + fields[i].getName() + ", offset: " + unsafe.objectFieldOffset(fields[i]));
408: }
409: } catch (Exception e) {
410: // Ignore those fields that throw an exception
411: }
412: }
413: } catch (Exception e) {
414: throw new TCRuntimeException(e);
415: }
416: }
417: }
418:
419: public String getFieldNameByOffset(long fieldOffset) {
420: Long fieldOffsetObj = new Long(fieldOffset);
421: setupFieldOffsetIfNecessary();
422:
423: Field field = (Field) this .offsetToFields.get(fieldOffsetObj);
424: if (field == null) {
425: if (super clazz != null) {
426: return super clazz.getFieldNameByOffset(fieldOffset);
427: } else {
428: throw new AssertionError(
429: "Field does not exist for offset: "
430: + fieldOffset);
431: }
432: } else {
433: StringBuffer sb = new StringBuffer(field
434: .getDeclaringClass().getName());
435: sb.append(".");
436: sb.append(field.getName());
437: return sb.toString();
438: }
439: }
440:
441: public boolean isPortableField(long fieldOffset) {
442: String fieldName = getFieldNameByOffset(fieldOffset);
443: TCField tcField = getField(fieldName);
444:
445: return tcField.isPortable();
446: }
447:
448: public boolean isProxyClass() {
449: return isProxyClass;
450: }
451: }
|