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  /*
18   * JDOImplHelper.java
19   *
20   */
21  
22  package javax.jdo.spi;
23  
24  import java.lang.reflect.Constructor;
25  
26  import java.text.DateFormat;
27  import java.text.ParsePosition;
28  import java.text.SimpleDateFormat;
29  
30  import java.util.ArrayList;
31  import java.util.Collection;
32  import java.util.Collections;
33  import java.util.Currency;
34  import java.util.Date;
35  import java.util.HashMap;
36  import java.util.HashSet;
37  import java.util.Iterator;
38  import java.util.List;
39  import java.util.Locale;
40  import java.util.Map;
41  import java.util.WeakHashMap;
42  
43  import javax.jdo.JDOException;
44  import javax.jdo.JDOFatalInternalException;
45  import javax.jdo.JDOFatalUserException;
46  import javax.jdo.JDOUserException;
47  
48  /*** This class is a helper class for JDO implementations.  It contains methods
49   * to register metadata for persistence-capable classes and to perform common
50   * operations needed by implementations, not by end users.
51   * <P><code>JDOImplHelper</code> allows construction of instances of 
52   * persistence-capable classes without using reflection.
53   * <P>Persistence-capable classes register themselves via a static method 
54   * at class load time.
55   * There is no security restriction on this access.  JDO implementations
56   * get access to the functions provided by this class only if they are
57   * authorized by the security manager.  To avoid having every call go through
58   * the security manager, only the call to get an instance is checked.  Once an 
59   * implementation
60   * has an instance, any of the methods can be invoked without security checks.
61   * @version 1.0.2
62   *
63   */
64  public class JDOImplHelper extends java.lang.Object {
65      
66      /*** This synchronized <code>HashMap</code> contains a static mapping of
67       * <code>PersistenceCapable</code> class to
68       * metadata for the class used for constructing new instances.  New entries
69       * are added by the static method in each <code>PersistenceCapable</code> 
70       * class.  Entries are never removed.
71       */    
72      private static Map registeredClasses = 
73              Collections.synchronizedMap(new HashMap ());
74      
75      /*** This Set contains all classes that have registered for setStateManager
76       * permissions via authorizeStateManagerClass.
77       */
78      private static Map authorizedStateManagerClasses = new WeakHashMap();
79  
80      /*** This list contains the registered listeners for 
81       * <code>RegisterClassEvent</code>s.
82       */
83      private static List listeners = new ArrayList();
84      
85      /*** The list of registered StateInterrogation instances
86       */
87      private static List stateInterrogations = new ArrayList();
88  
89      /*** The singleton <code>JDOImplHelper</code> instance.
90       */    
91      private static JDOImplHelper jdoImplHelper = new JDOImplHelper();
92      
93      /*** The Internationalization message helper.
94       */
95      private final static I18NHelper msg = 
96              I18NHelper.getInstance ("javax.jdo.Bundle"); //NOI18N
97      
98      /*** The DateFormat pattern.
99       */
100     private static String dateFormatPattern;
101 
102     /*** The default DateFormat instance.
103      */
104     private static DateFormat dateFormat;
105 
106     /*** Register the default DateFormat instance.
107      */
108     static {
109         jdoImplHelper.registerDateFormat(DateFormat.getDateTimeInstance());
110     }
111     
112     /*** Creates new JDOImplHelper */
113     private JDOImplHelper() {
114     }
115     
116     /*** Get an instance of <code>JDOImplHelper</code>.  This method
117      * checks that the caller is authorized for 
118      * <code>JDOPermission("getMetadata")</code>, and if not, throws 
119      * <code>SecurityException</code>.
120      * @return an instance of <code>JDOImplHelper</code>.
121      * @throws SecurityException if the caller is not authorized for 
122      * JDOPermission("getMetadata").
123      */    
124     public static JDOImplHelper getInstance() 
125         throws SecurityException {        
126         SecurityManager sec = System.getSecurityManager();
127         if (sec != null) { 
128             // throws exception if caller is not authorized
129             sec.checkPermission (JDOPermission.GET_METADATA);
130         }
131         return jdoImplHelper;
132     }
133     
134     /*** Get the field names for a <code>PersistenceCapable</code> class.  The 
135      * order of fields is the natural ordering of the <code>String</code> class
136      * (without considering localization).
137      * @param pcClass the <code>PersistenceCapable</code> class.
138      * @return the field names for the class.
139      */    
140     public String[] getFieldNames (Class pcClass) {
141         Meta meta = getMeta (pcClass);
142         return meta.getFieldNames();
143     }
144 
145     /*** Get the field types for a <code>PersistenceCapable</code> class.  The 
146      * order of fields is the same as for field names.
147      * @param pcClass the <code>PersistenceCapable</code> class.
148      * @return the field types for the class.
149      */    
150     public Class[] getFieldTypes (Class pcClass) {
151         Meta meta = getMeta (pcClass);
152         return meta.getFieldTypes();
153     }
154             
155     /*** Get the field flags for a <code>PersistenceCapable</code> class.  The 
156      * order of fields is the same as for field names.
157      * @param pcClass the <code>PersistenceCapable</code> class.
158      * @return the field types for the class.
159      */    
160     public byte[] getFieldFlags (Class pcClass) {
161         Meta meta = getMeta (pcClass);
162         return meta.getFieldFlags();
163     }
164             
165     /*** Get the persistence-capable superclass for a 
166      * <code>PersistenceCapable</code> class.
167      * @param pcClass the <code>PersistenceCapable</code> class.
168      * @return The <code>PersistenceCapable</code> superclass for this class,
169      * or <code>null</code> if there isn't one.
170      */    
171     public Class getPersistenceCapableSuperclass (Class pcClass) {
172         Meta meta = getMeta (pcClass);
173         return meta.getPersistenceCapableSuperclass();
174     }
175             
176     
177     /*** Create a new instance of the class and assign its 
178      * <code>jdoStateManager</code>.  The new instance has its 
179      * <code>jdoFlags</code> set to <code>LOAD_REQUIRED</code>.
180      * @see PersistenceCapable#jdoNewInstance(StateManager sm)
181      * @param pcClass the <code>PersistenceCapable</code> class.
182      * @param sm the <code>StateManager</code> which will own the new instance.
183      * @return the new instance, or <code>null</code> if the class is not 
184      * registered.
185      */    
186     public PersistenceCapable newInstance (Class pcClass, StateManager sm) {
187         Meta meta = getMeta (pcClass);
188         PersistenceCapable pcInstance = meta.getPC();
189         return pcInstance == null?null:pcInstance.jdoNewInstance(sm);
190     }
191     
192     /*** Create a new instance of the class and assign its 
193      * <code>jdoStateManager</code> and key values from the ObjectId.  If the 
194      * oid parameter is <code>null</code>, no key values are copied.
195      * The new instance has its <code>jdoFlags</code> set to 
196      * <code>LOAD_REQUIRED</code>.
197      * @see PersistenceCapable#jdoNewInstance(StateManager sm, Object oid)
198      * @param pcClass the <code>PersistenceCapable</code> class.
199      * @param sm the <code>StateManager</code> which will own the new instance.
200      * @return the new instance, or <code>null</code> if the class is not 
201      * registered.
202      * @param oid the ObjectId instance from which to copy key field values.
203  */    
204     public PersistenceCapable newInstance 
205             (Class pcClass, StateManager sm, Object oid) {
206         Meta meta = getMeta (pcClass);
207         PersistenceCapable pcInstance = meta.getPC();
208         return pcInstance == null?null:pcInstance.jdoNewInstance(sm, oid);
209     }
210     
211     /*** Create a new instance of the ObjectId class of this
212      * <code>PersistenceCapable</code> class.
213      * It is intended only for application identity. This method should
214      * not be called for classes that use single field identity;
215      * newObjectIdInstance(Class, Object) should be used instead. 
216      * If the class has been 
217      * enhanced for datastore identity, or if the class is abstract, 
218      * null is returned.
219      * @param pcClass the <code>PersistenceCapable</code> class.
220      * @return the new ObjectId instance, or <code>null</code> if the class 
221      * is not registered.
222      */    
223     public Object newObjectIdInstance (Class pcClass) {
224         Meta meta = getMeta (pcClass);
225         PersistenceCapable pcInstance = meta.getPC();
226         return pcInstance == null?null:pcInstance.jdoNewObjectIdInstance();
227     }
228     
229     /*** Create a new instance of the class used by the parameter Class
230      * for JDO identity, using the
231      * key constructor of the object id class. It is intended for single
232      * field identity. The identity
233      * instance returned has no relationship with the values of the primary key
234      * fields of the persistence-capable instance on which the method is called.
235      * If the key is the wrong class for the object id class, null is returned.
236      * <P>For classes that use single field identity, if the parameter is 
237      * of one of the following types, the behavior must be as specified:
238      * <ul><li><code>Number</code> or <code>Character</code>: the 
239      * parameter must be the single field
240      * type or the wrapper class of the primitive field type; the parameter
241      * is passed to the single field identity constructor
242      * </li><li><code>ObjectIdFieldSupplier</code>: the field value
243      * is fetched from the <code>ObjectIdFieldSupplier</code> and passed to the 
244      * single field identity constructor
245      * </li><li><code>String</code>: the String is passed to the 
246      * single field identity constructor
247      * </li></ul>
248      * @return the new ObjectId instance, or <code>null</code> 
249      * if the class is not registered.
250      * @param obj the <code>Object</code> form of the object id
251      * @param pcClass the <code>PersistenceCapable</code> class.
252      * @since 2.0
253      */
254     public Object newObjectIdInstance (Class pcClass, Object obj) {
255         Meta meta = getMeta (pcClass);
256         PersistenceCapable pcInstance = meta.getPC();
257         return (pcInstance == null)?null:pcInstance.jdoNewObjectIdInstance(obj);
258     }
259     
260     /*** Copy fields from an outside source to the key fields in the ObjectId.
261      * This method is generated in the <code>PersistenceCapable</code> class to
262      * generate a call to the field manager for each key field in the ObjectId.  
263      * <P>For example, an ObjectId class that has three key fields 
264      * (<code>int id</code>, <code>String name</code>, and 
265      * <code>Float salary</code>) would have the method generated:
266      * <P><code>
267      * void jdoCopyKeyFieldsToObjectId (Object oid, ObjectIdFieldSupplier fm) {
268      * <BR>    oid.id = fm.fetchIntField (0);
269      * <BR>    oid.name = fm.fetchStringField (1);
270      * <BR>    oid.salary = fm.fetchObjectField (2);
271      * <BR>}</code>
272      * <P>The implementation is responsible for implementing the 
273      * <code>ObjectIdFieldSupplier</code> to provide the values for the key 
274      * fields.
275      * @param pcClass the <code>PersistenceCapable Class</code>.
276      * @param oid the ObjectId target of the copy.
277      * @param fm the field manager that supplies the field values.
278  */    
279     public void copyKeyFieldsToObjectId 
280     (Class pcClass, PersistenceCapable.ObjectIdFieldSupplier fm, Object oid) {
281         Meta meta = getMeta (pcClass);
282         PersistenceCapable pcInstance = meta.getPC();
283         if (pcInstance == null) {
284             throw new JDOFatalInternalException (msg.msg(
285                     "ERR_AbstractClassNoIdentity", pcClass.getName())); //NOI18N
286         }
287         pcInstance.jdoCopyKeyFieldsToObjectId(fm, oid);
288     }
289 
290     /*** Copy fields to an outside source from the key fields in the ObjectId.
291      * This method is generated in the <code>PersistenceCapable</code> class to 
292      * generate a call to the field manager for each key field in the ObjectId.  
293      * For example, an ObjectId class that has three key fields 
294      * (<code>int id</code>, <code>String name</code>, and 
295      * <code>Float salary</code>) would have the method generated:
296      * <P><code>void jdoCopyKeyFieldsFromObjectId
297      * <BR>        (PersistenceCapable oid, ObjectIdFieldConsumer fm) {
298      * <BR>     fm.storeIntField (0, oid.id);
299      * <BR>     fm.storeStringField (1, oid.name);
300      * <BR>     fm.storeObjectField (2, oid.salary);
301      * <BR>}</code>
302      * <P>The implementation is responsible for implementing the
303      * <code>ObjectIdFieldConsumer</code> to store the values for the key 
304      * fields.
305      * @param pcClass the <code>PersistenceCapable</code> class
306      * @param oid the ObjectId source of the copy.
307      * @param fm the field manager that receives the field values.
308      */    
309     public void copyKeyFieldsFromObjectId
310     (Class pcClass, PersistenceCapable.ObjectIdFieldConsumer fm, Object oid) {
311         Meta meta = getMeta (pcClass);
312         PersistenceCapable pcInstance = meta.getPC();
313         if (pcInstance == null) {
314             throw new JDOFatalInternalException (msg.msg(
315                     "ERR_AbstractClassNoIdentity", pcClass.getName())); //NOI18N
316         }
317         pcInstance.jdoCopyKeyFieldsFromObjectId(fm, oid);
318     }
319     
320     /*** Register metadata by class.  The registration will be done in the
321      * class named <code>JDOImplHelper</code> loaded by the same or an
322      * ancestor class loader as the <code>PersistenceCapable</code> class
323      * performing the registration. 
324      *
325      * @param pcClass the <code>PersistenceCapable</code> class
326      * used as the key for lookup.
327      * @param fieldNames an array of <code>String</code> field names for 
328      * persistent and transactional fields
329      * @param fieldTypes an array of <code>Class</code> field types
330      * @param fieldFlags the Field Flags for persistent and transactional fields
331      * @param pc an instance of the <code>PersistenceCapable</code> class
332      * @param persistenceCapableSuperclass the most immediate superclass that is
333      * <code>PersistenceCapable</code>
334      */    
335     public static void registerClass (Class pcClass, 
336             String[] fieldNames, Class[] fieldTypes, 
337             byte[] fieldFlags, Class persistenceCapableSuperclass,
338             PersistenceCapable pc) {
339         if (pcClass == null) 
340             throw new NullPointerException(msg.msg("ERR_NullClass")); //NOI18N
341         Meta meta = new Meta (fieldNames, fieldTypes, 
342             fieldFlags, persistenceCapableSuperclass, pc);
343         registeredClasses.put (pcClass, meta);
344 
345         // handle class registration listeners
346         synchronized (listeners) {
347             if (!listeners.isEmpty()) {
348                 RegisterClassEvent event = new RegisterClassEvent(
349                     jdoImplHelper, pcClass, fieldNames, fieldTypes, 
350                     fieldFlags, persistenceCapableSuperclass);
351                 for (Iterator i = listeners.iterator(); i.hasNext();) {
352                     RegisterClassListener crl = 
353                         (RegisterClassListener)i.next();
354                     if (crl != null) {
355                         crl.registerClass(event);
356                     }
357                 }
358             }
359         }
360     }
361         
362     /***
363      * Unregister metadata by class loader. This method unregisters all
364      * registered <code>PersistenceCapable</code> classes loaded by the
365      * specified class loader. Any attempt to get metadata for unregistered
366      * classes will result in a <code>JDOFatalUserException</code>. 
367      * @param cl the class loader.
368      * @since 1.0.2
369      */
370     public void unregisterClasses (ClassLoader cl)
371     {
372         SecurityManager sec = System.getSecurityManager();
373         if (sec != null) { 
374             // throws exception if caller is not authorized
375             sec.checkPermission (JDOPermission.MANAGE_METADATA);
376         }
377         synchronized(registeredClasses) {
378             for (Iterator i = registeredClasses.keySet().iterator(); 
379                  i.hasNext();) {
380                 Class pcClass = (Class)i.next();
381                 // Note, the pc class was registered by calling the static
382                 // method JDOImplHelper.registerClass. This means the
383                 // JDOImplHelper class loader is the same as or an ancestor
384                 // of the class loader of the pc class. In this case method
385                 // getClassLoader does not perform a security check for
386                 // RuntimePermission("getClassLoader") and thus we do not 
387                 // need a privileged block for the getClassLoader call.
388                 if ((pcClass != null) && (pcClass.getClassLoader() == cl)) {
389                     // unregister pc class, if its class loader is the
390                     // specified one.
391                     i.remove();
392                 }
393             }
394         }
395     }
396 
397     /***
398      * Unregister metadata by class. This method unregisters the specified
399      * class. Any further attempt to get metadata for the specified class will
400      * result in a <code>JDOFatalUserException</code>. 
401      * @param pcClass the <code>PersistenceCapable</code> class to be 
402      * unregistered.
403      * @since 1.0.2
404      */
405     public void unregisterClass (Class pcClass)
406     {
407         if (pcClass == null) 
408             throw new NullPointerException(msg.msg("ERR_NullClass")); //NOI18N
409         SecurityManager sec = System.getSecurityManager();
410         if (sec != null) { 
411             // throws exception if caller is not authorized
412             sec.checkPermission (JDOPermission.MANAGE_METADATA);
413         }
414         registeredClasses.remove(pcClass);
415     }
416 
417     /*** 
418      * Add the specified <code>RegisterClassListener</code> to the listener 
419      * list.
420      * @param crl the listener to be added
421      */
422     public void addRegisterClassListener (RegisterClassListener crl) {
423         HashSet alreadyRegisteredClasses = null;
424         synchronized (listeners) {
425             listeners.add(crl);
426             // Make a copy of the existing set of registered classes.
427             // Between these two lines of code, any number of new class 
428             // registrations might occur, and will then all wait until this 
429             // synchronized block completes. Some of the class registrations 
430             // might be delivered twice to the newly registered listener.
431             alreadyRegisteredClasses = new HashSet (registeredClasses.keySet());
432         }
433         // new registrations will call the new listener while the following 
434         // occurs notify the new listener about already-registered classes
435         for (Iterator it = alreadyRegisteredClasses.iterator(); it.hasNext();) {
436             Class pcClass = (Class)it.next();
437             Meta meta = getMeta (pcClass);
438             RegisterClassEvent event = new RegisterClassEvent(
439                 this, pcClass, meta.getFieldNames(), meta.getFieldTypes(), 
440                 meta.getFieldFlags(), meta.getPersistenceCapableSuperclass());
441             crl.registerClass (event);
442         }
443     }
444 
445     /*** 
446      * Remove the specified <code>RegisterClassListener</code> from the listener
447      * list.
448      * @param crl the listener to be removed
449      */
450     public void removeRegisterClassListener (RegisterClassListener crl) {
451         synchronized (listeners) {
452             listeners.remove(crl);
453         }
454     }
455 
456     /***
457      * Returns a collection of class objects of the registered 
458      * persistence-capable classes.
459      * @return registered persistence-capable classes
460      */
461     public Collection getRegisteredClasses() {
462         return Collections.unmodifiableCollection(registeredClasses.keySet());
463     }
464 
465     /*** Look up the metadata for a <code>PersistenceCapable</code> class.
466      * @param pcClass the <code>Class</code>.
467      * @return the <code>Meta</code> for the <code>Class</code>.
468      */    
469     private static Meta getMeta (Class pcClass) {
470         Meta ret = (Meta) registeredClasses.get (pcClass);
471         if (ret == null) {
472             throw new JDOFatalUserException(
473                 msg.msg ("ERR_NoMetadata", pcClass.getName())); //NOI18N
474         }
475         return ret;
476     }
477     
478     /*** Register a class authorized to replaceStateManager.  The caller of
479      * this method must be authorized for JDOPermission("setStateManager").
480      * During replaceStateManager, a persistence-capable class will call
481      * the corresponding checkAuthorizedStateManager and the class of the
482      * instance of the parameter must have been registered.
483      * @param smClass a Class that is authorized for 
484      * JDOPermission("setStateManager").
485      * @throws SecurityException if the caller is not authorized for 
486      * JDOPermission("setStateManager").
487      * @since 1.0.1
488      */
489     public static void registerAuthorizedStateManagerClass (Class smClass) 
490         throws SecurityException {
491         if (smClass == null) 
492             throw new NullPointerException(msg.msg("ERR_NullClass")); //NOI18N
493         SecurityManager sm = System.getSecurityManager();
494         if (sm != null) {
495             sm.checkPermission(JDOPermission.SET_STATE_MANAGER);
496         }
497         synchronized (authorizedStateManagerClasses) {
498             authorizedStateManagerClasses.put(smClass, null);
499         }
500     }
501     
502     /*** Register classes authorized to replaceStateManager.  The caller of
503      * this method must be authorized for JDOPermission("setStateManager").
504      * During replaceStateManager, a persistence-capable class will call
505      * the corresponding checkAuthorizedStateManager and the class of the
506      * instance of the parameter must have been registered.
507      * @param smClasses a Collection of Classes that are authorized for 
508      * JDOPermission("setStateManager").
509      * @throws SecurityException if the caller is not authorized for 
510      * JDOPermission("setStateManager").
511      * @since 1.0.1
512      */
513     public static void registerAuthorizedStateManagerClasses (
514             Collection smClasses) throws SecurityException {
515         SecurityManager sm = System.getSecurityManager();
516         if (sm != null) {
517             sm.checkPermission(JDOPermission.SET_STATE_MANAGER);
518             synchronized (authorizedStateManagerClasses) {
519                 for (Iterator it = smClasses.iterator(); it.hasNext();) {
520                     Object smClass = it.next();
521                     if (!(smClass instanceof Class)) {
522                         throw new ClassCastException(
523                             msg.msg("ERR_StateManagerClassCast", //NOI18N
524                                 smClass.getClass().getName()));
525                     }
526                     registerAuthorizedStateManagerClass((Class)it.next());
527                 }
528             }
529         }
530     }
531     
532     /*** Check that the parameter instance is of a class that is authorized for
533      * JDOPermission("setStateManager").  This method is called by the
534      * replaceStateManager method in persistence-capable classes.
535      * A class that is passed as the parameter to replaceStateManager must be
536      * authorized for JDOPermission("setStateManager").  To improve performance,
537      * first the set of authorized classes is checked, and if not present, a
538      * regular permission check is made.  The regular permission check requires
539      * that all callers on the stack, including the persistence-capable class
540      * itself, must be authorized for JDOPermission("setStateManager").
541      * @param sm an instance of StateManager whose class is to be checked.
542      * @since 1.0.1
543      */
544     public static void checkAuthorizedStateManager (StateManager sm) {
545         checkAuthorizedStateManagerClass(sm.getClass());
546     }
547 
548     /*** Check that the parameter instance is a class that is authorized for
549      * JDOPermission("setStateManager").  This method is called by the
550      * constructors of JDO Reference Implementation classes.
551      * @param smClass a Class to be checked for JDOPermission("setStateManager")
552      * @since 1.0.1
553      */
554     public static void checkAuthorizedStateManagerClass (Class smClass) {
555         final SecurityManager scm = System.getSecurityManager();
556         if (scm == null) {
557             // if no security manager, no checking.
558             return;
559         }
560         synchronized(authorizedStateManagerClasses) {
561             if (authorizedStateManagerClasses.containsKey(smClass)) {
562                 return;
563             }
564         }
565         // if not already authorized, perform "long" security checking.
566         scm.checkPermission(JDOPermission.SET_STATE_MANAGER);
567     }
568 
569     /*** 
570      * Construct an instance of a key class using a String as input.
571      * This is a helper interface for use with ObjectIdentity.
572      * Classes without a String constructor (such as those in java.lang
573      * and java.util) will use this interface for constructing new instances.
574      * The result might be a singleton or use some other strategy.
575      */
576     public interface StringConstructor {
577         /***
578          * Construct an instance of the class for which this instance
579          * is registered.
580          * @param s the parameter for construction
581          * @return the constructed object
582          */
583         public Object construct(String s);
584     }
585     
586     /*** 
587      * Special StringConstructor instances for use with specific
588      * classes that have no public String constructor. The Map is
589      * keyed on class instance and the value is an instance of 
590      * StringConstructor.
591      */
592     static Map stringConstructorMap = new HashMap();
593 
594     /***
595      * 
596      * Register special StringConstructor instances. These instances
597      * are for constructing instances from String parameters where there
598      * is no String constructor for them.
599      * @param cls the class to register a StringConstructor for
600      * @param sc the StringConstructor instance
601      * @return the previous StringConstructor registered for this class
602      */
603     public Object registerStringConstructor(Class cls, StringConstructor sc) {
604         synchronized(stringConstructorMap) {
605             return stringConstructorMap.put(cls, sc);
606         }
607     }
608 
609     /*** Register the default special StringConstructor instances.
610      */
611     static {
612         JDOImplHelper helper = getInstance();
613         if (isClassLoadable("java.util.Currency")) {
614             helper.registerStringConstructor(
615                     Currency.class, new StringConstructor() {
616                 public Object construct(String s) {
617                     try {
618                         return Currency.getInstance(s);
619                     } catch (IllegalArgumentException ex) {
620                         throw new javax.jdo.JDOUserException(msg.msg(
621                             "EXC_CurrencyStringConstructorIllegalArgument", //NOI18N
622                             s), ex); 
623                     } catch (Exception ex) {
624                         throw new JDOUserException(msg.msg(
625                             "EXC_CurrencyStringConstructorException"), //NOI18N
626                             ex); 
627                     }
628                 }
629             });
630         }
631         helper.registerStringConstructor(Locale.class, new StringConstructor() {
632             public Object construct(String s) {
633                 try {
634                     return getLocale(s);
635                 } catch (Exception ex) {
636                     throw new JDOUserException(msg.msg(
637                         "EXC_LocaleStringConstructorException"), ex); //NOI18N
638                 }
639             }
640         });
641         helper.registerStringConstructor(Date.class, new StringConstructor() {
642             public synchronized Object construct(String s) {
643                 try {
644                     // first, try the String as a Long
645                     return new Date(Long.parseLong(s));
646                 } catch (NumberFormatException ex) {
647                     // not a Long; try the formatted date
648                     ParsePosition pp = new ParsePosition(0);
649                     Date result = dateFormat.parse(s, pp);
650                     if (result == null) {
651                         throw new JDOUserException (msg.msg(
652                             "EXC_DateStringConstructor", new Object[] //NOI18N
653                             {s, new Integer(pp.getErrorIndex()), 
654                              dateFormatPattern}));
655                     }
656                     return result;
657                 }
658             }
659         });
660     }
661     
662     /***
663      * Parse the String to a Locale.
664      */
665     private static Locale getLocale(String s) {
666         String lang = s;
667         int firstUnderbar = s.indexOf('_');
668         if (firstUnderbar == -1) {
669             // nothing but language
670             return new Locale(lang);
671         }
672         lang = s.substring(0, firstUnderbar);
673         String country;
674         int secondUnderbar = s.indexOf('_', firstUnderbar + 1);
675         if (secondUnderbar == -1) {
676             // nothing but language, country
677             country = s.substring(firstUnderbar + 1);
678             return new Locale(lang, country);
679         }
680         country = s.substring(firstUnderbar + 1, secondUnderbar);
681         String variant = s.substring(secondUnderbar + 1);
682         return new Locale(lang, country, variant);
683     }
684     /***
685      * Determine if a class is loadable in the current environment.
686      */
687     private static boolean isClassLoadable(String className) {
688         try {
689             Class.forName(className);
690             return true;
691         } catch (ClassNotFoundException ex) {
692             return false;
693         }
694     }
695     
696     /***
697      * Construct an instance of the parameter class, using the keyString
698      * as an argument to the constructor. If the class has a StringConstructor
699      * instance registered, use it. If not, try to find a constructor for
700      * the class with a single String argument. Otherwise, throw a
701      * JDOUserException.
702      * @param className the name of the class
703      * @param keyString the String parameter for the constructor
704      * @return the result of construction
705      */
706     public static Object construct(String className, String keyString) {
707         StringConstructor stringConstructor;
708         try {
709             Class keyClass = Class.forName(className);
710             synchronized(stringConstructorMap) {
711                 stringConstructor = 
712                         (StringConstructor) stringConstructorMap.get(keyClass);
713             }
714             if (stringConstructor != null) {
715                 return stringConstructor.construct(keyString);
716             } else {
717                 Constructor keyConstructor = 
718                     keyClass.getConstructor(new Class[]{String.class});
719                 return keyConstructor.newInstance(new Object[]{keyString});
720             }
721         } catch (JDOException ex) {
722             throw ex;
723         } catch (Exception ex) {
724              /* ClassNotFoundException,
725                 NoSuchMethodException,
726                 InstantiationException,
727                 IllegalAccessException,
728                 InvocationTargetException */
729             throw new JDOUserException(
730                 msg.msg("EXC_ObjectIdentityStringConstruction",  //NOI18N
731                 new Object[] {ex.toString(), className, keyString}), ex);
732         }
733     }
734 
735     /***
736      * Register a DateFormat instance for use with constructing Date 
737      * instances. The default is the default DateFormat instance.
738      * If the new instance implements SimpleDateFormat, get its pattern
739      * for error messages.
740      * @param df the DateFormat instance to use
741      */
742     public synchronized void registerDateFormat(DateFormat df) {
743         dateFormat = df;
744         if (df instanceof SimpleDateFormat) {
745             dateFormatPattern = ((SimpleDateFormat)df).toPattern();
746         } else {
747             dateFormatPattern = msg.msg("MSG_unknown"); //NOI18N
748         }
749     }
750 
751     /*** This is a helper class to manage metadata per persistence-capable
752      * class.  The information is used at runtime to provide field names and
753      * field types to the JDO Model.
754      *
755      * This is the value of the <code>HashMap</code> which
756      * relates the <code>PersistenceCapable Class</code>
757      * as a key to the metadata.
758      */    
759     static class Meta {
760         
761         /*** Construct an instance of <code>Meta</code>.
762          * @param fieldNames An array of <code>String</code>
763          * @param fieldTypes An array of <code>Class</code>
764          * @param fieldFlags an array of <code>int</code>
765          * @param persistenceCapableSuperclass the most immediate 
766          * <code>PersistenceCapable</code> superclass
767          * @param pc An instance of the <code>PersistenceCapable</code> class
768          */        
769         Meta (String[] fieldNames, Class[] fieldTypes, byte[] fieldFlags,
770               Class persistenceCapableSuperclass, PersistenceCapable pc) {
771             this.fieldNames = fieldNames;
772             this.fieldTypes = fieldTypes;
773             this.fieldFlags = fieldFlags;
774             this.persistenceCapableSuperclass = persistenceCapableSuperclass;
775             this.pc = pc;
776         }
777     
778         /*** This is an array of field names used
779          * for the Model at runtime.  The field
780          * is passed by the static class initialization.
781          */
782         String[] fieldNames;
783     
784         /*** Get the field names from the metadata.
785          * @return the array of field names.
786          */
787         String[] getFieldNames() {
788             return fieldNames;
789         }
790     
791         /*** This is an array of field types used
792          * for the Model at runtime.  The field
793          * is passed by the static class initialization.
794          */
795         Class[] fieldTypes;
796     
797         /*** Get the field types from the metadata.
798          * @return the array of field types.
799          */
800         Class[] getFieldTypes() {
801             return fieldTypes;
802         }
803     
804         /*** This is an array of field flags used
805          * for the Model at runtime.  The field
806          * is passed by the static class initialization.
807          */
808         byte[] fieldFlags;
809     
810         /*** Get the field types from the metadata.
811          * @return the array of field types.
812          */
813         byte[] getFieldFlags() {
814             return fieldFlags;
815         }
816 
817         /*** This is the <code>Class</code> instance of the 
818          * <code>PersistenceCapable</code> superclass.
819          */
820         Class persistenceCapableSuperclass;
821     
822         /*** Return the <code>PersistenceCapable</code> superclass.
823          * @return the <code>PersistenceCapable</code> superclass
824          */
825         Class getPersistenceCapableSuperclass() {
826             return persistenceCapableSuperclass;
827         }
828         /*** This is an instance of <code>PersistenceCapable</code>,
829          * used at runtime to create new instances.
830          */
831         PersistenceCapable pc;
832     
833         /*** Get an instance of the <code>PersistenceCapable</code> class.
834          * @return an instance of the <code>PersistenceCapable Class</code>.
835          */
836         PersistenceCapable getPC() {
837             return pc;
838         }
839     
840         /*** Return the string form of the metadata.
841          * @return the string form
842          */
843         public String toString() {
844             return "Meta-" + pc.getClass().getName(); //NOI18N
845         }
846     }
847     
848     /*** Add a StateInterrogation to the list. Create a new list
849      * in case there is an iterator open on the original list.
850      */
851     public synchronized void addStateInterrogation(StateInterrogation si) {
852         List newList = new ArrayList(stateInterrogations);
853         newList.add(si);
854         stateInterrogations = newList;
855     }
856     
857     /*** Remove a StateInterrogation from the list. Create a new list
858      * in case there is an iterator open on the original list.
859      */
860     public synchronized void removeStateInterrogation(StateInterrogation si) {
861         List newList = new ArrayList(stateInterrogations);
862         newList.remove(si);
863         stateInterrogations = newList;
864     }
865     
866     /*** Return an Iterator over all StateInterrogation instances.
867      * Synchronize to avoid add/remove/iterate conflicts.
868      */
869     private synchronized Iterator getStateInterrogationIterator() {
870         return stateInterrogations.iterator();
871     }
872     
873     /*** Mark a non-binary-compatible instance dirty. Delegate to all
874      * registered StateInterrogation instances until one of them
875      * handles the call.
876      */
877     public void nonBinaryCompatibleMakeDirty(Object pc, String fieldName) {
878         Iterator sit = getStateInterrogationIterator();
879         while (sit.hasNext()) {
880             StateInterrogation si = (StateInterrogation)sit.next();
881             if (si.makeDirty(pc, fieldName)) return;
882         }
883     }
884     
885     /*** Determine the state of a non-binary-compatible instance.
886      * Delegate to all registered StateInterrogation instances until
887      * one of them handles the call (returns a non-null Boolean 
888      * with the answer).
889      * The caller provides the stateless "method object" that does 
890      * the actual call to the StateInterrogation instance.
891      */
892     public boolean nonBinaryCompatibleIs(Object pc, 
893             StateInterrogationBooleanReturn sibr) {
894         Iterator sit = getStateInterrogationIterator();
895         while (sit.hasNext()) {
896             StateInterrogation si = (StateInterrogation)sit.next();
897             Boolean result = sibr.is(pc, si);
898             if (result != null) return result.booleanValue();
899         }
900         return false;
901     }
902     
903     /*** Return an object associated with a non-binary-compatible instance.
904      * Delegate to all registered StateInterrogation instances until
905      * one of them handles the call (returns a non-null answer).
906      * The caller provides the stateless "method object" that does 
907      * the actual call to the StateInterrogation instance.
908      */
909     public Object nonBinaryCompatibleGet(Object pc, 
910             StateInterrogationObjectReturn sibr) {
911         Iterator sit = getStateInterrogationIterator();
912         while (sit.hasNext()) {
913             StateInterrogation si = (StateInterrogation)sit.next();
914             Object result = sibr.get(pc, si);
915             if (result != null) return result;
916         }
917         return null;
918     }
919     
920     /*** This is an interface used to interrogate the state of an instance
921      * that does not implement PersistenceCapable. It is used for the
922      * methods that return a boolean value.
923      */
924     public static interface StateInterrogationBooleanReturn {
925         public Boolean is(Object pc, StateInterrogation si);
926     }
927     
928     /*** This is an interface used to interrogate the state of an instance
929      * that does not implement PersistenceCapable. It is used for the
930      * methods that return an Object value.
931      */
932     public static interface StateInterrogationObjectReturn {
933         public Object get(Object pc, StateInterrogation si);
934     }
935 }