Path: blob/master/test/langtools/tools/lib/builder/ClassBuilder.java
41152 views
/*1* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223package builder;2425import toolbox.ToolBox;2627import java.io.IOException;28import java.nio.file.Files;29import java.nio.file.Path;30import java.nio.file.Paths;31import java.util.ArrayList;32import java.util.List;33import java.util.ListIterator;34import java.util.regex.Matcher;35import java.util.regex.Pattern;36import java.util.stream.Collectors;37import java.util.stream.Stream;3839/**40* Builder for type declarations.41* Note: this implementation does not support everything and is not42* exhaustive.43*/44public class ClassBuilder extends AbstractBuilder {4546private final ToolBox tb;47private final String fqn;48private final String clsname;49private final String typeParameter;5051private String pkg;52private final List<String> imports;5354private String extendsType;55private final List<String> implementsTypes;56private final List<MemberBuilder> members;57private final List<ClassBuilder> inners;58private final List<ClassBuilder> nested;596061final static Pattern CLASS_RE = Pattern.compile("(.*)(<.*>)");6263/**64* Creates a class builder.65* @param tb the toolbox reference66* @param name the name of the type67*/68public ClassBuilder(ToolBox tb, String name) {69super(new Modifiers(), name);70this.tb = tb;7172Matcher m = CLASS_RE.matcher(name);73if (m.matches()) {74fqn = m.group(1);75typeParameter = m.group(2);76} else {77fqn = name;78typeParameter = null;79}80if (fqn.contains(".")) {81this.pkg = name.substring(0, fqn.lastIndexOf('.'));82clsname = fqn.substring(fqn.lastIndexOf('.') + 1);83} else {84clsname = fqn;85}86imports = new ArrayList<>();87implementsTypes = new ArrayList<>();88members = new ArrayList<>();89nested = new ArrayList<>();90inners = new ArrayList<>();91}9293/**94* Adds an import(s).95* @param i the import type.96* @return this builder.97*/98public ClassBuilder addImports(String i) {99imports.add(i);100return this;101}102103/**104* Sets the modifiers for this builder.105* @param modifiers the modifiers106* @return this builder107*/108public ClassBuilder setModifiers(String... modifiers) {109this.modifiers.setModifiers(modifiers);110return this;111}112113/**114* Sets the enclosing type's name.115*116* @param className the enclosing type's name117*/118@Override119void setClassName(String className) {120cname = className;121}122123/**124* Sets a comment for the element.125*126* @param comments for the element127* @return this builder.128*/129@Override130public ClassBuilder setComments(String... comments) {131super.setComments(comments);132return this;133}134135/**136* Sets a comment for the element. Typically used to set137* the user's preferences whether an automatic comment is138* required or no API comment.139*140* @param kind of comment, automatic or no comment.141* @return this builder.142*/143@Override144public ClassBuilder setComments(Comment.Kind kind) {145super.setComments(kind);146return this;147}148149/**150* Set the super-type of the type.151* @param name of the super type.152* @return this builder.153*/154public ClassBuilder setExtends(String name) {155extendsType = name;156return this;157}158159/**160* Adds an implements declaration(s).161* @param names the interfaces162* @return this builder.163*/164public ClassBuilder addImplements(String... names) {165implementsTypes.addAll(List.of(names));166return this;167}168169/**170* Adds a member(s) to the class declaration.171* @param mbs the member builder(s) representing member(s).172* @return this builder173*/174public ClassBuilder addMembers(MemberBuilder... mbs) {175for (MemberBuilder mb : mbs) {176members.add(mb);177mb.setClassName(fqn);178}179return this;180}181182/**183* Adds nested-classes, to an outer class to an outer builder.184* @param cbs class builder(s) of the nested classes.185* @return this builder.186*/187public ClassBuilder addNestedClasses(ClassBuilder... cbs) {188Stream.of(cbs).forEach(cb -> {189nested.add(cb);190cb.setClassName(fqn);191});192return this;193}194195/**196* Adds inner-classes, to an an outer class builder.197* @param cbs class builder(s) of the inner classes.198* @return this builder.199*/200public ClassBuilder addInnerClasses(ClassBuilder... cbs) {201Stream.of(cbs).forEach(cb -> {202inners.add(cb);203cb.setClassName(fqn);204});205return this;206}207208@Override209public String toString() {210OutputWriter ow = new OutputWriter();211if (pkg != null)212ow.println("package " + pkg + ";");213imports.forEach(i -> ow.println("import " + i + ";"));214switch (comments.kind) {215case AUTO:216ow.println("/** Class " + fqn + " */");217break;218case USER:219ow.println("/** ");220comments.comments.forEach(c -> ow.println(" * " + c));221ow.println(" */");222break;223case NO_API_COMMENT:224ow.println("// NO_API_COMMENT");225break;226}227ow.print(modifiers.toString());228ow.print(clsname);229if (typeParameter != null) {230ow.print(typeParameter + " ");231} else {232ow.print(" ");233}234if (extendsType != null && !extendsType.isEmpty()) {235ow.print("extends " + extendsType + " ");236}237if (!implementsTypes.isEmpty()) {238ow.print("implements ");239240ListIterator<String> iter = implementsTypes.listIterator();241while (iter.hasNext()) {242String s = iter.next() ;243ow.print(s);244if (iter.hasNext())245ow.print(", ");246}247}248ow.print("{");249if (!nested.isEmpty()) {250ow.println("");251nested.forEach(m -> ow.println(m.toString()));252}253254if (!members.isEmpty()) {255ow.println("");256members.forEach(m -> ow.println(m.toString()));257}258259ow.println("}");260if (!inners.isEmpty()) {261ow.println(" {");262inners.forEach(m -> ow.println(m.toString()));263ow.println("}");264}265return ow.toString();266}267268/**269* Writes out the java source for a type element. Package directories270* will be created as needed as inferred by the type name.271* @param srcDir the top level source directory.272* @throws IOException if an error occurs.273*/274public void write(Path srcDir) throws IOException {275Files.createDirectories(srcDir);276Path outDir = srcDir;277if (pkg != null && !pkg.isEmpty()) {278String pdir = pkg.replace(".", "/");279outDir = Paths.get(srcDir.toString(), pdir);280Files.createDirectories(outDir);281}282Path filePath = Paths.get(outDir.toString(), clsname + ".java");283tb.writeFile(filePath, this.toString());284}285286/**287* The member builder, this is the base class for all types of members.288*/289public static abstract class MemberBuilder extends AbstractBuilder {290public MemberBuilder(Modifiers modifiers, String name) {291super(modifiers, name);292}293294/**295* Sets the enclosing type's name.296* @param className the enclosing type's name297*/298@Override299void setClassName(String className) {300cname = className;301}302303/**304* Sets a comment for the element.305*306* @param comments for any element307* @return this builder.308*/309@Override310public MemberBuilder setComments(String... comments) {311super.setComments(comments);312return this;313}314315/**316* Sets a comment for the element. Typically used to set user's317* preferences whether an automatic comment is required or no API318* comment.319*320* @param kind of comment, automatic or no comment.321* @return this builder.322*/323@Override324public MemberBuilder setComments(Comment.Kind kind) {325super.setComments(kind);326return this;327}328329/**330* Sets a new modifier.331*332* @param modifiers333* @return this builder.334*/335@Override336public MemberBuilder setModifiers(String... modifiers) {337super.setModifiers(modifiers);338return this;339}340}341342/**343* The field builder.344*/345public static class FieldBuilder extends MemberBuilder {346private String fieldType;347private String value;348349private static final Pattern FIELD_RE = Pattern.compile("(.*)(\\s*=\\s*)(.*)(;)");350351/**352* Constructs a field with the modifiers and name of the field.353* The builder by default is configured to auto generate the354* comments for the field.355* @param name of the field356*/357public FieldBuilder(String name) {358super(new Modifiers(), name);359this.comments = new Comment(Comment.Kind.AUTO);360}361362/**363* Returns a field builder by parsing the string.364* ex: public static String myPlayingField;365* @param fieldString describing the field.366* @return a field builder.367*/368public static FieldBuilder parse(String fieldString) {369String prefix;370String value = null;371Matcher m = FIELD_RE.matcher(fieldString);372if (m.matches()) {373prefix = m.group(1).trim();374value = m.group(3).trim();375} else {376int end = fieldString.lastIndexOf(';') > 0377? fieldString.lastIndexOf(';')378: fieldString.length();379prefix = fieldString.substring(0, end).trim();380}381List<String> list = Stream.of(prefix.split(" "))382.filter(s -> !s.isEmpty()).collect(Collectors.toList());383if (list.size() < 2) {384throw new IllegalArgumentException("incorrect field string: "385+ fieldString);386}387String name = list.get(list.size() - 1);388String fieldType = list.get(list.size() - 2);389390FieldBuilder fb = new FieldBuilder(name);391fb.modifiers.setModifiers(list.subList(0, list.size() - 2));392fb.setFieldType(fieldType);393if (value != null)394fb.setValue(value);395396return fb;397}398399/**400* Sets the modifiers for this builder.401*402* @param mods403* @return this builder404*/405public FieldBuilder setModifiers(String mods) {406this.modifiers.setModifiers(mods);407return this;408}409410/**411* Sets the type of the field.412* @param fieldType the name of the type.413* @return this field builder.414*/415public FieldBuilder setFieldType(String fieldType) {416this.fieldType = fieldType;417return this;418}419420public FieldBuilder setValue(String value) {421this.value = value;422return this;423}424425@Override426public String toString() {427String indent = " ";428OutputWriter ow = new OutputWriter();429switch (comments.kind) {430case AUTO:431ow.println(indent + "/** Field " +432super.name + " in " + super.cname + " */");433break;434case INHERIT_DOC: case USER:435ow.println(indent + "/** " +436comments.toString() + " */");437break;438case NO_API_COMMENT:439ow.println(indent + "// NO_API_COMMENT");440break;441}442ow.print(indent + super.modifiers.toString() + " ");443ow.print(fieldType + " ");444ow.print(super.name);445if (value != null) {446ow.print(" = " + value);447}448ow.println(";");449return ow.toString();450}451}452453/**454* The method builder.455*/456public static class MethodBuilder extends MemberBuilder {457458private final List<Pair> params;459460private String returnType;461private List<String> body;462463final static Pattern METHOD_RE = Pattern.compile("(.*)(\\()(.*)(\\))(.*)");464465/**466* Constructs a method builder. The builder by default is configured467* to auto generate the comments for this method.468* @param name of the method.469*/470public MethodBuilder(String name) {471super(new Modifiers(), name);472comments = new Comment(Comment.Kind.AUTO);473params = new ArrayList<>();474body = null;475}476477/**478* Returns a method builder by parsing a string which479* describes a method.480* @param methodString the method description.481* @return a method builder.482*/483public static MethodBuilder parse(String methodString) {484Matcher m = METHOD_RE.matcher(methodString);485if (!m.matches())486throw new IllegalArgumentException("string does not match: "487+ methodString);488String prefix = m.group(1);489String params = m.group(3);490String suffix = m.group(5).trim();491492if (prefix.length() < 2) {493throw new IllegalArgumentException("incorrect method string: "494+ methodString);495}496497String[] pa = prefix.split(" ");498List<String> list = List.of(pa);499String name = list.get(list.size() - 1);500String returnType = list.get(list.size() - 2);501502MethodBuilder mb = new MethodBuilder(name);503mb.modifiers.setModifiers(list.subList(0, list.size() - 2));504mb.setReturn(returnType);505506pa = params.split(",");507Stream.of(pa).forEach(p -> {508p = p.trim();509if (!p.isEmpty())510mb.addParameter(p);511});512if (!suffix.isEmpty() || suffix.length() > 1) {513mb.setBody(suffix);514}515return mb;516}517518/**519* Sets the modifiers for this builder.520*521* @param modifiers522* @return this builder523*/524public MethodBuilder setModifiers(String modifiers) {525this.modifiers.setModifiers(modifiers);526return this;527}528529@Override530public MethodBuilder setComments(String... comments) {531super.setComments(comments);532return this;533}534535@Override536public MethodBuilder setComments(Comment.Kind kind) {537super.setComments(kind);538return this;539}540541/**542* Sets a return type for a method.543* @param returnType the return type.544* @return this method builder.545*/546public MethodBuilder setReturn(String returnType) {547this.returnType = returnType;548return this;549}550551/**552* Adds a parameter(s) to the method method builder.553* @param params a pair consisting of type and parameter name.554* @return this method builder.555*/556public MethodBuilder addParameters(Pair... params) {557this.params.addAll(List.of(params));558return this;559}560561/**562* Adds a parameter to the method method builder.563* @param type the type of parameter.564* @param name the parameter name.565* @return this method builder.566*/567public MethodBuilder addParameter(String type, String name) {568this.params.add(new Pair(type, name));569return this;570}571572/**573* Adds a parameter to the method builder, by parsing the string.574* @param s the parameter description such as "Double voltage"575* @return this method builder.576*/577public MethodBuilder addParameter(String s) {578String[] p = s.trim().split(" ");579return addParameter(p[0], p[p.length - 1]);580}581582/**583* Sets the body of the method, described by the string.584* Such as "{", "double i = v/r;", "return i;", "}"585* @param body of the methods586* @return587*/588public MethodBuilder setBody(String... body) {589if (body == null) {590this.body = null;591} else {592this.body = new ArrayList<>();593this.body.addAll(List.of(body));594}595return this;596}597598@Override599public String toString() {600OutputWriter ow = new OutputWriter();601String indent = " ";602switch (comments.kind) {603case AUTO:604ow.println(indent + "/** Method " + super.name + " in " + super.cname);605if (!params.isEmpty())606params.forEach(p -> ow.println(indent + " * @param " + p.second + " a param"));607if (returnType != null && !returnType.isEmpty() && !returnType.contains("void"))608ow.println(indent + " * @return returns something");609ow.println(indent + " */");610break;611case INHERIT_DOC: case USER:612ow.println(indent + "/** " + comments.toString() + " */");613break;614case NO_API_COMMENT:615ow.println(indent + "// NO_API_COMMENT");616break;617}618619ow.print(indent + super.modifiers.toString() + " ");620ow.print(returnType + " ");621ow.print(super.name + "(");622if (!params.isEmpty()) {623ListIterator<Pair> iter = params.listIterator();624while (iter.hasNext()) {625Pair p = iter.next();626ow.print(p.first + " " + p.second);627if (iter.hasNext())628ow.print(", ");629}630}631ow.print(")");632if (body == null) {633ow.println(";");634} else {635body.forEach(ow::println);636}637return ow.toString();638}639}640641//A sample, to test with an IDE.642// public static void main(String... args) throws IOException {643// ClassBuilder cb = new ClassBuilder(new ToolBox(), "foo.bar.Test<C extends A>");644// cb.addModifiers("public", "abstract", "static")645// .addImports("java.io").addImports("java.nio")646// .setComments("A comment")647// .addImplements("Serialization", "Something")648// .setExtends("java.lang.Object")649// .addMembers(650// FieldBuilder.parse("public int xxx;"),651// FieldBuilder.parse("public int yyy = 10;"),652// MethodBuilder.parse("public static void main(A a, B b, C c);")653// .setComments("CC"),654// MethodBuilder.parse("void foo(){//do something}")655//656// );657// ClassBuilder ic = new ClassBuilder(new ToolBox(), "IC");658// cb.addModifiers( "interface");659// cb.addNestedClasses(ic);660// System.out.println(cb.toString());661// cb.write(Paths.get("src-out"));662// }663}664665666