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