// Copyright 2012 Google Inc. All Rights Reserved. // // 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.eclipse.che.dto.generator; import org.eclipse.che.dto.server.DtoFactoryVisitor; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * Base template for the generated output file that contains all the DTOs. * <p/> * Note that we generate client and server DTOs in separate runs of the generator. * <p/> * The directionality of the DTOs only affects whether or not we expose methods to construct an instance of the DTO. We need both client and * server versions of all DTOs (irrespective of direction). */ public class DtoTemplate { public static class MalformedDtoInterfaceException extends RuntimeException { public MalformedDtoInterfaceException(String msg) { super(msg); } } // We keep a whitelist of allowed non-DTO generic types. static final Set<Class<?>> jreWhitelist = new HashSet<>(Arrays.asList(new Class<?>[]{String.class, Integer.class, Double.class, Float.class, Boolean.class})); private final List<DtoImpl> dtoInterfaces = new ArrayList<>(); // contains mapping for already implemented DTO interfaces private final Map<Class<?>, Set<Class<?>>> implementedDtoInterfaces = new HashMap<>(); private final String packageName; private final String className; private final String implType; /** * Walks the super interface hierarchy to determine if a Class implements some target interface transitively. */ static boolean implementsInterface(Class<?> i, Class<?> target) { if (i.equals(target)) { return true; } boolean rtn = false; Class<?>[] superInterfaces = i.getInterfaces(); for (Class<?> superInterface : superInterfaces) { rtn = rtn || implementsInterface(superInterface, target); } return rtn; } /** * Constructor. * * @param packageName * The name of the package for the outer DTO class. * @param className * The name of the outer DTO class. * @param implType * DTO impls type, "client" or "server". */ DtoTemplate(String packageName, String className, String implType) { this.packageName = packageName; this.className = className; this.implType = implType; } public String getImplType() { return implType; } public void addImplementation(Class<?> dtoInterface, Class<?> impl) { Set<Class<?>> classes = implementedDtoInterfaces.get(dtoInterface); if (classes == null) { implementedDtoInterfaces.put(dtoInterface, classes = new LinkedHashSet<>()); } classes.add(impl); } /** * Some DTO interfaces may be already implemented in dependencies of current project. Try to reuse them. If this method returns * <code>null</code> it means interface is not implemented yet. */ public Class<?> getDtoImplementation(Class<?> dtoInterface) { Set<Class<?>> classes = implementedDtoInterfaces.get(dtoInterface); if (classes == null || classes.isEmpty()) { return null; } for (Class<?> impl : classes) { if (impl.getSimpleName().equals(dtoInterface.getSimpleName() + "Impl")) { return impl; } } return null; } /** * Adds an interface to the DtoTemplate for code generation. * * @param i * interface to add */ public void addInterface(Class<?> i) { getDtoInterfaces().add(createDtoImplTemplate(i)); } /** @return the dtoInterfaces */ public List<DtoImpl> getDtoInterfaces() { return dtoInterfaces; } /** * Returns the source code for a class that contains all the DTO impls for any interfaces that were added via the {@link * #addInterface(Class)} method. */ @Override public String toString() { StringBuilder builder = new StringBuilder(); emitPreamble(builder); emitDtos(builder); emitPostamble(builder); return builder.toString(); } /** * Tests whether or not a given class is a part of our dto jar, and thus will eventually have a generated Impl that is serializable * (thus allowing it to be a generic type). */ boolean isDtoInterface(Class<?> potentialDto) { for (DtoImpl dto : getDtoInterfaces()) { if (dto.getDtoInterface().equals(potentialDto)) { return true; } } return false; } private DtoImpl createDtoImplTemplate(Class<?> i) { if ("server".equals(implType)) { return new DtoImplServerTemplate(this, i); } else if ("client".equals(implType)) { return new DtoImplClientTemplate(this, i); } throw new IllegalStateException("Unsupported DTO implementation type, must be 'client' or 'server'"); } private void emitDtos(StringBuilder builder) { for (DtoImpl dto : getDtoInterfaces()) { builder.append(dto.serialize()); } } private void emitPostamble(StringBuilder builder) { builder.append("\n}"); } private void emitPreamble(StringBuilder builder) { builder.append("/*******************************************************************************\n"); builder.append(" * Copyright (c) 2012-2014 Codenvy, S.A.\n"); builder.append(" * All rights reserved. This program and the accompanying materials\n"); builder.append(" * are made available under the terms of the Eclipse Public License v1.0\n"); builder.append(" * which accompanies this distribution, and is available at\n"); builder.append(" * http://www.eclipse.org/legal/epl-v10.html\n"); builder.append(" *\n"); builder.append(" * Contributors:\n"); builder.append(" * Codenvy, S.A. - initial API and implementation\n"); builder.append(" *******************************************************************************/\n\n\n"); builder.append("// GENERATED SOURCE. DO NOT EDIT.\npackage "); builder.append(packageName); builder.append(";\n\n"); if ("server".equals(implType)) { builder.append("import org.eclipse.che.dto.server.JsonSerializable;\n"); builder.append("\n"); builder.append("import com.google.gson.Gson;\n"); builder.append("import com.google.gson.GsonBuilder;\n"); builder.append("import com.google.gson.JsonArray;\n"); builder.append("import com.google.gson.JsonElement;\n"); builder.append("import com.google.gson.JsonNull;\n"); builder.append("import com.google.gson.JsonObject;\n"); builder.append("import com.google.gson.JsonParser;\n"); builder.append("import com.google.gson.JsonPrimitive;\n"); builder.append("\n"); builder.append("import java.util.List;\n"); builder.append("import java.util.Map;\n"); } if ("client".equals(implType)) { builder.append("import org.eclipse.che.ide.dto.ClientDtoFactoryVisitor;\n"); builder.append("import org.eclipse.che.ide.dto.DtoFactoryVisitor;\n"); builder.append("import org.eclipse.che.ide.dto.JsonSerializable;\n"); builder.append("import com.google.gwt.json.client.*;\n"); builder.append("import com.google.inject.Singleton;\n"); } builder.append("\n\n@SuppressWarnings({\"unchecked\", \"cast\"})\n"); if ("client".equals(implType)) { builder.append("@Singleton\n"); builder.append("@ClientDtoFactoryVisitor\n"); } // Note that we always use fully qualified path names when referencing Types // so we need not add any import statements for anything. builder.append("public class "); builder.append(className); if ("server".equals(implType)) { builder.append(" implements ").append(DtoFactoryVisitor.class.getCanonicalName()); } if ("client".equals(implType)) { builder.append(" implements ").append("DtoFactoryVisitor"); } builder.append(" {\n\n"); if ("server".equals(implType)) { builder.append(" private static final Gson gson = new GsonBuilder().disableHtmlEscaping().serializeNulls().create();\n\n"); builder.append(" @Override\n" + " public void accept(org.eclipse.che.dto.server.DtoFactory dtoFactory) {\n"); for (DtoImpl dto : getDtoInterfaces()) { String dtoInterface = dto.getDtoInterface().getCanonicalName(); builder.append(" dtoFactory.registerProvider(").append(dtoInterface).append(".class").append(", ") .append("new org.eclipse.che.dto.server.DtoProvider<").append(dtoInterface).append(">() {\n"); builder.append(" public Class<? extends ").append(dtoInterface).append("> getImplClass() {\n") .append(" return ").append(dto.getImplClassName()).append(".class;\n"); builder.append(" }\n\n"); builder.append(" public ").append(dtoInterface).append(" newInstance() {\n") .append(" return ").append(dto.getImplClassName()).append(".make();\n"); builder.append(" }\n\n"); builder.append(" public ").append(dtoInterface).append(" fromJson(String json) {\n") .append(" return ").append(dto.getImplClassName()).append(".fromJsonString(json);\n"); builder.append(" }\n\n"); builder.append(" public ").append(dtoInterface).append(" fromJson(com.google.gson.JsonElement json) {\n") .append(" return ").append(dto.getImplClassName()).append(".fromJsonElement(json);\n"); builder.append(" }\n\n"); builder.append(" public ").append(dtoInterface).append(" clone(").append(dtoInterface).append(" origin) {\n") .append(" return new ").append(dto.getImplClassName()).append("(origin);\n"); builder.append(" }\n"); builder.append(" });\n"); } builder.append(" }\n\n"); } if ("client".equals(implType)) { builder.append(" @Override\n").append(" public void accept(org.eclipse.che.ide.dto.DtoFactory dtoFactory) {\n"); for (DtoImpl dto : getDtoInterfaces()) { String dtoInterface = dto.getDtoInterface().getCanonicalName(); builder.append(" dtoFactory.registerProvider(").append(dtoInterface).append(".class").append(", ") .append("new org.eclipse.che.ide.dto.DtoProvider<").append(dtoInterface).append(">() {\n"); builder.append(" public Class<? extends ").append(dtoInterface).append("> getImplClass() {\n") .append(" return ").append(dto.getImplClassName()).append(".class;\n"); builder.append(" }\n\n"); builder.append(" public ").append(dtoInterface).append(" newInstance() {\n") .append(" return ").append(dto.getImplClassName()).append(".make();\n"); builder.append(" }\n\n"); builder.append(" public ").append(dtoInterface).append(" fromJson(String json) {\n") .append(" return ").append(dto.getImplClassName()).append(".fromJsonString(json);\n"); builder.append(" }\n"); builder.append(" });\n"); } builder.append(" }\n\n"); } } }