org.joda.beans.gen.BeanCodeGen Maven / Gradle / Ivy
/*
* Copyright 2001-present Stephen Colebourne
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.joda.beans.gen;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import org.joda.beans.JodaBeanUtils;
/**
* Code generator for the beans.
*
* This reads in a {@code .java} file, parses it, and writes out an updated version.
*/
public class BeanCodeGen {
/**
* Main method.
*
* This calls {@code System.exit}.
*
* @param args the arguments, not null
*/
public static void main(String[] args) {
BeanCodeGen gen;
try {
gen = createFromArgs(args);
} catch (RuntimeException ex) {
System.out.println(ex.getMessage());
System.out.println("");
System.out.println("Code generator");
System.out.println(" Usage java org.joda.beans.gen.BeanCodeGen [file]");
System.out.println(" Options");
System.out.println(" -R process all files recursively, default false");
System.out.println(" -indent=tab use a tab for indenting, default 4 spaces");
System.out.println(" -indent=[n] use n spaces for indenting, default 4");
System.out.println(" -prefix=[p] field prefix of p should be removed, no default");
System.out.println(" -eol=[e] end of line: 'lf'/'crlf'/'cr', default System.lineSeparator");
System.out.println(" -generated add @Generated annotation to generated code");
System.out.println(" -config=[f] config file: 'jdk'/'guava', default guava");
System.out.println(" -style=[s] default bean style: 'light'/'minimal'/'full', default smart");
System.out.println(" -verbose=[v] output logging with verbosity from 0 to 3, default 1");
System.out.println(" -nowrite output messages rather than writing, default is to write");
System.exit(0);
throw new InternalError("Unreachable");
}
try {
int changed = gen.process();
System.out.println("Finished, found " + changed + " changed files");
System.exit(0);
} catch (Exception ex) {
System.out.println();
ex.printStackTrace(System.out);
System.exit(1);
}
}
/**
* Creates an instance of {@code BeanCodeGen} from arguments.
*
* This is intended for tools and does not call {@code System.exit}.
*
* @param args the arguments, not null
* @return the code generator, not null
* @throws RuntimeException if unable to create
*/
public static BeanCodeGen createFromArgs(String[] args) {
if (args == null) {
throw new IllegalArgumentException("Arguments must not be null");
}
String indent = " ";
String prefix = "";
String eol = System.lineSeparator();
String defaultStyle = null;
boolean recurse = false;
boolean generatedAnno = false;
int verbosity = 1;
boolean write = true;
File file = null;
BeanGenConfig config = null;
if (args.length == 0) {
throw new IllegalArgumentException("No arguments specified");
}
for (int i = 0; i < args.length - 1; i++) {
String arg = args[i];
if (arg == null) {
throw new IllegalArgumentException("Argument must not be null: " + Arrays.toString(args));
}
if (arg.startsWith("-indent=tab")) {
indent = "\t";
} else if (arg.startsWith("-indent=")) {
indent = " ".substring(0, Integer.parseInt(arg.substring(8)));
} else if (arg.startsWith("-prefix=")) {
prefix = arg.substring(8);
} else if (arg.startsWith("-eol=")) {
switch (arg.substring(5)) {
case "lf":
eol = "\n";
break;
case "crlf":
eol = "\r\n";
break;
case "cr":
eol = "\r";
break;
case "system":
eol = System.lineSeparator();
break;
default:
throw new IllegalArgumentException("Value of 'eol' must be one of: 'lf', 'crlf', 'cr', 'system'");
}
} else if (arg.equals("-R")) {
recurse = true;
} else if (arg.equals("-generated")) {
if (generatedAnno) {
throw new IllegalArgumentException("Argument 'generated' must not be specified twice: " + Arrays.toString(args));
}
generatedAnno = true;
} else if (arg.startsWith("-config=")) {
if (config != null) {
throw new IllegalArgumentException("Argument 'config' must not be specified twice: " + Arrays.toString(args));
}
config = BeanGenConfig.parse(arg.substring(8));
} else if (arg.startsWith("-style=")) {
if (defaultStyle != null) {
throw new IllegalArgumentException("Argument 'style' must not be specified twice: " + Arrays.toString(args));
}
defaultStyle = arg.substring(7);
} else if (arg.startsWith("-verbose=")) {
verbosity = Integer.parseInt(arg.substring(9));
} else if (arg.startsWith("-v=")) {
System.out.println("Deprecated command line argument -v (use -verbose instead)");
verbosity = Integer.parseInt(arg.substring(3));
} else if (arg.equals("-nowrite")) {
write = false;
} else {
throw new IllegalArgumentException("Unknown argument: " + arg);
}
}
file = new File(args[args.length - 1]);
List files = findFiles(file, recurse);
if (config == null) {
config = BeanGenConfig.parse("guava");
}
config.setIndent(indent);
config.setPrefix(prefix);
config.setEol(eol);
if (defaultStyle != null) {
config.setDefaultStyle(defaultStyle);
}
config.setGeneratedAnno(generatedAnno);
return new BeanCodeGen(files, config, verbosity, write);
}
/**
* Finds the set of files to process.
*
* @param parent the root, not null
* @param recurse whether to recurse
* @return the files, not null
*/
private static List findFiles(final File parent, boolean recurse) {
final List result = new ArrayList<>();
if (parent.isDirectory()) {
File[] files = parent.listFiles();
files = (files != null ? files : new File[0]);
for (File child : files) {
if (child.isFile() && child.getName().endsWith(".java")) {
result.add(child);
}
}
if (recurse) {
for (File child : files) {
if (child.isDirectory() && child.getName().startsWith(".") == false) {
result.addAll(findFiles(child, recurse));
}
}
}
} else {
if (parent.getName().endsWith(".java")) {
result.add(parent);
}
}
return result;
}
//-----------------------------------------------------------------------
/** The files to process. */
private final List files;
/** The configuration to use. */
private final BeanGenConfig config;
/** The verbosity level. */
private final int verbosity;
/** Whether to write or not. */
private final boolean write;
/**
* Creates the generator for a single bean.
*
* To generate, use {@link #process()}.
*
* @param files the files to process, not null
* @param config the configuration to use, not null
* @param verbosity the verbosity, from 0 to 3
* @param write whether to write or not
*/
public BeanCodeGen(List files, BeanGenConfig config, int verbosity, boolean write) {
JodaBeanUtils.notNull(files, "files");
JodaBeanUtils.notNull(config, "config");
if (verbosity < 0 || verbosity > 3) {
throw new IllegalArgumentException("Invalid verbosity: " + verbosity);
}
this.files = files;
this.config = config;
this.verbosity = verbosity;
this.write = write;
}
//-----------------------------------------------------------------------
/**
* Processes the file, recursing as necessary, generating the source code.
*
* The number of altered files is returned.
*
* @return the number of changed files
* @throws Exception if an error occurs
*/
public int process() throws Exception {
int changed = 0;
for (File child : files) {
changed += (processFile(child) != null ? 1 : 0);
}
return changed;
}
/**
* Processes the file, recursing as necessary, generating the source code.
*
* The list of altered files is returned.
*
* @return the list of changed files, not null
* @throws Exception if an error occurs
*/
public List processFiles() throws Exception {
List changed = new ArrayList<>();
for (File child : files) {
File file = processFile(child);
if (file != null) {
changed.add(file);
}
}
return changed;
}
/**
* Processes the bean, generating the code.
*
* @param file the file to process, not null
* @return not-null if changed
* @throws Exception if an error occurs
*/
private File processFile(File file) throws Exception {
List original = readFile(file);
List content = new ArrayList<>(original);
BeanGen gen;
try {
BeanParser parser = new BeanParser(file, content, config);
gen = parser.parse();
} catch (BeanCodeGenException ex) {
throw ex;
} catch (Exception ex) {
throw new BeanCodeGenException(ex.getMessage(), ex, file);
}
if (gen.isBean()) {
if (verbosity >= 2) {
System.out.print(file + " [processing]");
}
gen.process();
if (contentDiffers(content, original)) {
if (write) {
if (verbosity >= 2) {
System.out.println(" [writing]");
} else if (verbosity == 1) {
System.out.println(file + " [writing]");
}
writeFile(file, content);
} else {
if (verbosity >= 2) {
System.out.println(" [changed not written]");
} else if (verbosity == 1) {
System.out.println(file + " [changed not written]");
}
}
return file;
} else {
if (verbosity >= 2) {
System.out.println(" [no change]");
}
}
} else {
gen.processNonBean();
if (contentDiffers(content, original)) {
if (write) {
if (verbosity >= 2) {
System.out.println(" [writing]");
} else if (verbosity == 1) {
System.out.println(file + " [writing]");
}
writeFile(file, content);
} else {
if (verbosity >= 2) {
System.out.println(" [changed not written]");
} else if (verbosity == 1) {
System.out.println(file + " [changed not written]");
}
}
return file;
} else {
if (verbosity == 3) {
System.out.println(file + " [ignored]");
}
}
}
return null;
}
// checks to see if the content differs from the original
// if the files differ only by @Override lines then they are considered to be equal
private boolean contentDiffers(List content, List original) {
Pattern overridePattern = Pattern.compile(" *[@]Override");
int contentIndex = 0;
int originalIndex = 0;
while (contentIndex < content.size() && originalIndex < original.size()) {
String contentLine = content.get(contentIndex);
String originalLine = original.get(originalIndex);
if (contentLine.equals(originalLine)) {
// lines match
contentIndex++;
originalIndex++;
} else if (overridePattern.matcher(originalLine).matches()) {
// original is an @Override line
originalIndex++;
} else {
return true;
}
}
if (contentIndex < content.size() || originalIndex < original.size()) {
return true;
}
return false;
}
//-----------------------------------------------------------------------
private List readFile(File file) throws Exception {
List content = new ArrayList<>(100);
try (BufferedReader is = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"))) {
String line;
while ((line = is.readLine()) != null) {
content.add(line);
}
return content;
}
}
private void writeFile(File file, List content) throws Exception {
try (BufferedWriter os = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"))) {
for (String line : content) {
os.write(line);
os.write(config.getEol());
}
}
}
}