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.util.*;
21  import java.lang.reflect.Modifier;
22  
23  import org.apache.jdo.impl.model.jdo.util.TypeSupport;
24  import org.apache.jdo.model.ModelException;
25  import org.apache.jdo.model.ModelFatalException;
26  import org.apache.jdo.model.java.JavaModel;
27  import org.apache.jdo.model.java.JavaType;
28  import org.apache.jdo.model.jdo.JDOClass;
29  import org.apache.jdo.model.jdo.JDOField;
30  import org.apache.jdo.model.jdo.JDOIdentityType;
31  import org.apache.jdo.model.jdo.JDOMember;
32  import org.apache.jdo.model.jdo.JDOModel;
33  import org.apache.jdo.model.jdo.JDOPackage;
34  import org.apache.jdo.model.jdo.JDOProperty;
35  
36  import org.apache.jdo.util.I18NHelper;
37  import org.apache.jdo.util.StringHelper;
38  
39  /***
40   * An instance of this class represents the JDO metadata of a persistence 
41   * capable class. This dynamic implementation only stores property
42   * values explicitly set by setter method. It does not store any
43   * calculated values such as list of managed or persistent fields, 
44   * list of field numbers etc. 
45   * <p>
46   * TBD:
47   * <ul>
48   * <li> Property change support
49   * </ul> 
50   *
51   * @author Michael Bouschen
52   * @since 1.1
53   * @version 2.0
54   */
55  public class JDOClassImplDynamic
56      extends JDOMemberImpl
57      implements JDOClass
58  {
59      /*** Property shortName. It defaults to the unqualified class name. */
60      protected String shortName;
61  
62      /*** Property identityType. Default see {@link #getIdentityType}. */
63      protected int identityType = JDOIdentityType.UNSPECIFIED;
64  
65      /*** Property objectIdClass. No default. */
66      protected transient JavaType objectIdClass;
67  
68      /*** Property declaredObjectIdClassName. No default. */
69      private String declaredObjectIdClassName;
70  
71      /*** Property requiresExtent. It defaults to <code>true</code>. */
72      private boolean requiresExtent = true;
73  
74      /*** Property pcSuperclassName. No default. */
75      private String pcSuperclassName;
76  
77      /*** Relationship JDOClass<->JDOClass. */
78      protected JDOClass pcSuperclass;
79  
80      /*** Property javaType. No default.*/
81      protected transient JavaType javaType;
82  
83      /*** Relationship JDOModel<->JDOClass. Initialized during creation.*/
84      private JDOModel declaringModel;
85  
86      /***
87       * Relationship JDOClass<->JDOMember.
88       * Map of fields declared by this JDOClass. Key is the unqualified 
89       * field name, value is the JDOField instance. 
90       */
91      private Map declaredFields = new HashMap();
92  
93      /*** 
94       * Map of properties having associated JDOField instances. Key is the
95       * unqualified field name, value is the JDOField instance. 
96       */
97      private Map associatedProperties = new HashMap();
98  
99      /***
100      * Relationship JDOClass<->JDOMember.
101      * Map of inner classes declared by this JDOClass. 
102      * Key is the unqualified name of the inner class, 
103      * value is the JDOClass instance of the inner class.
104      */
105     private Map declaredClasses = new HashMap();
106 
107     /*** Relationship JDOClass -> JDOPackage. */
108     private JDOPackage jdoPackage;
109 
110     /*** Flag indicating whether XML metadata is processed already. */
111     private boolean xmlMetadataLoaded = false;
112 
113     /*** I18N support */
114     protected final static I18NHelper msg =  
115         I18NHelper.getInstance(JDOClassImplDynamic.class);
116 
117     /*** Constructor. */
118     protected JDOClassImplDynamic(String name) {
119         super(name, null);
120     }
121 
122     /*** Constructor for inner classes. */
123     protected JDOClassImplDynamic(String name, JDOClass declaringClass) {
124         super(name, declaringClass);
125     }
126 
127     /*** 
128      * Get the short name of this JDOClass. The short name defaults to the
129      * unqualified class name, if not explicitly set by method
130      * {@link #setShortName(String shortName)}.
131      * @return the short name of this JDOClass.
132      */
133     public String getShortName() {
134         if (shortName != null)
135             // return short name, if explicitly set by the setter
136             return shortName;
137 
138         return StringHelper.getShortClassName(getName());
139     }
140     
141     /*** 
142      * Set the short name of this JDOClass.
143      * @param shortName the short name.
144      */
145     public void setShortName(String shortName) {
146         this.shortName = shortName;
147     }
148     
149     /*** 
150      * Get the JDO identity type of this JDOClass.
151      * The identity type of the least-derived persistence-capable class defines
152      * the identity type for all persistence-capable classes that extend it.
153      * The identity type of the least-derived persistence-capable class is
154      * defaulted to {@link JDOIdentityType#APPLICATION} if objectid-class is 
155      * specified, and {@link JDOIdentityType#DATASTORE}, if not. 
156      * @return the JDO identity type, one of 
157      * {@link JDOIdentityType#APPLICATION}, 
158      * {@link JDOIdentityType#DATASTORE}, or 
159      * {@link JDOIdentityType#NONDURABLE}
160      */
161     public int getIdentityType() {
162         if (identityType != JDOIdentityType.UNSPECIFIED) {
163             // return identity type, if explicitly set by the setter
164             return identityType;
165         }
166         
167         // not set => caclulate 
168         JDOClass pcRoot = getPersistenceCapableRootClass();
169         int result = 0;
170         if (pcRoot == this) {
171             // this is the least-derived pc class
172             result = (pcRoot.getDeclaredObjectIdClassName() != null) ? 
173                 JDOIdentityType.APPLICATION : JDOIdentityType.DATASTORE; 
174         }
175         else {
176             // get the identityType from the least-derived pc class
177             result = pcRoot.getIdentityType();
178         }
179 
180         return result;
181     }
182 
183     /*** 
184      * Set the object identity type of this JDOClass.
185      * @param identityType an integer indicating the JDO identity type, one of:
186      * {@link JDOIdentityType#APPLICATION}, 
187      * {@link JDOIdentityType#DATASTORE}, or 
188      * {@link JDOIdentityType#NONDURABLE}
189      */
190     public void setIdentityType(int identityType) {
191         this.identityType = identityType;
192     }
193     
194     /*** 
195      * Get the JavaType representation of the object identity class 
196      * (primary key class) for this JDOClass. 
197      * @return the JavaType representation of the object identity class.
198      */
199     public JavaType getObjectIdClass() {
200         if (objectIdClass != null) {
201             // return objectIdClass if explicitly set by the setter
202             return objectIdClass;
203         }
204 
205         // not set => try to resolve ObjectId class 
206         JavaType type = null;
207         String name = getDeclaredObjectIdClassName();
208         if (name != null) {
209             JavaModel javaModel = getDeclaringModel().getJavaModel();
210             type = javaModel.getJavaType(name);
211             if (Modifier.isAbstract(type.getModifiers()))
212                 // do not return ObjectId class if abstract
213                 type = null;
214         }
215         else {
216             JDOClass superclass = getPersistenceCapableSuperclass();
217             if (superclass != null) {
218                 type = superclass.getObjectIdClass();
219             }
220         }
221         return type;
222     }
223 
224     /*** 
225      * Set the JavaType representation of the object identity class 
226      * (primary key class) for this JDOClass. 
227      * @param objectIdClass the JavaType representation of the 
228      * object identity class
229      */
230     public void setObjectIdClass(JavaType objectIdClass) {
231         this.objectIdClass = objectIdClass;
232     }
233 
234     /*** 
235      * Get the fully qualified name of the object identity class 
236      * (primary key class) for this JDOClass. 
237      * @return the name of the object identity class.
238      */
239     public String getDeclaredObjectIdClassName() {
240         if (declaredObjectIdClassName != null) {
241             // ObjectId is declared, but it might not be qualified
242             int index = declaredObjectIdClassName.indexOf('.');
243             if (index == -1) {
244                 // not qualified => try to resolve it
245                 JavaType type = TypeSupport.resolveType(getDeclaringModel(), 
246                     declaredObjectIdClassName, getPackagePrefix());
247                 if (type == null) {
248                     throw new ModelFatalException(
249                         msg.msg("EXC_CannotResolveObjectIdClass", //NOI18N
250                                 declaredObjectIdClassName, getName()));
251                 }
252                 this.declaredObjectIdClassName = type.getName();
253             }
254         }
255         else {
256             // not declared, check for single field ObjectId class
257             JDOField[] declaredPKFields = getDeclaredPrimaryKeyFields();
258             if ((declaredPKFields != null) && (declaredPKFields.length == 1)) {
259                 // there is one pk field declared by this class => 
260                 // check the type
261                 JavaType fieldType = declaredPKFields[0].getType();
262                 if (fieldType != null) {
263                     return TypeSupport.getSingleFieldObjectIdClassName(
264                         fieldType.getName());
265                 }
266             }
267         }
268         return declaredObjectIdClassName;
269     }
270     
271     /*** 
272      * Set the fully qualified name of the object identity class 
273      * (primary key class) for this JDOClass. 
274      * @param declaredObjectIdClassName the name of the object identity class
275      */
276     public void setDeclaredObjectIdClassName(String declaredObjectIdClassName) {
277         this.declaredObjectIdClassName = declaredObjectIdClassName;
278     }
279 
280     /***
281      * Determines whether an extent must be managed for the 
282      * persistence-capable class described by this JDOClass.
283      * @return <code>true</code> if this class must manage an extent; 
284      * <code>false</code> otherwise
285      */
286     public boolean requiresExtent() {
287         return requiresExtent;
288     }
289     
290     /***
291      * Set whether an extent must be managed for the 
292      * persistence-capable class described by this JDOClass.
293      * @param requiresExtent <code>true</code> if this class must manage 
294      * an extent; <code>false</code> otherwise
295      */
296     public void setRequiresExtent(boolean requiresExtent) {
297         this.requiresExtent = requiresExtent;
298     }
299 
300     /***
301      * Get the fully qualified class name of the persistence-capable superclass 
302      * of the persistence-capable class described by this JDOClass. If this 
303      * class does not have a persistence-capable superclass then 
304      * <code>null</code> is returned.
305      * @return the fully qualified name of the persistence-capable superclass 
306      * or <code>null</code> if there is no persistence-capable superclass 
307      */
308     public String getPersistenceCapableSuperclassName() {
309         if (pcSuperclassName != null) {
310             // pcSuperclassName is declared, but it might not be qualified
311             int index = pcSuperclassName.indexOf('.');
312             if (index == -1) {
313                 // not qualified => try to resolve it
314                 JavaType type = TypeSupport.resolveType(getDeclaringModel(),
315                     pcSuperclassName, getPackagePrefix());
316                 if (type == null) {
317                     throw new ModelFatalException(
318                         msg.msg("EXC_CannotResolvePCSuperClass", //NOI18N
319                                 pcSuperclassName, getName()));
320                 }
321                 this.pcSuperclassName = type.getName();
322             }
323         }
324         return pcSuperclassName;
325     }
326     
327     /***
328      * Set the fully qualified class name of the persistence-capable superclass 
329      * of the persistence-capable class described by this JDOClass.
330      * @param pcSuperclassName the fully qualified name of the 
331      * persistence-capable superclass 
332      */
333     public void setPersistenceCapableSuperclassName(String pcSuperclassName) {
334         this.pcSuperclassName = pcSuperclassName;
335     }
336 
337     /***
338      * Provides the JavaType representaion corresponding to this JDOClass.
339      * <p>
340      * Note the difference between Object.getClass() and this method. The
341      * former returns the class of the object in hand, this returns the class
342      * of the object represented by this meta data.
343      * @return the JavaType object corresponding to this JDOClass.
344      */
345     public JavaType getJavaType() {
346         if (javaType != null) {
347             // return java type, if explicitly set by the setter
348             return javaType;
349         }
350         
351         // not set => calculate
352         JavaModel javaModel = declaringModel.getJavaModel();
353         return javaModel.getJavaType(getName());
354     }
355 
356     /***
357      * Set the JavaType representation corresponding to this JDOClass.
358      * @param javaType the JavaType representation for this JDOClass
359      */
360     public void setJavaType(JavaType javaType) {
361         this.javaType = javaType;
362     }
363 
364     /*** 
365      * Determines whether the XML metadata for the class represented by this
366      * JDOClass has been loaded. 
367      * @return <code>true</code> if XML metadata is loaded;
368      * <code>false</code> otherwise
369      */
370     public boolean isXMLMetadataLoaded() {
371         return xmlMetadataLoaded;
372     }
373 
374     /***
375      * Sets the flag indicating that the class XML metadata for this
376      * JDOClass is loaded to <code>true</code>.
377      */
378     public void setXMLMetadataLoaded() {
379         this.xmlMetadataLoaded = true;
380     }
381 
382     /*** 
383      * Remove the supplied member from the collection of members maintained by
384      * this JDOClass.
385      * @param member the member to be removed
386      * @exception ModelException if impossible
387      */
388     public void removeDeclaredMember(JDOMember member) throws ModelException {
389         if (member == null) {
390             throw new ModelException(
391                 msg.msg("EXC_InvalidMember", "null")); //NOI18N
392         }
393         String name = member.getName();
394         if (member instanceof JDOField) {
395             JDOField field = (JDOField) member;
396             // nullify mappedByName which removes mappedBy info 
397             field.setMappedByName(null);
398             // nullify relationship which updates its inverse
399             field.setRelationship(null);
400             if (associatedProperties.containsValue(member)) {
401                 associatedProperties.remove(name);
402             }
403             else {
404                 declaredFields.remove(name); 
405             }
406 
407             // There might be a property with the field to be removed as
408             // associated JDOField => remove the property too.
409             JDOProperty prop = getAssociatedProperty(field);
410             if (prop != null) {
411                 removeDeclaredMember(prop);
412             }
413         }
414         else if (member instanceof JDOClass) {
415             // inner class
416             declaredClasses.remove(name);
417         }
418         else {
419             throw new ModelException(
420                 msg.msg("EXC_InvalidMember", name)); //NOI18N
421         }
422     }
423     
424     /*** 
425      * Returns the collection of JDOMember instances declared by this
426      * JDOClass in form of an array.
427      * @return the members declared by this JDOClass
428      */
429     public JDOMember[] getDeclaredMembers() {
430         List copy = new ArrayList(declaredFields.values());
431         copy.addAll(declaredClasses.values());
432         return (JDOMember[])copy.toArray(new JDOMember[copy.size()]);
433     }
434 
435     /***
436      * Returns the declaring JDOModel of this JDOClass.
437      * @return the JDOModel that owns this JDOClass
438      */
439     public JDOModel getDeclaringModel() {
440         return declaringModel;
441     }
442 
443     /***
444      * Set the declaring JDOModel for this JDOClass.
445      * @param model the declaring JDOModel of this JDOClass
446      */
447     public void setDeclaringModel(JDOModel model) {
448         this.declaringModel = model;
449     }
450     
451     /***
452      * Returns the JDOClass instance for the persistence-capable superclass 
453      * of this JDOClass. If this class does not have a persistence-capable 
454      * superclass then <code>null</code> is returned.
455      * @return the JDClass instance of the persistence-capable superclass
456      * or <code>null</code> if there is no persistence-capable superclass 
457      */
458     public JDOClass getPersistenceCapableSuperclass() {
459         if (pcSuperclass != null) {
460             // return pcSuperclass if explicitly set by the setter
461             return pcSuperclass;
462             
463         }
464         
465         // not set => try to resolve persistence capable superclass
466         String name = getPersistenceCapableSuperclassName();
467         if (pcSuperclassName != null) {
468             JavaType type = TypeSupport.resolveType(
469                 getDeclaringModel(), pcSuperclassName, getPackagePrefix());
470             if (type == null) {
471                 throw new ModelFatalException(
472                     msg.msg("EXC_CannotResolvePCSuperClass", //NOI18N
473                             pcSuperclassName, getName()));
474             }
475             JDOClass jdoClass = type.getJDOClass();
476             // pcSuperclassName might be unqualified
477             this.pcSuperclassName = type.getName();
478             return jdoClass;
479         }
480 
481         return null;
482     }
483     
484     /***
485      * Set the JDOClass for the persistence-capable superclass 
486      * of this JDOClass.
487      * @param pcSuperclass the JDClass instance of the persistence-capable
488      * superclass
489      */
490     public void setPersistenceCapableSuperclass(JDOClass pcSuperclass) {
491         this.pcSuperclass = pcSuperclass;
492         this.pcSuperclassName = 
493             pcSuperclass != null ? pcSuperclass.getName() : null;
494     }
495 
496     /***
497      * Returns the JDOPackage instance corresponding to the package name 
498      * of this JDOClass. 
499      * @return the JDOPackage instance of this JDOClass.
500      */
501     public JDOPackage getJDOPackage() {
502         return jdoPackage;
503     }
504 
505     /***
506      * Sets the JDOPackage instance corresponding to the package name 
507      * of this JDOClass.
508      * @param jdoPackage the JDOPackage of this JDOClass.
509      */
510     public void setJDOPackage(JDOPackage jdoPackage) {
511         this.jdoPackage = jdoPackage;
512     }
513     
514     /***
515      * This method returns a JDOField instance for the field with the specified 
516      * name. If this JDOClass already declares such a field, the existing 
517      * JDOField instance is returned. Otherwise, it creates a new JDOField 
518      * instance, sets its declaring JDOClass and returns the new instance.
519      * <P> 
520      * Note, if the field numbers for the managed fields of this JDOClass are 
521      * calculated, this methid will fail to create a new JDOField. Any new field
522      * would possibly invalidate existing field number 
523      * @param name the name of the field
524      * @exception ModelException if impossible
525      */
526     public JDOField createJDOField(String name) throws ModelException {
527         // check whether there is a field with the specified name
528         JDOField field = getDeclaredField(name);
529         if (field == null) {
530             field = newJDOFieldInstance(name);
531             declaredFields.put(name, field);
532         }
533         else if (field instanceof JDOProperty) {
534             throw new ModelException(
535                 msg.msg("EXC_ExistingJDOProperty", name)); //NOI18N
536         }
537         return field;
538     }
539     
540     /***
541      * This method returns a JDOProperty instance for the property with the
542      * specified name. If this JDOClass already declares such a property, the
543      * existing JDOProperty instance is returned. Otherwise, it creates a new
544      * JDOProperty instance, sets its declaring JDOClass and returns the new
545      * instance.
546      * @param name the name of the property
547      * @return a JDOProperty instance for the specified property
548      * @exception ModelException if impossible
549      */
550     public JDOProperty createJDOProperty(String name) throws ModelException {
551         // check whether there is a field or property with the specified name
552         JDOProperty property = null;
553         JDOField field = getDeclaredField(name);
554         if (field == null) {
555             property = newJDOPropertyInstance(name);
556             declaredFields.put(name, property);
557         } 
558         else if (field instanceof JDOProperty) {
559             property = (JDOProperty) field;
560         }
561         else {
562             throw new ModelException(
563                 msg.msg("EXC_ExistingJDOField", name)); //NOI18N
564         }
565         return property;
566     }
567 
568     /***
569      * This method returns a JDOProperty instance for the property with the
570      * specified name and associated field. If this JDOClass already declares
571      * such a property the existing JDOProperty instance is returned. If it
572      * declares a property with the specified name but different associated
573      * field, then a ModelException is thrown. If there is no such property,
574      * the method creates a new JDOProperty instance, sets its declaring
575      * JDOClass and associated field and returns the new instance. 
576      * @param name the name of the property
577      * @param associatedField the associated JDOField 
578      * @return a JDOProperty instance for the specified property
579      * @exception ModelException if impossible
580      */
581     public JDOProperty createJDOProperty(String name, 
582                                          JDOField associatedJDOField)
583         throws ModelException
584     {
585         JDOProperty property = (JDOProperty) associatedProperties.get(name);
586         if (property == null) {
587             property = newJDOPropertyInstance(name, associatedJDOField);
588             associatedProperties.put(name, property);
589         } 
590         else {
591             if (property.getAssociatedJDOField() != associatedJDOField) {
592                 throw new ModelException(
593                     msg.msg("EXC_ExistingJDOAssociatedProperty", //NOI18N
594                             name, associatedJDOField)); 
595             }
596         }
597         return property;
598     }
599     
600     /***
601      * This method returns a JDOClass instance representing an inner class of 
602      * this JDOClass If this JDOClass already declares such an inner class, 
603      * the existing JDOClass instance is returned. Otherwise, it creates a new 
604      * JDOClass instance, sets its declaring JDOClass and returns the new
605      * instance.
606      * @param name the name of the inner class
607      * @exception ModelException if impossible
608      */
609     public JDOClass createJDOClass(String name) throws ModelException {
610         JDOClass innerClass = (JDOClass)declaredClasses.get(name);
611         if (innerClass == null) {
612             innerClass = newJDOClassInstance(name);
613             declaredClasses.put(name, innerClass);
614         }
615         return innerClass;
616     }
617 
618     /***
619      * Returns the collection of JDOClass instances declared by this JDOClass.  
620      * @return the classes declared by this JDOClass
621      */
622     public JDOClass[] getDeclaredClasses() {
623         return (JDOClass[])declaredClasses.values().toArray(
624             new JDOClass[declaredClasses.size()]);
625     }
626 
627     /***
628      * Returns the collection of JDOField instances declared by this JDOClass 
629      * in the form of an array. This does not include inherited fields.
630      * @return the fields declared by this JDOClass
631      */
632     public JDOField[] getDeclaredFields() {
633         Collection tmp = declaredFields.values();
634         return (JDOField[])tmp.toArray(new JDOField[tmp.size()]);
635     }
636 
637     /***
638      * Returns the collection of managed JDOField instances declared by this
639      * JDOClass in the form of an array. The returned array does not include 
640      * inherited fields. A field is a managed field, if it has the 
641      * persistence-modifier 
642      * {@link org.apache.jdo.model.jdo.PersistenceModifier#PERSISTENT} or 
643      * {@link org.apache.jdo.model.jdo.PersistenceModifier#TRANSACTIONAL}. 
644      * The position of the fields in the returned array equals their
645      * relative field number as returned by
646      * {@link JDOField#getRelativeFieldNumber()}. The following holds
647      * true for any field in the returned array: 
648      * <ul>
649      * <li> <code>getDeclaredManagedFields()[i].getRelativeFieldNumber() 
650      * == i</code>
651      * <li> <code>getDeclaredManagedFields()[field.getRelativeFieldNumber()] 
652      * == field</code>
653      * </ul> 
654      * @return the managed fields declared by this JDOClass
655      */
656     public JDOField[] getDeclaredManagedFields() {
657         // Get the list of declared fields, skip the non managed fields
658         // and store the remaining fields into a list
659         List fieldList = new ArrayList();
660         for (Iterator i = declaredFields.values().iterator(); i.hasNext();) {
661             JDOField field = (JDOField)i.next();
662             if (field.isManaged())
663                 fieldList.add(field);
664         }
665             
666         // Sort all declared fields. JDOFieldImpl implements Comparable.
667         // It uses the field name for comparison.
668         Collections.sort(fieldList);
669         JDOField[] fields = new JDOField[fieldList.size()];
670         fieldList.toArray(fields);
671         return fields;
672     }
673 
674     /***
675      * Returns the collection of managed JDOField instances of this JDOClass 
676      * in the form of an array. The returned array includes inherited fields.
677      * A field is a managed field, if it has the persistence-modifier 
678      * {@link org.apache.jdo.model.jdo.PersistenceModifier#PERSISTENT} or 
679      * {@link org.apache.jdo.model.jdo.PersistenceModifier#TRANSACTIONAL}. 
680      * The position of the fields in the returned array equals their
681      * absolute field number as returned by 
682      * {@link JDOField#getFieldNumber()}. The following holds true for
683      * any field in the returned array: 
684      * <ul>
685      * <li> <code>getManagedFields()[i].getFieldNumber() == i</code>
686      * <li> <code>getManagedFields()[field.getFieldNumber()] == field</code>
687      * </ul> 
688      * @return the managed fields of this JDOClass
689      */
690     public JDOField[] getManagedFields() {
691         JDOField[] fields = null;
692         JDOField[] declared = getDeclaredManagedFields();
693         JDOClass superclass = getPersistenceCapableSuperclass();
694         if (superclass == null) {
695             // no pc superclass
696             fields = declared;
697         }
698         else {
699             // pc superclass
700             JDOField[] inherited = superclass.getManagedFields();
701             fields = new JDOField[inherited.length+declared.length];
702             System.arraycopy(inherited, 0, fields, 0, inherited.length);
703             System.arraycopy(declared, 0, fields, 
704                              inherited.length, declared.length);
705         }
706 
707         return fields;
708     }
709 
710     /***
711      * Returns the collection of persistent JDOField instances of this JDOClass 
712      * in the form of an array. The returned array includes inherited fields.
713      * A field is a persistent field, if it has the persistence-modifier 
714      * {@link org.apache.jdo.model.jdo.PersistenceModifier#PERSISTENT}.
715      * Please note, the position of the fields in the returned array might not 
716      * equal their absolute field number as returned by 
717      * {@link JDOField#getFieldNumber()}.
718      * @return the persistent fields of this JDOClass
719      */
720     public JDOField[] getPersistentFields() {
721         JDOField[] fields = getManagedFields();
722         JDOField[] tmp = new JDOField[fields.length];
723         int length = 0;
724         for (int i = 0; i < fields.length; i++) {
725             JDOField field = fields[i];
726             if (field.isPersistent()) {
727                 tmp[length++] = field;
728             }
729         }
730         // now fill he returned array
731         // the array should have the correct length
732         JDOField[] result = new JDOField[length];
733         System.arraycopy(tmp, 0, result, 0, length);
734 
735         return result;
736     }
737 
738     /***
739      * Returns the collection of identifying fields of this JDOClass in the form
740      * of an array. The method returns the JDOField instances defined as 
741      * primary key fields (see {@link JDOField#isPrimaryKey}).
742      * @return the identifying fields of this JDOClass
743      */
744     public JDOField[] getPrimaryKeyFields() {
745         JDOField[] fields = getManagedFields();
746         JDOField[] tmp = new JDOField[fields.length];
747         int length = 0;
748         for (int i = 0; i < fields.length; i++) {
749             JDOField field = fields[i];
750             if (fields[i].isPrimaryKey()) {
751                 tmp[length++] = field;
752             }
753         }
754         // now fill the returned array 
755         // the array should have the correct length
756         JDOField[] result = new JDOField[length];
757         System.arraycopy(tmp, 0, result, 0, length);
758 
759         return result;
760     }
761 
762     /***
763      * Returns the collection of persistent relationship fields of this JDOClass
764      * in the form of an array. The method returns the JDOField instances 
765      * defined as relationship (method {@link JDOField#getRelationship} returns
766      * a non null value) and having the persistence-modifier 
767      * {@link org.apache.jdo.model.jdo.PersistenceModifier#PERSISTENT}.
768      * @return the persistent relationship fields of this JDOClass
769      */
770     public JDOField[] getPersistentRelationshipFields() {
771         JDOField[] fields = getPersistentFields();
772         JDOField[] tmp = new JDOField[fields.length];
773         int length = 0;
774         for (int i = 0; i < fields.length; i++) {
775             JDOField field = fields[i];
776             if (field.isPersistent() && field.isRelationship()) {
777                 tmp[length++] = field;
778             }
779         }
780         // now fill the returned array,
781         // the arrays should have the correct length
782         JDOField[] result = new JDOField[length];
783         System.arraycopy(tmp, 0, result, 0, length);
784 
785         return result;
786     }
787 
788     /***
789      * Returns the collection of default fetch group fields of this JDOClass
790      * in the form of an array. The method returns the JDOField instances 
791      * defined as part of the default fetch group 
792      * (method {@link JDOField#isDefaultFetchGroup} returns <code>true</code>.
793      * @return the default fetch group fields of this JDOClass
794      * @since 1.1
795      */
796     public JDOField[] getDefaultFetchGroupFields() {
797         JDOField[] fields = getManagedFields();
798         JDOField[] tmp = new JDOField[fields.length];
799         int length = 0;
800         for (int i = 0; i < fields.length; i++) {
801             JDOField field = fields[i];
802             if (field.isDefaultFetchGroup()) {
803                 tmp[length++] = field;
804             }
805         }
806         // now fill defaultFetchGroupFields
807         // the arrays should have the correct length
808         JDOField[] result = new JDOField[length];
809         System.arraycopy(tmp, 0, result, 0, length);
810 
811         return result;
812     }
813 
814     /***
815      * Returns an array of absolute field numbers of the managed fields of this
816      * JDOClass. The returned array includes field numbers of inherited fields.
817      * A field is a managed field, if it has the persistence-modifier 
818      * {@link org.apache.jdo.model.jdo.PersistenceModifier#PERSISTENT} or 
819      * {@link org.apache.jdo.model.jdo.PersistenceModifier#TRANSACTIONAL}. 
820      * Only managed fields have a valid field number, thus the field number in 
821      * the returned array equals its index:
822      * <br>
823      *  <code>getManagedFields()[i] == i</code>
824      */
825     public int[] getManagedFieldNumbers() {
826         JDOField[] fields = getManagedFields();
827         int[] fieldNumbers = new int[fields.length];
828         for (int i = 0; i < fields.length; i++) {
829             fieldNumbers[i] = i;
830         }
831 
832         return fieldNumbers;
833     }
834 
835     /***
836      * Returns an array of absolute field numbers of the persistent fields of 
837      * this JDOClass. The returned array includes field numbers of inherited 
838      * fields. A persistent field has the persistence-modifier 
839      * {@link org.apache.jdo.model.jdo.PersistenceModifier#PERSISTENT}.
840      */
841     public int[] getPersistentFieldNumbers() {
842         JDOField[] fields = getManagedFields();
843         int[] tmp = new int[fields.length];
844         int length = 0;
845         for (int i = 0; i < fields.length; i++) {
846             JDOField field = fields[i];
847             if (field.isPersistent()) {
848                 tmp[length++] = i;
849             }
850         }
851         // now fill the returned array, it should have the correct length
852         int[] fieldNumbers = new int[length];
853         System.arraycopy(tmp, 0, fieldNumbers, 0, length);
854 
855         return fieldNumbers;
856     }
857     
858     /***
859      * Returns an array of absolute field numbers of the identifying fields 
860      * of this JDOClass. A field number is included in the returned array, 
861      * iff the corresponding JDOField instance is defined as primary  key field
862      * (see {@link JDOField#isPrimaryKey}).
863      * @return array of numbers of the identifying fields
864      */
865     public int[] getPrimaryKeyFieldNumbers() {
866         JDOField[] fields = getManagedFields();
867         int[] tmp = new int[fields.length];
868         int length = 0;
869         for (int i = 0; i < fields.length; i++) {
870             JDOField field = fields[i];
871             if (field.isPrimaryKey()) {
872                 tmp[length++] = i;
873             }
874         }
875         // now fill the returned array, it should have the correct length
876         int[] fieldNumbers = new int[length];
877         System.arraycopy(tmp, 0, fieldNumbers, 0, length);
878 
879         return fieldNumbers;
880     }
881 
882     /***
883      * Returns an array of absolute field numbers of the non identifying, 
884      * persistent fields of this JDOClass. A field number is included in the 
885      * returned array, iff the corresponding JDOField instance is persistent and 
886      * not a not a primary key field (see {@link JDOField#isPrimaryKey}).
887      * A field is a persistent field, if it has the persistence-modifier 
888      * {@link org.apache.jdo.model.jdo.PersistenceModifier#PERSISTENT} or 
889      * (see {@link JDOField#getPersistenceModifier}). 
890      * @return array of numbers of the non identifying, persistent fields
891      */
892     public int[] getPersistentNonPrimaryKeyFieldNumbers() {
893         JDOField[] fields = getManagedFields();
894         int[] tmp = new int[fields.length];
895         int length = 0;
896         for (int i = 0; i < fields.length; i++) {
897             JDOField field = fields[i];
898             if (field.isPersistent() && !field.isPrimaryKey()) {
899                 tmp[length++] = i;
900             }
901         }
902         // now fill the returned array, it should have the correct length
903         int[] fieldNumbers = new int[length];
904         System.arraycopy(tmp, 0, fieldNumbers, 0, length);
905 
906         return fieldNumbers;
907     }
908     
909     /***
910      * Returns an array of absolute field numbers of persistent relationship 
911      * fields of this JDOClass. A field number is included in the returned 
912      * array, iff the corresponding JDOField instance is a relationship (method 
913      * {@link JDOField#getRelationship} returns a non null value) and has the 
914      * persistence-modifier 
915      * {@link org.apache.jdo.model.jdo.PersistenceModifier#PERSISTENT}.
916      * @return the field numbers of the persistent relationship fields
917      */
918     public int[] getPersistentRelationshipFieldNumbers() {
919         JDOField[] fields = getManagedFields();
920         int[] tmp = new int[fields.length];
921         int length = 0;
922         for (int i = 0; i < fields.length; i++) {
923             JDOField field = fields[i];
924             if (field.isPersistent() && field.isRelationship()) {
925                 tmp[length++] = i;
926             }
927         }
928         // now fill the returned array, it should have the correct length
929         int[] fieldNumbers = new int[length];
930         System.arraycopy(tmp, 0, fieldNumbers, 0, length);
931 
932         return fieldNumbers;
933     }
934 
935     /***
936      * Returns an array of absolute field numbers of persistent, serializable 
937      * fields of this JDOClass. A field number is included in the returned 
938      * array, iff the corresponding JDOField instance is serializable (method 
939      * {@link JDOField#isSerializable} returns <code>true</code>) and has the 
940      * persistence-modifier 
941      * {@link org.apache.jdo.model.jdo.PersistenceModifier#PERSISTENT}.
942      * @return the field numbers of serializable fields
943      */
944     public int[] getPersistentSerializableFieldNumbers() {
945         JDOField[] fields = getManagedFields();
946         int[] tmp = new int[fields.length];
947         int length = 0;
948         for (int i = 0; i < fields.length; i++) {
949             JDOField field = fields[i];
950             if (field.isPersistent() && field.isSerializable()) {
951                 tmp[length++] = i;
952             }
953         }
954         // now fill the returned array it should have the correct length
955         int[] fieldNumbers = new int[length];
956         System.arraycopy(tmp, 0, fieldNumbers, 0, length);
957 
958         return fieldNumbers;
959     }
960     
961     /***
962      * Returns JDOField metadata for a particular managed field specified by 
963      * field name. It returns <code>null</code> if the specified name does not 
964      * denote a managed field of this JDOClass. The field name may be 
965      * unqualified and or qualified (see {@link #getField(String fieldName)}).
966      * @param fieldName the name of the managed field for which field metadata
967      * is needed.
968      * @return JDOField metadata for the managed field or <code>null</code>
969      * if there is no such field.
970      */
971     public JDOField getManagedField(String fieldName) {
972         JDOField field = getField(fieldName);
973         if ((field != null) && !field.isManaged())
974             // return null for a non managed field
975             return null;
976         return field;
977     }
978     
979     /***
980      * Returns JDOField metadata for a particular field specified by field name.
981      * It returns <code>null</code> if the specified name does not denote a 
982      * field of this JDOClass.
983      * <p>
984      * The method supports lookup by unqualified and by qualified field name. 
985      * <ul>
986      * <li> In the case of an unqualified field name the method starts checking 
987      * this JDOClass for a field with the specified name. If this class does not
988      * define such a field, it checks the inheritance hierarchy starting with 
989      * its direct persistence-capable superclass. The method finds the first 
990      * field with the specified name in a bootom-up lookup of the inheritance 
991      * hierarchy. Hidden fields are not visible.
992      * <li> In the case of a qualified field name the method assumes a fully 
993      * qualified class name (called qualifier class) as the field qualifier. 
994      * The qualifier class must be a either this class or a persistence-capable 
995      * superclass (direct or indirect) of this class. Then the method searches 
996      * the field definition in the inheritance hierarchy staring with the 
997      * qualifier class. Any field declarations with the same name in subclasses
998      * of the qualifier class are not considered. This form allows accessing 
999      * fields hidden by subclasses. The method returns <code>null</code> if the 
1000      * qualifier class does not denote a valid class or if the qualifier class 
1001      * is not a persistence-capable superclass of this class.
1002      * </ul>
1003      * @param fieldName the unqualified or qualified name of field for which 
1004      * field metadata is needed.
1005      * @return JDOField metadata for the field or <code>null</code>
1006      * if there is no such field.
1007      */
1008     public JDOField getField(String fieldName) {
1009         // check fieldName
1010         if ((fieldName == null) || (fieldName.length() == 0)) {
1011             return null;
1012         }
1013         
1014         JDOField field = null;
1015         int index = fieldName.lastIndexOf('.');
1016         if (index != -1) {
1017             // qualified field name
1018             String className = fieldName.substring(0, index);
1019             fieldName = fieldName.substring(index + 1);
1020             // move to the specified class in the inheritance hierarchy,
1021             // starting with the current class and get the field from there
1022             for (JDOClassImplDynamic next = this; next != null; 
1023                  next = (JDOClassImplDynamic)next.getPersistenceCapableSuperclass()) {
1024                  if (className.equals(next.getName())) {
1025                      field = next.getFieldInternal(fieldName);
1026                  }
1027             }
1028         }
1029         else {
1030             // unqualified field name => call getFieldInternal
1031             field = getFieldInternal(fieldName);
1032         }
1033         
1034         return field;
1035     }
1036          
1037     /***
1038      * Provides metadata for a particular field specified by the absolute field 
1039      * number. The field number must be a valid absolute field number for this 
1040      * JDOClass: <code>0 <= fieldNumber < this.getManagedFields().length</code>
1041      * If the field number is valid the returned JDoField instance denotes a 
1042      * managed field, meaning the field has the persistence-modifier 
1043      * {@link org.apache.jdo.model.jdo.PersistenceModifier#PERSISTENT} or 
1044      * {@link org.apache.jdo.model.jdo.PersistenceModifier#TRANSACTIONAL}. 
1045      * If the field number is not valid then the method returns
1046      * <code>null</code>. 
1047      * @param fieldNumber the number for which field metadata is needed.
1048      * @return JDOField metadata for the field or <code>null</code>
1049      * if there is no such field.
1050      */
1051     public JDOField getField(int fieldNumber) {   
1052         JDOField field = null;
1053         JDOField[] fields = getManagedFields();
1054         if ((0 <= fieldNumber) && (fieldNumber < fields.length))
1055             field = fields[fieldNumber];
1056         return field;
1057     }
1058 
1059     /*** 
1060      * Returns JDOField metadata for a particular declared field for the
1061      * specified name. Please note, the method does not return inherited
1062      * fields. The field name must not be qualified by a class name. The
1063      * method returns <code>null</code> if the field name does not denote a
1064      * field declared by JDOClass.
1065      * @param name the unqualified name of field for which field metadata 
1066      * is needed.
1067      * @return JDOField metadata for the field or <code>null</code>
1068      * if there is no such field declared by this JDOClass.
1069      */
1070     public JDOField getDeclaredField(String name) {
1071         return (JDOField) declaredFields.get(name);
1072     }
1073 
1074     /***
1075      * Returns JDOProperty metadata for a property with the specified name
1076      * having an associated JDOField. The method returns <code>null</code>, if
1077      * the name does not denote a property with an associated JDOField of this
1078      * JDOClass. Please note, the method does not check for properties without
1079      * an associated JDOField. It will return <code>null</code> if there is
1080      * a property with the specified name, but this property does not have an
1081      * associated JDOField.
1082      * @param name the name of property with an associated JDOField for which
1083      * metadata is needed.
1084      * @return JDOProperty metadata for the property with an associated
1085      * JDOField or <code>null</code> if there is no such property.
1086      */
1087     public JDOProperty getAssociatedProperty(String name) {
1088         // first check the associated properties from this class
1089         JDOProperty prop = (JDOProperty) associatedProperties.get(name);
1090         if (prop != null) {
1091             return prop;
1092         }
1093         
1094         // not in this class => check superclass
1095         JDOClass superclass = getPersistenceCapableSuperclass();
1096         if (superclass != null) {
1097             return superclass.getAssociatedProperty(name);
1098         }
1099         
1100         // not found => return null
1101         return null;
1102     }
1103 
1104     /***
1105      * Returns JDOProperty metadata for a property having the specified
1106      * JDOField as associated JDOField. The method returns <code>null</code>,
1107      * if this JDOClass does not have a property with the specified JDOField
1108      * as associated JDOField.
1109      * @param JDOField the assoaciated JDOField of the property for which
1110      * metadata is needed.
1111      * @return JDOProperty metadata for the property the specified JDOField as
1112      * associated JDOField or <code>null</code> if there is no such property.
1113      */
1114     public JDOProperty getAssociatedProperty(JDOField field) {
1115         Collection props = associatedProperties.values();
1116         for (Iterator i = props.iterator(); i.hasNext();) {
1117             JDOProperty prop = (JDOProperty)i.next();
1118             if (prop.getAssociatedJDOField() == field) {
1119                 // found property => return 
1120                 return prop;
1121             }
1122         }
1123 
1124         // not found => return null
1125         return null;
1126     }
1127 
1128     /***
1129      * Returns the number of managed fields declared in the class represented
1130      * by this JDOClass. This does not include inherited fields.
1131      * @return number of declared managed fields
1132      */
1133     public int getDeclaredManagedFieldCount() {
1134         return getDeclaredManagedFields().length;
1135     }
1136     
1137     /***
1138      * Returns the number of inherited managed fields for the class
1139      * represented by this JDOClass.
1140      * @return number of inherited managed fields
1141      */
1142     public int getInheritedManagedFieldCount() {
1143         int count = 0;
1144         JDOClass superclass = getPersistenceCapableSuperclass();
1145         if (superclass != null) {
1146             count = 
1147                 superclass.getInheritedManagedFieldCount() + 
1148                 superclass.getDeclaredManagedFieldCount();
1149         }
1150     
1151         return count;
1152     }
1153     
1154     /***
1155      * Returns the number of managed fields for the class represented by this
1156      * JDOClass. The value returned by this method is equal to
1157      * <code>getDeclaredManagedFieldCount() +
1158      * getInheritedManagedFieldCount()</code>.
1159      * @return number of managed fields
1160      */
1161     public int getManagedFieldCount() {
1162         return getDeclaredManagedFieldCount() + getInheritedManagedFieldCount();
1163     }
1164     
1165     /***
1166      * Returns the package name including a terminating dot if this class has a 
1167      * package. The method returns the empty string if this class is in the 
1168      * default package.
1169      * @return package prefix for this class.
1170      */
1171     public String getPackagePrefix() {
1172         String className = getName();
1173         int index = className.lastIndexOf('.');
1174         return (index == -1) ? "" : className.substring(0, index + 1); //NOI18N
1175     }
1176     
1177     /***
1178      * Returns the least-derived (topmost) persistence-capable class in the 
1179      * hierarchy of this JDOClass. It returns this JDOClass if it has no 
1180      * persistence-capable superclass.
1181      * @return the topmost persistence-capable class in the hierarchy.
1182      */
1183     public JDOClass getPersistenceCapableRootClass() {
1184         JDOClass superclass = getPersistenceCapableSuperclass();
1185         if (superclass == null) {
1186             // no superclass => return this
1187             return this;
1188         }
1189         else {
1190             return superclass.getPersistenceCapableRootClass();
1191         }
1192     }
1193 
1194     //========= Internal helper methods ==========
1195 
1196     /***
1197      * Returns the JDOField definition for the specified field. 
1198      * The method expects unqualified field names. The method 
1199      * performs a bottom up lookup in the case of multiple fields 
1200      * with the same name in an inheritance hierarchy. So it starts
1201      * checking this class, then it checks its superclas, etc.
1202      * @param fieldName the unqualified field name
1203      * @return the corresponding JDOField instance if exists; 
1204      * <code>null</code> otherwise.
1205      */
1206     protected JDOField getFieldInternal(String fieldName) {
1207         // first check the declared fields
1208         JDOField field = (JDOField)declaredFields.get(fieldName);
1209         if (field != null) {
1210             return field;
1211         }
1212         
1213         // not in this class => check superclass
1214         JDOClassImplDynamic superclass = 
1215             (JDOClassImplDynamic)getPersistenceCapableSuperclass();
1216         if (superclass != null) {
1217             return superclass.getFieldInternal(fieldName);
1218         }
1219         
1220         // not found => return null
1221         return null;
1222     }
1223     
1224     /***
1225      * Returns the collection of identifying declared fields of this JDOClass
1226      * in the form of an array. The method returns the JDOField instances
1227      * declared by this JDOClass defined as primary key fields (see {@link
1228      * JDOField#isPrimaryKey}). 
1229      * @return the identifying fields of this JDOClass
1230      */
1231     protected JDOField[] getDeclaredPrimaryKeyFields() {
1232         JDOField[] fields = getDeclaredFields();
1233         JDOField[] tmp = new JDOField[fields.length];
1234         int length = 0;
1235         for (int i = 0; i < fields.length; i++) {
1236             JDOField field = fields[i];
1237             if (field.isManaged() && field.isPrimaryKey()) {
1238                 tmp[length++] = field;
1239             }
1240         }
1241         // now fill the returned array 
1242         // the array should have the correct length
1243         JDOField[] result = new JDOField[length];
1244         System.arraycopy(tmp, 0, result, 0, length);
1245 
1246         return result;
1247     }
1248 
1249     /***
1250      * Returns a new instance of the JDOClass implementation class.
1251      */
1252     protected JDOClass newJDOClassInstance(String name) {
1253         return new JDOClassImplDynamic(name, this);
1254     }
1255 
1256     /***
1257      * Returns a new instance of the JDOField implementation class.
1258      */
1259     protected JDOField newJDOFieldInstance(String name) {
1260         return new JDOFieldImplDynamic(name, this);
1261     }
1262 
1263     /***
1264      * Returns a new instance of the JDOProperty implementation class.
1265      */
1266     protected JDOProperty newJDOPropertyInstance(String name) {
1267         return new JDOPropertyImplDynamic(name, this);
1268     }
1269     
1270     /***
1271      * Returns a new instance of the JDOProperty implementation class.
1272      */
1273     protected JDOProperty newJDOPropertyInstance(
1274         String name, JDOField associatedJDOField) throws ModelException {
1275         return new JDOAssociatedPropertyImplDynamic(
1276             name, this, associatedJDOField);
1277     }
1278     
1279 }