View Javadoc

1   /*
2    *  Copyright 2001-2013 Stephen Colebourne
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  package org.joda.beans.gen;
17  
18  import java.io.BufferedReader;
19  import java.io.BufferedWriter;
20  import java.io.File;
21  import java.io.FileInputStream;
22  import java.io.FileOutputStream;
23  import java.io.InputStreamReader;
24  import java.io.OutputStreamWriter;
25  import java.io.PrintWriter;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.List;
29  
30  import org.joda.beans.JodaBeanUtils;
31  
32  /**
33   * Code generator for the beans.
34   * <p>
35   * This reads in a {@code .java} file, parses it, and writes out an updated version.
36   * 
37   * @author Stephen Colebourne
38   */
39  public class BeanCodeGen {
40  
41      /**
42       * Main method.
43       * <p>
44       * This calls {@code System.exit}.
45       * 
46       * @param args  the arguments, not null
47       */
48      public static void main(String[] args) {
49          BeanCodeGen gen = null;
50          try {
51              gen = createFromArgs(args);
52          } catch (RuntimeException ex) {
53              System.out.println("Code generator");
54              System.out.println("  Usage java org.joda.beans.gen.BeanCodeGen [file]");
55              System.out.println("  Options");
56              System.out.println("    -R                process all files recursively, default false");
57              System.out.println("    -indent=tab       use a tab for indenting, default 4 spaces");
58              System.out.println("    -indent=[n]       use n spaces for indenting, default 4");
59              System.out.println("    -prefix=[p]       field prefix of p should be removed, no default");
60              System.out.println("    -verbose=[v]      output logging with verbosity from 0 to 3, default 1");
61              System.out.println("    -nowrite          output messages rather than writing, default is to write");
62              System.exit(0);
63          }
64          try {
65              int changed = gen.process();
66              System.out.println("Finished, found " + changed + " changed files");
67              System.exit(0);
68          } catch (Exception ex) {
69              System.out.println();
70              ex.printStackTrace(System.out);
71              System.exit(1);
72          }
73      }
74  
75      /**
76       * Creates an instance of {@code BeanCodeGen} from arguments.
77       * <p>
78       * This is intended for tools and does not call {@code System.exit}.
79       * 
80       * @param args  the arguments, not null
81       * @return the code generator, not null
82       * @throws RuntimeException if unable to create
83       */
84      public static BeanCodeGen createFromArgs(String[] args) {
85          if (args == null) {
86              throw new IllegalArgumentException("Arguments must not be null");
87          }
88          String indent = "    ";
89          String prefix = "";
90          boolean recurse = false;
91          int verbosity = 1;
92          boolean write = true;
93          File file = null;
94          if (args.length == 0) {
95              throw new IllegalArgumentException("No arguments specified");
96          }
97          for (int i = 0; i < args.length - 1; i++) {
98              String arg = args[i];
99              if (arg == null) {
100                 throw new IllegalArgumentException("Argument must not be null: " + Arrays.toString(args));
101             }
102             if (arg.startsWith("-indent=tab")) {
103                 indent = "\t";
104             } else if (arg.startsWith("-indent=")) {
105                 indent = "          ".substring(0, Integer.parseInt(arg.substring(8)));
106             } else if (arg.startsWith("-prefix=")) {
107                 prefix = arg.substring(8);
108             } else if (arg.equals("-R")) {
109                 recurse = true;
110             } else if (arg.startsWith("-verbose=")) {
111                 verbosity = Integer.parseInt(arg.substring(3));
112             } else if (arg.startsWith("-v=")) {
113                 System.out.println("Deprecated command line argument -v (use -verbose instead)");
114                 verbosity = Integer.parseInt(arg.substring(3));
115             } else if (arg.equals("-nowrite")) {
116                 write = false;
117             } else {
118                 throw new IllegalArgumentException("Unknown argument: " + arg);
119             }
120         }
121         file = new File(args[args.length - 1]);
122         
123         List<File> files = findFiles(file, recurse);
124         return new BeanCodeGen(files, indent, prefix, verbosity, write);
125     }
126 
127     /**
128      * Finds the set of files to process.
129      * 
130      * @param parent  the root, not null
131      * @param recurse  whether to recurse
132      * @return the files, not null
133      */
134     private static List<File> findFiles(final File parent, boolean recurse) {
135         final List<File> result = new ArrayList<File>();
136         if (parent.isDirectory()) {
137             File[] files = parent.listFiles();
138             for (File child : files) {
139                 if (child.isFile() && child.getName().endsWith(".java")) {
140                     result.add(child);
141                 }
142             }
143             if (recurse) {
144                 for (File child : files) {
145                     if (child.isDirectory() && child.getName().startsWith(".") == false) {
146                         result.addAll(findFiles(child, recurse));
147                     }
148                 }
149             }
150         } else {
151             if (parent.getName().endsWith(".java")) {
152                 result.add(parent);
153             }
154         }
155         return result;
156     }
157 
158     //-----------------------------------------------------------------------
159     /** The files to process. */
160     private final List<File> files;
161     /** The indent to use. */
162     private final String indent;
163     /** The prefix to use. */
164     private final String prefix;
165     /** The verbosity level. */
166     private final int verbosity;
167     /** Whether to write or not. */
168     private final boolean write;
169 
170     /**
171      * Creates the generator for a single bean.
172      * <p>
173      * To generate, use {@link #process()}.
174      * 
175      * @param files  the files to process, not null
176      * @param indent  the indent to use, which will be directly inserted in the output, not null
177      * @param prefix  the prefix to use, which will be directly inserted in the output, not null
178      * @param verbosity  the verbosity, from 0 to 3
179      * @param write  whether to write or not
180      */
181     public BeanCodeGen(List<File> files, String indent, String prefix, int verbosity, boolean write) {
182         JodaBeanUtils.notNull(files, "files");
183         JodaBeanUtils.notNull(indent, "indent");
184         JodaBeanUtils.notNull(prefix, "prefix");
185         if (verbosity < 0 || verbosity > 3) {
186             throw new IllegalArgumentException("Invalid verbosity: " + verbosity);
187         }
188         this.files = files;
189         this.indent = indent;
190         this.prefix = prefix;
191         this.verbosity = verbosity;
192         this.write = write;
193     }
194 
195     //-----------------------------------------------------------------------
196     /**
197      * Processes the file, recursing as necessary, generating the code.
198      * 
199      * @return the number of changed files
200      */
201     public int process() throws Exception {
202         int changed = 0;
203         for (File child : files) {
204             changed += (processFile(child) ? 1 : 0);
205         }
206         return changed;
207     }
208 
209     /**
210      * Processes the bean, generating the code.
211      * 
212      * @param file  the file to process, not null
213      * @return true if changed
214      */
215     private boolean processFile(File file) throws Exception {
216         List<String> original = readFile(file);
217         List<String> content = new ArrayList<String>(original);
218         BeanGen gen;
219         try {
220             gen = new BeanGen(content, indent, prefix);
221         } catch (Exception ex) {
222             throw new RuntimeException("Error in bean: " + file, ex);
223         }
224         if (gen.isBean()) {
225             if (verbosity >= 2) {
226                 System.out.print(file + "  [processing]");
227             }
228             gen.process();
229             if (content.equals(original) == false) {
230                 if (write) {
231                     if (verbosity >= 2) {
232                         System.out.println(" [writing]");
233                     } else if (verbosity == 1) {
234                         System.out.println(file + "  [writing]");
235                     }
236                     writeFile(file, content);
237                 } else {
238                     if (verbosity >= 2) {
239                         System.out.println(" [changed not written]");
240                     } else if (verbosity == 1) {
241                         System.out.println(file + "  [changed not written]");
242                     }
243                 }
244                 return true;
245             } else {
246                 if (verbosity >= 2) {
247                     System.out.println(" [no change]");
248                 }
249             }
250         } else {
251             if (verbosity == 3) {
252                 System.out.println(file + "  [ignored]");
253             }
254         }
255         return false;
256     }
257 
258     //-----------------------------------------------------------------------
259     private List<String> readFile(File file) throws Exception {
260         List<String> content = new ArrayList<String>(100);
261         BufferedReader is = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
262         try {
263             String line;
264             while ((line = is.readLine()) != null) {
265                 content.add(line);
266             }
267             return content;
268         } finally {
269             is.close();
270         }
271     }
272 
273     private void writeFile(File file, List<String> content) throws Exception {
274         PrintWriter os = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8")));
275         try {
276             for (String line : content) {
277                 os.println(line);
278             }
279         } finally {
280             os.close();
281         }
282     }
283 
284 }