001: /*
002: * Copyright 2004 (C) TJDO.
003: * All rights reserved.
004: *
005: * This software is distributed under the terms of the TJDO License version 1.0.
006: * See the terms of the TJDO License in the documentation provided with this software.
007: *
008: * $Id: ClassMetaData.java,v 1.10 2004/01/18 03:01:06 jackknifebarber Exp $
009: */
010:
011: package com.triactive.jdo.model;
012:
013: import com.triactive.jdo.ClassNotPersistenceCapableException;
014: import com.triactive.jdo.util.MacroString;
015: import com.triactive.jdo.util.XMLHelper;
016: import java.lang.reflect.Field;
017: import java.lang.reflect.Modifier;
018: import java.net.URL;
019: import java.util.Arrays;
020: import java.util.ArrayList;
021: import java.util.Collections;
022: import java.util.HashMap;
023: import java.util.HashSet;
024: import java.util.Iterator;
025: import java.util.List;
026: import java.util.Map;
027: import java.util.Set;
028: import java.util.WeakHashMap;
029: import javax.jdo.JDOFatalInternalException;
030: import javax.jdo.JDOFatalUserException;
031: import javax.jdo.JDOUserException;
032: import javax.jdo.spi.JDOImplHelper;
033: import javax.jdo.spi.PersistenceCapable;
034: import org.w3c.dom.Element;
035: import org.w3c.dom.Node;
036: import org.w3c.dom.NodeList;
037: import org.apache.log4j.Category;
038:
039: public final class ClassMetaData extends MetaData {
040: private static final Category LOG = Category
041: .getInstance(ClassMetaData.class);
042:
043: private static Map metaDataByClass = new WeakHashMap();
044: private static Map searchedPathsByClassLoader = new WeakHashMap();
045:
046: public static synchronized ClassMetaData forClass(Class c) {
047: if (c.isArray() || c.isInterface() || c.isPrimitive())
048: return null;
049:
050: if (metaDataByClass.containsKey(c))
051: return (ClassMetaData) metaDataByClass.get(c);
052:
053: ClassLoader cl = c.getClassLoader();
054:
055: if (cl == null)
056: cl = ClassLoader.getSystemClassLoader();
057:
058: Set searchedPaths = (Set) searchedPathsByClassLoader.get(cl);
059:
060: if (searchedPaths == null) {
061: searchedPaths = new HashSet();
062: searchedPathsByClassLoader.put(cl, searchedPaths);
063: }
064:
065: ClassMetaData cmd = null;
066: Iterator i = possiblePathsFor(c).iterator();
067:
068: while (cmd == null && i.hasNext()) {
069: String path = (String) i.next();
070:
071: if (!searchedPaths.contains(path)) {
072: if (LOG.isDebugEnabled())
073: LOG.debug("Searching for metadata at " + path
074: + " using " + cl);
075:
076: URL url = cl.getResource(path);
077:
078: if (url != null)
079: cmd = loadMetadataFileLookingFor(url, c, cl);
080:
081: searchedPaths.add(path);
082: }
083: }
084:
085: if (cmd != null)
086: cmd.assertIsValidated();
087:
088: return cmd;
089: }
090:
091: private static List possiblePathsFor(Class c) {
092: ArrayList paths = new ArrayList();
093:
094: paths.add("META-INF/package.jdo");
095: paths.add("WEB-INF/package.jdo");
096: paths.add("package.jdo");
097:
098: String path = c.getName().replace('.', '/');
099:
100: for (int slash = 0; (slash = path.indexOf('/', slash)) >= 0;)
101: paths.add(path.substring(0, ++slash) + "package.jdo");
102:
103: paths.add(path + ".jdo");
104:
105: /* Add 1.0-style naming convention to the end of the search list. */
106: for (int slash = path.length() - 1; (slash = path.lastIndexOf(
107: '/', slash)) >= 0;)
108: paths.add(path.substring(0, slash--) + ".jdo");
109:
110: return paths;
111: }
112:
113: private static ClassMetaData loadMetadataFileLookingFor(URL url,
114: Class targetClass, ClassLoader cl) {
115: if (LOG.isDebugEnabled())
116: LOG.debug("Loading metadata from " + url);
117:
118: ClassMetaData found = null;
119: Element docElement;
120:
121: try {
122: docElement = XMLHelper.getDocumentBuilder().parse(
123: url.openStream()).getDocumentElement();
124: } catch (Exception e) {
125: throw new XMLMetaDataException(url,
126: "Could not access or parse metadata file", e);
127: }
128:
129: NodeList nodes = docElement.getElementsByTagName("class");
130:
131: for (int i = 0; i < nodes.getLength(); ++i) {
132: Element clsElement = (Element) nodes.item(i);
133: String packageName = ((Element) clsElement.getParentNode())
134: .getAttribute("name");
135: String className = packageName + '.'
136: + clsElement.getAttribute("name");
137: Class c;
138:
139: try {
140: /*
141: * Important that 'true' is passed here so that each class is
142: * initialized. That's needed so that the static initialization
143: * block registers with JDOImplHelper, which will be consulted
144: * during ClassMetaData validation.
145: */
146: c = Class.forName(className, true, cl);
147: } catch (ClassNotFoundException e) {
148: throw new XMLMetaDataException(url, "Class" + className
149: + " not found", e);
150: }
151:
152: if (!metaDataByClass.containsKey(c)) {
153: ClassMetaData cmd = new ClassMetaData(c, url,
154: clsElement);
155: metaDataByClass.put(c, cmd);
156:
157: if (c.equals(targetClass))
158: found = cmd;
159: }
160: }
161:
162: return found;
163: }
164:
165: public static final byte NEW = 0, CONSTRUCTED = 1, VALIDATED = 2;
166:
167: public static final int NO_IDENTITY = 0, DATASTORE_IDENTITY = 1,
168: APPLICATION_IDENTITY = 2;
169:
170: private static final List identityTypeValues = Arrays
171: .asList(new String[] { "nondurable", "datastore",
172: "application" });
173:
174: private final Class clazz;
175: private final ClassLoader loader;
176: private final String packageName;
177: private final URL loadedFrom;
178: private final int identityType;
179: private final Class identityClass;
180: private final boolean requiresExtent;
181: private final Class pcSuperclass;
182: private final List managedFields = new ArrayList();
183: private final Map fieldNumbersByName = new HashMap();
184:
185: private byte state = NEW;
186: private boolean validating = false;
187:
188: /* Fields below are only valid after state == VALIDATED. */
189: private ClassMetaData pcSuperclassMetaData;
190: private int inheritedFieldCount;
191: private int totalFieldCount;
192: private int[] allFieldNumbers;
193: private int[] transactionalFieldNumbers;
194: private int[] persistentFieldNumbers;
195: private int[] defaultFetchGroupFieldNumbers;
196: private int[] secondClassMutableFieldNumbers;
197: private boolean[] transactionalFieldFlags;
198: private boolean[] persistentFieldFlags;
199: private boolean[] defaultFetchGroupFieldFlags;
200: private boolean[] secondClassMutableFieldFlags;
201:
202: private ClassMetaData(Class clazz, URL loadedFrom,
203: Element clsElement) {
204: super (loadedFrom, clsElement);
205:
206: String className = clazz.getName();
207: int lastDot = className.lastIndexOf('.');
208:
209: this .clazz = clazz;
210: this .loader = clazz.getClassLoader();
211: this .packageName = lastDot < 0 ? "" : className.substring(0,
212: lastDot);
213: this .loadedFrom = loadedFrom;
214:
215: /* Process the "identity-type" attribute. */
216: String idTypeAttr = clsElement.getAttribute("identity-type");
217:
218: if (idTypeAttr.length() > 0) {
219: identityType = identityTypeValues.indexOf(idTypeAttr);
220:
221: if (identityType < 0)
222: throw new XMLMetaDataException(loadedFrom,
223: "Unrecognized identity-type " + idTypeAttr);
224: } else
225: identityType = DATASTORE_IDENTITY;
226:
227: if (identityType == APPLICATION_IDENTITY) {
228: /* Process the "objectid-class" attribute. */
229: String objIDClassName = clsElement
230: .getAttribute("objectid-class");
231: if (objIDClassName.length() == 0)
232: throw new XMLMetaDataException(loadedFrom,
233: "Missing objectid-class attribute for class "
234: + clazz.getName());
235:
236: if (objIDClassName.indexOf('.') < 0)
237: objIDClassName = packageName + '.' + objIDClassName;
238:
239: try {
240: identityClass = Class.forName(objIDClassName, true,
241: loader);
242: } catch (ClassNotFoundException e) {
243: throw new XMLMetaDataException(loadedFrom,
244: "Object ID class for class" + clazz.getName()
245: + " not found", e);
246: }
247: } else
248: identityClass = null;
249:
250: /* Process the "requires-extent" attribute. */
251: String requiresExtentAttr = clsElement
252: .getAttribute("requires-extent");
253: if (requiresExtentAttr.length() > 0)
254: requiresExtent = Boolean.valueOf(requiresExtentAttr)
255: .booleanValue();
256: else
257: requiresExtent = true;
258:
259: /* Process the "persistence-capable-superclass" attribute. */
260: String pcSuperclassName = clsElement
261: .getAttribute("persistence-capable-superclass");
262: if (pcSuperclassName.length() > 0) {
263: Class c;
264:
265: try {
266: c = Class.forName(pcSuperclassName, true, loader);
267: } catch (ClassNotFoundException e) {
268: try {
269: c = Class.forName(packageName + '.'
270: + pcSuperclassName, true, loader);
271: } catch (ClassNotFoundException e1) {
272: throw new XMLMetaDataException(loadedFrom,
273: "Class not found", e1);
274: }
275: }
276:
277: pcSuperclass = c;
278:
279: if (pcSuperclass.equals(clazz)
280: || !pcSuperclass.isAssignableFrom(clazz))
281: throw new XMLMetaDataException(loadedFrom,
282: "Specified persistence capable superclass "
283: + pcSuperclass.getName()
284: + " is not a superclass of "
285: + clazz.getName());
286: } else
287: pcSuperclass = null;
288:
289: /* Process "field" child elements. */
290: for (Node node = clsElement.getFirstChild(); node != null; node = node
291: .getNextSibling()) {
292: if (node instanceof Element) {
293: Element child = (Element) node;
294: String childTag = child.getTagName();
295:
296: if (childTag.equals("field"))
297: addField(new FieldMetaData(this , child));
298: }
299: }
300:
301: /*
302: * Add any eligible fields that are declared in the class but not in
303: * its metadata.
304: */
305: Field declaredFields[] = clazz.getDeclaredFields();
306:
307: for (int i = 0; i < declaredFields.length; ++i) {
308: Field f = declaredFields[i];
309:
310: if (!fieldNumbersByName.containsKey(f.getName())) {
311: int m = f.getModifiers();
312:
313: if (!Modifier.isFinal(m) && !Modifier.isStatic(m)
314: && !Modifier.isTransient(m)
315: && Types.isDefaultPersistentType(f.getType())) {
316: addField(new FieldMetaData(this , f));
317: }
318: }
319: }
320:
321: /*
322: * Sort the field list by field name, then create the field name to
323: * field number map.
324: */
325: Collections.sort(managedFields);
326: Iterator iter = managedFields.iterator();
327:
328: for (int i = 0; iter.hasNext(); ++i) {
329: FieldMetaData fmd = (FieldMetaData) iter.next();
330: fieldNumbersByName.put(fmd.getName(), new Integer(i));
331: }
332:
333: state = CONSTRUCTED;
334: }
335:
336: /** Helper method used during only construction. */
337: private void addField(FieldMetaData fm) {
338: String name = fm.getName();
339: if (fieldNumbersByName.containsKey(name))
340: throw new DuplicateFieldException(loadedFrom,
341: "Duplicate field name " + name + " in class "
342: + clazz.getName());
343:
344: fieldNumbersByName.put(name, null);
345:
346: if (fm.getPersistenceModifier() != FieldMetaData.PERSISTENCE_MODIFIER_NONE)
347: managedFields.add(fm);
348: }
349:
350: private synchronized void assertIsConstructed() {
351: if (state < CONSTRUCTED)
352: throw new JDOFatalInternalException(
353: "Object not yet constructed: " + this );
354: }
355:
356: public String getJavaName() {
357: return clazz.getName();
358: }
359:
360: public Class getPCClass() {
361: return clazz;
362: }
363:
364: public String getPackageName() {
365: return packageName;
366: }
367:
368: public URL getSourceURL() {
369: return loadedFrom;
370: }
371:
372: public Class getPCSuperclass() {
373: return pcSuperclass;
374: }
375:
376: public int getIdentityType() {
377: return identityType;
378: }
379:
380: public Class getIdentityClass() {
381: return identityClass;
382: }
383:
384: public boolean requiresExtent() {
385: return requiresExtent;
386: }
387:
388: public int getFieldCount() {
389: assertIsConstructed();
390: return managedFields.size();
391: }
392:
393: public FieldMetaData getFieldRelative(int relativeFieldNumber) {
394: assertIsConstructed();
395: return (FieldMetaData) managedFields.get(relativeFieldNumber);
396: }
397:
398: public int getRelativeFieldNumber(String name) {
399: assertIsConstructed();
400: Integer i = (Integer) fieldNumbersByName.get(name);
401:
402: return i == null ? -1 : i.intValue();
403: }
404:
405: public String getViewImports() {
406: return getVendorExtension(MY_VENDOR, "view-imports");
407: }
408:
409: public String getViewDefinition(String vendorID) {
410: String viewDef = null;
411:
412: if (vendorID != null)
413: viewDef = getVendorExtension(MY_VENDOR, "view-definition"
414: + '-' + vendorID);
415:
416: if (viewDef == null)
417: viewDef = getVendorExtension(MY_VENDOR, "view-definition");
418:
419: return viewDef;
420: }
421:
422: private synchronized boolean assertIsValidated() {
423: assertIsConstructed();
424:
425: if (state < VALIDATED) {
426: if (validating)
427: throw new JDOFatalInternalException(
428: "assertIsValidated() called recursively");
429:
430: validating = true;
431:
432: try {
433: validateAgainstClass();
434: } finally {
435: validating = false;
436: }
437:
438: return true;
439: } else
440: return false;
441: }
442:
443: private void validateAgainstClass() {
444: /*
445: * If the class has already been enhanced, verify that it agrees with
446: * the current metadata.
447: */
448: if (Types.isEnhancedClass(clazz)) {
449: JDOImplHelper helper = JDOImplHelper.getInstance();
450: String[] fieldNames = helper.getFieldNames(clazz);
451: Class[] fieldTypes = helper.getFieldTypes(clazz);
452: byte[] fieldFlags = helper.getFieldFlags(clazz);
453:
454: if (fieldNames.length != managedFields.size())
455: throw new ClassMetaDataMismatchException(clazz,
456: " class has " + fieldNames.length
457: + " fields, metadata has "
458: + managedFields.size() + " fields");
459:
460: for (int i = 0; i < fieldNames.length; ++i) {
461: FieldMetaData fmd = (FieldMetaData) managedFields
462: .get(i);
463:
464: if (!fieldNames[i].equals(fmd.getName()))
465: throw new ClassMetaDataMismatchException(clazz,
466: " class has '" + fieldNames[i]
467: + "' for field " + i
468: + ", metadata has '"
469: + fmd.getName() + "'");
470:
471: if (!fieldTypes[i].equals(fmd.getType()))
472: throw new ClassMetaDataMismatchException(clazz,
473: " class has type "
474: + fieldTypes[i].getName()
475: + " for field " + i
476: + ", metadata has "
477: + fmd.getType().getName());
478:
479: /*
480: * Verify that our expectations re. the field flags agree with
481: * those of the enhanced class.
482: *
483: * Enhancers are notorious for getting the "default" value for
484: * default-fetch-group, i.e. the value to use if nothing is
485: * given in the metadata, wrong sometimes. So for this one case
486: * we tolerate a mismatch between enhanced class and metadata
487: * and use the setting indicated by the enhanced class.
488: */
489: byte expectedFlag;
490: byte actualFlag = (byte) (fieldFlags[i] & (PersistenceCapable.CHECK_READ
491: | PersistenceCapable.CHECK_WRITE
492: | PersistenceCapable.MEDIATE_READ | PersistenceCapable.MEDIATE_WRITE));
493:
494: if (fmd.getPersistenceModifier() == FieldMetaData.PERSISTENCE_MODIFIER_TRANSACTIONAL) {
495: expectedFlag = PersistenceCapable.CHECK_WRITE;
496: } else if (fmd.isPrimaryKeyPart()) {
497: expectedFlag = PersistenceCapable.MEDIATE_WRITE;
498: } else if (fmd.isInDefaultFetchGroup()) {
499: expectedFlag = PersistenceCapable.CHECK_READ
500: | PersistenceCapable.CHECK_WRITE;
501:
502: /*
503: * Metadata says DFG=true. If the enhanced class disagrees,
504: * go with the enhanced class.
505: */
506: if (actualFlag == (PersistenceCapable.MEDIATE_READ | PersistenceCapable.MEDIATE_WRITE)) {
507: LOG
508: .warn(new ClassMetaDataFlagMismatchException(
509: clazz, i, expectedFlag,
510: actualFlag).getMessage()
511: + ": will use the value in class (non-default fetch group)");
512:
513: expectedFlag = PersistenceCapable.MEDIATE_READ
514: | PersistenceCapable.MEDIATE_WRITE;
515: fmd.setDefaultFetchGroup(false);
516: }
517: } else {
518: expectedFlag = PersistenceCapable.MEDIATE_READ
519: | PersistenceCapable.MEDIATE_WRITE;
520:
521: /*
522: * Metadata says DFG=false. If the enhanced class disagrees,
523: * go with the enhanced class.
524: */
525: if (actualFlag == (PersistenceCapable.CHECK_READ | PersistenceCapable.CHECK_WRITE)) {
526: LOG
527: .warn(new ClassMetaDataFlagMismatchException(
528: clazz, i, expectedFlag,
529: actualFlag).getMessage()
530: + ": will use the value in class (default fetch group)");
531:
532: expectedFlag = PersistenceCapable.CHECK_READ
533: | PersistenceCapable.CHECK_WRITE;
534: fmd.setDefaultFetchGroup(true);
535: }
536: }
537:
538: if (expectedFlag != actualFlag)
539: throw new ClassMetaDataFlagMismatchException(clazz,
540: i, expectedFlag, actualFlag);
541: }
542: }
543:
544: if (pcSuperclass != null) {
545: pcSuperclassMetaData = forClass(pcSuperclass);
546:
547: if (pcSuperclassMetaData == null)
548: throw new XMLMetaDataException(loadedFrom,
549: "Specified persistence capable superclass "
550: + pcSuperclass.getName()
551: + " is not persistence capable");
552:
553: inheritedFieldCount = pcSuperclassMetaData
554: .getInheritedFieldCount()
555: + pcSuperclassMetaData.getFieldCount();
556: } else
557: inheritedFieldCount = 0;
558:
559: totalFieldCount = inheritedFieldCount + managedFields.size();
560: allFieldNumbers = new int[totalFieldCount];
561: transactionalFieldFlags = new boolean[totalFieldCount];
562: persistentFieldFlags = new boolean[totalFieldCount];
563: defaultFetchGroupFieldFlags = new boolean[totalFieldCount];
564: secondClassMutableFieldFlags = new boolean[totalFieldCount];
565: int transactionalFieldCount = 0;
566: int persistentFieldCount = 0;
567: int defaultFetchGroupFieldCount = 0;
568: int secondClassMutableFieldCount = 0;
569:
570: for (int i = 0; i < totalFieldCount; ++i) {
571: allFieldNumbers[i] = i;
572:
573: FieldMetaData fmd = getFieldAbsoluteInternal(i);
574:
575: if (fmd.getPersistenceModifier() == FieldMetaData.PERSISTENCE_MODIFIER_TRANSACTIONAL) {
576: transactionalFieldFlags[i] = true;
577: ++transactionalFieldCount;
578: } else {
579: persistentFieldFlags[i] = true;
580: ++persistentFieldCount;
581:
582: if (fmd.isInDefaultFetchGroup()) {
583: defaultFetchGroupFieldFlags[i] = true;
584: ++defaultFetchGroupFieldCount;
585: }
586:
587: if (Types.isSecondClassMutableType(fmd.getType())) {
588: secondClassMutableFieldFlags[i] = true;
589: ++secondClassMutableFieldCount;
590: }
591: }
592: }
593:
594: transactionalFieldNumbers = new int[transactionalFieldCount];
595: persistentFieldNumbers = new int[persistentFieldCount];
596: defaultFetchGroupFieldNumbers = new int[defaultFetchGroupFieldCount];
597: secondClassMutableFieldNumbers = new int[secondClassMutableFieldCount];
598:
599: for (int i = 0, txn = 0, per = 0, dfg = 0, scm = 0; i < totalFieldCount; ++i) {
600: if (transactionalFieldFlags[i])
601: transactionalFieldNumbers[txn++] = i;
602:
603: if (persistentFieldFlags[i])
604: persistentFieldNumbers[per++] = i;
605:
606: if (defaultFetchGroupFieldFlags[i])
607: defaultFetchGroupFieldNumbers[dfg++] = i;
608:
609: if (secondClassMutableFieldFlags[i])
610: secondClassMutableFieldNumbers[scm++] = i;
611: }
612:
613: state = VALIDATED;
614: }
615:
616: private FieldMetaData getFieldAbsoluteInternal(
617: int absoluteFieldNumber) {
618: if (absoluteFieldNumber < inheritedFieldCount) {
619: if (pcSuperclassMetaData == null)
620: return null;
621: else
622: return pcSuperclassMetaData
623: .getFieldAbsoluteInternal(absoluteFieldNumber);
624: } else
625: return (FieldMetaData) managedFields
626: .get(absoluteFieldNumber - inheritedFieldCount);
627: }
628:
629: public int getInheritedFieldCount() {
630: assertIsValidated();
631: return inheritedFieldCount;
632: }
633:
634: public FieldMetaData getFieldAbsolute(int absoluteFieldNumber) {
635: assertIsValidated();
636: return getFieldAbsoluteInternal(absoluteFieldNumber);
637: }
638:
639: public int getAbsoluteFieldNumber(String name) {
640: assertIsValidated();
641: int lastDot = name.lastIndexOf('.');
642:
643: if (lastDot >= 0) {
644: /* Field name is of the form "com.acme.MyClass.myField". */
645: Class c;
646:
647: try {
648: c = Class.forName(name.substring(0, lastDot), true,
649: loader);
650: } catch (ClassNotFoundException e) {
651: throw new JDOUserException(
652: "Class in field name not found: " + name, e);
653: }
654:
655: ClassMetaData cmd = ClassMetaData.forClass(c);
656:
657: if (cmd == null)
658: throw new ClassNotPersistenceCapableException(c);
659:
660: if (!c.isAssignableFrom(clazz))
661: throw new JDOUserException("Class in field name "
662: + name + " not compatible with actual class "
663: + clazz.getName());
664:
665: return cmd.getAbsoluteFieldNumber(name
666: .substring(lastDot + 1));
667: } else {
668: /* Field name is of the form "myField" */
669: int i = getRelativeFieldNumber(name);
670:
671: if (i < 0) {
672: if (pcSuperclassMetaData != null)
673: i = pcSuperclassMetaData
674: .getAbsoluteFieldNumber(name);
675: } else
676: i += inheritedFieldCount;
677:
678: return i;
679: }
680: }
681:
682: public int[] getAllFieldNumbers() {
683: assertIsValidated();
684: return allFieldNumbers;
685: }
686:
687: public int[] getTransactionalFieldNumbers() {
688: assertIsValidated();
689: return transactionalFieldNumbers;
690: }
691:
692: public int[] getPersistentFieldNumbers() {
693: assertIsValidated();
694: return persistentFieldNumbers;
695: }
696:
697: public int[] getDefaultFetchGroupFieldNumbers() {
698: assertIsValidated();
699: return defaultFetchGroupFieldNumbers;
700: }
701:
702: public int[] getSecondClassMutableFieldNumbers() {
703: assertIsValidated();
704: return secondClassMutableFieldNumbers;
705: }
706:
707: public boolean[] getTransactionalFieldFlags() {
708: assertIsValidated();
709: return transactionalFieldFlags;
710: }
711:
712: public boolean[] getPersistentFieldFlags() {
713: assertIsValidated();
714: return persistentFieldFlags;
715: }
716:
717: public boolean[] getDefaultFetchGroupFieldFlags() {
718: assertIsValidated();
719: return defaultFetchGroupFieldFlags;
720: }
721:
722: public boolean[] getSecondClassMutableFieldFlags() {
723: assertIsValidated();
724: return secondClassMutableFieldFlags;
725: }
726:
727: public List getReferencedClasses(String vendorID) {
728: Set referenced = new HashSet();
729: List orderedMetaData = new ArrayList();
730:
731: getReferencedClasses(vendorID, orderedMetaData, referenced);
732:
733: return orderedMetaData;
734: }
735:
736: /**
737: * Get the ordered <code>ClassMetaData</code>s for classes referenced
738: * from this <code>ClassMetaData</code>. This will add the
739: * <code>ClassMetaData</code>s to <code>orderedCmds</code> ordered by
740: * dependency, and to <code>referenced</code> for fast lookups.
741: * <p>
742: * This method uses recursion to add all referenced
743: * <code>ClassMetaData</code> for any fields, identity classes,
744: * super classes, and classes referenced by a view definition.
745: *
746: * @param vendorID The vendorID for the database. This is used to
747: * get the appropriate view definition.
748: * @param orderedCmds A List that all ordered <code>ClassMetaData</code>s
749: * will be added to.
750: * @param referenced A Set that all <code>ClassMetaData</code>s are
751: * added to. This is used for fast lookups with contains().
752: */
753: void getReferencedClasses(final String vendorID,
754: final List orderedCmds, final Set referenced) {
755: assertIsValidated();
756: Map viewReferences = new HashMap();
757: getReferencedClasses(vendorID, orderedCmds, referenced,
758: viewReferences);
759: }
760:
761: /**
762: * Get the ordered <code>ClassMetaData</code>s for classes referenced
763: * from this <code>ClassMetaData</code>. This will add the
764: * <code>ClassMetaData</code>s to <code>orderedCmds</code> ordered by
765: * dependency, and to <code>referenced</code> for fast lookups.
766: * <p>
767: * This method uses recursion to add all referenced
768: * <code>ClassMetaData</code> for any fields, identity classes,
769: * super classes, and classes referenced by a view definition.
770: *
771: * @param vendorID The vendorID for the database. This is used to
772: * get the appropriate view definition.
773: * @param orderedCmds A List that all ordered <code>ClassMetaData</code>s
774: * will be added to.
775: * @param referenced A Set that all <code>ClassMetaData</code>s are
776: * added to. This is used for fast lookups with contains().
777: * @param viewReferences A Map mapping Class to a Set of referenced
778: * classes for all views.
779: */
780: private void getReferencedClasses(final String vendorID,
781: final List orderedCmds, final Set referenced,
782: final Map viewReferences) {
783: /*
784: * Recursively call getReferencedClasses(...) before adding them
785: * to the orderedCmds and referenced. This will ensure that any
786: * classes with dependencies on them are put in the orderedCmds List
787: * in the correct order.
788: */
789: if (!referenced.contains(this )) {
790: /*
791: * Go ahead and add this class to the referenced Set, it will
792: * get added to the orderedCmds List after all classes that this
793: * depends on have been added.
794: */
795: referenced.add(this );
796:
797: Iterator iter = managedFields.iterator();
798:
799: while (iter.hasNext()) {
800: FieldMetaData fmd = (FieldMetaData) iter.next();
801: fmd.getReferencedClasses(vendorID, orderedCmds,
802: referenced);
803: }
804:
805: if (pcSuperclass != null) {
806: forClass(pcSuperclass).getReferencedClasses(vendorID,
807: orderedCmds, referenced);
808: }
809:
810: if (identityClass != null) {
811: ClassMetaData icmd = forClass(identityClass);
812: if (icmd != null)
813: icmd.getReferencedClasses(vendorID, orderedCmds,
814: referenced);
815: }
816:
817: if (getViewDefinition(vendorID) != null) {
818: MacroString viewDef = new MacroString(this .clazz,
819: getViewImports(), getViewDefinition(vendorID));
820: viewDef
821: .substituteMacros(new MacroString.MacroHandler() {
822: public void onIdentifierMacro(
823: MacroString.IdentifierMacro im) {
824: addViewReference(viewReferences,
825: im.clazz);
826: forClass(im.clazz)
827: .getReferencedClasses(vendorID,
828: orderedCmds,
829: referenced,
830: viewReferences);
831: }
832:
833: public void onParameterMacro(
834: MacroString.ParameterMacro pm) {
835: throw new JDOUserException(
836: "Parameter macros not allowed in view definitions: "
837: + pm);
838: }
839: });
840: }
841:
842: orderedCmds.add(this );
843: }
844: }
845:
846: /**
847: * Add a reference from this class to the referenced class. Check the
848: * view references for circular dependencies. If there are any circular
849: * dependencies, throw a JDOUserException.
850: * @param viewReferences The Map of Class to Set of referenced Classes
851: * to add the reference to.
852: * @exception JDOFatalUserException If a circular reference is found in
853: * the view definitions.
854: */
855: private void addViewReference(Map viewReferences, Class referenced)
856: throws JDOFatalUserException {
857: if (this .clazz != referenced) {
858: /*
859: * Add this reference to the Map.
860: */
861: Set referencedSet = (Set) viewReferences.get(this .clazz);
862: if (null == referencedSet) {
863: referencedSet = new HashSet();
864: viewReferences.put(this .clazz, referencedSet);
865: }
866:
867: referencedSet.add(referenced);
868:
869: /*
870: * Check to see if there is a circular dependency. This will
871: * be true if the referenced class references this class.
872: */
873: checkForCircularReferences(viewReferences, this .clazz,
874: referenced, null);
875: }
876: }
877:
878: /**
879: * Check for any circular references between referencer and referencee.
880: * If one is found, throw a JDOFatalUserException with the chain of references.
881: * @param viewReferences The Map of view references to check.
882: * @param referencer The class that has the reference.
883: * @param referencee The class that is being referenced.
884: * @param referenceChain The List of class that have been referenced so far.
885: * @exception JDOFatalUserException If a circular reference is found in
886: * the view definitions.
887: */
888: private void checkForCircularReferences(Map viewReferences,
889: Class referencer, Class referencee, List referenceChain)
890: throws JDOFatalUserException {
891: Set classes = (Set) viewReferences.get(referencee);
892: if (null != classes) {
893: /*
894: * Initialize the chain of references if needed. Add the referencee
895: * to the chain.
896: */
897: if (null == referenceChain) {
898: referenceChain = new ArrayList();
899: referenceChain.add(referencer);
900: }
901: referenceChain.add(referencee);
902:
903: /*
904: * Iterate through all referenced classes from the referencee. If
905: * any reference the referencer, throw an exception.
906: */
907: for (Iterator it = classes.iterator(); it.hasNext();) {
908: Class current = (Class) it.next();
909:
910: if (current == referencer) {
911: StringBuffer error = new StringBuffer(
912: "A circular dependency exists between views: ");
913:
914: for (Iterator chainIter = referenceChain.iterator(); chainIter
915: .hasNext();) {
916: error.append(chainIter.next());
917: if (chainIter.hasNext()) {
918: error.append(" -> ");
919: }
920: }
921:
922: throw new JDOFatalUserException(error.toString());
923: } else {
924: /*
925: * Make a recursive call to check for any nested dependencies.
926: * For example, A references B, B references C, C references A.
927: */
928: checkForCircularReferences(viewReferences,
929: referencer, current, referenceChain);
930: }
931: }
932: }
933: }
934: }
|