View Javadoc

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