/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * 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: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.util; import org.eclipse.che.ide.api.extension.Extension; import org.eclipse.che.ide.api.extension.ExtensionRegistry; import org.eclipse.che.ide.api.extension.SDK; import com.google.gwt.core.client.GWT; import com.google.gwt.core.ext.Generator; import com.google.gwt.core.ext.GeneratorContext; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.JConstructor; import com.google.gwt.core.ext.typeinfo.JType; import com.google.gwt.core.ext.typeinfo.TypeOracle; import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; import com.google.gwt.user.rebind.SourceWriter; import com.google.inject.Inject; import com.google.inject.Provider; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * This class generates implementation for {@link org.eclipse.che.ide.api.extension.ExtensionRegistry} that contains descriptive information about * extensions present in IDE Bundle. * Generator processes all Types found by TypeOracle, looking for {@link Extension} annotation. * Each class annotated with Extension is processed to retrieve dependency information. Dependencies * are collected by reading constructor annotated with {@link Inject}. All arguments retrieved and asked if * annotated with {@link Extension} of {@link SDK}. * * @author <a href="mailto:nzamosenchuk@exoplatform.com">Nikolay Zamosenchuk</a> */ public class ExtensionRegistryGenerator extends Generator { /** {@inheritDoc} */ @Override public String generate(TreeLogger logger, GeneratorContext context, String typeName) throws UnableToCompleteException { TypeOracle typeOracle = context.getTypeOracle(); JClassType extensionManager = typeOracle.findType(typeName); if (extensionManager == null) { logger.log(TreeLogger.ERROR, "Can't find interface type '" + typeName + "'", null); throw new UnableToCompleteException(); } if (extensionManager.isInterface() == null) { logger.log(TreeLogger.ERROR, extensionManager.getQualifiedSourceName() + " is not an interface", null); throw new UnableToCompleteException(); } List<JClassType> extensions = new ArrayList<>(); for (JClassType type : typeOracle.getTypes()) { if (type.isAnnotationPresent(Extension.class)) { extensions.add(type); } } String packageName = extensionManager.getPackage().getName(); String className = extensionManager.getSimpleSourceName() + "Impl"; generateClass(logger, context, packageName, className, extensions); return packageName + "." + className; } /** * Generate the content of the class * * @param logger * @param context * @param packageName * @param className * @param extensions * @throws UnableToCompleteException */ private void generateClass(TreeLogger logger, GeneratorContext context, String packageName, String className, List<JClassType> extensions) throws UnableToCompleteException { PrintWriter pw = context.tryCreate(logger, packageName, className); if (pw == null) { return; } ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(packageName, className); // generate imports generateImports(extensions, composerFactory); // interface composerFactory.addImplementedInterface(ExtensionRegistry.class.getCanonicalName()); // get source writer SourceWriter sw = composerFactory.createSourceWriter(context, pw); // begin class definition // fields sw.println("private final Map<String, ExtensionDescription> extensions = new HashMap<>();"); generateConstructor(className, extensions, sw); // methods generateGetExtensionsMethod(sw); // close it out sw.outdent(); sw.println("}"); context.commit(logger, pw); } /** * Inject imports * * @param extensions * @param composerFactory */ private void generateImports(List<JClassType> extensions, ClassSourceFileComposerFactory composerFactory) { // imports composerFactory.addImport(GWT.class.getCanonicalName()); composerFactory.addImport(Extension.class.getCanonicalName()); composerFactory.addImport(ExtensionRegistry.class.getCanonicalName()); composerFactory.addImport(Inject.class.getCanonicalName()); composerFactory.addImport(Provider.class.getCanonicalName()); composerFactory.addImport(List.class.getCanonicalName()); composerFactory.addImport(ArrayList.class.getCanonicalName()); composerFactory.addImport(Map.class.getCanonicalName()); composerFactory.addImport(HashMap.class.getCanonicalName()); // import for extensions for (JClassType jClassType : extensions) { composerFactory.addImport(jClassType.getQualifiedSourceName()); } } /** * Generate constructor with dependencies added into field * * @param className * @param extensions * @param sw * @throws UnableToCompleteException */ private void generateConstructor(String className, List<JClassType> extensions, SourceWriter sw) throws UnableToCompleteException { // constructor sw.indent(); sw.print("public %s()", className); sw.println("{"); sw.indent(); { for (JClassType extension : extensions) { sw.println("{"); sw.indent(); /* Array<DependencyDescription> deps = Collections.<DependencyDescription> createArray(); */ generateDependenciesForExtension(sw, extension); Extension annotation = extension.getAnnotation(Extension.class); /* extensions.put("ide.ext.demo", new ExtensionDescription("ide.ext.demo", "1.0.0", "Demo extension", deps, demoExtProvider)); */ // the class's fqn String extensionId = extension.getQualifiedSourceName(); sw.println("extensions.put(\"%s\", new ExtensionDescription(\"%s\",\"%s\",\"%s\",\"%s\",deps));", escape(extensionId), escape(extensionId), escape(annotation.version()), escape(annotation.title()), escape(annotation.description())); sw.outdent(); sw.println("}"); } } sw.outdent(); sw.println("}"); } /** * Writes dependency gathering code, like: * <p/> * Array<DependencyDescription> deps = Collections.<DependencyDescription> createArray(); * deps.add(new DependencyDescription("ide.api.ui.menu", "")); * deps.add(new DependencyDescription("extension.demo", "1.0.0-alpha")); * * @param sw * @param extension * @throws UnableToCompleteException */ private void generateDependenciesForExtension(SourceWriter sw, JClassType extension) throws UnableToCompleteException { // expected code /* Array<DependencyDescription> deps = Collections.<DependencyDescription> createArray(); deps.add(new DependencyDescription("ide.api.ui.menu", "")); */ if (extension.getConstructors().length == 0) { throw new UnableToCompleteException(); } sw.println("List<DependencyDescription> deps = new ArrayList<>();"); JConstructor jConstructor = extension.getConstructors()[0]; JType[] parameterTypes = jConstructor.getParameterTypes(); for (JType jType : parameterTypes) { JClassType argType = jType.isClassOrInterface(); if (argType != null && (argType.isAnnotationPresent(SDK.class) || argType.isAnnotationPresent(Extension.class))) { String id = ""; String version = ""; if (argType.isAnnotationPresent(SDK.class)) { id = argType.getAnnotation(SDK.class).title(); } else if (argType.isAnnotationPresent(Extension.class)) { id = argType.getQualifiedSourceName(); version = argType.getAnnotation(Extension.class).version(); } sw.println("deps.add(new DependencyDescription(\"%s\", \"%s\"));", escape(id), escape(version)); } } } /** * Generate the code for {@link ExtensionRegistry#getExtensionDescriptions()} * * @param sw */ private void generateGetExtensionsMethod(SourceWriter sw) { /* @Override public StringMap<ExtensionDescription> getExtensionDescriptions() { return extensions; } */ sw.println("@Override"); sw.println("public Map<String, ExtensionDescription> getExtensionDescriptions()"); sw.println("{"); sw.indent(); sw.println("return extensions;"); sw.outdent(); sw.println("}"); } }