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}