-
Notifications
You must be signed in to change notification settings - Fork 156
Temp plugin PR for discussion #2273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
david-beaumont
wants to merge
17
commits into
openjdk:lworld
Choose a base branch
from
david-beaumont:temp_plugin
base: lworld
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
ce03d3d
Annotation processing for generating value class sources
david-beaumont 1151df6
Tidying up
david-beaumont 89841e1
Add copyright
david-beaumont 8f9c78c
Working tests and moved processor
david-beaumont e73f516
More tests and tidying
david-beaumont 9fd0a04
rewrite test to allow skipping
david-beaumont 10c3b28
feedback
david-beaumont a2d1dd8
better error handling
david-beaumont e27349e
better error handling
david-beaumont 60cf891
Simpler temp directory
david-beaumont 2aeca48
better but still not working
david-beaumont d0cc446
better compile macro call - still not working
david-beaumont f12c712
feedback updates and tidying
david-beaumont 18a5e21
Finally working
david-beaumont 1423745
Remove unwanted lint warning suppression
david-beaumont 75aa3b0
Temporary plugin work
david-beaumont ccf24ac
Fix plugin name
david-beaumont File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
225 changes: 225 additions & 0 deletions
225
make/jdk/src/classes/build/tools/valhalla/valuetypes/GenValueClassPlugin.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,225 @@ | ||
| package build.tools.valhalla.valuetypes; | ||
|
|
||
| import com.sun.source.tree.CompilationUnitTree; | ||
| 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.tools.javac.tree.JCTree; | ||
| import com.sun.tools.javac.tree.JCTree.JCClassDecl; | ||
| import com.sun.tools.javac.tree.TreeInfo; | ||
| import com.sun.tools.javac.tree.TreeScanner; | ||
|
|
||
| import javax.tools.Diagnostic; | ||
| import javax.tools.FileObject; | ||
| import java.io.File; | ||
| import java.io.IOException; | ||
| import java.io.InputStreamReader; | ||
| import java.io.OutputStreamWriter; | ||
| import java.io.Reader; | ||
| import java.io.Writer; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Objects; | ||
|
|
||
| import static java.nio.file.StandardOpenOption.CREATE; | ||
| import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; | ||
|
|
||
| /** | ||
| * Plugin for generating preview sources of classes annotated as value classes | ||
| * for preview mode. | ||
| * | ||
| * <p>Classes seen by this plugin (annotated with {@code @MigratedValueClass} | ||
| * will have their source files re-written into the specified output directory | ||
| * for compilation as preview classes. Note that more than one class in a given | ||
| * source file may be annotated. | ||
| * | ||
| * <p>Class re-writing is achieved by injecting the "value" keyword in front of | ||
| * class declarations for all annotated elements in the original source file. | ||
| * | ||
| * <p>Note that there are two annotations in use for value classes, but since | ||
| * we must generate sources for abstract classes, we only process one of them. | ||
| * <ul> | ||
| * <li>{@code @jdk.internal.ValueBased} appears on concrete value classes. | ||
| * <li>{@code @jdk.internal.MigratedValueClass} appears on concrete and | ||
| * abstract value classes. | ||
| * </ul> | ||
| */ | ||
| public final class GenValueClassPlugin implements Plugin { | ||
| private static final String VALUE_CLASS_ANNOTATION = "@jdk.internal.MigratedValueClass"; | ||
|
|
||
| @Override | ||
| public String getName() { | ||
| return "GenValueClassPlugin"; | ||
| } | ||
|
|
||
| @Override | ||
| public void init(JavacTask task, String... args) { | ||
| if (args.length == 0) { | ||
| throw new IllegalArgumentException("Plugin " + getName() + ": missing output directory argument"); | ||
| } | ||
| Path outDir = Path.of(args[0]); | ||
| if (!Files.isDirectory(outDir)) { | ||
| throw new IllegalArgumentException("Plugin " + getName() + ": no such output directory: " + outDir); | ||
| } | ||
| task.addTaskListener(new ValueClassGenerator(outDir)); | ||
| } | ||
|
|
||
| private record ValueClassGenerator(Path outDir) implements TaskListener { | ||
| @Override | ||
| public void finished(TaskEvent e) { | ||
| CompilationUnitTree compilation = e.getCompilationUnit(); | ||
|
|
||
| List<JCClassDecl> classes = new ArrayList<>(); | ||
| new TreeScanner() { | ||
| @Override | ||
| public void visitClassDef(JCClassDecl cls) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even if the test for "having an annotation" isn't working, the code should just result in nothing happening, but that's not the case, it just fails (without any useful output). |
||
| boolean hasAnnotation = cls.getModifiers().getAnnotations().stream() | ||
| .peek(a -> System.out.println("--> " + a)) | ||
| .anyMatch(a -> a.toString().equals(VALUE_CLASS_ANNOTATION)); | ||
| if (hasAnnotation) { | ||
| classes.add(cls); | ||
| } | ||
| super.visitClassDef(cls); | ||
| } | ||
| }.scan((JCTree) compilation); | ||
|
|
||
| if (!classes.isEmpty()) { | ||
| Path srcPath = filePath(compilation.getSourceFile()); | ||
| String moduleName = compilation.getModule().getName().toString(); | ||
| String packageName = compilation.getPackage().toString(); | ||
| generateValueClassSource(srcPath, moduleName, packageName, classes); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Write a transformed version of the given Java source file with the | ||
| * {@code value} keyword inserted before the class declaration of each | ||
| * annotated type element. | ||
| */ | ||
| private void generateValueClassSource( | ||
| Path srcPath, String moduleName, String packageName, List<JCClassDecl> classes) { | ||
|
|
||
| System.out.println("Module: " + moduleName); | ||
| System.out.println("Package: " + packageName); | ||
| System.out.println("Classes: " + classes.stream().map(c -> c.type.toString()).toList()); | ||
|
|
||
| Path relPath = moduleRelativePath(srcPath, packageName); | ||
| Path outPath = outDir.resolve(moduleName).resolve(relPath); | ||
|
|
||
| System.out.println("Out path: " + outPath); | ||
|
|
||
| try { | ||
| Files.createDirectories(outPath.getParent()); | ||
|
|
||
| List<Integer> insertPositions = | ||
| classes.stream().map(this::valueKeywordInsertPosition).sorted().toList(); | ||
|
|
||
| System.out.println("Insert positions: " + insertPositions); | ||
|
|
||
| // For partial rebuilds, generated sources may still exist, so we overwrite them. | ||
| try (Reader reader = new InputStreamReader(Files.newInputStream(srcPath)); | ||
| Writer output = new OutputStreamWriter( | ||
| Files.newOutputStream(outPath, CREATE, TRUNCATE_EXISTING))) { | ||
| int curPos = 0; | ||
| for (int nxtPos : insertPositions) { | ||
| int nextChunkLen = nxtPos - curPos; | ||
| long written = new LimitedReader(reader, nextChunkLen).transferTo(output); | ||
| if (written != nextChunkLen) { | ||
| throw new IOException("Unexpected number of characters transferred." | ||
| + " Expected " + nextChunkLen + " but was " + written); | ||
| } | ||
| curPos = nxtPos; | ||
| // curPos is at the end of the modifier section, so add a leading space. | ||
| // curPos ---v | ||
| // [modifiers] class... -->> [modifiers] value class... | ||
| output.write(" value"); | ||
| } | ||
| // Trailing section to end-of-file transferred from original reader. | ||
| reader.transferTo(output); | ||
| } | ||
| } catch (IOException e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
|
|
||
| private Path moduleRelativePath(Path srcPath, String pkgName) { | ||
| Path relPath = Path.of(pkgName.replace('.', File.separatorChar)).resolve(srcPath.getFileName()); | ||
| if (!srcPath.endsWith(relPath)) { | ||
| throw new IllegalStateException(String.format( | ||
| "Expected trailing path %s for source file %s", relPath, srcPath)); | ||
| } | ||
| return relPath; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the character offset in the original source file at which to insert | ||
| * the {@code value} keyword. The offset is the end of the modifiers section, | ||
| * which must immediately precede the class declaration. | ||
| */ | ||
| private int valueKeywordInsertPosition(JCClassDecl classDecl) { | ||
| int pos = TreeInfo.getStartPos(classDecl.getModifiers()); | ||
| if (pos == Diagnostic.NOPOS) { | ||
| throw new IllegalStateException("Missing position information: " + classDecl); | ||
| } | ||
| return pos; | ||
| } | ||
| } | ||
|
|
||
| private static Path filePath(FileObject file) { | ||
| return Path.of(file.toUri()); | ||
| } | ||
|
|
||
| /** | ||
| * A forwarding reader which guarantees to read no more than | ||
| * {@code maxCharCount} characters from the underlying stream. | ||
| */ | ||
| private static final class LimitedReader extends Reader { | ||
| // These are short-lived, no need to null the delegate when closed. | ||
| private final Reader delegate; | ||
| // This should never go negative. | ||
| private int remainingChars; | ||
|
|
||
| /** | ||
| * Creates a limited reader which reads up to {@code maxCharCount} chars | ||
| * from the given stream. | ||
| * | ||
| * @param delegate underlying reader | ||
| * @param maxCharCount maximum chars to read (can be 0) | ||
| */ | ||
| LimitedReader(Reader delegate, int maxCharCount) { | ||
| this.delegate = Objects.requireNonNull(delegate); | ||
| this.remainingChars = Math.max(maxCharCount, 0); | ||
| } | ||
|
|
||
| @Override | ||
| public int read(char[] cbuf, int off, int len) throws IOException { | ||
| if (remainingChars > 0) { | ||
| int readLimit = Math.min(remainingChars, len); | ||
| int count = delegate.read(cbuf, off, readLimit); | ||
| // Only update remainingChars if something was read. | ||
| if (count > 0) { | ||
| if (count > remainingChars) { | ||
| throw new IOException( | ||
| "Underlying Reader exceeded requested read limit." + | ||
| " Expected at most " + readLimit + " but read " + count); | ||
| } | ||
| remainingChars -= count; | ||
| } | ||
| // Can return 0 or -1 here (the underlying reader could finish first). | ||
| return count; | ||
| } else if (remainingChars == 0) { | ||
| return -1; | ||
| } else { | ||
| throw new AssertionError("Remaining character count should never be negative!"); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void close() { | ||
| // Do not close the delegate since this is conceptually just a view. | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if BUILD_VALUETYPE_GENSRC here has to match the one in ToolsJdk.gmk, but I assume so for now.
Might even be worth a plugin macro/rule since there are now 2 cases and generating the service provider stuff could be made common.