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