001/*
002 *  Copyright 2001-2013 Stephen Colebourne
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.joda.beans.gen;
017
018import java.io.BufferedReader;
019import java.io.BufferedWriter;
020import java.io.File;
021import java.io.FileInputStream;
022import java.io.FileOutputStream;
023import java.io.InputStreamReader;
024import java.io.OutputStreamWriter;
025import java.io.PrintWriter;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.List;
029
030import org.joda.beans.JodaBeanUtils;
031
032/**
033 * Code generator for the beans.
034 * <p>
035 * This reads in a {@code .java} file, parses it, and writes out an updated version.
036 * 
037 * @author Stephen Colebourne
038 */
039public class BeanCodeGen {
040
041    /**
042     * Main method.
043     * <p>
044     * This calls {@code System.exit}.
045     * 
046     * @param args  the arguments, not null
047     */
048    public static void main(String[] args) {
049        BeanCodeGen gen = null;
050        try {
051            gen = createFromArgs(args);
052        } catch (RuntimeException ex) {
053            System.out.println("Code generator");
054            System.out.println("  Usage java org.joda.beans.gen.BeanCodeGen [file]");
055            System.out.println("  Options");
056            System.out.println("    -R                process all files recursively, default false");
057            System.out.println("    -indent=tab       use a tab for indenting, default 4 spaces");
058            System.out.println("    -indent=[n]       use n spaces for indenting, default 4");
059            System.out.println("    -prefix=[p]       field prefix of p should be removed, no default");
060            System.out.println("    -verbose=[v]      output logging with verbosity from 0 to 3, default 1");
061            System.out.println("    -nowrite          output messages rather than writing, default is to write");
062            System.exit(0);
063        }
064        try {
065            int changed = gen.process();
066            System.out.println("Finished, found " + changed + " changed files");
067            System.exit(0);
068        } catch (Exception ex) {
069            System.out.println();
070            ex.printStackTrace(System.out);
071            System.exit(1);
072        }
073    }
074
075    /**
076     * Creates an instance of {@code BeanCodeGen} from arguments.
077     * <p>
078     * This is intended for tools and does not call {@code System.exit}.
079     * 
080     * @param args  the arguments, not null
081     * @return the code generator, not null
082     * @throws RuntimeException if unable to create
083     */
084    public static BeanCodeGen createFromArgs(String[] args) {
085        if (args == null) {
086            throw new IllegalArgumentException("Arguments must not be null");
087        }
088        String indent = "    ";
089        String prefix = "";
090        boolean recurse = false;
091        int verbosity = 1;
092        boolean write = true;
093        File file = null;
094        if (args.length == 0) {
095            throw new IllegalArgumentException("No arguments specified");
096        }
097        for (int i = 0; i < args.length - 1; i++) {
098            String arg = args[i];
099            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}