| /* | |
| * 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")); | |
| // } | |
| } |