e10778acce
Co-authored-by: Michael Ernst <mernst@openjdk.org> Reviewed-by: prappo, jjg, iris
665 lines
21 KiB
Java
665 lines
21 KiB
Java
/*
|
|
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
package builder;
|
|
|
|
import toolbox.ToolBox;
|
|
|
|
import java.io.IOException;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.nio.file.Paths;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.ListIterator;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Stream;
|
|
|
|
/**
|
|
* Builder for type declarations.
|
|
* Note: this implementation does not support everything and is not
|
|
* exhaustive.
|
|
*/
|
|
public class ClassBuilder extends AbstractBuilder {
|
|
|
|
private final ToolBox tb;
|
|
private final String fqn;
|
|
private final String clsname;
|
|
private final String typeParameter;
|
|
|
|
private String pkg;
|
|
private final List<String> imports;
|
|
|
|
private String extendsType;
|
|
private final List<String> implementsTypes;
|
|
private final List<MemberBuilder> members;
|
|
private final List<ClassBuilder> inners;
|
|
private final List<ClassBuilder> nested;
|
|
|
|
|
|
final static Pattern CLASS_RE = Pattern.compile("(.*)(<.*>)");
|
|
|
|
/**
|
|
* Creates a class builder.
|
|
* @param tb the toolbox reference
|
|
* @param name the name of the type
|
|
*/
|
|
public ClassBuilder(ToolBox tb, String name) {
|
|
super(new Modifiers(), name);
|
|
this.tb = tb;
|
|
|
|
Matcher m = CLASS_RE.matcher(name);
|
|
if (m.matches()) {
|
|
fqn = m.group(1);
|
|
typeParameter = m.group(2);
|
|
} else {
|
|
fqn = name;
|
|
typeParameter = null;
|
|
}
|
|
if (fqn.contains(".")) {
|
|
this.pkg = name.substring(0, fqn.lastIndexOf('.'));
|
|
clsname = fqn.substring(fqn.lastIndexOf('.') + 1);
|
|
} else {
|
|
clsname = fqn;
|
|
}
|
|
imports = new ArrayList<>();
|
|
implementsTypes = new ArrayList<>();
|
|
members = new ArrayList<>();
|
|
nested = new ArrayList<>();
|
|
inners = new ArrayList<>();
|
|
}
|
|
|
|
/**
|
|
* Adds an import(s).
|
|
* @param i the import type.
|
|
* @return this builder.
|
|
*/
|
|
public ClassBuilder addImports(String i) {
|
|
imports.add(i);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the modifiers for this builder.
|
|
* @param modifiers the modifiers
|
|
* @return this builder
|
|
*/
|
|
public ClassBuilder setModifiers(String... modifiers) {
|
|
this.modifiers.setModifiers(modifiers);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the enclosing type's name.
|
|
*
|
|
* @param className the enclosing type's name
|
|
*/
|
|
@Override
|
|
void setClassName(String className) {
|
|
cname = className;
|
|
}
|
|
|
|
/**
|
|
* Sets a comment for the element.
|
|
*
|
|
* @param comments for the element
|
|
* @return this builder.
|
|
*/
|
|
@Override
|
|
public ClassBuilder setComments(String... comments) {
|
|
super.setComments(comments);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets a comment for the element. Typically used to set
|
|
* the user's preferences whether an automatic comment is
|
|
* required or no API comment.
|
|
*
|
|
* @param kind of comment, automatic or no comment.
|
|
* @return this builder.
|
|
*/
|
|
@Override
|
|
public ClassBuilder setComments(Comment.Kind kind) {
|
|
super.setComments(kind);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set the super-type of the type.
|
|
* @param name of the super type.
|
|
* @return this builder.
|
|
*/
|
|
public ClassBuilder setExtends(String name) {
|
|
extendsType = name;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Adds an implements declaration(s).
|
|
* @param names the interfaces
|
|
* @return this builder.
|
|
*/
|
|
public ClassBuilder addImplements(String... names) {
|
|
implementsTypes.addAll(List.of(names));
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Adds a member(s) to the class declaration.
|
|
* @param mbs the member builder(s) representing member(s).
|
|
* @return this builder
|
|
*/
|
|
public ClassBuilder addMembers(MemberBuilder... mbs) {
|
|
for (MemberBuilder mb : mbs) {
|
|
members.add(mb);
|
|
mb.setClassName(fqn);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Adds nested-classes, to an outer class to an outer builder.
|
|
* @param cbs class builder(s) of the nested classes.
|
|
* @return this builder.
|
|
*/
|
|
public ClassBuilder addNestedClasses(ClassBuilder... cbs) {
|
|
Stream.of(cbs).forEach(cb -> {
|
|
nested.add(cb);
|
|
cb.setClassName(fqn);
|
|
});
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Adds inner-classes, to an outer class builder.
|
|
* @param cbs class builder(s) of the inner classes.
|
|
* @return this builder.
|
|
*/
|
|
public ClassBuilder addInnerClasses(ClassBuilder... cbs) {
|
|
Stream.of(cbs).forEach(cb -> {
|
|
inners.add(cb);
|
|
cb.setClassName(fqn);
|
|
});
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
OutputWriter ow = new OutputWriter();
|
|
if (pkg != null)
|
|
ow.println("package " + pkg + ";");
|
|
imports.forEach(i -> ow.println("import " + i + ";"));
|
|
switch (comments.kind) {
|
|
case AUTO:
|
|
ow.println("/** Class " + fqn + " */");
|
|
break;
|
|
case USER:
|
|
ow.println("/** ");
|
|
comments.comments.forEach(c -> ow.println(" * " + c));
|
|
ow.println(" */");
|
|
break;
|
|
case NO_API_COMMENT:
|
|
ow.println("// NO_API_COMMENT");
|
|
break;
|
|
}
|
|
ow.print(modifiers.toString());
|
|
ow.print(clsname);
|
|
if (typeParameter != null) {
|
|
ow.print(typeParameter + " ");
|
|
} else {
|
|
ow.print(" ");
|
|
}
|
|
if (extendsType != null && !extendsType.isEmpty()) {
|
|
ow.print("extends " + extendsType + " ");
|
|
}
|
|
if (!implementsTypes.isEmpty()) {
|
|
ow.print("implements ");
|
|
|
|
ListIterator<String> iter = implementsTypes.listIterator();
|
|
while (iter.hasNext()) {
|
|
String s = iter.next() ;
|
|
ow.print(s);
|
|
if (iter.hasNext())
|
|
ow.print(", ");
|
|
}
|
|
}
|
|
ow.print("{");
|
|
if (!nested.isEmpty()) {
|
|
ow.println("");
|
|
nested.forEach(m -> ow.println(m.toString()));
|
|
}
|
|
|
|
if (!members.isEmpty()) {
|
|
ow.println("");
|
|
members.forEach(m -> ow.println(m.toString()));
|
|
}
|
|
|
|
ow.println("}");
|
|
if (!inners.isEmpty()) {
|
|
ow.println(" {");
|
|
inners.forEach(m -> ow.println(m.toString()));
|
|
ow.println("}");
|
|
}
|
|
return ow.toString();
|
|
}
|
|
|
|
/**
|
|
* Writes out the java source for a type element. Package directories
|
|
* will be created as needed as inferred by the type name.
|
|
* @param srcDir the top level source directory.
|
|
* @throws IOException if an error occurs.
|
|
*/
|
|
public void write(Path srcDir) throws IOException {
|
|
Files.createDirectories(srcDir);
|
|
Path outDir = srcDir;
|
|
if (pkg != null && !pkg.isEmpty()) {
|
|
String pdir = pkg.replace(".", "/");
|
|
outDir = Paths.get(srcDir.toString(), pdir);
|
|
Files.createDirectories(outDir);
|
|
}
|
|
Path filePath = Paths.get(outDir.toString(), clsname + ".java");
|
|
tb.writeFile(filePath, this.toString());
|
|
}
|
|
|
|
/**
|
|
* The member builder, this is the base class for all types of members.
|
|
*/
|
|
public static abstract class MemberBuilder extends AbstractBuilder {
|
|
public MemberBuilder(Modifiers modifiers, String name) {
|
|
super(modifiers, name);
|
|
}
|
|
|
|
/**
|
|
* Sets the enclosing type's name.
|
|
* @param className the enclosing type's name
|
|
*/
|
|
@Override
|
|
void setClassName(String className) {
|
|
cname = className;
|
|
}
|
|
|
|
/**
|
|
* Sets a comment for the element.
|
|
*
|
|
* @param comments for any element
|
|
* @return this builder.
|
|
*/
|
|
@Override
|
|
public MemberBuilder setComments(String... comments) {
|
|
super.setComments(comments);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets a comment for the element. Typically used to set user's
|
|
* preferences whether an automatic comment is required or no API
|
|
* comment.
|
|
*
|
|
* @param kind of comment, automatic or no comment.
|
|
* @return this builder.
|
|
*/
|
|
@Override
|
|
public MemberBuilder setComments(Comment.Kind kind) {
|
|
super.setComments(kind);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets a new modifier.
|
|
*
|
|
* @param modifiers
|
|
* @return this builder.
|
|
*/
|
|
@Override
|
|
public MemberBuilder setModifiers(String... modifiers) {
|
|
super.setModifiers(modifiers);
|
|
return this;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The field builder.
|
|
*/
|
|
public static class FieldBuilder extends MemberBuilder {
|
|
private String fieldType;
|
|
private String value;
|
|
|
|
private static final Pattern FIELD_RE = Pattern.compile("(.*)(\\s*=\\s*)(.*)(;)");
|
|
|
|
/**
|
|
* Constructs a field with the modifiers and name of the field.
|
|
* The builder by default is configured to auto generate the
|
|
* comments for the field.
|
|
* @param name of the field
|
|
*/
|
|
public FieldBuilder(String name) {
|
|
super(new Modifiers(), name);
|
|
this.comments = new Comment(Comment.Kind.AUTO);
|
|
}
|
|
|
|
/**
|
|
* Returns a field builder by parsing the string.
|
|
* ex: public static String myPlayingField;
|
|
* @param fieldString describing the field.
|
|
* @return a field builder.
|
|
*/
|
|
public static FieldBuilder parse(String fieldString) {
|
|
String prefix;
|
|
String value = null;
|
|
Matcher m = FIELD_RE.matcher(fieldString);
|
|
if (m.matches()) {
|
|
prefix = m.group(1).trim();
|
|
value = m.group(3).trim();
|
|
} else {
|
|
int end = fieldString.lastIndexOf(';') > 0
|
|
? fieldString.lastIndexOf(';')
|
|
: fieldString.length();
|
|
prefix = fieldString.substring(0, end).trim();
|
|
}
|
|
List<String> list = Stream.of(prefix.split(" "))
|
|
.filter(s -> !s.isEmpty()).collect(Collectors.toList());
|
|
if (list.size() < 2) {
|
|
throw new IllegalArgumentException("incorrect field string: "
|
|
+ fieldString);
|
|
}
|
|
String name = list.get(list.size() - 1);
|
|
String fieldType = list.get(list.size() - 2);
|
|
|
|
FieldBuilder fb = new FieldBuilder(name);
|
|
fb.modifiers.setModifiers(list.subList(0, list.size() - 2));
|
|
fb.setFieldType(fieldType);
|
|
if (value != null)
|
|
fb.setValue(value);
|
|
|
|
return fb;
|
|
}
|
|
|
|
/**
|
|
* Sets the modifiers for this builder.
|
|
*
|
|
* @param mods
|
|
* @return this builder
|
|
*/
|
|
public FieldBuilder setModifiers(String mods) {
|
|
this.modifiers.setModifiers(mods);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the type of the field.
|
|
* @param fieldType the name of the type.
|
|
* @return this field builder.
|
|
*/
|
|
public FieldBuilder setFieldType(String fieldType) {
|
|
this.fieldType = fieldType;
|
|
return this;
|
|
}
|
|
|
|
public FieldBuilder setValue(String value) {
|
|
this.value = value;
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
String indent = " ";
|
|
OutputWriter ow = new OutputWriter();
|
|
switch (comments.kind) {
|
|
case AUTO:
|
|
ow.println(indent + "/** Field " +
|
|
super.name + " in " + super.cname + " */");
|
|
break;
|
|
case INHERIT_DOC: case USER:
|
|
ow.println(indent + "/** " +
|
|
comments.toString() + " */");
|
|
break;
|
|
case NO_API_COMMENT:
|
|
ow.println(indent + "// NO_API_COMMENT");
|
|
break;
|
|
}
|
|
ow.print(indent + super.modifiers.toString() + " ");
|
|
ow.print(fieldType + " ");
|
|
ow.print(super.name);
|
|
if (value != null) {
|
|
ow.print(" = " + value);
|
|
}
|
|
ow.println(";");
|
|
return ow.toString();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The method builder.
|
|
*/
|
|
public static class MethodBuilder extends MemberBuilder {
|
|
|
|
private final List<Pair> params;
|
|
|
|
private String returnType;
|
|
private List<String> body;
|
|
|
|
final static Pattern METHOD_RE = Pattern.compile("(.*)(\\()(.*)(\\))(.*)");
|
|
|
|
/**
|
|
* Constructs a method builder. The builder by default is configured
|
|
* to auto generate the comments for this method.
|
|
* @param name of the method.
|
|
*/
|
|
public MethodBuilder(String name) {
|
|
super(new Modifiers(), name);
|
|
comments = new Comment(Comment.Kind.AUTO);
|
|
params = new ArrayList<>();
|
|
body = null;
|
|
}
|
|
|
|
/**
|
|
* Returns a method builder by parsing a string which
|
|
* describes a method.
|
|
* @param methodString the method description.
|
|
* @return a method builder.
|
|
*/
|
|
public static MethodBuilder parse(String methodString) {
|
|
Matcher m = METHOD_RE.matcher(methodString);
|
|
if (!m.matches())
|
|
throw new IllegalArgumentException("string does not match: "
|
|
+ methodString);
|
|
String prefix = m.group(1);
|
|
String params = m.group(3);
|
|
String suffix = m.group(5).trim();
|
|
|
|
if (prefix.length() < 2) {
|
|
throw new IllegalArgumentException("incorrect method string: "
|
|
+ methodString);
|
|
}
|
|
|
|
String[] pa = prefix.split(" ");
|
|
List<String> list = List.of(pa);
|
|
String name = list.get(list.size() - 1);
|
|
String returnType = list.get(list.size() - 2);
|
|
|
|
MethodBuilder mb = new MethodBuilder(name);
|
|
mb.modifiers.setModifiers(list.subList(0, list.size() - 2));
|
|
mb.setReturn(returnType);
|
|
|
|
pa = params.split(",");
|
|
Stream.of(pa).forEach(p -> {
|
|
p = p.trim();
|
|
if (!p.isEmpty())
|
|
mb.addParameter(p);
|
|
});
|
|
if (!suffix.isEmpty() || suffix.length() > 1) {
|
|
mb.setBody(suffix);
|
|
}
|
|
return mb;
|
|
}
|
|
|
|
/**
|
|
* Sets the modifiers for this builder.
|
|
*
|
|
* @param modifiers
|
|
* @return this builder
|
|
*/
|
|
public MethodBuilder setModifiers(String modifiers) {
|
|
this.modifiers.setModifiers(modifiers);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public MethodBuilder setComments(String... comments) {
|
|
super.setComments(comments);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public MethodBuilder setComments(Comment.Kind kind) {
|
|
super.setComments(kind);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets a return type for a method.
|
|
* @param returnType the return type.
|
|
* @return this method builder.
|
|
*/
|
|
public MethodBuilder setReturn(String returnType) {
|
|
this.returnType = returnType;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Adds a parameter(s) to the method builder.
|
|
* @param params a pair consisting of type and parameter name.
|
|
* @return this method builder.
|
|
*/
|
|
public MethodBuilder addParameters(Pair... params) {
|
|
this.params.addAll(List.of(params));
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Adds a parameter to the method builder.
|
|
* @param type the type of parameter.
|
|
* @param name the parameter name.
|
|
* @return this method builder.
|
|
*/
|
|
public MethodBuilder addParameter(String type, String name) {
|
|
this.params.add(new Pair(type, name));
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Adds a parameter to the method builder, by parsing the string.
|
|
* @param s the parameter description such as "Double voltage"
|
|
* @return this method builder.
|
|
*/
|
|
public MethodBuilder addParameter(String s) {
|
|
String[] p = s.trim().split(" ");
|
|
return addParameter(p[0], p[p.length - 1]);
|
|
}
|
|
|
|
/**
|
|
* Sets the body of the method, described by the string.
|
|
* Such as "{", "double i = v/r;", "return i;", "}"
|
|
* @param body of the methods
|
|
* @return
|
|
*/
|
|
public MethodBuilder setBody(String... body) {
|
|
if (body == null) {
|
|
this.body = null;
|
|
} else {
|
|
this.body = new ArrayList<>();
|
|
this.body.addAll(List.of(body));
|
|
}
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
OutputWriter ow = new OutputWriter();
|
|
String indent = " ";
|
|
switch (comments.kind) {
|
|
case AUTO:
|
|
ow.println(indent + "/** Method " + super.name + " in " + super.cname);
|
|
if (!params.isEmpty())
|
|
params.forEach(p -> ow.println(indent + " * @param " + p.second + " a param"));
|
|
if (returnType != null && !returnType.isEmpty() && !returnType.contains("void"))
|
|
ow.println(indent + " * @return returns something");
|
|
ow.println(indent + " */");
|
|
break;
|
|
case INHERIT_DOC: case USER:
|
|
ow.println(indent + "/** " + comments.toString() + " */");
|
|
break;
|
|
case NO_API_COMMENT:
|
|
ow.println(indent + "// NO_API_COMMENT");
|
|
break;
|
|
}
|
|
|
|
ow.print(indent + super.modifiers.toString() + " ");
|
|
ow.print(returnType + " ");
|
|
ow.print(super.name + "(");
|
|
if (!params.isEmpty()) {
|
|
ListIterator<Pair> iter = params.listIterator();
|
|
while (iter.hasNext()) {
|
|
Pair p = iter.next();
|
|
ow.print(p.first + " " + p.second);
|
|
if (iter.hasNext())
|
|
ow.print(", ");
|
|
}
|
|
}
|
|
ow.print(")");
|
|
if (body == null) {
|
|
ow.println(";");
|
|
} else {
|
|
body.forEach(ow::println);
|
|
}
|
|
return ow.toString();
|
|
}
|
|
}
|
|
|
|
//A sample, to test with an IDE.
|
|
// public static void main(String... args) throws IOException {
|
|
// ClassBuilder cb = new ClassBuilder(new ToolBox(), "foo.bar.Test<C extends A>");
|
|
// cb.addModifiers("public", "abstract", "static")
|
|
// .addImports("java.io").addImports("java.nio")
|
|
// .setComments("A comment")
|
|
// .addImplements("Serialization", "Something")
|
|
// .setExtends("java.lang.Object")
|
|
// .addMembers(
|
|
// FieldBuilder.parse("public int xxx;"),
|
|
// FieldBuilder.parse("public int yyy = 10;"),
|
|
// MethodBuilder.parse("public static void main(A a, B b, C c);")
|
|
// .setComments("CC"),
|
|
// MethodBuilder.parse("void foo(){//do something}")
|
|
//
|
|
// );
|
|
// ClassBuilder ic = new ClassBuilder(new ToolBox(), "IC");
|
|
// cb.addModifiers( "interface");
|
|
// cb.addNestedClasses(ic);
|
|
// System.out.println(cb.toString());
|
|
// cb.write(Paths.get("src-out"));
|
|
// }
|
|
}
|