All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.vertx.codetrans.CodeTransProcessor Maven / Gradle / Ivy

package io.vertx.codetrans;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import io.vertx.codetrans.annotations.CodeTranslate;
import io.vertx.codetrans.lang.groovy.GroovyLang;
import io.vertx.codetrans.lang.js.JavaScriptLang;
import io.vertx.codetrans.lang.kotlin.KotlinLang;
import io.vertx.codetrans.lang.ruby.RubyLang;
import io.vertx.codetrans.lang.scala.ScalaLang;
import io.vertx.core.Verticle;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * A processor plugin generate scripts from {@link io.vertx.core.Verticle} class. It scans all the compiled
 * classes and tries to generate corresponding scripts for each class.

*

* The script is named after the verticle fqn using the last atom of the package name and the lower * cased class name, for example : {@code examples.http.Server} maps to {@code http/server.js}, * {@code http/server.groovy}, etc...

*

* The processor is only active when the option {@code codetrans.output} is set to a valid directory where the scripts * will be written. A log codetrans.log will also be written with the processor activity. *

* The processor can be configured using the {@code condetrans.config} property targeting a JSON file. The JSON file * contains a set of exclusions and is structured as follows: *

*

 *     {
 *       "excludes": [
 *        {
 *          "package" : "the (java) package to exclude",
 *          "langs" : ["lang1", "lang2"]
 *        }
 *       ]
 *     }
 * 
*

