View Javadoc

1   package org.hyphenType.documentation.rbgenerator;
2   
3   import static org.hyphenType.exit.CanonicalExitCode.ERROR;
4   import static org.hyphenType.exit.CanonicalExitCode.SUCCESS;
5   
6   import java.io.BufferedOutputStream;
7   import java.io.ByteArrayOutputStream;
8   import java.io.File;
9   import java.io.FileNotFoundException;
10  import java.io.FileOutputStream;
11  import java.io.IOException;
12  import java.io.OutputStream;
13  import java.io.PrintWriter;
14  import java.lang.annotation.Annotation;
15  import java.lang.reflect.InvocationTargetException;
16  import java.lang.reflect.Method;
17  import java.text.DateFormat;
18  import java.util.ArrayList;
19  import java.util.Arrays;
20  import java.util.Date;
21  import java.util.List;
22  
23  import org.hyphenType.datastructure.annotations.ArgumentsObject;
24  import org.hyphenType.datastructure.annotations.Option;
25  import org.hyphenType.datastructure.annotations.OptionValue;
26  import org.hyphenType.datastructure.annotations.SimpleArgument;
27  import org.hyphenType.datastructure.parser.option.StructureOption;
28  import org.hyphenType.datastructure.parser.option.StructureOptionArgument;
29  import org.hyphenType.datastructure.parser.simple.StructureSimpleArgument;
30  import org.hyphenType.documentation.Description;
31  import org.hyphenType.documentation.DocumentationFormatterEngine;
32  import org.hyphenType.exceptions.InvalidOptionsInterfaceException;
33  import org.hyphenType.exit.ExitStatusConstant;
34  import org.hyphenType.lexerparser.LexerParser;
35  import org.hyphenType.lexerparser.exceptions.MandatoryMapValueNotFoundException;
36  import org.hyphenType.lexerparser.exceptions.MandatorySimpleArgumentNotFoundException;
37  import org.hyphenType.lexerparser.exceptions.MandatoryValueNotFoundException;
38  import org.hyphenType.lexerparser.exceptions.RegexMismatchException;
39  import org.hyphenType.lexerparser.exceptions.StringParseException;
40  import org.hyphenType.lexerparser.exceptions.StringParsingErrorException;
41  import org.hyphenType.optionprocessors.ArgumentsProcessorEngine;
42  import org.hyphenType.util.DefaultAnnotation;
43  import org.hyphenType.wrapper.StandAloneAppWrapper;
44  
45  public class RBGenerator extends StandAloneAppWrapper {
46  
47      @SuppressWarnings("unchecked")
48      public void main(RBGeneratorOptions args) {
49          
50          if (args.h()) {
51              args.printDocumentation();
52              args.exit(SUCCESS);
53          }
54  
55          if (args.optionsClass()==null) {
56              System.err.println("No options interface. Giving up. Use --help for more details.");
57              args.exit(ERROR);
58          }
59          
60          if (args.f() && args.fileName().equals("")) {
61              System.err.println("You should inform a file name. Giving up. Use --help for more details.");
62              args.exit(ERROR);
63          }
64          
65          try {
66              
67              LexerParser lexParser = new LexerParser(args.optionsClass(), false);
68              ArgumentsObject argumentsObject = lexParser.getArgsObject();
69              
70              /*
71               * ALIASES
72               */
73              ByteArrayOutputStream aliasesBao = new ByteArrayOutputStream();
74              PrintWriter aliasesPw = new PrintWriter(aliasesBao);
75              aliasesPw.println("#");
76              aliasesPw.println("# Aliases");
77              aliasesPw.println("#");
78              aliasesPw.println();
79              
80              /*
81               * ARGUMENTS OBJECT
82               */
83              aliasesPw.println(String.format("alias.ao = %s", ArgumentsObject.class.getName()));
84              ByteArrayOutputStream aoBao = new ByteArrayOutputStream();
85              PrintWriter aoPw = new PrintWriter(aoBao);
86              aoPw.println("#");
87              aoPw.println("# Arguments object");
88              aoPw.println("#");
89              aoPw.println();
90              aoPw.println(String.format("${ao}.description = %s", argumentsObject.description()));
91              aoPw.println(String.format("${ao}.doubleHyphenInLongOptions = %s", argumentsObject.doubleHyphenInLongOptions()));
92              aoPw.println(String.format("${ao}.singleHyphen = %s", argumentsObject.singleHyphen()));
93              aoPw.println(String.format("${ao}.doubleHyphen = %s", argumentsObject.doubleHyphen()));
94              aoPw.println(String.format("${ao}.equals = %s", argumentsObject.equals()));
95              aoPw.println(String.format("${ao}.documentStatusCodes = %s", argumentsObject.documentStatusCodes()));
96              aoPw.println(String.format("${ao}.preferredDocumentationFormatter = %s", argumentsObject.preferredDocumentationFormatter().getName()));
97  
98              
99              /*
100              * ARGUMENT PROCESSORS
101              */
102             ByteArrayOutputStream processorsBao = new ByteArrayOutputStream();
103             PrintWriter processorsPw = new PrintWriter(processorsBao);
104             processorsPw.println("#");
105             processorsPw.println("# Argument processors");
106             processorsPw.println("#");
107             processorsPw.println();
108             boolean found = false;
109             for (Annotation annotation : args.optionsClass().getAnnotations()) {
110                 if (annotation.annotationType().getEnclosingClass() != null && ArgumentsProcessorEngine.class.isAssignableFrom(annotation.annotationType().getEnclosingClass())) {
111                     found = true;
112                     Class<?> processorAnnotationClass = annotation.annotationType();
113                     /* Not compatible with two processors with the same simple name, such as x.y.A and w.z.A.
114                      * But this is not a big problem, since the user can always his own set of aliases to
115                      * manually eliminate this collision.
116                      */
117                     String aliasName = processorAnnotationClass.getSimpleName();
118                     aliasesPw.println(String.format("alias.p.%s = %s", aliasName, processorAnnotationClass.getName()));
119                     processorsPw.println("# " + annotation.annotationType().getSimpleName());
120                     processorsPw.println();
121                     for (Method m : processorAnnotationClass.getDeclaredMethods()) {
122                         if (m.isAnnotationPresent(Description.class)) {
123                             processorsPw.println("# " + m.getAnnotation(Description.class).value());
124                         }
125                         try {
126                             if (m.getReturnType().isArray()) {
127                                 if (m.getReturnType().getComponentType().isAnnotation()) {
128                                     processorsPw.print(expandAnnotationArray(String.format("${p.%s}.%s", aliasName, m.getName()), (Annotation[])m.invoke(annotation), m.getReturnType().getComponentType()));
129                                 } else {
130                                     processorsPw.println(String.format("${p.%s}.%s = %s", aliasName, m.getName(), Arrays.toString((Object[])m.invoke(annotation))));
131                                 }
132                             } else {
133                                 processorsPw.println(String.format("${p.%s}.%s = %s", aliasName, m.getName(), m.invoke(annotation)));
134                             }
135                             /* We do not expect any of these following exceptions to ever happen.
136                              */
137                         } catch (IllegalArgumentException e) {
138                             e.printStackTrace();
139                         } catch (IllegalAccessException e) {
140                             e.printStackTrace();
141                         } catch (InvocationTargetException e) {
142                             e.printStackTrace();
143                         }
144                     }
145                 }
146             }
147             if(!found) {
148                 processorsPw.println("# No argument processor found");
149             }
150             
151             /*
152              * OPTIONS
153              */
154             aliasesPw.println("alias.Interface = " + args.optionsClass().getName());
155             aliasesPw.println("alias.Option = " + Option.class.getName());
156             aliasesPw.println("alias.OptionValue = " + OptionValue.class.getName());
157             aliasesPw.println("alias.OptionArgument = " + OptionValue.class.getName());
158             ByteArrayOutputStream optionsBao = new ByteArrayOutputStream();
159             PrintWriter optionsPw = new PrintWriter(optionsBao);
160             optionsPw.println("#");
161             optionsPw.println("# Options");
162             optionsPw.println("#");
163             optionsPw.println();
164             List<StructureOption> options = lexParser.getParsedOptions();
165             for(StructureOption option : options) {
166                 optionsPw.println(String.format("${Interface}.%s@${Option}.names = %s", option.method.getName(), option.alternatives));
167                 optionsPw.println(String.format("${Interface}.%s@${Option}.description = %s", option.method.getName(), option.description));
168                 if(option.value!=null) {
169                     optionsPw.println(String.format("${Interface}.%s@${OptionValue}.name = %s", option.value.method.getName(), option.value.name));
170                     optionsPw.println(String.format("${Interface}.%s@${OptionValue}.option = %s", option.value.method.getName(), option.method.getName()));
171                     optionsPw.println(String.format("${Interface}.%s@${OptionValue}.mandatory = %s", option.value.method.getName(), option.value.mandatory));
172                     optionsPw.println(String.format("${Interface}.%s@${OptionValue}.arraySeparator = %s", option.value.method.getName(), option.value.arraySeparator));
173                     optionsPw.println(String.format("${Interface}.%s@${OptionValue}.arrayUseFileSeparator = %s", option.value.method.getName(), option.value.arrayUseFileSeparator));
174                 }
175                 for(StructureOptionArgument argument : option.arguments) {
176                     optionsPw.println(String.format("${Interface}.%s@${OptionArgument}.name = %s", argument.method.getName(), argument.getName()));
177                     optionsPw.println(String.format("${Interface}.%s@${OptionArgument}.option = %s", argument.method.getName(), option.method.getName()));
178                     optionsPw.println(String.format("${Interface}.%s@${OptionArgument}.index = %s", argument.method.getName(), argument.getIndex()));
179                     optionsPw.println(String.format("${Interface}.%s@${OptionArgument}.regex = %s", argument.method.getName(), argument.getRegex()));
180                     optionsPw.println(String.format("${Interface}.%s@${OptionArgument}.channels = %s", argument.method.getName(), argument.getChannels()));
181                     optionsPw.println(String.format("${Interface}.%s@${OptionArgument}.mandatory = %s", argument.method.getName(), argument.isMandatory()));
182                     optionsPw.println(String.format("${Interface}.%s@${OptionArgument}.description = %s", argument.method.getName(), argument.getDescription()));
183                 }
184             }
185             
186             /*
187              * SIMPLE ARGUMENTS
188              */
189             aliasesPw.println("alias.SimpleArgument = " + SimpleArgument.class.getName());
190             ByteArrayOutputStream simpleArgumentsBao = new ByteArrayOutputStream();
191             PrintWriter simpleArgumentsPw = new PrintWriter(simpleArgumentsBao);
192             simpleArgumentsPw.println("#");
193             simpleArgumentsPw.println("# Simple Arguments");
194             simpleArgumentsPw.println("#");
195             simpleArgumentsPw.println();
196             
197             List<StructureSimpleArgument> simpleArguments = lexParser.getSimpleArguments();
198             for(StructureSimpleArgument simpleArgument : simpleArguments) {
199                 simpleArgumentsPw.println(String.format("${Interface}.%s@${SimpleArgument}.name = %s", simpleArgument.method.getName(), simpleArgument.getName()));
200                 simpleArgumentsPw.println(String.format("${Interface}.%s@${SimpleArgument}.index = %s", simpleArgument.method.getName(), simpleArgument.getIndex()));
201                 simpleArgumentsPw.println(String.format("${Interface}.%s@${SimpleArgument}.description = %s", simpleArgument.method.getName(), simpleArgument.getDescription()));
202                 simpleArgumentsPw.println(String.format("${Interface}.%s@${SimpleArgument}.regex = %s", simpleArgument.method.getName(), simpleArgument.getRegex()));
203                 simpleArgumentsPw.println(String.format("${Interface}.%s@${SimpleArgument}.mandatory = %s", simpleArgument.method.getName(), simpleArgument.isMandatory()));
204                 simpleArgumentsPw.println(String.format("${Interface}.%s@${SimpleArgument}.channels = %s", simpleArgument.method.getName(), simpleArgument.getChannels()));
205             }
206             
207             /*
208              * EXIT STATUS
209              */
210             aliasesPw.println("alias.status = " + argumentsObject.statusCodeEnum().getName());
211             aliasesPw.println("alias.stdoc = " + ExitStatusConstant.class.getName());
212             ByteArrayOutputStream statusBao = new ByteArrayOutputStream();
213             PrintWriter statusPw = new PrintWriter(statusBao);
214             statusPw.println("#");
215             statusPw.println("# Exit status");
216             statusPw.println("#");
217             Class<? extends Enum> statusCodeEnumClass = (Class<? extends Enum>) argumentsObject.statusCodeEnum();
218             for(Enum enumConstant : statusCodeEnumClass.getEnumConstants()) {
219                 statusPw.println();
220                 try {
221                     if(argumentsObject.statusCodeEnum().getField(enumConstant.toString()).isAnnotationPresent(Description.class)) {
222                         String description = argumentsObject.statusCodeEnum().getField(enumConstant.toString()).getAnnotation(Description.class).value();
223                         description = description.replaceAll("\n", "\n# ");
224                         statusPw.println(String.format("# %s", description));
225                     }
226                     /* I do not really care about these exceptions, since o came from the class I am checking.
227                      * So unless there is some bug in the reflection API (which is frankly unlikely), the above
228                      * code should work fine.
229                      */
230                 } catch (SecurityException e) {
231                     e.printStackTrace();
232                 } catch (NoSuchFieldException e) {
233                     e.printStackTrace();
234                 }
235                 ExitStatusConstant constant = DefaultAnnotation.getAnnotation((Enum)enumConstant, ExitStatusConstant.class);
236                 if(constant.userDescription()==null) {
237                     statusPw.println(String.format("${status}.%s@${stdoc}.userDescription = ", enumConstant.toString()));
238                 } else {
239                     statusPw.println(String.format("${status}.%s@${stdoc}.userDescription = %s", enumConstant.toString(), constant.userDescription()));
240                 }
241                 if(constant.message()==null) {
242                     statusPw.println(String.format("${status}.%s@${stdoc}.message = ", enumConstant.toString()));
243                 } else {
244                     statusPw.println(String.format("${status}.%s@${stdoc}.message = %s", enumConstant.toString(), constant.message()));
245                 }
246                 
247                 // TODO Test
248                 for (int i = 0; i< constant.catches().length; i++) {
249                     statusPw.println(String.format("${status}.%s@${stdoc}.exceptions[%d] = %s", enumConstant.toString(), i, constant.catches()[i].getName()));
250                 }
251             }
252             
253             /*
254              * DOCUMENTATION FORMATTER
255              */
256             ByteArrayOutputStream formatterBao = new ByteArrayOutputStream();
257             PrintWriter formatterPw = new PrintWriter(formatterBao);
258             
259             ArrayList<Class<? extends Annotation>> documentationFormattersList = new ArrayList<Class<? extends Annotation>>();
260             // Adding the preferred formatter
261             documentationFormattersList.add(argumentsObject.preferredDocumentationFormatter());
262             // Adding each of the formatters configured by annotations present in the options interface
263             for (Annotation annotation : args.optionsClass().getAnnotations()) {
264                 if (
265                     annotation.annotationType().getEnclosingClass()!=null &&
266                     DocumentationFormatterEngine.class.isAssignableFrom(annotation.annotationType().getEnclosingClass()) &&
267                     !documentationFormattersList.contains(annotation.annotationType())
268                 ) {
269                     documentationFormattersList.add(annotation.annotationType());
270                 }
271             }
272             
273             formatterPw.println("#");
274             formatterPw.println("# Documentation formatters");
275             formatterPw.println("#");
276             for (Class<? extends Annotation> formatterClass : documentationFormattersList) {
277                 // Not compatible with documentation formatter annotations with identical simple names
278                 aliasesPw.println(String.format("alias.f.%s = %s", formatterClass.getSimpleName(), formatterClass.getName()));
279                 formatterPw.println();
280                 formatterPw.println("# " + formatterClass.getSimpleName());
281                 formatterPw.println();
282                 
283                 Annotation annotation = args.optionsClass().getAnnotation(formatterClass);
284                 
285                 for(Method m : formatterClass.getDeclaredMethods()) {
286                     if(m.isAnnotationPresent(Description.class)) {
287                         String description = m.getAnnotation(Description.class).value();
288                         description = description.replaceAll("\n", "\n# ");
289                         formatterPw.println(String.format("# %s", description));
290                     }
291                     
292                     if (annotation == null) {
293                         if(m.getDefaultValue() == null) {
294                             formatterPw.println(String.format("${f.%s}.%s = ", formatterClass.getSimpleName(), m.getName()));
295                         } else {
296                             formatterPw.println(String.format("${f.%s}.%s = %s", formatterClass.getSimpleName(), m.getName(), m.getDefaultValue()));
297                         }
298                     } else {
299                         try {
300                             formatterPw.println(String.format("${f.%s}.%s = %s", formatterClass.getSimpleName(), m.getName(), m.invoke(annotation)));
301                             // We do not expect the following exceptions be ever thrown.
302                         } catch (IllegalArgumentException e) {
303                             e.printStackTrace();
304                         } catch (IllegalAccessException e) {
305                             e.printStackTrace();
306                         } catch (InvocationTargetException e) {
307                             e.printStackTrace();
308                         }
309                     }
310                 }
311             }
312             
313             
314             /*
315              * ERROR MESSAGES
316              */
317             ByteArrayOutputStream errorBao = new ByteArrayOutputStream();
318             PrintWriter errorPw = new PrintWriter(errorBao);
319             errorPw.println("#");
320             errorPw.println("# Internal error messages");
321             errorPw.println("#");
322             errorPw.println();
323             errorPw.println(MandatoryMapValueNotFoundException.class.getName() + ".DEFAULT_PATTERN = " + MandatoryMapValueNotFoundException.DEFAULT_PATTERN);
324             errorPw.println(MandatorySimpleArgumentNotFoundException.class.getName() + ".DEFAULT_PATTERN = " + MandatorySimpleArgumentNotFoundException.DEFAULT_PATTERN);
325             errorPw.println(MandatoryValueNotFoundException.class.getName() + ".DEFAULT_PATTERN = " + MandatoryValueNotFoundException.DEFAULT_PATTERN);
326             errorPw.println(RegexMismatchException.class.getName() + ".DEFAULT_PATTERN = " + RegexMismatchException.DEFAULT_PATTERN);
327             errorPw.println(StringParseException.class.getName() + ".DEFAULT_PATTERN = " + StringParseException.DEFAULT_PATTERN);
328             errorPw.println(StringParsingErrorException.class.getName() + ".DEFAULT_PATTERN = " + StringParsingErrorException.DEFAULT_PATTERN);
329             
330             /*
331              * FLUSHING IT ALL
332              */
333             aliasesPw.flush();
334             aliasesBao.flush();
335             aoPw.flush();
336             aoBao.flush();
337             processorsPw.flush();
338             processorsBao.flush();
339             optionsPw.flush();
340             optionsBao.flush();
341             simpleArgumentsPw.flush();
342             simpleArgumentsBao.flush();
343             statusPw.flush();
344             statusBao.flush();
345             formatterPw.flush();
346             formatterBao.flush();
347             errorPw.flush();
348             errorBao.flush();
349 
350             /*
351              * WRITING TO OUTPUT
352              */
353             ArrayList<OutputStream> outputStreams = new ArrayList<OutputStream>();
354             if(args.f()) {
355                 String fileNameStem;
356                 String fileNameExtension;
357                 if(args.fileName().contains(".")) {
358                     fileNameStem = args.fileName().substring(0, args.fileName().lastIndexOf('.'));
359                     if(args.fileName().endsWith(".")) {
360                         fileNameExtension = "properties";
361                     }
362                     else {
363                         fileNameExtension = args.fileName().substring(args.fileName().lastIndexOf('.') + 1);
364                     }
365                 } else {
366                     fileNameStem = args.fileName();
367                     fileNameExtension = "properties";
368                     outputStreams.add(new FileOutputStream(new File(String.format("%s.%s", fileNameStem, fileNameExtension))));
369                 }
370                 
371                 for(String variant : args.supportedLanguages()) {
372                     outputStreams.add(new FileOutputStream(new File(String.format("%s_%s.%s", fileNameStem, variant, fileNameExtension))));
373                 }
374             } else {
375                 outputStreams.add(System.out);
376             }
377             
378             for(OutputStream os : outputStreams) {
379                 BufferedOutputStream bos = new BufferedOutputStream(os, 1024);
380                 PrintWriter pw = new PrintWriter(bos);
381                 Date now = new Date();
382                 pw.println("# ");
383                 pw.println(String.format("# Configuration file for the %s options interface.", args.optionsClass().getName()));
384                 pw.println(String.format("# Automatically generated using %s on %s, %s.", RBGenerator.class.getName(), DateFormat.getDateInstance(DateFormat.FULL).format(now), DateFormat.getTimeInstance(DateFormat.FULL).format(now)));
385                 pw.println("# ");
386                 pw.println();
387                 pw.println(aliasesBao.toString());
388                 pw.println();
389                 pw.println(aoBao.toString());
390                 pw.println();
391                 pw.println(processorsBao.toString());
392                 pw.println();
393                 pw.println(optionsBao.toString());
394                 pw.println();
395                 pw.println(simpleArgumentsBao.toString());
396                 pw.println();
397                 pw.println(statusBao.toString());
398                 pw.println();
399                 pw.println(formatterBao.toString());
400                 pw.println();
401                 pw.println(errorBao.toString());
402                 pw.flush();
403                 bos.flush();
404                 os.flush();
405                 os.close();
406             }
407             
408         } catch (InvalidOptionsInterfaceException e) {
409             // TODO Auto-generated catch block
410             e.printStackTrace();
411         } catch (FileNotFoundException e) {
412             // TODO Auto-generated catch block
413             e.printStackTrace();
414         } catch (IOException e) {
415             // TODO Auto-generated catch block
416             e.printStackTrace();
417         }
418     }
419     
420     public static String expandAnnotationArray(String prefix, Annotation[] array, Class<?> arrayClass) {
421         String result = "";
422         try {
423             int i = 0;
424             for (Annotation annotation : array) {
425 
426                 for (Method m : arrayClass.getDeclaredMethods()) {
427                     if (m.getReturnType().isArray() && m.getReturnType().getComponentType().isAnnotation()) {
428                         result += expandAnnotationArray(String.format("%s[%d].%s", prefix, i, m.getName()), (Annotation[]) m.invoke(annotation), m.getReturnType().getComponentType());
429                     } else {
430                         result += String.format("%s[%d].%s = %s\n", prefix, i, m.getName(), m.invoke(annotation));
431                     }
432                 }
433                 i++;
434             }
435         } catch (IllegalArgumentException e) {
436             e.printStackTrace();
437         } catch (IllegalAccessException e) {
438             e.printStackTrace();
439         } catch (InvocationTargetException e) {
440             e.printStackTrace();
441         }
442         return result;
443     }
444 }