View Javadoc

1   /*
2    * Copyright 2005 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at 
7    * 
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software 
11   * distributed under the License is distributed on an "AS IS" BASIS, 
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
13   * See the License for the specific language governing permissions and 
14   * limitations under the License.
15   */
16  
17  package org.apache.jdo.impl.model.jdo;
18  
19  import java.io.InputStream;
20  import java.io.InputStreamReader;
21  import java.io.IOException;
22  
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.ConcurrentModificationException;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.NoSuchElementException;
32  import java.util.Set;
33  
34  import javax.xml.parsers.ParserConfigurationException;
35  
36  import org.apache.commons.logging.Log;
37  import org.apache.commons.logging.LogFactory;
38  
39  import org.apache.jdo.impl.model.jdo.xml.JDOHandler;
40  import org.apache.jdo.impl.model.jdo.xml.JDOHandlerImpl;
41  import org.apache.jdo.impl.model.jdo.xml.JDOParser;
42  import org.apache.jdo.model.ModelException;
43  import org.apache.jdo.model.ModelFatalException;
44  import org.apache.jdo.model.java.JavaModel;
45  import org.apache.jdo.model.java.JavaType;
46  import org.apache.jdo.model.jdo.JDOClass;
47  import org.apache.jdo.model.jdo.JDOModel;
48  import org.apache.jdo.model.jdo.JDOModelFactory;
49  import org.apache.jdo.model.jdo.JDOPackage;
50  
51  import org.apache.jdo.util.I18NHelper;
52  import org.apache.jdo.util.StringHelper;
53  
54  import org.xml.sax.SAXException;
55  import org.xml.sax.InputSource;
56  
57  /***
58   * A JDOModel instance bundles a number of JDOClass instances used by an 
59   * application. It provides factory methods to create and retrieve JDOClass 
60   * instances. A fully qualified class name must be unique within a JDOModel 
61   * instance. The model supports multiple classes having the same fully qualified 
62   * name by different JDOModel instances.
63   * <p>
64   * The dynamic JDOModel implementation does not store any internally
65   * calculated values. It is intended to be used in an environment
66   * where JDO metatdata are likely to be changed (e.g. at development
67   * time).
68   * <br> 
69   * There are two exceptions:
70   * <ul>
71   * <li>JDOModelImplDynamic caches JDOClass instances. This means a
72   * second lookup of the same class will return the same JDOClass
73   * instance.
74   * <li>JDOModelImplDynamic manages a list of xml resources (.jdo
75   * files) that are processed already, to avoid reading the same
76   * resource again.
77   * <p>
78   * TBD:
79   * <ul>
80   * <li> Other implementations of JavaModel interface: Development
81   * <li> Check synchronization.
82   * <li> Check non validating XML parsing
83   * <li> Open issue: a .jdo file might contain XML for multiple classes. 
84   * Today all the metadata is stored in the same jdoModel instance, but at runtime
85   * class loading determines the correct jdoModel instance. 
86   * Either we need to be able to change the declaringModel of a JDOClass instance. 
87   * Or reading a .jdo only load metadata for the requested class, so all the other 
88   * metadata is ignored.
89   * </ul>
90   *
91   * @author Michael Bouschen
92   * @since 1.1
93   * @version 2.0
94   */
95  public class JDOModelImplDynamic extends JDOElementImpl implements JDOModel {
96  
97      /*** 
98       * Map of JDOPackage managed by this JDOModel instance,
99       * key is the package name.
100      */
101     private Map jdoPackages = new HashMap();
102 
103     /*** 
104      * Map of JDOClass objects managed by this JDOModel instance, 
105      * key is the fully qualified class name. 
106      */
107     private Map jdoClasses = new HashMap();
108 
109     /***
110      * Set of names of XML resources that are processed already 
111      * (see {link #lookupXMLMetadata(String className)}.
112      */
113     private Set processedResources = new HashSet();
114 
115     /*** The JavaModel used to get type info. */
116     private JavaModel javaModel;
117 
118     /*** The default for loadXMLMetadata. */ 
119     private final boolean loadXMLMetadataDefault;
120     
121     /*** */
122     private final UnresolvedRelationshipHelper unresolvedRelationshipHelper = 
123         new UnresolvedRelationshipHelper();
124 
125     /*** I18N support */
126     protected final static I18NHelper msg =
127         I18NHelper.getInstance(JDOModelImplDynamic.class);
128 
129     /*** XML Logger */
130     protected static Log xmlLogger = 
131         LogFactory.getFactory().getInstance("org.apache.jdo.impl.model.jdo.xml"); // NOI18N
132 
133     /*** Logger */
134     protected static Log logger =
135         LogFactory.getFactory().getInstance("org.apache.jdo.impl.model.jdo"); // NOI18N
136 
137     /*** 
138      * Constructor. 
139      * JDOModel instances are created using the JDOModelFactory only.
140      */
141     protected JDOModelImplDynamic(
142         JavaModel javaModel, boolean loadXMLMetadataDefault) {
143         super();
144         setJavaModel(javaModel);
145         this.loadXMLMetadataDefault = loadXMLMetadataDefault;
146         try {
147             javaModel.setJDOModel(this);
148         }
149         catch (ModelException ex) {
150             throw new ModelFatalException(msg.msg("ERR_CannotSetJDOModel"), ex); //NOI18N
151         }
152     }
153 
154     /*** 
155      * The method returns a JDOClass instance for the specified package name.
156      * If this JDOModel contains the corresponding JDOPackage instance,
157      * the existing instance is returned. Otherwise, it creates a new JDOPackage
158      * instance and returns the new instance.
159      * @param packageName the name of the JDOPackage instance 
160      * to be returned
161      * @return a JDOPackage instance for the specified package name
162      * @exception ModelException if impossible
163      */
164     publicJDOPackage createJDOPackage(String packageName)/package-summary.html">ong> JDOPackage createJDOPackage(String packageName) 
165         throws ModelException {
166         JDOPackage jdoPackage = getJDOPackage(packageName)/package-summary.html">JDOPackage jdoPackage = getJDOPackage(packageName);
167         if (jdoPackage == null) {
168             jdoPackage = new JDOPackageImpl();
169             jdoPackage.setName(packageName);
170             jdoPackage.setDeclaringModel(this);
171             jdoPackages.put(packageName, jdoPackage);
172         }
173         return jdoPackage;
174     }
175     
176     /*** 
177      * The method returns the JDOPackage instance for the specified package 
178      * name, if present. The method returns <code>null</code> if it cannot 
179      * find a JDOPackage instance for the specified name. 
180      * @param packageName the name of the JDOPackage instance 
181      * to be returned
182      * @return a JDOPackage instance for the specified package name 
183      * or <code>null</code> if not present
184      */
185     publicJDOPackage getJDOPackage(String packageName) {/package-summary.html">ong> JDOPackage getJDOPackage(String packageName) {
186         return</strong> (JDOPackage)jdoPackages.get(packageName);
187     }
188 
189     /***
190      * Returns the collection of JDOPackage instances declared by this JDOModel 
191      * in the format of an array.
192      * @return the packages declared by this JDOModel
193      */
194     public JDOPackage[] getDeclaredPackages() {
195         return (JDOPackage[])jdoPackages.values().toArray(
196             new JDOPackage[jdoPackages.size()]);
197     }
198 
199     /***
200      * The method returns a JDOClass instance for the specified fully qualified
201      * class name. If this JDOModel contains the corresponding JDOClass instance,
202      * the existing instance is returned. Otherwise, it creates a new JDOClass 
203      * instance, sets its declaringModel and returns the new instance.
204      * <p>
205      * Whether this method reads XML metatdata or not is determined at
206      * JDOModel creation time (see flag <code>loadXMLMetadataDefault</code> 
207      * in {@link JDOModelFactory#getJDOModel(JavaModel javaModel, boolean
208      * loadXMLMetadataDefault)}). Invoking this method is method is equivalent
209      * to <code>createJDOClass(className, loadXMLMetadataDefault)</code>.
210      * @param className the fully qualified class name of the JDOClass
211      * instance to be returned
212      * @return a JDOClass instance for the specified class name
213      * @exception ModelException if impossible
214      */
215     public JDOClass createJDOClass(String className) throws ModelException {
216         return createJDOClass(className, loadXMLMetadataDefault);
217     }
218 
219     /***
220      * The method returns a JDOClass instance for the specified fully qualified
221      * class name. If this JDOModel contains the corresponding JDOClass instance,
222      * the existing instance is returned. Otherwise, if the flag loadXMLMetadata
223      * is set to <code>true</code> the method tries to find the JDOClass 
224      * instance by reading the XML metadata. If it could not be found the method
225      * creates a new JDOClass instance, sets its declaringModel and returns the 
226      * instance.
227      * @param className the fully qualified class name of the JDOClass instance 
228      * to be returned
229      * @param loadXMLMetadata indicated whether to read XML metadata or not
230      * @return a JDOClass instance for the specified class name
231      * @exception ModelException if impossible
232      */
233     public synchronized JDOClass createJDOClass(String className, 
234                                                 boolean loadXMLMetadata)
235         throws ModelException {
236         JDOClass jdoClass = getJDOClass(className, loadXMLMetadata);
237         if (jdoClass == null) {
238             if (logger.isDebugEnabled())
239                 logger.debug("JDOModel.createJDOClass: " + //NOI18N
240                              "create new JDOClass instance " + className); //NOI18N
241             jdoClass = newJDOClassInstance(className);
242             jdoClass.setDeclaringModel(this);
243             jdoClasses.put(className, jdoClass);
244             // create the corresponding JDOPackage
245             jdoClass.setJDOPackage(createJDOPackage(getPackageName(className)));
246         }
247         return jdoClass;
248     }
249 
250     /***
251      * The method returns the JDOClass instance for the specified fully 
252      * qualified class name if present. The method returns <code>null</code> 
253      * if it cannot find a JDOClass instance for the specified name. 
254      * <p>
255      * Whether this method reads XML metatdata or not is determined at
256      * JDOModel creation time (see flag <code>loadXMLMetadataDefault</code> 
257      * in {@link JDOModelFactory#getJDOModel(JavaModel javaModel, boolean 
258      * loadXMLMetadataDefault)}). Invoking this method is method is equivalent
259      * to <code>createJDOClass(className, loadXMLMetadataDefault)</code>.
260      * @param className the fully qualified class name of the JDOClass
261      * instance to be returned
262      * @return a JDOClass instance for the specified class name 
263      * or <code>null</code> if not present
264      */
265     public JDOClass getJDOClass(String className) {
266         return getJDOClass(className, loadXMLMetadataDefault);
267     }
268     
269     /***
270      * The method returns the JDOClass instance for the specified fully 
271      * qualified class name if present. If the flag loadXMLMetadata is set 
272      * to <code>true</code> the method tries to find the JDOClass instance by 
273      * reading the XML metadata. The method returns null if it cannot find a 
274      * JDOClass instance for the specified name.
275      * @param className the fully qualified class name of the JDOClass instance 
276      * to be returned
277      * @param loadXMLMetadata indicate whether to read XML metatdata or not
278      * @return a JDOClass instance for the specified class name
279      * or <code>null</code> if not present
280      * @exception ModelException if impossible
281      */
282     public synchronized JDOClass getJDOClass(String className, 
283                                              boolean loadXMLMetadata) {
284 
285         boolean trace = logger.isTraceEnabled();
286 
287         // check whether the class is known to be non PC
288         if (isKnownNonPC(className)) {
289             if (trace)
290                 logger.trace("JDOModel.getJDOClass: " + className + //NOI18N
291                              " known to be non-persistence-capable"); //NOI18N
292             return null;
293         }
294 
295         JDOClass jdoClass = (JDOClass)jdoClasses.get(className);
296         if (trace)
297             logger.trace("JDOModel.getJDOClass: " + className + //NOI18N
298                          ((jdoClass != null) ? " cached" : " not cached")); //NOI18N
299 
300         // check for XML metatdata
301         if (loadXMLMetadata) {
302             if (jdoClass == null)
303                 jdoClass = lookupXMLMetadata(className);
304             else if (!jdoClass.isXMLMetadataLoaded())
305                 jdoClass = lookupXMLMetadata(jdoClass);
306 
307             if (jdoClass == null) {
308                 // we loaded XML metadata, but there is no metadata
309                 // for this class => known to be non persistence-capable
310                 knownNonPC(className);
311             }
312         }
313 
314         return jdoClass;
315     }
316 
317     /***
318      * The method returns the JDOClass instance for the specified short name
319      * (see {@link JDOClass#getShortName()}) or <code>null</code> if it cannot
320      * find a JDOClass instance with the specified short name. 
321      * <p>
322      * The method searches the list of JDOClasses currently managed by this
323      * JDOModel instance. It does not attempt to load any metadata if it
324      * cannot find a JDOClass instance with the specified short name. The
325      * metadata for a JDOClass returned by this method must have been loaded
326      * before by any of the methods
327      * {@link #createJDOClass(String className)},
328      * {@link #createJDOClass(String className, boolean loadXMLMetadataDefault)},
329      * {@link #getJDOClass(String className)}, or
330      * {@link #getJDOClass(String className, boolean loadXMLMetadataDefault)}.
331      * @param shortName the short name of the JDOClass instance to be returned
332      * @return a JDOClass instance for the specified short name 
333      * or <code>null</code> if not present
334      */
335     public synchronized JDOClass getJDOClassForShortName(String shortName) {
336         if (StringHelper.isEmpty(shortName))
337             return null;
338 
339         for (Iterator i = jdoClasses.values().iterator(); i.hasNext();) {
340             JDOClass jdoClass = (JDOClass)i.next();
341             if (shortName.equals(jdoClass.getShortName()))
342                 // found => return
343                 return jdoClass;
344         }
345 
346         return null;
347     }
348 
349     /***
350      * Returns the collection of JDOClass instances declared by this JDOModel 
351      * in the format of an array.
352      * @return the classes declared by this JDOModel
353      */
354     public synchronized JDOClass[] getDeclaredClasses()  {
355         return (JDOClass[])jdoClasses.values().toArray(
356             new JDOClass[jdoClasses.size()]);
357     }
358 
359     /***
360      * Returns the JavaModel bound to this JDOModel instance.
361      * @return the JavaModel
362      */
363     public JavaModel getJavaModel() {
364         return javaModel;
365     }
366     
367     /***
368      * Sets the JavaModel for this JDOModel instance.
369      * @param javaModel the JavaModel
370      */
371     public void setJavaModel(JavaModel javaModel) {
372         this.javaModel = javaModel;
373     }
374     
375     /***
376      * Returns the parent JDOModel instance of this JDOModel.
377      * @return the parent JDOModel
378      */
379     public JDOModel getParent() {
380         if (javaModel != null) {
381             JavaModel parentJavaModel = javaModel.getParent();
382             if (parentJavaModel != null)
383                 return parentJavaModel.getJDOModel();
384         }
385         return null; 
386     }
387 
388     /***
389      * This method returns the JDOClass instance that defines the specified type
390      * as its objectId class. In the case of an inheritance hierarchy it returns 
391      * the top most persistence-capable class of the hierarchy (see 
392      * {@link JDOClass#getPersistenceCapableSuperclass}).
393      * @param objectIdClass the type representation of the ObjectId class
394      * @return the JDOClass defining the specified class as ObjectId class
395      */
396     public JDOClass getJDOClassForObjectIdClass(JavaType objectIdClass) {
397         // Note, method getJDOClassForObjectIdClass is not synchronized to
398         // avoid a deadlock with PC class registration.
399         if (logger.isTraceEnabled())
400             logger.trace("JDOModel.getJDOClassForObjectIdClass: " + //NOI18N
401                          "check objectIdClass " +objectIdClass); //NOI18N
402                         
403         if (objectIdClass == null)
404             return null;
405 
406         JDOClass jdoClass = null;
407         String objectIdClassName = objectIdClass.getName();
408         // check all JDOClasses for this JDOModel instance
409         List classesToActivate = new ArrayList();
410         while (true) {
411             try {
412                 for (Iterator i = jdoClasses.values().iterator(); i.hasNext();) {
413                     JDOClass next = (JDOClass)i.next();
414                     if (next.isXMLMetadataLoaded()) {
415                         // XML metadata is loaded => check the objectIdClass
416                         if (objectIdClassName.equals(
417                                 next.getDeclaredObjectIdClassName())) {
418                             // found => return
419                             return next;
420                         }
421                     }
422                     else {
423                         // XML metadata is NOT loaded => 
424                         // store the class for later processing.
425                         // Do not load the XML metadata here. This might create
426                         // new JDOClass instances in this model for other pc
427                         // classes listed in the same .jdo file. This would
428                         // change the jdoClasses map while its values are
429                         // iterated => ConcurrentModificationException
430                         classesToActivate.add(next);
431                     }
432                 }
433                 // No ConcurrentModificationException => break the loop 
434                 break;
435             }
436             catch (ConcurrentModificationException ex) {
437                 // ConcurrentModificationException means a JDOClass was
438                 // added to the JDOModel in parallel => 
439                 // start the loop again.
440             }
441         }
442                 
443         // None of the activated JDOClasses knows the objectIdClass =>
444         // activate the classes that were registered but not activated 
445         // and check these classes
446         for (Iterator i = classesToActivate.iterator(); i.hasNext();) {
447             JDOClass next = (JDOClass)i.next();
448             lookupXMLMetadata(next);
449 
450             if (objectIdClass.equals(next.getDeclaredObjectIdClassName())) {
451                 // found => return
452                 return next;
453             }
454         }
455 
456         // objectIdClass not found in this model => return null
457         return null;
458     }
459 
460     //========= Internal helper methods ==========
461     
462     /*** Returns a new instance of the JDOClass implementation class. */
463     protected JDOClass newJDOClassInstance(String name) {
464         return new JDOClassImplDynamic(name);
465     }
466 
467     /***
468      * Checks whether the type with the specified name does NOT denote a
469      * persistence-capable class.
470      * @param typeName name of the type to be checked
471      * @return <code>true</code> if types is a name of a primitive type; 
472      * <code>false</code> otherwise
473      */
474     protected boolean isKnownNonPC(String typeName) {
475         // Any class from packages java and javax are supposed to be non-pc.
476         return typeName.startsWith("java.") || //NOI18N
477                typeName.startsWith("javax."); //NOI18N
478     }
479 
480     /*** 
481      * Hook called when a class is known to be non persistence
482      * capable. Subclasses might want to keep track of classes marked
483      * as non pc classes and use this in the implementation of
484      * {link #isKnownNonPC(String typeName)}.
485      * @param className the name of the non-pc class
486      */
487     protected void knownNonPC(String className) {
488     }
489 
490     /***
491      * The method seaches JDO metadata for the class with the specified
492      * class name. Chapter 18 of the JDO specification defines the search
493      * order. The method skips resources that have been tried to load
494      * before, no matter whether the resource currently exist. 
495      * The method returns the populated JDOClass instance, if there is XML
496      * metadata available for the specified class name. Otherwise it
497      * returns <code>null</code>. 
498      * <p>
499      * The purpose of this method is to populate an existing JDOClass
500      * instance with the JDO metadata. It throws an exception if there is
501      * no jdo file for this class. The specified jdoClass must not be
502      * <code>null</code>; otherwise a NullPointerException is thrown.
503      * @param jdoClass the non-activated JDOClass instance.
504      * @return the JDOClass instance for the specified class name or
505      * <code>null</code> if there is no XML metadata.
506      * @exception ModelFatalException indicates a problem while parsing the
507      * XML file or a missing XML file.
508      * @exception NullPointerException the specified jdoClass is
509      * <code>null</code>.
510      */
511     private JDOClass lookupXMLMetadata(JDOClass jdoClass)
512         throws ModelFatalException, NullPointerException {
513         String className = jdoClass.getName();
514         JDOClass activated = lookupXMLMetadata(className);
515         if (activated == null) {
516             throw new ModelFatalException(msg.msg(
517                 "EXC_MissingJDOMetadata", className)); //NOI18N
518         }
519         else if (activated != jdoClass) {
520             throw new ModelFatalException(msg.msg(
521                 "ERR_MultipleJDOClassInstances", className)); //NOI18N
522         }
523         return jdoClass;
524     }
525 
526     /***
527      * The method seaches JDO metadata for the class with the specified
528      * class name. Chapter 18 of the JDO specification defines the search
529      * order. The method skips resources that have been tried to load
530      * before, no matter whether the resource currently exist. 
531      * The method returns the populated JDOClass instance, if there is XML
532      * metadata available for the specified class name. Otherwise it
533      * returns <code>null</code>. 
534      * @param className the name of the class to check for XML metadata.
535      * @return the JDOClass instance for the specified class name or
536      * <code>null</code> if there is no XML metadata.
537      */
538     private JDOClass lookupXMLMetadata(String className) {
539         boolean debug = xmlLogger.isDebugEnabled();
540         JDOClass jdoClass = null;
541         
542         if (debug)
543             xmlLogger.debug("JDOModel.lookupXMLMetadata:" + // NOI18N
544                             " lookup XML for class " + className); // NOI18N
545         // Iterate possible resources to find JDO metadata for className
546         Iterator i = new MetadataResourceNameIterator(className);
547         while((jdoClass == null) && i.hasNext()) {
548             String resource = (String)i.next();
549             if (processedResources.contains(resource)) {
550                 if (debug)
551                     xmlLogger.debug("  XML " + resource + //NOI18N
552                                     " processed already"); //NOI18N
553                 // processed already => skip
554                 continue;
555             }
556             else {
557                 // add resource to the list of processed resources
558                 // Note, this adds the resource no matter whether the
559                 // resource existst or not. This implies this JDOModel 
560                 // instance will not check this resource again, 
561                 // even if it exists, again.
562                 processedResources.add(resource);
563                 // load resource
564                 jdoClass = loadXMLResource(resource, className);
565             }
566         }
567         if (debug)
568             xmlLogger.debug("JDOModel.lookupXMLMetadata: " +  //NOI18N
569                             ((jdoClass!=null)?"found":"no") + //NOI18N
570                             " JDO metadata for class " + className); //NOI18N
571         return jdoClass;
572     }
573 
574     /***
575      * Load the specified resource assuming it contains JDO metadata for
576      * one or more persistence capable classes. 
577      * The method returns the populated JDOClass instance, if the specified
578      * resource has XML metadata for the specified class name. Otherwise it
579      * returns <code>null</code>. 
580      * @param resource resource to load
581      * @param className the name of the class to check for XML metadata.
582      * @return the JDOClass instance for the specified class name or
583      * <code>null</code> if the specified resource has no XML metadata.
584      */
585     private JDOClass loadXMLResource(String resource, String className) {
586         boolean debug = xmlLogger.isDebugEnabled();
587         JDOClass jdoClass = null;
588         InputStream stream = javaModel.getInputStreamForResource(resource);
589         if (stream == null) {
590             if (debug)
591                 xmlLogger.debug("  XML " + resource + " not available"); //NOI18N
592         }
593         else {
594             // resource exists => parse it
595             // Store all pc classes specified in this resource in this
596             // JDOModel instance => pass this to the handler
597             JDOHandler handler = new JDOHandlerImpl(this);
598             JDOParser parser = new JDOParser(handler);
599             try {
600                 if (debug)
601                     xmlLogger.debug("  XML " + resource +  //NOI18N
602                                     " found, start parsing ..."); //NOI18N
603                 parser.parse(new InputSource(new InputStreamReader(stream)));
604             }
605             catch (SAXException ex) {
606                 throw new ModelFatalException(
607                     msg.msg("EXC_XMLError", resource), ex); //NOI18N
608             }
609             catch (ParserConfigurationException ex) {
610                 throw new ModelFatalException(
611                     msg.msg("EXC_XMLError", resource), ex); //NOI18N
612             }
613             catch (IOException ex) {
614                 throw new ModelFatalException(
615                     msg.msg("EXC_XMLError", resource), ex); //NOI18N
616             }
617             finally {
618                 try { stream.close(); }
619                 catch (IOException ex) { 
620                     // ignore close exception, stream will be nullified anyway 
621                 }
622             }
623             stream = null;
624             
625             // post process loaded JDOClasses
626             Collection newJDOClasses = handler.handledJDOClasses();
627             if (debug)
628                 xmlLogger.debug("  XML " + resource + //NOI18N
629                                 " has JDO metadata for class(es) " + //NOI18N
630                                 newJDOClasses);
631             for (Iterator i = newJDOClasses.iterator(); i.hasNext();) {
632                 JDOClass next = (JDOClass)i.next();
633                 if (className.equals(next.getName())) {
634                     jdoClass = next;
635                 }
636                 checkSuperclass(next);
637             }
638             if (jdoClass == null)
639                 if (debug)
640                     xmlLogger.debug("  XML " + resource + //NOI18N
641                                     " does not have JDO metadata for class " + //NOI18N
642                                     className); 
643         }
644         return jdoClass;
645     }
646 
647     /***
648      * Updates the pcSuperclass property of the specified JDOClass and all its 
649      * superclasses. 
650      * @param jdoClass the class to be checked
651      */
652     private void checkSuperclass(JDOClass jdoClass) {
653         if (jdoClass != null) {
654             JDOClass superclass = jdoClass.getPersistenceCapableSuperclass();
655             checkSuperclass(superclass);
656         }
657     }
658 
659     /**</package-summary/html">Returns the package name of a class name *//package-summary.html">em>* Returns the package name of a class name */
660     private String getPackageName(String className) {
661         int index = className.lastIndexOf('.');
662         return (index == -1) ? "" : className.substring(0, index); //NOI18N
663     }
664     
665     /***
666      * Returns the UnresolvedRelationshipHelper instance for this JDOModel
667      * instance. 
668      * @return the current UnresolvedRelationshipHelper
669      */
670     UnresolvedRelationshipHelper getUnresolvedRelationshipHelper() {
671         return unresolvedRelationshipHelper;
672     }
673 
674     /***
675      * This Iterator implementation iterates resource names of possible JDO
676      * metadata files for the specified class name. Chapter 18 of the JDO
677      * specification defines the search order as follows: 
678      * META-INF/package.jdo, WEB-INF/package.jdo, package.jdo, 
679      * <package>/.../<package>/package.jdo, and <package>/<class>.jdo.
680      */
681     private static class MetadataResourceNameIterator 
682         implements Iterator {
683         /*** Suffix of a JDO metadata file. */
684         private static final String JDO_SUFFIX = ".jdo"; //NOI18N
685 
686         /package-summary/html">The name of a package JDO metadata file/ *//package-summary.html">/*** The name of a package JDO metadata file. */
687         private static final String PACKAGE_JDO = "package" + JDO_SUFFIX; //NOI18N
688 
689         /package-summary/html">List of constant package JDO metadata file names/ *//package-summary.html">/*** List of constant package JDO metadata file names. */
690         private static final String[] constantResources = { 
691             "META-INF/" + PACKAGE_JDO, //NOI18N
692             "WEB-INF/" + PACKAGE_JDO,  //NOI18N
693             PACKAGE_JDO 
694         };
695 
696         /*** Indicates whether this iterator has more elements. */
697         private boolean hasNext = true;
698 
699         /*** The class name as resource name. */
700         private final String prefix;
701 
702         /package-summary/html">Current index in the list of constant package JDO metadata file names/ *//package-summary.html">/*** Current index in the list of constant package JDO metadata file names. */
703         private int constantResourcesIndex = 0;
704 
705         /*** Current index in the prefix. */
706         private int fromIndex = 0;
707 
708         /*** Constructor. */
709         public MetadataResourceNameIterator(String className) {
710             this.prefix = className.replace('.', '/');
711         }
712         
713         /***
714          * Returns <code>true</code> if the iteration has more elements.
715          * @return <code>true</code> if the iterator has more elements.
716          */
717         public boolean hasNext() {
718             return hasNext;
719         }
720         
721         /***
722          * Returns the next resource name.
723          * @return the next resource name.
724          * @exception NoSuchElementException iteration has no more elements. 
725          */
726         public Object next() {
727             String resource = null;
728             if (!hasNext) {
729                 throw new NoSuchElementException();
730             }
731             else if (constantResourcesIndex < constantResources.length) {
732                 // Use a constant resource name, if there is one left in
733                 // the iteration.
734                 resource = constantResources[constantResourcesIndex];
735                 constantResourcesIndex++;
736             }
737             else {
738                 // No constant resource name left
739                 // build resource names from the package of the class name
740                 // Check for the next package, fromIndex is the index of
741                 // the '/' used in the previous iteration.
742                 int index = prefix.indexOf('/', fromIndex);
743                 if (index != -1) {
744                     // string needs to include '/' => use index+1
745                     resource = prefix.substring(0, index + 1) + PACKAGE_JDO;
746                     fromIndex = index + 1;
747                 }
748                 else {
749                     // no more package jdo files left => use class .jdo file 
750                     resource = prefix + JDO_SUFFIX;
751                     // the class jdo file is the last resource to be checked 
752                     hasNext = false;
753                 }
754             }
755             return resource;
756         }
757 
758         /***
759          * This Iterator does not implement this method.
760          * @exception UnsupportedOperationException if the
761          * <code>remove</code> operation is not supported by this
762          * Iterator. 
763          */
764         public void remove() {
765             throw new UnsupportedOperationException();
766         }
767         
768     }
769 
770 }