* The {@code package} element is mandatory. {@code Langs} is optional. When not set, all languages are skipped. * Languages are identified by their extensions. * * @author Julien Viet * @author Clement Escoffier */ public class CodeTransProcessor extends AbstractProcessor { private File outputDir; private CodeTranslator translator; private List langs; private Set folders = new HashSet<>(); // The copied folders so we don't do the job twice private PrintWriter log; private ObjectNode config; private Map> abc = new HashMap<>(); private RenderMode renderMode; @Override public Set getSupportedOptions() { return Collections.singleton("codetrans.output"); } @Override public Set getSupportedAnnotationTypes() { return Collections.singleton("*"); } @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); String outputOption = processingEnv.getOptions().get("codetrans.output"); if (outputOption != null) { outputDir = new File(outputOption); } translator = new CodeTranslator(processingEnv); langs = new ArrayList<>(); String renderOpt = processingEnv.getOptions().get("codetrans.render"); renderMode = renderOpt != null ? RenderMode.valueOf(renderOpt.toUpperCase()) : RenderMode.EXAMPLE; String langsOpt = processingEnv.getOptions().get("codetrans.langs"); Set langs; if (langsOpt != null) { langs = new HashSet<>(Arrays.asList(langsOpt.split("\\s*,\\s*"))); } else { langs = new HashSet<>(Arrays.asList("js", "ruby", "kotlin", "groovy")); } String configOpt = processingEnv.getOptions().get("codetrans.config"); if (configOpt != null) { ObjectMapper mapper = new ObjectMapper() .enable(JsonParser.Feature.ALLOW_COMMENTS) .enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES); File file = new File(configOpt); try { config = (ObjectNode) mapper.readTree(file); } catch (IOException e) { System.err.println("[ERROR] Cannot read configuration file " + file.getAbsolutePath() + " : " + e.getMessage()); e.printStackTrace(); } } for (String lang : langs) { Lang l; switch (lang) { case "kotlin": l = new KotlinLang(); break; case "groovy": l = new GroovyLang(); break; case "js": l = new JavaScriptLang(); break; case "scala": l = new ScalaLang(); break; case "ruby": l = new RubyLang(); break; default: continue; } this.langs.add(l); if (config != null) { JsonNode n = config.get(lang); if (n != null && n.getNodeType() == JsonNodeType.OBJECT) { JsonNode excludes = n.get("excludes"); if (excludes != null && excludes.getNodeType() == JsonNodeType.ARRAY) { Set t = new HashSet<>(); abc.put(l.id(), t); for (int i = 0;i < excludes.size();i++) { JsonNode c = excludes.get(i); if (c.getNodeType() == JsonNodeType.STRING) { TextNode tn = (TextNode) c; t.add(tn.asText()); } } } } } } } private PrintWriter getLogger() throws Exception { if (log == null) { log = new PrintWriter(new FileWriter(new File(outputDir, "codetrans.log"), false), true); } return log; } private void copyDirRec(File srcFolder, File dstFolder, PrintWriter log) throws Exception { if (!folders.contains(dstFolder)) { folders.add(dstFolder); Path srcPath = srcFolder.toPath(); Path dstPath = dstFolder.toPath(); SimpleFileVisitor copyingVisitor = new SimpleFileVisitor() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { Path targetPath = dstPath.resolve(srcPath.relativize(dir)); if (!Files.exists(targetPath)) { log.println("Creating dir " + targetPath); Files.createDirectory(targetPath); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path srcFile, BasicFileAttributes attrs) throws IOException { if (!srcFile.getFileName().toString().endsWith(".java")) { log.println("Copying resource " + srcFile + " to " + dstPath); Path dstFile = dstPath.resolve(srcPath.relativize(srcFile)); Files.copy(srcFile, dstFile, StandardCopyOption.REPLACE_EXISTING); } return FileVisitResult.CONTINUE; } }; Files.walkFileTree(srcPath, copyingVisitor); } } @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { if (roundEnv.processingOver()) { if (log != null) { log.close(); } return false; } if (outputDir != null && (outputDir.exists() || outputDir.mkdirs())) { LinkedHashMap methods = new LinkedHashMap<>(); try { PrintWriter log = getLogger(); // Process all verticles automatically TypeMirror verticleType = processingEnv.getElementUtils().getTypeElement(Verticle.class.getName()).asType(); for (Element rootElt : roundEnv.getRootElements()) { Set modifiers = rootElt.getModifiers(); if (rootElt.getKind() == ElementKind.CLASS && !modifiers.contains(Modifier.ABSTRACT) && modifiers.contains(Modifier.PUBLIC) && processingEnv.getTypeUtils().isSubtype(rootElt.asType(), verticleType)) { TypeElement typeElt = (TypeElement) rootElt; for (Element enclosedElt : typeElt.getEnclosedElements()) { if (enclosedElt.getKind() == ElementKind.METHOD) { ExecutableElement methodElt = (ExecutableElement) enclosedElt; if (methodElt.getSimpleName().toString().equals("start") && methodElt.getParameters().isEmpty()) { methods.put(methodElt, true); } } } } } // Process CodeTranslate annotations roundEnv.getElementsAnnotatedWith(CodeTranslate.class).forEach(annotatedElt -> { methods.put((ExecutableElement) annotatedElt, false); }); // Generate for (Map.Entry method : methods.entrySet()) { ExecutableElement methodElt = method.getKey(); boolean isVerticle = method.getValue(); TypeElement typeElt = (TypeElement) methodElt.getEnclosingElement(); FileObject obj = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, "", typeElt.getQualifiedName().toString().replace('.', '/') + ".java"); File srcFolder = new File(obj.toUri()).getParentFile(); for (Lang lang : langs) { if (isSkipped(typeElt, lang) || isSkipped(methodElt, lang)) { log.write("Skipping " + lang.id() + " translation for " + typeElt.getQualifiedName() + "#" + methodElt.getSimpleName()); continue; } List fqn = Arrays.asList(typeElt.toString().split("\\.")); File dstFolder = new File(outputDir, lang.id()); File f = lang.createSourceFile(dstFolder, fqn, !isVerticle ? methodElt.getSimpleName().toString() : null); if (f.getParentFile().exists() || f.getParentFile().mkdirs()) { try { String translation = translator.translate(methodElt, isVerticle, lang, renderMode); Files.write(f.toPath(), translation.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); log.println("Generated " + f.getAbsolutePath()); copyDirRec(srcFolder, f.getParentFile(), log); } catch (Exception e) { log.println("Skipping generation of " + typeElt.getQualifiedName()); e.printStackTrace(log); } } } } } catch (Exception e) { e.printStackTrace(); } return true; } else { return false; } } private boolean isSkipped(ExecutableElement methodElt, Lang lang) { Set excl = abc.get(lang.id()); if (excl != null) { TypeElement typeElt = (TypeElement) methodElt.getEnclosingElement(); String match = "" + typeElt.getQualifiedName(); if (excl.contains(match)) { return true; } match += "#" + methodElt.getSimpleName(); if (excl.contains(match)) { return true; } } return false; } /** * Checks whether the generation of the given class to the given lang is explicitly excluded. Exclusions are * managed in the configuration file. If no configuration file is provided, the translation is not skipped. * * @param type the type * @param lang the language * @return {@code true} if the translation is skipped, {@code false} otherwise. */ private boolean isSkipped(TypeElement type, Lang lang) { if (config == null) { // no config, no exclusions return false; } ArrayNode excludes = (ArrayNode) config.get("excludes"); for (JsonNode exclude : excludes) { // Structure: // { // "package": "the package to exclude", (mandatory) // "langs": ["lang 1", "lang 2"] // } // If not langs - skip all languages if (exclude.get("package") == null) { throw new IllegalStateException("Malformed configuration - Missing 'package' attribute in the 'codetrans" + ".config' file"); } String pck = exclude.get("package").asText(); ArrayNode langs = (ArrayNode) exclude.get("langs"); if (type.getQualifiedName().toString().startsWith(pck) && isLanguageSkipped(langs, lang)) { return true; } } return false; } private boolean isLanguageSkipped(ArrayNode langs, Lang lang) { if (langs == null) { // If not langs, exclude all. return true; } for (JsonNode node : langs) { if (node.asText().equalsIgnoreCase(lang.getExtension())) { return true; } } return false; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy