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

org.openrewrite.java.Java11Parser Maven / Gradle / Ivy

/*
 * Copyright 2020 the original author or authors.
 * 

* 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 *

* https://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.openrewrite.java; import com.sun.tools.javac.comp.*; import com.sun.tools.javac.file.JavacFileManager; import com.sun.tools.javac.main.JavaCompiler; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Options; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Timer; import org.openrewrite.Formatting; import org.openrewrite.internal.lang.NonNullApi; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.tree.J; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.tools.JavaFileManager; import javax.tools.StandardLocation; import java.io.IOException; import java.io.PrintWriter; import java.io.UncheckedIOException; import java.io.Writer; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.stream.Collectors; import static java.util.stream.Collectors.toList; import static java.util.stream.StreamSupport.stream; /** * This parser is NOT thread-safe, as the OpenJDK parser maintains in-memory caches in static state. */ @NonNullApi public class Java11Parser implements JavaParser { private static final Logger logger = LoggerFactory.getLogger(Java11Parser.class); @Nullable private final List classpath; private final Charset charset; private final MeterRegistry meterRegistry; /** * When true, enables a parser to use class types from the in-memory type cache rather than performing * a deep equality check. Useful when deep class types have already been built from a separate parsing phase * and we want to parse some code snippet without requiring the classpath to be fully specified, using type * information we've already learned about in a prior phase. */ private final boolean relaxedClassTypeMatching; private final JavacFileManager pfm; private final Context context = new Context(); private final JavaCompiler compiler; private final ResettableLog compilerLog = new ResettableLog(context); private final Collection styles; private Java11Parser(@Nullable List classpath, Charset charset, boolean relaxedClassTypeMatching, MeterRegistry meterRegistry, boolean logCompilationWarningsAndErrors, Collection styles) { this.meterRegistry = meterRegistry; this.classpath = classpath; this.charset = charset; this.relaxedClassTypeMatching = relaxedClassTypeMatching; this.styles = styles; this.pfm = new JavacFileManager(context, true, charset); // otherwise, consecutive string literals in binary expressions are concatenated by the parser, losing the original // structure of the expression! Options.instance(context).put("allowStringFolding", "false"); // MUST be created (registered with the context) after pfm and compilerLog compiler = new JavaCompiler(context); // otherwise the JavacParser will use EmptyEndPosTable, effectively setting -1 as the end position // for every tree element compiler.genEndPos = true; compiler.keepComments = true; compilerLog.setWriters(new PrintWriter(new Writer() { @Override public void write(char[] cbuf, int off, int len) { var log = new String(Arrays.copyOfRange(cbuf, off, len)); if (logCompilationWarningsAndErrors && !log.isBlank()) { logger.warn(log); } } @Override public void flush() { } @Override public void close() { } })); } public static Builder builder() { return new Builder(); } @Override public List parse(List sourceFiles, @Nullable Path relativeTo) { if (classpath != null) { // override classpath if (context.get(JavaFileManager.class) != pfm) { throw new IllegalStateException("JavaFileManager has been forked unexpectedly"); } try { pfm.setLocation(StandardLocation.CLASS_PATH, classpath.stream().map(Path::toFile).collect(toList())); } catch (IOException e) { throw new UncheckedIOException(e); } } var fileObjects = pfm.getJavaFileObjects(filterSourceFiles(sourceFiles).toArray(Path[]::new)); var cus = stream(fileObjects.spliterator(), false) .collect(Collectors.toMap( p -> Paths.get(p.toUri()), filename -> Timer.builder("rewrite.parse") .description("The time spent by the JDK in parsing and tokenizing the source file") .tag("file.type", "Java") .tag("step", "JDK parsing") .register(meterRegistry) .record(() -> compiler.parse(filename)), (e2, e1) -> e1, LinkedHashMap::new)); try { initModules(cus.values()); enterAll(cus.values()); // For some reason this is necessary in JDK 9+, where the the internal block counter that // annotationsBlocked() tests against remains >0 after attribution. Annotate annotate = Annotate.instance(context); while (annotate.annotationsBlocked()) { annotate.unblockAnnotations(); // also flushes once unblocked } compiler.attribute(new TimedTodo(compiler.todo)); } catch (Throwable t) { // when symbol entering fails on problems like missing types, attribution can often times proceed // unhindered, but it sometimes cannot (so attribution is always a BEST EFFORT in the presence of errors) logger.warn("Failed symbol entering or attribution", t); } return cus.entrySet().stream().map(cuByPath -> Timer.builder("rewrite.parse") .description("The time spent mapping the OpenJDK AST to Rewrite's AST") .tag("file.type", "Java") .tag("step", "Map to Rewrite AST") .register(meterRegistry) .record(() -> { var path = cuByPath.getKey(); logger.trace("Building AST for {}", path.toAbsolutePath().getFileName()); try { Java11ParserVisitor parser = new Java11ParserVisitor( relativeTo == null ? path : relativeTo.relativize(path), Files.readString(path, charset), relaxedClassTypeMatching, styles); return (J.CompilationUnit) parser.scan(cuByPath.getValue(), Formatting.EMPTY); } catch (IOException e) { throw new UncheckedIOException(e); } }) ).collect(toList()); } @Override public Java11Parser reset() { compilerLog.reset(); pfm.flush(); Check.instance(context).newRound(); Annotate.instance(context).newRound(); Enter.instance(context).newRound(); Modules.instance(context).newRound(); return this; } /** * Initialize modules */ private void initModules(Collection cus) { var modules = Modules.instance(context); modules.initModules(com.sun.tools.javac.util.List.from(cus)); } /** * Enter symbol definitions into each compilation unit's scope */ private void enterAll(Collection cus) { var enter = Enter.instance(context); var compilationUnits = com.sun.tools.javac.util.List.from( cus.toArray(JCTree.JCCompilationUnit[]::new)); enter.main(compilationUnits); } private static class ResettableLog extends Log { protected ResettableLog(Context context) { super(context); } public void reset() { sourceMap.clear(); } } private List filterSourceFiles(List sourceFiles) { return sourceFiles.stream().filter(source -> source.getFileName().toString().endsWith(".java")) .collect(Collectors.toList()); } private class TimedTodo extends Todo { private final Todo todo; private Timer.Sample sample; private TimedTodo(Todo todo) { super(new Context()); this.todo = todo; } @Override public boolean isEmpty() { if (sample != null) { sample.stop(Timer.builder("rewrite.parse") .description("The time spent by the JDK in type attributing the source file") .tag("file.type", "Java") .tag("step", "Type attribution") .register(meterRegistry)); } return todo.isEmpty(); } @Override public Env remove() { this.sample = Timer.start(); return todo.remove(); } } public static class Builder extends JavaParser.Builder { @Override public Java11Parser build() { return new Java11Parser(classpath, charset, relaxedClassTypeMatching, meterRegistry, logCompilationWarningsAndErrors, styles); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy