/******************************************************************************* * Copyright (c) 2017 Red Hat. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Red Hat - Initial Contribution *******************************************************************************/ package org.eclipse.che.api.languageserver.generator; import com.google.common.base.Predicate; import org.reflections.Reflections; import org.reflections.scanners.SubTypesScanner; import org.reflections.util.ClasspathHelper; import org.reflections.util.ConfigurationBuilder; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Method; import java.net.URL; import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; /** * DtoGenerator generates che-style DTO's for lsp4j protocol classes. * Subclasses specialize this class to generate client-side and server-side implementations. * * @author Thomas Mäder */ public abstract class DtoGenerator { /** * Standard indent size to use. */ public static final String INDENT = " "; protected final JsonImpl json; public DtoGenerator(JsonImpl json) { this.json = json; } /** * Return the simple class name to use for the dto class for the given class. * * @param clazz * @return */ public static String dtoName(Class<? extends Object> clazz) { return clazz.getSimpleName() + "Dto"; } static boolean isSetter(Class<?> receiverClass, Method m) { Method getter = getterFor(receiverClass, m); return m.getName().startsWith("set") && m.getName().length() > 3 && Character.isUpperCase(m.getName().charAt(3)) && getter != null && getter.getReturnType() == m.getParameterTypes()[0]; } private static Method getterFor(Class<?> receiverClass, Method m) { if (m.getParameterTypes().length != 1) { return null; } if (boolean.class == m.getParameterTypes()[0] || Boolean.class == m.getParameterTypes()[0]) { String root = m.getName().substring(3); try { return receiverClass.getMethod("is" + root, new Class<?>[]{}); } catch (NoSuchMethodException e) { try { return receiverClass.getMethod("get" + root, new Class<?>[]{}); } catch (NoSuchMethodException e1) { StringBuilder b = new StringBuilder(root); b.setCharAt(0, Character.toLowerCase(root.charAt(0))); try { return receiverClass.getMethod(b.toString(), new Class<?>[]{}); } catch (NoSuchMethodException e3) { return null; } } } } try { return receiverClass.getMethod(m.getName().replaceFirst("set", "get"), new Class<?>[]{}); } catch (NoSuchMethodException e) { return null; } } /** * Generate dto classes. * * @param targetFolder * the base folder to generate code to. This is the package folder root. * @param targetName * the simple class name to use for the generated class * @param targetPackage * the package name to use for the generated class * @param sourcePackages * the source packages to use. THe packages must be on the class path at execution time. * @throws IOException */ public void generate(File targetFolder, String targetName, String targetPackage, String[] sourcePackages, String[] classNames) throws IOException { File targetFile = new File(targetFolder, targetPackage.replace('.', File.separatorChar) + File.separatorChar + targetName + ".java"); targetFile.getParentFile().mkdirs(); Set<URL> urls = new HashSet<>(); for (String pkg : sourcePackages) { urls.addAll(ClasspathHelper.forPackage(pkg)); } Reflections reflection = new Reflections(new ConfigurationBuilder().setUrls(urls) .setScanners(new SubTypesScanner(false)) .filterInputsBy(new Predicate<String>() { @Override public boolean apply(String input) { for (String pkg : sourcePackages) { if (input.startsWith(pkg)) { return input.indexOf('.', pkg.length() + 1) == input.lastIndexOf('.'); } } return false; } })); Set<Class<?>> allTypes = reflection.getSubTypesOf(Object.class); for (String className : classNames) { try { allTypes.add(Class.forName(className)); } catch (ClassNotFoundException e) { e.printStackTrace(); } } allTypes = allTypes.stream().filter((cls) -> !cls.isInterface()).collect(Collectors.toSet()); try (PrintWriter out = new PrintWriter(targetFile, "utf-8")) { out.print("package "); out.print(targetPackage); out.println(";"); writeEnvImports(out); json.writeImports(out); out.println("import java.util.Map.Entry;"); out.println("import java.util.ArrayList;"); out.println("import java.util.HashMap;"); out.println("import org.eclipse.lsp4j.jsonrpc.messages.Either;"); out.println("import org.eclipse.che.api.languageserver.util.EitherUtil;"); out.println("import org.eclipse.che.api.languageserver.util.JsonUtil;"); out.println("import org.eclipse.che.api.languageserver.shared.util.JsonDecision;"); for (Class<? extends Object> clazz : allTypes) { out.print("import "); out.print(clazz.getCanonicalName()); out.println(";"); } out.println(); writeEnvClassAnnotations(out); out.println(String.format("public class %1$s implements DtoFactoryVisitor {", targetName)); for (Class<? extends Object> clazz : allTypes) { writeDTOClass(INDENT, out, clazz, allTypes); out.println(); writeDTOProvider(INDENT, out, clazz); out.println(); } out.println(INDENT + "public void accept(DtoFactory dtoFactory) {"); for (Class<? extends Object> clazz : allTypes) { out.println(INDENT + INDENT + String.format("dtoFactory.registerProvider(%1$s.class, new %2$s());", clazz.getSimpleName(), dtoProviderName(clazz))); } out.println(INDENT + "}"); ToDtoGenerator.generateMakeDto(INDENT, out, allTypes); out.println("}"); out.flush(); } } /** * Write environment-specific target class annotations. * * @param out */ protected abstract void writeEnvClassAnnotations(PrintWriter out); /** * Write environment-specific imports. * * @param out */ protected abstract void writeEnvImports(PrintWriter out); private void writeDTOClass(String indent, PrintWriter out, Class<? extends Object> clazz, Set<Class<? extends Object>> classes) { out.println(indent + String.format("public static class %1$s extends %2$s implements JsonSerializable {", dtoName(clazz), clazz.getSimpleName())); out.println(); out.println(indent + INDENT + String.format("public %1$s() {", dtoName(clazz))); out.println(indent + INDENT + "}"); out.println(); writeCopyConstructor(indent + INDENT, out, clazz, classes); out.println(); writeToJson(indent + INDENT, out, clazz); out.println(); writeEnvSpecificToJson(indent, out); out.println(indent + INDENT + "public String toJson() {"); out.println(indent + INDENT + INDENT + "return toJsonElement().toString();"); out.println(indent + INDENT + "}"); out.println(); out.println(String.format(indent + INDENT + "public static %1$s fromJson(String json) {", dtoName(clazz))); out.println(indent + INDENT + INDENT + "if (json == null) {"); out.println(indent + INDENT + INDENT + INDENT + "return null;"); out.println(indent + INDENT + INDENT + "}"); out.println(indent + INDENT + INDENT + String.format("return fromJson(%1$s);", json.parse("json"))); out.println(indent + INDENT + "}"); out.println(String.format(indent + INDENT + "public static %1$s fromJson(%2$s jsonVal) {", dtoName(clazz), json.element())); out.println(indent + INDENT + INDENT + "if (jsonVal == null) {"); out.println(indent + INDENT + INDENT + INDENT + "return null;"); out.println(indent + INDENT + INDENT + "}"); out.println(indent + INDENT + INDENT + String.format("%1$s json= %2$s;", json.object(), json.objectValue("jsonVal"))); out.println(indent + INDENT + INDENT + String.format("%1$s result= new %1$s();", dtoName(clazz))); FromJsonGenerator fromJsonGenerator = new FromJsonGenerator(json); for (Method m : clazz.getMethods()) { if (isSetter(clazz, m)) { fromJsonGenerator.generateFromJson(indent + INDENT + INDENT, out, m, "result", "json"); } } out.println(indent + INDENT + INDENT + "return result;"); out.println(indent + INDENT + "}"); out.println(indent + "}"); } protected void writeEnvSpecificToJson(String indent, PrintWriter out) { } private void writeCopyConstructor(String indent, PrintWriter out, Class<? extends Object> clazz, Set<Class<? extends Object>> classes) { out.println(indent + String.format("public %1$s(%2$s o) {", dtoName(clazz), clazz.getName())); ToDtoGenerator toJsonGenerator = new ToDtoGenerator(classes); for (Method m : clazz.getMethods()) { if (isSetter(clazz, m)) { toJsonGenerator.generateToDto(indent + INDENT, out, clazz, m, "o"); } } out.println(indent + "}"); } private void writeToJson(String indent, PrintWriter out, Class<? extends Object> clazz) { out.println(indent + String.format("public %1$s toJsonElement() {", json.element())); out.println(indent + INDENT + String.format("%1$s result = new %1$s();", json.object())); ToJsonGenerator toJsonGenerator = new ToJsonGenerator(json); for (Method m : clazz.getMethods()) { if (isSetter(clazz, m)) { toJsonGenerator.generateToJson(indent + INDENT, out, clazz, m); } } out.println(indent + INDENT + "return result;\n"); out.println(indent + "}"); } private void writeDTOProvider(String indent, PrintWriter out, Class<? extends Object> clazz) { String source = indent + "public static class %1$s implements DtoProvider<%2$s> {\n" + indent + " public Class<? extends %2$s> getImplClass() {\n" + indent + " return %2$s.class;\n" + indent + " }\n" + indent + "\n" + indent + " public %2$s newInstance() {\n" + indent + " return new %2$s();\n" + indent + " }\n" + indent + "\n" + indent + " public %2$s fromJson(String json) {\n" + indent + " return %2$s.fromJson(json);\n" + indent + " }\n" + indent + INDENT + "public %2$s fromJson(%3$s json) {\n" + indent + " return %2$s.fromJson(json);\n" + indent + " }\n" + indent + INDENT + "public %2$s clone(%2$s dto) {\n" + indent + INDENT + INDENT + "return %2$s.fromJson(dto.toJson());\n" + indent + " }\n" + indent + "}"; out.println(String.format(source, dtoProviderName(clazz), dtoName(clazz), json.element())); } private String dtoProviderName(Class<? extends Object> clazz) { return clazz.getSimpleName() + "DtoProvider"; } }