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.enhancer.meta.prop;
18  
19  import java.lang.reflect.Modifier;
20  
21  import java.util.Iterator;
22  import java.util.Enumeration;
23  import java.util.Map;
24  import java.util.List;
25  import java.util.Collection;
26  import java.util.HashSet;
27  import java.util.HashMap;
28  import java.util.ArrayList;
29  import java.util.Properties;
30  import java.util.StringTokenizer;
31  
32  import java.text.MessageFormat;
33  
34  import org.apache.jdo.impl.enhancer.meta.EnhancerMetaDataFatalError;
35  import org.apache.jdo.impl.enhancer.meta.EnhancerMetaDataUserException;
36  
37  
38  
39  /***
40   * This class parses properties containing meta data information
41   * about classes. The syntax of the properties is the following:
42   * <ul>
43   *   <li> the keys in the properties file are fully qualified classnames or
44   *        fully qualified fieldnames </li>
45   *   <li> a fields is separated by a classname with a hash mark ('#')
46   *        (e.g. "test.Test#test1") </li>
47   *   <li> all classnames are given in a natural form (e.g.
48   *        "java.lang.Integer", "java.lang.Integer[][]", "int",
49   *        "test.Test$Test1") </li>
50   *   <li> property keys are classnames and fieldnames
51   *        (e.g. "test.Test=...", "test.Test#field1=...") <br> </li>
52   *   <li> Classnames can have the following attributes:
53   *     <ul>
54   *       <li> jdo:{persistent|transactional} </li>
55   *       <li> super: &#60;classname&#62; </li>
56   *       <li> oid: &#60;classname&#62; </li>
57   *       <li> access: {public|protected|package|private} </li>
58   *     </ul> </li>
59   *   <li> Fieldnames can have the following attributes:
60   *     <ul>
61   *       <li> type:&#60;type&#62; </li>
62   *       <li> access: {public|protected|package|private} </li>
63   *       <li> jdo:{persistent|transactional|transient} </li>
64   *       <li> annotation:{key|dfg|mediated} </li>
65   *     </ul> </li>
66   *   <li> the names of the attributes can be ommitted: you can say <br>
67   *        test.Test1#field1=jdo:persistent,type:java.lang.String,key,... <br>
68   *        or <br>
69   *        test.Test1#field1=persistent,java.lang.String,key,... <br>
70   *        or <br>
71   *        test.Test1#field1=jdo:persistent,java.lang.String,key,... <br> </li>
72   *   <li> in order to find fields of a class, a line for the class has to be
73   *        specified in the properties: To find the field
74   *        <code>test.Test1#field</code>, the keys <code>test.Test1</code> and
75   *        <code>test.Test1#Field</code> have to be present. </li>
76   * </ul>
77   * This class is not thread safe.
78   */
79  final class MetaDataProperties
80  {
81      /***
82       *  The delimiter of a property key between the class- and fieldname.
83       */
84      static final char FIELD_DELIMITER = '#';
85  
86      /***
87       *  A string of delimiter characters between attributes.
88       */
89      static final String PROPERTY_DELIMITERS = " \t,;";
90  
91      /***
92       *  A delimiter character between attribute name and attribute value
93       */
94      static final char PROPERTY_ASSIGNER = ':';
95  
96      // attribute names for classes and fields
97      static final String PROPERTY_ACCESS_MODIFIER = "access";
98      static final String PROPERTY_JDO_MODIFIER    = "jdo";
99      static final String PROPERTY_SUPER_CLASSNAME = "super";
100     static final String PROPERTY_OID_CLASSNAME   = "oid";
101     static final String PROPERTY_TYPE            = "type";
102     static final String PROPERTY_ANNOTATION_TYPE = "annotation";
103 
104     // values of the access attribute of classes and fields.
105     static final String ACCESS_PRIVATE       = "private";
106     static final String ACCESS_PACKAGE_LOCAL = "package";
107     static final String ACCESS_PROTECTED     = "protected";
108     static final String ACCESS_PUBLIC        = "public";
109 
110     // values of the jdo attribute of classes and fields.
111     static final String JDO_TRANSIENT     = "transient";
112     static final String JDO_PERSISTENT    = "persistent";
113     static final String JDO_TRANSACTIONAL = "transactional";
114 
115     // values of the annotation type attribute of fields.
116     static final String ANNOTATION_TYPE_KEY      = "key";
117     static final String ANNOTATION_TYPE_DFG      = "dfg";
118     static final String ANNOTATION_TYPE_MEDIATED = "mediated";
119 
120     /***
121      *  The properties to parse.
122      */
123     private Properties properties;
124 
125     /***
126      *  A map of already read class properties. The keys are the
127      *  classnames, the values are the appropriate
128      *  <code>JDOClass</code>-object.
129      */
130     private final Map cachedJDOClasses = new HashMap();
131 
132     /***
133      *  A constant for the cache indicating that a given classname
134      *  if not specified in the properties.
135      */
136     static private final JDOClass NULL = new JDOClass(null);
137 
138     /***
139      *  A temporary vector (this is the reason why the implementation is not
140      *  thread safe).
141      */
142     private final List tmpTokens = new ArrayList();
143 
144     /***
145      *  Creates a new object with the given properties.
146      *
147      *  @param  props  The properties.
148      */
149     public MetaDataProperties(Properties props)
150     {
151         this.properties = props;
152     }
153 
154     /***
155      *  Get the information about the class with the given name.
156      *
157      *  @param  classname  The classname.
158      *  @return  The information about the class or <code>null</code> if no
159      *           information is given.
160      *  @throws  EnhancerMetaDataUserException  If something went wrong parsing
161      *                                     the properties.
162      */
163     public final JDOClass getJDOClass(String classname)
164         throws EnhancerMetaDataUserException
165     {
166         classname = NameHelper.toCanonicalClassName(classname);
167         JDOClass clazz = (JDOClass)cachedJDOClasses.get(classname);
168         if (clazz == NULL) { //already searched but not found
169             return null;
170         }
171         if (clazz != null) {
172             return clazz;
173         }
174 
175         //load it from the properties file
176         String s = properties.getProperty(classname);
177         if (s == null) { //class not defined
178             cachedJDOClasses.put(classname, NULL);
179             return null;
180         }
181 
182         //the class could be found in the properties
183         clazz = parseJDOClass(classname, s);  //parse the class attributes
184         parseJDOFields(clazz);  //parse all fields
185         validateDependencies(clazz);  //check dependencies
186         cachedJDOClasses.put(clazz.getName(), clazz);
187 
188         return clazz;
189     }
190 
191     /***
192      *  Gets the information about the specified field.
193      *
194      *  @param  classname  The name of the class.
195      *  @param  fieldname  The name of the field of the class.
196      *  @return  The information about the field or <code>null</code> if
197      *           no information could be found.
198      *  @throws  EnhancerMetaDataUserException  If something went wrong parsing
199      *                                     the properties.
200      */
201     public final JDOField getJDOField(String fieldname,
202                                       String classname)
203         throws EnhancerMetaDataUserException
204     {
205         JDOClass clazz = getJDOClass(classname);
206         return (clazz != null ? clazz.getField(fieldname) : null);
207     }
208 
209     /***
210      *  Gets all classnames in the properties.
211      *
212      *  @return  All classnames in the properties.
213      */
214     public final String[] getKnownClassNames()
215     {
216         Collection classnames = new HashSet();
217         for (Enumeration names = properties.propertyNames();
218              names.hasMoreElements();) {
219             String name = (String)names.nextElement();
220             if (name.indexOf(FIELD_DELIMITER) < 0) {
221                 classnames.add(NameHelper.fromCanonicalClassName(name));
222             }
223         }
224 
225         return (String[])classnames.toArray(new String[classnames.size()]);
226     }
227 
228     /***
229      *  Parses the attributes-string of a class and puts them into a
230      *  <code>JDOClass</code>-object.
231      *
232      *  @param  classname  The name of the class.
233      *  @param  attributes  The attribute-string as specified in the properties.
234      *  @return  The create <code>JDOClass</code>-object.
235      *  @throws  EnhancerMetaDataUserException  If something went wrong parsing
236      *                                     the attributes.
237      */
238     private final JDOClass parseJDOClass(String classname,
239                                          String attributes)
240         throws EnhancerMetaDataUserException
241     {
242         List props = parseProperties(attributes);
243 
244         // check each property
245         for (int i = 0; i < props.size(); i++) {
246             final Property prop = (Property)props.get(i);
247             validateClassProperty(prop, classname);
248         }
249 
250         // check dependencies of all properties
251         checkForDuplicateProperties(props, classname);
252 
253         // properties are OK - assign them to the JDOClass object
254         JDOClass clazz = new JDOClass(classname);
255         for (int i = 0; i < props.size(); i++) {
256             Property prop = (Property)props.get(i);
257             if (prop.name.equals(PROPERTY_ACCESS_MODIFIER)) {
258                 clazz.setModifiers(getModifiers(prop.value));
259             } else if (prop.name.equals(PROPERTY_JDO_MODIFIER)) {
260                 clazz.setPersistent(prop.value.equals(JDO_PERSISTENT));
261             } else if (prop.name.equals(PROPERTY_SUPER_CLASSNAME)) {
262                 clazz.setSuperClassName(prop.value);
263             } else if (prop.name.equals(PROPERTY_OID_CLASSNAME)) {
264                 clazz.setOidClassName(prop.value);
265             }
266         }
267 
268         return clazz;
269     }
270 
271     /***
272      *  Checks if the given attribute-property of a class is valid.
273      *
274      *  @param  prop       The attribute-property.
275      *  @param  classname  The classname.
276      *  @throws  EnhancerMetaDataUserException  If the validation failed.
277      */
278     static private void validateClassProperty(Property prop,
279                                               String classname)
280         throws EnhancerMetaDataUserException
281     {
282         String value = prop.value;
283         if (prop.name == null) {
284             // try to guess the property name
285             if (value.equals(ACCESS_PUBLIC)
286                 || value.equals(ACCESS_PROTECTED)
287                 || value.equals(ACCESS_PACKAGE_LOCAL)
288                 || value.equals(ACCESS_PRIVATE)) {
289                 // assume access modifier
290                 prop.name = PROPERTY_ACCESS_MODIFIER;
291             } else if (value.equals(JDO_PERSISTENT)
292                        || value.equals(JDO_TRANSIENT)) {
293                 // assume persistence modifier
294                 prop.name = PROPERTY_JDO_MODIFIER;
295             }
296             //@olsen: not unique anymore, could also be oid class name
297             // else {
298             //     //assume the the given value is the superclassname
299             //     prop.name = PROPERTY_SUPER_CLASSNAME;
300             // }
301         } else {
302             // do we have a valid property name?
303             String name = prop.name;
304             checkPropertyName(prop.name,
305                               new String[]{
306                                   PROPERTY_ACCESS_MODIFIER,
307                                   PROPERTY_JDO_MODIFIER,
308                                   PROPERTY_SUPER_CLASSNAME,
309                                   PROPERTY_OID_CLASSNAME
310                               },
311                               classname);
312 
313             // do we have a valid property value?
314             checkPropertyValue(prop,
315                                new String[]{
316                                    ACCESS_PUBLIC,
317                                    ACCESS_PROTECTED,
318                                    ACCESS_PACKAGE_LOCAL,
319                                    ACCESS_PRIVATE
320                                },
321                                PROPERTY_ACCESS_MODIFIER,
322                                classname);
323             checkPropertyValue(prop,
324                                new String[]{
325                                    JDO_TRANSIENT,
326                                    JDO_PERSISTENT
327                                },
328                                PROPERTY_JDO_MODIFIER,
329                                classname);
330         }
331     }
332 
333     /***
334      *  Parses all fields of a given class.
335      *
336      *  @param  clazz  the representation of the class
337      *  @throws EnhancerMetaDataUserException on parse errors
338      */
339     private final void parseJDOFields(JDOClass clazz)
340         throws EnhancerMetaDataUserException
341     {
342         //search for fields of the class
343         for (Enumeration names = properties.propertyNames();
344              names.hasMoreElements();) {
345             String name = (String)names.nextElement();
346             if (name.startsWith(clazz.getName() + FIELD_DELIMITER)) {
347                 //field found
348                 String fieldname
349                     = name.substring(name.indexOf(FIELD_DELIMITER) + 1,
350                                      name.length());
351                 validateFieldName(fieldname, clazz.getName());
352                 clazz.addField(parseJDOField(properties.getProperty(name),
353                                              fieldname, clazz));
354             }
355         }
356         clazz.sortFields();
357     }
358 
359     /***
360      *  Parses the attribute-string of a field.
361      *
362      *  @param  attributes  The attribute-string.
363      *  @param  fieldname   The fieldname.
364      *  @param  clazz       The class to field belongs to.
365      *  @throws  EnhancerMetaDataUserException  on parse errors
366      */
367     private final JDOField parseJDOField(String attributes,
368                                          String fieldname,
369                                          JDOClass clazz)
370         throws EnhancerMetaDataUserException
371     {
372         List props = parseProperties(attributes);
373 
374         //check each property
375         for (int i = 0; i < props.size(); i++) {
376             Property prop = (Property)props.get(i);
377             validateFieldProperty(prop, fieldname, clazz.getName());
378         }
379 
380         //check dependencies of all properties
381         checkForDuplicateProperties(props,
382                                     clazz.getName() + FIELD_DELIMITER
383                                     + fieldname);
384 
385         //properties are OK - assign them to the JDOField object
386         JDOField field = new JDOField(fieldname);
387         for (int i = 0; i < props.size(); i++) {
388             Property prop = (Property)props.get(i);
389             if (prop.name.equals(PROPERTY_ACCESS_MODIFIER)) {
390                 field.setModifiers(getModifiers(prop.value));
391             } else if (prop.name.equals(PROPERTY_JDO_MODIFIER)) {
392                 field.setJdoModifier(prop.value);
393             } else if (prop.name.equals(PROPERTY_TYPE)) {
394                 field.setType(prop.value);
395             } else if (prop.name.equals(PROPERTY_ANNOTATION_TYPE)) {
396                 field.setAnnotationType(prop.value);
397             }
398         }
399 
400         return field;
401     }
402 
403     /***
404      *  Checks if the given attribute-property if valid for a field.
405      *
406      *  @param  prop       The attribute-property.
407      *  @param  fieldname  The fieldname.
408      *  @param  classname  The classname.
409      *  @throws  EnhancerMetaDataUserException  If the check fails.
410      */
411     private final void validateFieldProperty(Property prop,
412                                              String fieldname,
413                                              String classname)
414         throws EnhancerMetaDataUserException
415     {
416         //try to guess the property name
417         String value = prop.value;
418         if (prop.name == null) {
419             if (value.equals(ACCESS_PUBLIC)  ||
420                 value.equals(ACCESS_PROTECTED)  ||
421                 value.equals(ACCESS_PACKAGE_LOCAL)  ||
422                 value.equals(ACCESS_PRIVATE)) {
423                 // access modifier
424                 prop.name = PROPERTY_ACCESS_MODIFIER;
425             } else if (value.equals(JDO_PERSISTENT)  ||
426                      value.equals(JDO_TRANSIENT)  ||
427                      value.equals(JDO_TRANSACTIONAL)) {
428                 // persistence modifier
429                 prop.name = PROPERTY_JDO_MODIFIER;
430             } else if (value.equals(ANNOTATION_TYPE_KEY)  ||
431                      value.equals(ANNOTATION_TYPE_DFG)  ||
432                      value.equals(ANNOTATION_TYPE_MEDIATED)) {
433                 // annotation type
434                 prop.name = PROPERTY_ANNOTATION_TYPE;
435             } else {
436                 //assume the the given value is the type
437                 prop.name = PROPERTY_TYPE;
438             }
439         } else {
440             String entry = classname + FIELD_DELIMITER + fieldname;
441 
442             //do we have a valid property name?
443             checkPropertyName(prop.name,
444                               new String[]{
445                                   PROPERTY_ACCESS_MODIFIER,
446                                   PROPERTY_JDO_MODIFIER,
447                                   PROPERTY_TYPE,
448                                   PROPERTY_ANNOTATION_TYPE
449                               },
450                               entry);
451 
452             //do we have a valid property value
453             checkPropertyValue(prop,
454                                new String[]{
455                                    ACCESS_PUBLIC,
456                                    ACCESS_PROTECTED,
457                                    ACCESS_PACKAGE_LOCAL,
458                                    ACCESS_PRIVATE
459                                },
460                                PROPERTY_ACCESS_MODIFIER,
461                                entry);
462             checkPropertyValue(prop,
463                                new String[]{
464                                    JDO_PERSISTENT,
465                                    JDO_TRANSIENT,
466                                    JDO_TRANSACTIONAL
467                                },
468                                PROPERTY_JDO_MODIFIER,
469                                entry);
470             checkPropertyValue(prop,
471                                new String[]{
472                                    ANNOTATION_TYPE_KEY,
473                                    ANNOTATION_TYPE_DFG,
474                                    ANNOTATION_TYPE_MEDIATED
475                                },
476                                PROPERTY_ANNOTATION_TYPE,
477                                entry);
478         }
479     }
480 
481     /***
482      *  Validates dependencies between a class and its fields and between.
483      *
484      *  @param clazz the class
485      *  @throws  EnhancerMetaDataUserException  if the validation fails
486      */
487     private final void validateDependencies(JDOClass clazz)
488         throws EnhancerMetaDataUserException
489     {
490         final List fields = clazz.getFields();
491         for (Iterator i = fields.iterator(); i.hasNext();) {
492             JDOField field = (JDOField)i.next();
493 
494             // check the jdo field modifier
495             if (field.isPersistent() && clazz.isTransient()) {
496                 // non-persistent classes cannot have persistent fields
497                 final String msg
498                     = getMsg(Msg.ERR_TRANSIENT_CLASS_WITH_PERSISTENT_FIELD,
499                              new String[]{
500                                  clazz.getName(),
501                                  field.getName() });
502                 throw new EnhancerMetaDataUserException(msg);
503             }
504             if (field.isTransactional() && clazz.isTransient()) {
505                 // non-persistent classes cannot have transactional fields
506                 final String msg
507                     = getMsg(Msg.ERR_TRANSIENT_CLASS_WITH_TRANSACTIONAL_FIELD,
508                              new String[]{
509                                  clazz.getName(),
510                                  field.getName() });
511                 throw new EnhancerMetaDataUserException(msg);
512             }
513             if (!field.isKnownTransient() && !field.isManaged()) {
514                 // unspecified persistence modifier
515                 final String msg
516                     = getMsg(Msg.ERR_UNSPECIFIED_FIELD_PERSISTENCE_MODIFIER,
517                              new String[]{
518                                  clazz.getName(),
519                                  field.getName() });
520                 throw new EnhancerMetaDataUserException(msg);
521             }
522 
523             // check annotation type
524             if (!field.isAnnotated() && field.isManaged()) {
525                 // unspecified annotation type
526                 final String msg
527                     = getMsg(Msg.ERR_UNSPECIFIED_FIELD_ANNOTATION_TYPE,
528                              new String[]{
529                                  clazz.getName(),
530                                  field.getName() });
531                 throw new EnhancerMetaDataUserException(msg);
532             }
533             if (field.isAnnotated() && !field.isManaged()) {
534                 // non managed field with annotation type
535                 final String msg
536                     = getMsg(Msg.ERR_NON_MANAGED_ANNOTATED_FIELD,
537                              new String[]{
538                                  clazz.getName(),
539                                  field.getName() });
540                 throw new EnhancerMetaDataUserException(msg);
541             }
542             if (field.isAnnotated() && clazz.isTransient()) {
543                 // a non-persistent class cannot have an annotated field
544                 final String msg
545                     = getMsg(Msg.ERR_TRANSIENT_CLASS_WITH_ANNOTATED_FIELD,
546                              new String[]{
547                                  clazz.getName(),
548                                  field.getName() });
549                 throw new EnhancerMetaDataUserException(msg);
550             }
551         }
552     }
553 
554     /***
555      *  Checks if a given fieldname is a valid Java identifier.
556      *
557      *  @param  fieldname  The fieldname.
558      *  @param  classname  The corresponding classname.
559      *  @throws  EnhancerMetaDataUserException  If the check fails.
560      */
561     static private void validateFieldName(String fieldname,
562                                           String classname)
563         throws EnhancerMetaDataUserException
564     {
565         if (fieldname.length() == 0) {
566             final String msg
567                 = getMsg(Msg.ERR_EMPTY_FIELDNAME,
568                          new String[]{ classname });
569             throw new EnhancerMetaDataUserException(msg);
570         }
571 
572         if (!Character.isJavaIdentifierStart(fieldname.charAt(0))) {
573             final String msg
574                 = getMsg(Msg.ERR_INVALID_FIELDNAME,
575                          new String[]{ classname, fieldname });
576             throw new EnhancerMetaDataUserException(msg);
577         }
578 
579         for (int i = fieldname.length() - 1; i >= 0; i--) {
580             final char c = fieldname.charAt(i);
581             if (!Character.isJavaIdentifierPart(c)) {
582                 final String msg
583                     = getMsg(Msg.ERR_INVALID_FIELDNAME,
584                              new String[]{ classname, fieldname });
585                 throw new EnhancerMetaDataUserException(msg);
586             }
587         }
588     }
589 
590     /***
591      *  Checks if an attribute-property was entered twice for a class or field.
592      *
593      *  @param  props  The properties.
594      *  @param  entry  The class- or fieldname.
595      *  @throws  EnhancerMetaDataUserException  If the check fails.
596      */
597     static private void checkForDuplicateProperties(List props,
598                                                     String entry)
599         throws EnhancerMetaDataUserException
600     {
601         for (int i = 0; i < props.size(); i++) {
602             for (int j = i + 1; j < props.size(); j++) {
603                 Property p1 = (Property)props.get(i);
604                 Property p2 = (Property)props.get(j);
605                 if (p1.name.equals(p2.name) && !p1.value.equals(p2.value)) {
606                     final String msg
607                         = getMsg(Msg.ERR_DUPLICATE_PROPERTY_NAME,
608                                  new String[]{
609                                      entry,
610                                      p1.name,
611                                      p1.value,
612                                      p2.value });
613                     throw new EnhancerMetaDataUserException(msg);
614                 }
615             }
616         }
617     }
618 
619     /***
620      *  Checks if an attribute name is recognized by the parser.
621      *
622      *  @param  name        The name of the attribute.
623      *  @param  validnames  A list of valid names(the attribute name has to
624      *                      be in this list).
625      *  @param  entry       The class- or fieldname.
626      *  @throws  EnhancerMetaDataUserException  If the check fails.
627      */
628     static private void checkPropertyName(String name,
629                                           String[] validnames,
630                                           String entry)
631         throws EnhancerMetaDataUserException
632     {
633         for (int i = 0; i < validnames.length; i++) {
634             if (name.equals(validnames[i])) {
635                 return;
636             }
637         }
638 
639         final String msg
640             = getMsg(Msg.ERR_INVALID_PROPERTY_NAME,
641                      new String[]{ entry, name });
642         throw new EnhancerMetaDataUserException(msg);
643     }
644 
645     /***
646      *  Checks if the given value of an attribute-property is recognized by
647      *  by the parser if that value belongs to a given attribute name.
648      *
649      *  @param  prop         The attribute-property(with name and value).
650      *  @param  validvalues  A list of valid values.
651      *  @param  name         The name of the attribute-property to check.
652      *  @param  entry        The class- or fieldname.
653      *  @throws  EnhancerMetaDataUserException  If the check fails.
654      */
655     static private void checkPropertyValue(Property  prop,
656                                            String[] validvalues,
657                                                  String    name,
658                                                  String    entry)
659         throws EnhancerMetaDataUserException
660     {
661         if ( !prop.name.equals(name)) {
662             return;
663         }
664 
665         for (int i = 0; i < validvalues.length; i++) {
666             if (prop.value.equals(validvalues[i])) {
667                 return;
668             }
669         }
670 
671         final String msg
672             = getMsg(Msg.ERR_INVALID_PROPERTY_VALUE,
673                      new String[]{ entry, name, prop.value });
674         throw new EnhancerMetaDataUserException(msg);
675     }
676 
677     /***
678      *  Formats an error message with the given parameters.
679      *
680      *  @param  msg     The message with format strings.
681      *  @param  params  The params to format the message with.
682      *  @return  The formatted error message.
683      */
684     static final String getMsg(String   msg,
685                                String[] params)
686     {
687         return MessageFormat.format(msg, params);
688     }
689 
690     /***
691      *  Parses the attribute-string of a class- or fieldname.
692      *
693      *  @param  attributes  The attribute-string.
694      *  @return  A list of <code>Propert<</code>-objects for the attributes.
695      *  @exception  EnhancerMetaDataUserException  If the parsing fails.
696      */
697     final List parseProperties(String attributes)
698         throws EnhancerMetaDataUserException
699     {
700         tmpTokens.clear();
701         for (StringTokenizer t
702                  = new StringTokenizer(attributes, PROPERTY_DELIMITERS);
703              t.hasMoreTokens();) {
704             tmpTokens.add(parseProperty(t.nextToken()));
705         }
706 
707         return tmpTokens;
708     }
709 
710     /***
711      *  Parses the given attribute and splits it into name and value.
712      *
713      *  @param  attribute  The attribute-string.
714      *  @return  The <code>Propert</code>-object.
715      *  @exception  EnhancerMetaDataUserException  If the parsing fails.
716      */
717     private final Property parseProperty(String attribute)
718         throws EnhancerMetaDataUserException
719     {
720         Property prop = new Property();
721         int idx = attribute.indexOf(PROPERTY_ASSIGNER);
722         if (idx < 0) {
723             prop.value = attribute;
724         } else {
725             prop.name = attribute.substring(0, idx);
726             prop.value = attribute.substring(idx + 1, attribute.length());
727             if (prop.name.length() == 0 || prop.value.length() == 0) {
728                 final String msg
729                     = getMsg(Msg.ERR_EMPTY_PROPERTY_NAME_OR_VALUE,
730                              new String[]{ attribute });
731                 throw new EnhancerMetaDataUserException(msg);
732             }
733         }
734 
735         return prop;
736     }
737 
738     /***
739      * Returns the modifier value for a Java modifier name.
740      */
741     static private int getModifiers(String modifier)
742     {
743         if (modifier.equals(ACCESS_PUBLIC)) {
744             return Modifier.PUBLIC;
745         }
746         if (modifier.equals(ACCESS_PRIVATE)) {
747             return Modifier.PRIVATE;
748         }
749         if (modifier.equals(ACCESS_PROTECTED)) {
750             return Modifier.PROTECTED;
751         }
752         return 0;
753     }
754 
755     /***
756      *  A simple test to run from the command line.
757      *
758      *  @param  argv  The command line arguments.
759      */
760     public static void main(String[] argv)
761     {
762         if (argv.length != 1) {
763             System.err.println("Error: no property filename specified");
764             return;
765         }
766         final Properties p = new Properties();
767         try {
768             java.io.InputStream in
769                 = new java.io.FileInputStream(new java.io.File(argv[0]));
770             p.load(in);
771             in.close();
772             System.out.println("PROPERTIES: " + p);
773             System.out.println("############");
774             final MetaDataProperties props = new MetaDataProperties(p);
775             String[] classnames = props.getKnownClassNames();
776             for (int i = 0; i < classnames.length; i++) {
777                 String classname = classnames[i];
778                 System.out.println(classname + ": "
779                                    + props.getJDOClass(classname));
780             }
781         } catch(Throwable ex) {
782             ex.printStackTrace(System.err);
783         }
784     }
785 
786     /***
787      *  The holder-class for the name and the value of a property.
788      */
789     static private final class Property
790     {
791         /***
792          *  The name of the property.
793          */
794         String name = null;
795 
796         /***
797          *  The value of the property.
798          */
799         String value = null;
800 
801         /***
802          *  Creates a string-representation of this object.
803          *
804          *  @return  The string-representation of this object.
805          */
806         public final String toString()
807         {
808             return '<' + name + ':' + value + '>';
809         }
810     }
811 
812     //^olsen: -> Bundle.properties
813 
814     /***
815      *  Holds all unformatted error messages.
816      */
817     static private interface Msg
818     {
819         // the unformatted error messages
820         static final String PREFIX = "Error Parsing meta data properties: ";
821 
822         static final String ERR_EMPTY_FIELDNAME =
823         PREFIX + "The class ''{0}'' may not have an empty fieldname.";
824 
825         static final String ERR_INVALID_FIELDNAME =
826         PREFIX + "The field name ''{1}'' of class ''{0}'' is not valid.";
827 
828         static final String ERR_EMPTY_PROPERTY_NAME_OR_VALUE  =
829         PREFIX + "The property name and value may not be empty if a ''" + 
830         PROPERTY_ASSIGNER + "'' is specified: ''{0}''.";
831 
832         static final String ERR_INVALID_PROPERTY_NAME =
833         PREFIX + "Invalid property name for entry ''{0}'': ''{1}''.";
834 
835         static final String ERR_INVALID_PROPERTY_VALUE =
836         PREFIX + "Invalid value for property ''{1}'' of entry ''{0}'': ''{2}''.";
837 
838         static final String ERR_DUPLICATE_PROPERTY_NAME =
839         PREFIX + "The property ''{1}'' for the entry ''{0}'' entered twice with values: ''{2}'' and ''{3}''.";
840 
841         static final String ERR_UNSPECIFIED_FIELD_PERSISTENCE_MODIFIER =
842         PREFIX + "No persistence modifier specified for field: ''{0}.{1}''.";
843 
844         static final String ERR_TRANSIENT_CLASS_WITH_PERSISTENT_FIELD =
845         PREFIX + "A non-persistent class cannot have a persistent field(class ''{0}'' with field ''{1})''.";
846 
847         static final String ERR_TRANSIENT_CLASS_WITH_TRANSACTIONAL_FIELD =
848         PREFIX + "A non-persistent class cannot have a transactional field(class ''{0}'' with field ''{1})''.";
849 
850         static final String ERR_UNSPECIFIED_FIELD_ANNOTATION_TYPE =
851         PREFIX + "No annotation type specified for field: ''{0}.{1}''.";
852 
853         static final String ERR_TRANSIENT_CLASS_WITH_ANNOTATED_FIELD =
854         PREFIX + "A non-persistent class cannot have an annotated field(''{1}'' of class ''{0}'') can''t have a fetch group.";
855 
856         static final String ERR_NON_MANAGED_ANNOTATED_FIELD =
857         PREFIX + "A non-managed field(''{1}'' of class ''{0}'') can''t be a annotated.";
858     }
859 }