1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  package org.exolab.castor.builder.factory;
15  
16  import java.util.Enumeration;
17  
18  import org.exolab.castor.builder.AnnotationBuilder;
19  import org.exolab.castor.builder.BuilderConfiguration;
20  import org.exolab.castor.builder.FactoryState;
21  import org.exolab.castor.builder.GroupNaming;
22  import org.exolab.castor.builder.SGTypes;
23  import org.exolab.castor.builder.SourceGenerator;
24  import org.exolab.castor.builder.TypeConversion;
25  import org.exolab.castor.builder.binding.ExtendedBinding;
26  import org.exolab.castor.builder.binding.XMLBindingComponent;
27  import org.exolab.castor.builder.binding.xml.EnumBindingType;
28  import org.exolab.castor.builder.binding.xml.EnumMember;
29  import org.exolab.castor.builder.types.XSString;
30  import org.exolab.castor.builder.types.XSType;
31  import org.exolab.castor.xml.schema.Facet;
32  import org.exolab.castor.xml.schema.SimpleType;
33  import org.exolab.javasource.JAnnotationType;
34  import org.exolab.javasource.JArrayType;
35  import org.exolab.javasource.JClass;
36  import org.exolab.javasource.JConstructor;
37  import org.exolab.javasource.JDocComment;
38  import org.exolab.javasource.JEnum;
39  import org.exolab.javasource.JEnumConstant;
40  import org.exolab.javasource.JField;
41  import org.exolab.javasource.JMethod;
42  import org.exolab.javasource.JModifiers;
43  import org.exolab.javasource.JParameter;
44  import org.exolab.javasource.JSourceCode;
45  import org.exolab.javasource.JType;
46  
47  
48  
49  
50  
51  
52  
53  public final class EnumerationFactory extends BaseFactory {
54  
55    
56  
57  
58    private TypeConversion _typeConversion;
59  
60    
61  
62  
63  
64    private boolean _caseInsensitive = false;
65  
66    
67  
68  
69  
70    private int _maxSuffix = 0;
71  
72    
73  
74  
75    private int _maxEnumerationsPerClass;
76  
77    
78  
79  
80  
81  
82  
83  
84    public EnumerationFactory(final BuilderConfiguration config, final GroupNaming groupNaming,
85        final SourceGenerator sourceGenerator) {
86      super(config, null, groupNaming, sourceGenerator);
87      _typeConversion = new TypeConversion(getConfig());
88  
89      
90      _maxEnumerationsPerClass = config.getMaximumNumberOfConstants();
91    } 
92  
93    
94  
95  
96  
97  
98  
99  
100 
101   void processEnumerationAsNewObject(final ExtendedBinding binding, final SimpleType simpleType,
102       final FactoryState state) {
103     
104     _maxSuffix = 0;
105     boolean generateConstantDefinitions = true;
106     int numberOfEnumerationFacets = simpleType.getNumberOfFacets(Facet.ENUMERATION);
107     if (numberOfEnumerationFacets > _maxEnumerationsPerClass) {
108       generateConstantDefinitions = false;
109     }
110 
111     Enumeration<Facet> enumeration = simpleType.getFacets(Facet.ENUMERATION);
112 
113     XMLBindingComponent component = new XMLBindingComponent(getConfig(), getGroupNaming());
114     if (binding != null) {
115       component.setBinding(binding);
116       component.setView(simpleType);
117     }
118 
119     
120     boolean useValuesAsName = true;
121     useValuesAsName = selectNamingScheme(component, enumeration, useValuesAsName);
122 
123     enumeration = simpleType.getFacets(Facet.ENUMERATION);
124 
125     JClass jClass = state.getJClass();
126     String className = jClass.getLocalName();
127 
128     if (component.getJavaClassName() != null) {
129       className = component.getJavaClassName();
130     }
131 
132     
133     if (state.getJClass() instanceof JEnum) {
134       createJava5Enum(simpleType, state, component, useValuesAsName, enumeration);
135       return;
136     }
137 
138     JField field = null;
139     JField fHash = new JField(SGTypes.createHashtable(getConfig().useJava50()), "_memberTable");
140     fHash.setInitString("init()");
141     fHash.getModifiers().setStatic(true);
142 
143     JSourceCode jsc = null;
144 
145     
146     JConstructor constructor = jClass.getConstructor(0);
147     constructor.getModifiers().makePrivate();
148     constructor.addParameter(new JParameter(JType.INT, "type"));
149     constructor.addParameter(new JParameter(SGTypes.STRING, "value"));
150     jsc = constructor.getSourceCode();
151     jsc.add("this.type = type;");
152     jsc.add("this.stringValue = value;");
153 
154     createValueOfMethod(jClass, className);
155     createEnumerateMethod(jClass, className);
156     createToStringMethod(jClass, className);
157     createInitMethod(jClass);
158     createReadResolveMethod(jClass);
159 
160     
161     int count = 0;
162 
163     while (enumeration.hasMoreElements()) {
164       Facet facet = (Facet) enumeration.nextElement();
165 
166       String value = facet.getValue();
167 
168       String typeName = null;
169       String objName = null;
170 
171       if (useValuesAsName) {
172         objName = translateEnumValueToIdentifier(component.getEnumBinding(), facet);
173       } else {
174         objName = "VALUE_" + count;
175       }
176 
177       
178       
179       typeName = objName + "_TYPE";
180 
181       
182       boolean addInitializerCode = true;
183       if (jClass.getField(objName) != null) {
184         
185         
186         
187         jClass.removeField(objName);
188         jClass.removeField(typeName);
189         addInitializerCode = false;
190       }
191 
192       if (generateConstantDefinitions) {
193         
194         field = new JField(JType.INT, typeName);
195         field.setComment("The " + value + " type");
196         JModifiers modifiers = field.getModifiers();
197         modifiers.setFinal(true);
198         modifiers.setStatic(true);
199         modifiers.makePublic();
200         field.setInitString(Integer.toString(count));
201         jClass.addField(field);
202 
203         
204         field = new JField(jClass, objName);
205         field.setComment("The instance of the " + value + " type");
206 
207         modifiers = field.getModifiers();
208         modifiers.setFinal(true);
209         modifiers.setStatic(true);
210         modifiers.makePublic();
211 
212         StringBuilder init = new StringBuilder(32);
213         init.append("new ");
214         init.append(className);
215         init.append("(");
216         init.append(typeName);
217         init.append(", \"");
218         init.append(escapeValue(value));
219         init.append("\")");
220 
221         field.setInitString(init.toString());
222         jClass.addField(field);
223 
224       }
225       
226 
227       if (addInitializerCode) {
228         jsc = getSourceCodeForInitMethod(jClass);
229         jsc.add("members.put(\"");
230         jsc.append(escapeValue(value));
231         if (_caseInsensitive) {
232           jsc.append("\".toLowerCase(), ");
233         } else {
234           jsc.append("\", ");
235         }
236         if (generateConstantDefinitions) {
237           jsc.append(objName);
238         } else {
239           String init = new StringBuilder(32).append("new ").append(className).append('(')
240               .append(count).append(", \"").append(escapeValue(value)).append("\")").toString();
241           jsc.append(init);
242         }
243         jsc.append(");");
244       }
245 
246       ++count;
247     }
248 
249     
250     final JMethod method = jClass.getMethod(this.getInitMethodName(_maxSuffix), 0);
251     method.getSourceCode().add("return members;");
252 
253     
254     
255     
256     
257     
258     
259     jClass.addField(fHash);
260 
261     
262     field = new JField(JType.INT, "type");
263     field.getModifiers().setFinal(true);
264     jClass.addField(field);
265 
266     
267     field = new JField(SGTypes.STRING, "stringValue");
268     field.setInitString("null");
269     jClass.addField(field);
270 
271     createGetTypeMethod(jClass, className);
272   } 
273 
274   private void createJava5Enum(final SimpleType simpleType, final FactoryState state,
275       final XMLBindingComponent component, final boolean useValuesAsName,
276       final Enumeration<Facet> enumeration) {
277 
278     AnnotationBuilder[] annotationBuilders =
279         state.getSGStateInfo().getSourceGenerator().getAnnotationBuilders();
280 
281     JEnum jEnum = (JEnum) state.getJClass();
282 
283     jEnum.removeInterface("java.io.Serializable");
284     jEnum.removeAnnotation(new JAnnotationType("SuppressWarnings"));
285 
286     
287     JField valueField = new JField(new JClass("java.lang.String"), "value");
288     JModifiers modifiers = new JModifiers();
289     modifiers.setFinal(true);
290     modifiers.makePrivate();
291     valueField.setModifiers(modifiers);
292     jEnum.addField(valueField);
293 
294     
295     JField enumConstantsField =
296         new JField(new JClass("java.util.Map<java.lang.String, " + jEnum.getLocalName() + ">"),
297             "enumConstants");
298     modifiers = new JModifiers();
299     modifiers.setFinal(true);
300     modifiers.makePrivate();
301     modifiers.setStatic(true);
302     enumConstantsField.setModifiers(modifiers);
303     enumConstantsField
304         .setInitString("new java.util.HashMap<java.lang.String, " + jEnum.getLocalName() + ">()");
305     jEnum.addField(enumConstantsField);
306 
307     
308     JSourceCode sourceCode = jEnum.getStaticInitializationCode();
309     sourceCode.add("for (" + jEnum.getLocalName() + " c: " + jEnum.getLocalName() + ".values()) {");
310     sourceCode.indent();
311     sourceCode.indent();
312     sourceCode.add(jEnum.getLocalName() + "." + enumConstantsField.getName() + ".put(c."
313         + valueField.getName() + ", c);");
314     sourceCode.unindent();
315     sourceCode.add("}");
316 
317     addValueMethod(jEnum);
318     addFromValueMethod(jEnum, enumConstantsField);
319     addSetValueMethod(jEnum);
320     addToStringMethod(jEnum);
321 
322     JConstructor constructor = jEnum.createConstructor();
323     constructor.addParameter(new JParameter(new JClass("java.lang.String"), "value"));
324     constructor.setSourceCode("this.value = value;");
325     modifiers = new JModifiers();
326     modifiers.makePrivate();
327     constructor.setModifiers(modifiers);
328     jEnum.addConstructor(constructor);
329 
330     int enumCount = 0;
331     while (enumeration.hasMoreElements()) {
332       Facet facet = enumeration.nextElement();
333       JEnumConstant enumConstant;
334       if (useValuesAsName) {
335         enumConstant =
336             new JEnumConstant(translateEnumValueToIdentifier(component.getEnumBinding(), facet),
337                 new String[] {"\"" + facet.getValue() + "\""});
338       } else {
339         enumConstant =
340             new JEnumConstant("VALUE_" + enumCount, new String[] {"\"" + facet.getValue() + "\""});
341       }
342 
343       
344       for (AnnotationBuilder annotationBuilder : annotationBuilders) {
345         annotationBuilder.addEnumConstantAnnotations(facet, enumConstant);
346       }
347 
348       jEnum.addEnumConstant(enumConstant);
349       enumCount++;
350     }
351 
352     
353     for (AnnotationBuilder annotationBuilder : annotationBuilders) {
354       annotationBuilder.addEnumAnnotations(simpleType, jEnum);
355     }
356   }
357 
358   
359 
360 
361 
362 
363   private void addValueMethod(JEnum jEnum) {
364     JMethod valueMethod =
365         new JMethod("value", new JClass("java.lang.String"), "the value of this constant");
366     valueMethod.setSourceCode("return this.value;");
367     jEnum.addMethod(valueMethod, false);
368   }
369 
370   
371 
372 
373 
374 
375   private void addToStringMethod(JEnum jEnum) {
376     JMethod toStringMethod =
377         new JMethod("toString", new JClass("java.lang.String"), "the value of this constant");
378     toStringMethod.setSourceCode("return this.value;");
379     jEnum.addMethod(toStringMethod, false);
380   }
381 
382   
383 
384 
385 
386 
387   private void addSetValueMethod(JEnum jEnum) {
388     JMethod setValueMethod = new JMethod("setValue");
389     setValueMethod.addParameter(new JParameter(new JClass("java.lang.String"), "value"));
390     jEnum.addMethod(setValueMethod, false);
391   }
392 
393   
394 
395 
396 
397 
398   private void addFromValueMethod(JEnum jEnum, JField enumConstantsField) {
399     JModifiers modifiers;
400     JSourceCode sourceCode;
401     JMethod fromValueMethod = new JMethod("fromValue", jEnum, "the constant for this value");
402     fromValueMethod.addParameter(new JParameter(new JClass("java.lang.String"), "value"));
403     sourceCode = new JSourceCode();
404     sourceCode.add(jEnum.getLocalName() + " c = " + jEnum.getLocalName() + "."
405         + enumConstantsField.getName() + ".get(value);");
406     sourceCode.add("if (c != null) {");
407     sourceCode.indent();
408     sourceCode.add("return c;");
409     sourceCode.unindent();
410     sourceCode.add("}");
411     sourceCode.add("throw new IllegalArgumentException(value);");
412     fromValueMethod.setSourceCode(sourceCode);
413     modifiers = new JModifiers();
414     modifiers.setStatic(true);
415     fromValueMethod.setModifiers(modifiers);
416     jEnum.addMethod(fromValueMethod, false);
417   }
418 
419   
420 
421 
422 
423 
424 
425 
426   private JSourceCode getSourceCodeForInitMethod(final JClass jClass) {
427     final JMethod currentInitMethod = jClass.getMethod(getInitMethodName(_maxSuffix), 0);
428     if (currentInitMethod.getSourceCode().size() > _maxEnumerationsPerClass) {
429       ++_maxSuffix;
430       JMethod mInit = createInitMethod(jClass);
431       currentInitMethod.getSourceCode().add("members.putAll(" + mInit.getName() + "());");
432       currentInitMethod.getSourceCode().add("return members;");
433 
434       return mInit.getSourceCode();
435     }
436     return currentInitMethod.getSourceCode();
437   }
438 
439   
440 
441 
442 
443 
444 
445   private String getInitMethodName(final int index) {
446     if (index == 0) {
447       return "init";
448     }
449 
450     return "init" + index;
451   }
452 
453   private boolean selectNamingScheme(final XMLBindingComponent component,
454       final Enumeration<Facet> enumeration, final boolean useValuesAsName) {
455     boolean duplicateTranslation = false;
456     short numberOfTranslationToSpecialCharacter = 0;
457 
458     while (enumeration.hasMoreElements()) {
459       Facet facet = enumeration.nextElement();
460       String possibleId = translateEnumValueToIdentifier(component.getEnumBinding(), facet);
461       if (possibleId.equals("_")) {
462         numberOfTranslationToSpecialCharacter++;
463         if (numberOfTranslationToSpecialCharacter > 1) {
464           duplicateTranslation = true;
465         }
466       }
467 
468       if (!getJavaNaming().isValidJavaIdentifier(possibleId)) {
469         return false;
470       }
471     }
472 
473     if (duplicateTranslation) {
474       return false;
475     }
476     return useValuesAsName;
477   }
478 
479   
480 
481 
482 
483 
484 
485   private void createGetTypeMethod(final JClass jClass, final String className) {
486     JMethod mGetType = new JMethod("getType", JType.INT, "the type of this " + className);
487     mGetType.getSourceCode().add("return this.type;");
488     JDocComment jdc = mGetType.getJDocComment();
489     jdc.appendComment("Returns the type of this " + className);
490     jClass.addMethod(mGetType);
491   }
492 
493   
494 
495 
496 
497 
498   private void createReadResolveMethod(final JClass jClass) {
499     JDocComment jdc;
500     JSourceCode jsc;
501     JMethod mReadResolve = new JMethod("readResolve", SGTypes.OBJECT, "this deserialized object");
502     mReadResolve.getModifiers().makePrivate();
503     jClass.addMethod(mReadResolve);
504     jdc = mReadResolve.getJDocComment();
505     jdc.appendComment(" will be called during deserialization to replace ");
506     jdc.appendComment("the deserialized object with the correct constant ");
507     jdc.appendComment("instance.");
508     jsc = mReadResolve.getSourceCode();
509     jsc.add("return valueOf(this.stringValue);");
510   }
511 
512   
513 
514 
515 
516 
517 
518   private JMethod createInitMethod(final JClass jClass) {
519     final String initMethodName = getInitMethodName(_maxSuffix);
520     JMethod mInit = new JMethod(initMethodName, SGTypes.createHashtable(getConfig().useJava50()),
521         "the initialized Hashtable for the member table");
522     jClass.addMethod(mInit);
523     mInit.getModifiers().makePrivate();
524     mInit.getModifiers().setStatic(true);
525     if (getConfig().useJava50()) {
526       mInit.getSourceCode().add("java.util.Hashtable<Object, Object> members"
527           + " = new java.util.Hashtable<Object, Object>();");
528     } else {
529       mInit.getSourceCode().add("java.util.Hashtable members = new java.util.Hashtable();");
530     }
531     return mInit;
532   }
533 
534   
535 
536 
537 
538 
539 
540   private void createToStringMethod(final JClass jClass, final String className) {
541     JMethod mToString =
542         new JMethod("toString", SGTypes.STRING, "the String representation of this " + className);
543     jClass.addMethod(mToString);
544     JDocComment jdc = mToString.getJDocComment();
545     jdc.appendComment("Returns the String representation of this ");
546     jdc.appendComment(className);
547     mToString.getSourceCode().add("return this.stringValue;");
548   }
549 
550   
551 
552 
553 
554 
555 
556   private void createEnumerateMethod(final JClass jClass, final String className) {
557     
558     
559     JMethod mEnumerate = new JMethod("enumerate",
560         SGTypes.createEnumeration(SGTypes.OBJECT, getConfig().useJava50(), true),
561         "an Enumeration over all possible instances of " + className);
562     mEnumerate.getModifiers().setStatic(true);
563     jClass.addMethod(mEnumerate);
564     JDocComment jdc = mEnumerate.getJDocComment();
565     jdc.appendComment("Returns an enumeration of all possible instances of ");
566     jdc.appendComment(className);
567     mEnumerate.getSourceCode().add("return _memberTable.elements();");
568   }
569 
570   
571 
572 
573 
574 
575 
576   private void createValueOfMethod(final JClass jClass, final String className) {
577     JMethod mValueOf =
578         new JMethod("valueOf", jClass, "the " + className + " value of parameter 'string'");
579     mValueOf.addParameter(new JParameter(SGTypes.STRING, "string"));
580     mValueOf.getModifiers().setStatic(true);
581     jClass.addMethod(mValueOf);
582 
583     JDocComment jdc = mValueOf.getJDocComment();
584     jdc.appendComment("Returns a new " + className);
585     jdc.appendComment(" based on the given String value.");
586 
587     JSourceCode jsc = mValueOf.getSourceCode();
588     jsc.add(
589         "java.lang.Object obj = null;\n" + "if (string != null) {\n"
590             + " obj = _memberTable.get(string{1});\n" + "}\n" + "if (obj == null) {\n"
591             + " String err = \"'\" + string + \"' is not a valid {0}\";\n"
592             + " throw new IllegalArgumentException(err);\n" + "}\n" + "return ({0}) obj;",
593         className, (_caseInsensitive ? ".toLowerCase()" : ""));
594 
595   }
596 
597   
598 
599 
600 
601 
602 
603 
604 
605 
606 
607 
608 
609 
610 
611 
612 
613 
614 
615 
616 
617 
618 
619 
620 
621 
622   void processEnumerationAsBaseType(final ExtendedBinding binding, final SimpleType simpleType,
623       final FactoryState state) {
624     SimpleType base = (SimpleType) simpleType.getBaseType();
625     XSType baseType = null;
626 
627     if (base == null) {
628       baseType = new XSString();
629     } else {
630       baseType = _typeConversion.convertType(base, getConfig().useJava50());
631     }
632 
633     Enumeration<Facet> enumeration = simpleType.getFacets(Facet.ENUMERATION);
634 
635     JClass jClass = state.getJClass();
636     String className = jClass.getLocalName();
637 
638     JField fValues = null;
639     JDocComment jdc = null;
640     JSourceCode jsc = null;
641 
642     
643     JConstructor constructor = jClass.getConstructor(0);
644     constructor.getModifiers().makePrivate();
645 
646     fValues = new JField(new JArrayType(baseType.getJType(), getConfig().useJava50()), "values");
647 
648     
649     
650     int count = 0;
651 
652     StringBuilder values = new StringBuilder("{\n");
653 
654     while (enumeration.hasMoreElements()) {
655       Facet facet = (Facet) enumeration.nextElement();
656       String value = facet.getValue();
657 
658       
659 
660       
661       
662       
663 
664       if (count > 0) {
665         values.append(",\n");
666       }
667 
668       
669       values.append("    ");
670 
671       if (baseType.getType() == XSType.STRING_TYPE) {
672         values.append('\"');
673         
674         values.append(escapeValue(value));
675         values.append('\"');
676       } else {
677         values.append(value);
678       }
679 
680       ++count;
681     }
682 
683     values.append("\n}");
684 
685     fValues.setInitString(values.toString());
686     jClass.addField(fValues);
687 
688     
689     JMethod method =
690         new JMethod("valueOf", jClass, "the String value of the provided " + baseType.getJType());
691     method.addParameter(new JParameter(SGTypes.STRING, "string"));
692     method.getModifiers().setStatic(true);
693     jClass.addMethod(method);
694     jdc = method.getJDocComment();
695     jdc.appendComment("Returns the " + baseType.getJType());
696     jdc.appendComment(" based on the given String value.");
697     jsc = method.getSourceCode();
698 
699     jsc.add("for (int i = 0; i < values.length; i++) {");
700     jsc.add("}");
701     jsc.add("throw new IllegalArgumentException(\"");
702     jsc.append("Invalid value for ");
703     jsc.append(className);
704     jsc.append(": \" + string + \".\");");
705   } 
706 
707   
708 
709 
710 
711 
712 
713 
714 
715 
716 
717 
718 
719 
720 
721 
722 
723 
724 
725   private String translateEnumValueToIdentifier(final EnumBindingType enumBinding,
726       final Facet facet) {
727     String enumValue = facet.getValue();
728 
729     try {
730       String enumerationValue = null;
731       int intVal = Integer.parseInt(facet.getValue());
732 
733       String customMemberName = null;
734       if (enumBinding != null) {
735         customMemberName = getCustomMemberName(enumBinding, String.valueOf(intVal));
736       }
737 
738       if (customMemberName != null) {
739         enumerationValue = customMemberName;
740       } else {
741         if (intVal >= 0) {
742           enumerationValue = "VALUE_" + intVal;
743         } else {
744           enumerationValue = "VALUE_NEG_" + Math.abs(intVal);
745         }
746       }
747 
748       return enumerationValue;
749     } catch (NumberFormatException e) {
750       
751     }
752 
753     StringBuilder sb = new StringBuilder(32);
754     String customMemberName = null;
755 
756     if (enumBinding != null) {
757       customMemberName = getCustomMemberName(enumBinding, enumValue);
758     }
759 
760     if (customMemberName != null) {
761       sb.append(customMemberName);
762     } else {
763       sb.append(enumValue.toUpperCase());
764       int i = 0;
765       while (i < sb.length()) {
766         char c = sb.charAt(i);
767         if ("[](){}<>'`\"".indexOf(c) >= 0) {
768           sb.deleteCharAt(i);
769           i--;
770         } else if (Character.isWhitespace(c) || "\\/?~!@#$%^&*-+=:;.,".indexOf(c) >= 0) {
771           sb.setCharAt(i, '_');
772         }
773         i++;
774       }
775     }
776     return sb.toString();
777   }
778 
779   
780 
781 
782 
783 
784 
785 
786   private String getCustomMemberName(final EnumBindingType enumBinding, final String enumValue) {
787     
788     String customMemberName = null;
789     EnumMember[] enumMembers = enumBinding.getEnumMember();
790     for (EnumMember enumMember : enumMembers) {
791       if (enumMember.getValue().equals(enumValue)) {
792         customMemberName = enumMember.getJavaName();
793       }
794     }
795     return customMemberName;
796   }
797 
798   
799 
800 
801 
802 
803   public void setCaseInsensitive(final boolean caseInsensitive) {
804     _caseInsensitive = caseInsensitive;
805   }
806 
807   
808 
809 
810 
811 
812 
813   private static String escapeValue(final String str) {
814     if (str == null) {
815       return str;
816     }
817 
818     StringBuilder sb = new StringBuilder();
819     char[] chars = str.toCharArray();
820 
821     for (char ch : chars) {
822       switch (ch) {
823         case '\\':
824         case '\"':
825         case '\'':
826           sb.append('\\');
827           break;
828         default:
829           break;
830       }
831       sb.append(ch);
832     }
833     return sb.toString();
834   } 
835 
836 }