package xapi.javac.dev.plugin; import com.sun.source.tree.ClassTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.Tree; import com.sun.source.tree.Tree.Kind; import com.sun.source.util.JavacTask; import com.sun.source.util.Plugin; import com.sun.source.util.TaskEvent; import com.sun.source.util.TaskListener; import com.sun.source.util.Trees; import com.sun.tools.javac.api.BasicJavacTask; import com.sun.tools.javac.api.JavacTrees; import com.sun.tools.javac.processing.JavacProcessingEnvironment; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import org.apache.tools.ant.filters.StringInputStream; import xapi.io.X_IO; import xapi.javac.dev.api.CompilerService; import xapi.javac.dev.api.JavacService; import xapi.javac.dev.model.GwtCreateInvocationSite; import xapi.javac.dev.util.ClassLiteralResolver; import xapi.javac.dev.search.GwtCreateSearchVisitor; import xapi.log.X_Log; import xapi.source.X_Source; import xapi.util.X_Util; import javax.annotation.processing.Filer; import javax.lang.model.element.Name; import javax.tools.FileObject; import javax.tools.StandardLocation; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.regex.Pattern; import java.util.stream.Collectors; public class GwtCreatePlugin implements Plugin { @Override public String getName() { return "GwtCreatePlugin"; } static class GwtCreateTransformation { String file; JavacProcessingEnvironment env; Map<int[], GwtCreateInvocationSite> edits; JCCompilationUnit compilationUnit; public GwtCreateTransformation(String file, Map<int[], GwtCreateInvocationSite> edits, JavacProcessingEnvironment env, JCCompilationUnit compilationUnit) { this.file = file; this.env = env; this.compilationUnit = compilationUnit; this.edits = edits; } protected String rebind(GwtCreateInvocationSite edit) { return "new dist."+ edit.getType().toString()+"()"; // For now, no rebinding } public void process() { for (int[] pos : edits.keySet()) { GwtCreateInvocationSite edit = edits.get(pos); int start = pos[0]-7; // Remove the method name, create( start -= 4; // Remove the qualifying GWT. portion; TODO handle static method ref file = file.substring(0, start) + rebind(edit) + file.substring(pos[1]+1); } String originalPackage = String.valueOf(X_Util.firstNotNull(compilationUnit.getPackageName(), "")); String pkg = X_Source.qualifiedName("dist", originalPackage); if (originalPackage.isEmpty()) { file = "package dist;\n\n" + file; } else { final String packageRegex = "package\\s+" + originalPackage.replaceAll("[.]", "[.]") + ";"; file = file.replaceFirst(packageRegex, "package " + pkg+";"); } // TODO get the actual resources directory in use, to check what we're actually doing Filer filer = env.getFiler(); final String fileName = compilationUnit.getSourceFile().getName(); String suffix = originalPackage.replace('.', File.separatorChar) + File.separatorChar; String[] path = fileName.split(Pattern.quote(suffix)); String name = path[path.length-1]; FileObject res; X_Log.info(getClass(), "path", path, "pkg", pkg); try { res = filer.createResource(StandardLocation.SOURCE_OUTPUT, pkg, name); } catch (IOException e) { X_Log.error(getClass(), "Unable to create resource "+ X_Source.qualifiedName(pkg, name)+" to write to", e); throw X_Util.rethrow(e); } // X_Log.info(getClass(), file); // Save a super-sourced copy of this file in the generated directory X_Log.info(getClass(), "Writing to generated file "+res.getName()); try (OutputStream out = res.openOutputStream()){ X_IO.drain(out, new StringInputStream(file)); } catch (IOException e) { X_Log.error(getClass(), "Unable to save resource to "+res+"."+name+" to write to", e); throw X_Util.rethrow(e); } } } List<GwtCreateTransformation> transforms = new ArrayList<>(); @Override public void init(final JavacTask javac, String... args) { final BasicJavacTask task = (BasicJavacTask) javac; final Context context = task.getContext(); final JavacProcessingEnvironment env = JavacProcessingEnvironment.instance(context); final Trees trees = JavacTrees.instance(task); final HashSet<String> seen = new HashSet<>(); final JavacService service = JavacService.instanceFor(task); task.addTaskListener(new TaskListener() { @Override public void started(TaskEvent taskEvent) { X_Log.info(getClass(), taskEvent.getKind(), taskEvent.getCompilationUnit() == null ? "no compilation unit" : taskEvent.getCompilationUnit().getTypeDecls() .stream().map(Tree::getKind).map(Kind::name).collect(Collectors.joining(",")) , taskEvent.getSourceFile() == null ? "no source file" : "File: " + taskEvent.getSourceFile().getName()); } @Override public void finished(TaskEvent taskEvent) { if(taskEvent.getKind() == TaskEvent.Kind.ANALYZE) { JCCompilationUnit compilationUnit = (JCCompilationUnit) taskEvent.getCompilationUnit(); ClassTree cls = service.getClassTree(compilationUnit); final Name simple = cls.getSimpleName(); String pkgName = service.getPackageName(compilationUnit); if (!seen.add(X_Source.qualifiedName(pkgName, simple.toString()))) { return; } if (pkgName.matches( "(com[.]google[.]gwt[.]core[.](?:client|shared))" )) { X_Log.debug(getClass(), "Ignoring class in gwt core/shared packages"); return; } final Context ctx = task.getContext(); CompilerService classWorld = CompilerService.compileServiceFrom(service); final List<GwtCreateInvocationSite> results = new LinkedList<>(); new GwtCreateSearchVisitor(ctx).scan(compilationUnit, results); new ClassLiteralResolver(results, classWorld).scan(compilationUnit, null); if (!results.isEmpty()) { Map<int[], GwtCreateInvocationSite> edits = new TreeMap<>((int[] a, int[] b) -> { assert a.length == 2; assert b.length == 2; return a[0] < b[0]? 1 : a[0] == b[0]? 0 : -1; }); // load the original source file (in UTF-8) results.forEach(gwt -> { ExpressionTree source = gwt.getInvocation(); JCTree ast = (JCTree)source; DiagnosticPosition pos = ast.pos(); int start = pos.getStartPosition(); int end = pos.getEndPosition(compilationUnit.endPositions); edits.put(new int[]{start, end}, gwt); X_Log.info(getClass(), start + ":"+end,gwt); }); String file; try (InputStream in = compilationUnit.getSourceFile().openInputStream()) { file = X_IO.toStringUtf8(in); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e); } GwtCreateTransformation transform = new GwtCreateTransformation(file, edits, env, compilationUnit); classWorld.onFinished(() -> { transform.process(); }); // classWorld.maybeFinish(task); } } } }); } }