From 07c9984a89001c1c1197b542579c1615211e58ee Mon Sep 17 00:00:00 2001 From: Rob Stryker Date: Mon, 8 Jul 2024 04:18:06 -0400 Subject: [PATCH] An extension point allowing third parties to override source to dom tree implementations (#2560) This extension point would allow third party products to override the conversion between java source files and the JDT dom tree with a custom implementation. In order to prevent rogue extenders from simply breaking people's installations, a system property selecting the preferred ICompilationUnitResolver must be set, as well. Signed-off-by: Rob Stryker Co-authored-by: Rob Stryker Co-authored-by: Mickael Istria --- org.eclipse.jdt.core.tests.model/plugin.xml | 7 + .../CompilationUnitResolverDiscoveryTest.java | 193 ++++++++++++++++++ .../jdt/core/tests/dom/RunConverterTests.java | 5 +- .../org/eclipse/jdt/core/dom/ASTParser.java | 91 +++------ .../jdt/core/dom/CompilationUnitResolver.java | 103 ++++++++++ .../dom/CompilationUnitResolverDiscovery.java | 91 +++++++++ .../core/dom/ICompilationUnitResolver.java | 123 +++++++++++ org.eclipse.jdt.core/plugin.properties | 1 + org.eclipse.jdt.core/plugin.xml | 8 + .../schema/compilationUnitResolver.exsd | 121 +++++++++++ 10 files changed, 684 insertions(+), 59 deletions(-) create mode 100644 org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/CompilationUnitResolverDiscoveryTest.java create mode 100644 org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolverDiscovery.java create mode 100644 org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/ICompilationUnitResolver.java create mode 100644 org.eclipse.jdt.core/schema/compilationUnitResolver.exsd diff --git a/org.eclipse.jdt.core.tests.model/plugin.xml b/org.eclipse.jdt.core.tests.model/plugin.xml index e01bda0978d..5e1f96c1b7d 100644 --- a/org.eclipse.jdt.core.tests.model/plugin.xml +++ b/org.eclipse.jdt.core.tests.model/plugin.xml @@ -92,5 +92,12 @@ + + + + diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/CompilationUnitResolverDiscoveryTest.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/CompilationUnitResolverDiscoveryTest.java new file mode 100644 index 00000000000..4191dddd080 --- /dev/null +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/CompilationUnitResolverDiscoveryTest.java @@ -0,0 +1,193 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat Inc and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat Inc - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.core.tests.dom; + +import java.util.List; +import java.util.Map; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.WorkingCopyOwner; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ASTRequestor; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.FileASTRequestor; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.PackageDeclaration; +import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; +import org.eclipse.jdt.internal.core.dom.ICompilationUnitResolver; + +import junit.framework.Test; + +public class CompilationUnitResolverDiscoveryTest extends ConverterTestSetup { + + ICompilationUnit workingCopy; + + @Override + public void setUpSuite() throws Exception { + super.setUpSuite(); + this.ast = AST.newAST(AST.getJLSLatest(), false); + } + + public CompilationUnitResolverDiscoveryTest(String name) { + super(name); + } + + public static Test suite() { + String javaVersion = System.getProperty("java.version"); + int index = -1; + if ( (index = javaVersion.indexOf('-')) != -1) { + javaVersion = javaVersion.substring(0, index); + } else { + if (javaVersion.length() > 3) { + javaVersion = javaVersion.substring(0, 3); + } + } + long jdkLevel = CompilerOptions.versionToJdkLevel(javaVersion); + if (jdkLevel >= ClassFileConstants.JDK9) { + isJRE9 = true; + } + return buildModelTestSuite(CompilationUnitResolverDiscoveryTest.class); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + if (this.workingCopy != null) { + this.workingCopy.discardWorkingCopy(); + this.workingCopy = null; + } + } + + public void testCompilationUnitResolverNoSysprop() throws JavaModelException { + String SELECTED_SYSPROP = "ICompilationUnitResolver"; + String original = System.getProperty(SELECTED_SYSPROP); + try { + System.clearProperty(SELECTED_SYSPROP); + ICompilationUnit sourceUnit = getCompilationUnit("Converter9" , "src", "testBug497719_001", "X.java"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + ASTNode result = runConversion(this.ast.apiLevel(), sourceUnit, true, true); + assertTrue("Not a compilation unit", result.getNodeType() == ASTNode.COMPILATION_UNIT); + CompilationUnit compilationUnit = (CompilationUnit) result; + ASTNode node = getASTNode(compilationUnit, 0, 0); + assertEquals("Not a compilation unit", ASTNode.METHOD_DECLARATION, node.getNodeType()); + MethodDeclaration methodDeclaration = (MethodDeclaration) node; + IMethodBinding mb = methodDeclaration.resolveBinding(); + assertEquals(mb.getClass().getName(), "org.eclipse.jdt.core.dom.MethodBinding"); + } finally { + if (original == null) { + System.clearProperty(SELECTED_SYSPROP); + } else { + System.setProperty(SELECTED_SYSPROP, original); + } + } + } + + public void testCompilationUnitResolverInvalidSysprop() throws JavaModelException { + String SELECTED_SYSPROP = "ICompilationUnitResolver"; + String original = System.getProperty(SELECTED_SYSPROP); + try { + System.setProperty(SELECTED_SYSPROP, "doesNotExist"); + ICompilationUnit sourceUnit = getCompilationUnit("Converter9" , "src", "testBug497719_001", "X.java"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + ASTNode result = runConversion(this.ast.apiLevel(), sourceUnit, true, true); + assertTrue("Not a compilation unit", result.getNodeType() == ASTNode.COMPILATION_UNIT); + CompilationUnit compilationUnit = (CompilationUnit) result; + ASTNode node = getASTNode(compilationUnit, 0, 0); + assertEquals("Not a compilation unit", ASTNode.METHOD_DECLARATION, node.getNodeType()); + MethodDeclaration methodDeclaration = (MethodDeclaration) node; + IMethodBinding mb = methodDeclaration.resolveBinding(); + assertEquals(mb.getClass().getName(), "org.eclipse.jdt.core.dom.MethodBinding"); + } finally { + if (original == null) { + System.clearProperty(SELECTED_SYSPROP); + } else { + System.setProperty(SELECTED_SYSPROP, original); + } + } + } + + public void testCompilationUnitResolverValidSysprop() throws JavaModelException { + String SELECTED_SYSPROP = "ICompilationUnitResolver"; + String original = System.getProperty(SELECTED_SYSPROP); + try { + System.setProperty(SELECTED_SYSPROP, "org.eclipse.jdt.core.tests.model.resolver1"); + ICompilationUnit sourceUnit = getCompilationUnit("Converter9" , "src", "testBug497719_001", "X.java"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + ASTNode result = runConversion(this.ast.apiLevel(), sourceUnit, true, true); + assertTrue("Not a compilation unit", result.getNodeType() == ASTNode.COMPILATION_UNIT); + CompilationUnit compilationUnit = (CompilationUnit) result; + PackageDeclaration pd = compilationUnit.getPackage(); + assertNotNull(pd); + String pdName = pd.getName().toString(); + assertEquals(pdName, "compilationUnitResolverDiscoveryTest"); + } finally { + if (original == null) { + System.clearProperty(SELECTED_SYSPROP); + } else { + System.setProperty(SELECTED_SYSPROP, original); + } + } + } + + + /* + * Custom made interface that implements exactly to the test + */ + public static final class TEST_RESOLVER implements ICompilationUnitResolver { + @Override + public CompilationUnit toCompilationUnit(org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit, + boolean initialNeedsToResolveBinding, IJavaProject project, List classpaths, + int focalPosition, int apiLevel, Map compilerOptions, + WorkingCopyOwner parsedUnitWorkingCopyOwner, WorkingCopyOwner typeRootWorkingCopyOwner, int flags, + IProgressMonitor monitor) { + // Return a mostly-invalid hard-coded dom tree for the purpose of this test + AST ast = AST.newAST(apiLevel, JavaCore.ENABLED.equals(compilerOptions.get(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES))); + CompilationUnit res = ast.newCompilationUnit(); + PackageDeclaration pack = ast.newPackageDeclaration(); + pack.setName(ast.newSimpleName("compilationUnitResolverDiscoveryTest")); + res.setPackage(pack); + return res; + } + + @Override + public void resolve(String[] sourceFilePaths, String[] encodings, String[] bindingKeys, + FileASTRequestor requestor, int apiLevel, Map compilerOptions, + List classpathList, int flags, IProgressMonitor monitor) { + } + + @Override + public void parse(ICompilationUnit[] compilationUnits, ASTRequestor requestor, int apiLevel, + Map compilerOptions, int flags, IProgressMonitor monitor) { + // irrelevant for test + } + + @Override + public void parse(String[] sourceFilePaths, String[] encodings, FileASTRequestor requestor, int apiLevel, + Map compilerOptions, int flags, IProgressMonitor monitor) { + // irrelevant for test + } + + @Override + public void resolve(ICompilationUnit[] compilationUnits, String[] bindingKeys, ASTRequestor requestor, + int apiLevel, Map compilerOptions, IJavaProject project, + WorkingCopyOwner workingCopyOwner, int flags, IProgressMonitor monitor) { + // irrelevant for test + } + } +} diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/RunConverterTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/RunConverterTests.java index 27d16366a8d..bff6b2b5a00 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/RunConverterTests.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/dom/RunConverterTests.java @@ -16,9 +16,11 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; + +import org.eclipse.jdt.core.tests.junit.extension.TestCase; + import junit.framework.Test; import junit.framework.TestSuite; -import org.eclipse.jdt.core.tests.junit.extension.TestCase; @SuppressWarnings({"rawtypes", "unchecked"}) public class RunConverterTests extends junit.framework.TestCase { @@ -61,6 +63,7 @@ public static Class[] getAllTestClasses() { ASTConverterStringTemplateTest.class, ASTConverterSuperAfterStatements.class, ASTConverterEitherOrMultiPatternTest.class, + CompilationUnitResolverDiscoveryTest.class }; } public static Test suite() { diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ASTParser.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ASTParser.java index 0a247a25dd1..f49ee7d596d 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ASTParser.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ASTParser.java @@ -17,6 +17,8 @@ import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -33,7 +35,6 @@ import org.eclipse.jdt.core.WorkingCopyOwner; import org.eclipse.jdt.core.compiler.CategorizedProblem; import org.eclipse.jdt.core.compiler.CharOperation; -import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall; import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath; @@ -49,6 +50,7 @@ import org.eclipse.jdt.internal.core.DefaultWorkingCopyOwner; import org.eclipse.jdt.internal.core.JavaModelManager; import org.eclipse.jdt.internal.core.PackageFragment; +import org.eclipse.jdt.internal.core.dom.ICompilationUnitResolver; import org.eclipse.jdt.internal.core.dom.util.DOMASTUtil; import org.eclipse.jdt.internal.core.util.CodeSnippetParsingUtil; import org.eclipse.jdt.internal.core.util.RecordedParsingInformation; @@ -221,6 +223,8 @@ public static ASTParser newParser(int level) { */ private int bits; + private final ICompilationUnitResolver unitResolver; + /** * Creates a new AST parser for the given API level. *

@@ -233,6 +237,7 @@ public static ASTParser newParser(int level) { ASTParser(int level) { DOMASTUtil.checkASTLevel(level); this.apiLevel = level; + this.unitResolver = CompilationUnitResolverDiscovery.getInstance(); initializeDefaults(); } @@ -245,10 +250,13 @@ private List getClasspath() throws IllegalStateException { } if (this.sourcepaths != null) { for (int i = 0, max = this.sourcepaths.length; i < max; i++) { - String encoding = this.sourcepathsEncodings == null ? null : this.sourcepathsEncodings[i]; - main.processPathEntries( - Main.DEFAULT_SIZE_CLASSPATH, - allClasspaths, this.sourcepaths[i], encoding, true, false); + String sourcePath = this.sourcepaths[i]; + if (sourcePath != null) { + String encoding = this.sourcepathsEncodings == null ? null : this.sourcepathsEncodings[i]; + main.processPathEntries( + Main.DEFAULT_SIZE_CLASSPATH, + allClasspaths, sourcePath, encoding, true, false); + } } } if (this.classpaths != null) { @@ -957,9 +965,9 @@ public void createASTs(ICompilationUnit[] compilationUnits, String[] bindingKeys if ((this.bits & CompilationUnitResolver.BINDING_RECOVERY) != 0) { flags |= ICompilationUnit.ENABLE_BINDINGS_RECOVERY; } - CompilationUnitResolver.resolve(compilationUnits, bindingKeys, requestor, this.apiLevel, this.compilerOptions, this.project, this.workingCopyOwner, flags, monitor); + this.unitResolver.resolve(safeCopyOf(compilationUnits), safeCopyOf(bindingKeys), requestor, this.apiLevel, safeUnmodifiableMap(this.compilerOptions), this.project, this.workingCopyOwner, flags, monitor); } else { - CompilationUnitResolver.parse(compilationUnits, requestor, this.apiLevel, this.compilerOptions, flags, monitor); + this.unitResolver.parse(safeCopyOf(compilationUnits), requestor, this.apiLevel, safeUnmodifiableMap(this.compilerOptions), flags, monitor); } } finally { // reset to defaults to allow reuse (and avoid leaking) @@ -967,6 +975,14 @@ public void createASTs(ICompilationUnit[] compilationUnits, String[] bindingKeys } } + private static T[] safeCopyOf(T[] original) { + return original == null ? null : Arrays.copyOf(original, original.length); + } + + private static Map safeUnmodifiableMap(Map m) { + return m == null ? null : Collections.unmodifiableMap(m); + } + /** * Creates ASTs for a batch of compilation units. * When bindings are being resolved, processing a @@ -1052,9 +1068,9 @@ public void createASTs(String[] sourceFilePaths, String[] encodings, String[] bi if ((this.bits & CompilationUnitResolver.BINDING_RECOVERY) != 0) { flags |= ICompilationUnit.ENABLE_BINDINGS_RECOVERY; } - CompilationUnitResolver.resolve(sourceFilePaths, encodings, bindingKeys, requestor, this.apiLevel, this.compilerOptions, getClasspath(), flags, monitor); + this.unitResolver.resolve(safeCopyOf(sourceFilePaths), safeCopyOf(encodings), safeCopyOf(bindingKeys), requestor, this.apiLevel, safeUnmodifiableMap(this.compilerOptions), getClasspath(), flags, monitor); } else { - CompilationUnitResolver.parse(sourceFilePaths, encodings, requestor, this.apiLevel, this.compilerOptions, flags, monitor); + this.unitResolver.parse(safeCopyOf(sourceFilePaths), safeCopyOf(encodings), requestor, this.apiLevel, safeUnmodifiableMap(this.compilerOptions), flags, monitor); } } finally { // reset to defaults to allow reuse (and avoid leaking) @@ -1159,9 +1175,8 @@ private ASTNode internalCreateASTCached(IProgressMonitor monitor) { } break; case K_COMPILATION_UNIT : - CompilationUnitDeclaration compilationUnitDeclaration = null; try { - NodeSearcher searcher = null; + boolean useSearcher = false; org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit = null; WorkingCopyOwner wcOwner = this.workingCopyOwner; if (this.typeRoot instanceof ClassFileWorkingCopy) { @@ -1229,68 +1244,28 @@ private ASTNode internalCreateASTCached(IProgressMonitor monitor) { throw new IllegalStateException(); } if ((this.bits & CompilationUnitResolver.PARTIAL) != 0) { - searcher = new NodeSearcher(this.focalPointPosition); + useSearcher = true; } int flags = 0; if ((this.bits & CompilationUnitResolver.STATEMENT_RECOVERY) != 0) { flags |= ICompilationUnit.ENABLE_STATEMENTS_RECOVERY; } - if (searcher == null && ((this.bits & CompilationUnitResolver.IGNORE_METHOD_BODIES) != 0)) { + if (!useSearcher && ((this.bits & CompilationUnitResolver.IGNORE_METHOD_BODIES) != 0)) { flags |= ICompilationUnit.IGNORE_METHOD_BODIES; } + if (needToResolveBindings) { if ((this.bits & CompilationUnitResolver.BINDING_RECOVERY) != 0) { flags |= ICompilationUnit.ENABLE_BINDINGS_RECOVERY; } - try { - // parse and resolve - compilationUnitDeclaration = - CompilationUnitResolver.resolve( - sourceUnit, - this.project, - getClasspath(), - searcher, - this.compilerOptions, - this.workingCopyOwner, - flags, - monitor); - } catch (JavaModelException e) { - flags &= ~ICompilationUnit.ENABLE_BINDINGS_RECOVERY; - compilationUnitDeclaration = CompilationUnitResolver.parse( - sourceUnit, - searcher, - this.compilerOptions, - flags); - needToResolveBindings = false; - } - } else { - compilationUnitDeclaration = CompilationUnitResolver.parse( - sourceUnit, - searcher, - this.compilerOptions, - flags, - this.project); - needToResolveBindings = false; } - CompilationUnit result = CompilationUnitResolver.convert( - compilationUnitDeclaration, - sourceUnit.getContents(), - this.apiLevel, - this.compilerOptions, - needToResolveBindings, - wcOwner, - needToResolveBindings ? new DefaultBindingResolver.BindingTables() : null, - flags, - monitor, - this.project != null, - this.project); + + CompilationUnit result = this.unitResolver.toCompilationUnit(sourceUnit, needToResolveBindings, this.project, getClasspath(), useSearcher ? this.focalPointPosition : -1, this.apiLevel, safeUnmodifiableMap(this.compilerOptions), this.workingCopyOwner, wcOwner, flags, monitor); result.setTypeRoot(this.typeRoot); return result; } finally { - if (compilationUnitDeclaration != null - && ((this.bits & CompilationUnitResolver.RESOLVE_BINDING) != 0)) { - compilationUnitDeclaration.cleanUp(); - } + // unitResolver should already handle this. + // Leaving this finally in place to avoid changing indentation } } throw new IllegalStateException(); diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolver.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolver.java index 34fedcae59c..7f4aaa5d881 100644 --- a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolver.java +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolver.java @@ -76,12 +76,59 @@ import org.eclipse.jdt.internal.core.NameLookup; import org.eclipse.jdt.internal.core.SourceRefElement; import org.eclipse.jdt.internal.core.SourceTypeElementInfo; +import org.eclipse.jdt.internal.core.dom.ICompilationUnitResolver; import org.eclipse.jdt.internal.core.util.BindingKeyResolver; import org.eclipse.jdt.internal.core.util.CommentRecorderParser; import org.eclipse.jdt.internal.core.util.DOMFinder; @SuppressWarnings({ "rawtypes", "unchecked" }) class CompilationUnitResolver extends Compiler { + + private static final class ECJCompilationUnitResolver implements ICompilationUnitResolver { + + @Override + public void resolve(String[] sourceFilePaths, String[] encodings, String[] bindingKeys, + FileASTRequestor requestor, int apiLevel, Map compilerOptions, List classpath, + int flags, IProgressMonitor monitor) { + CompilationUnitResolver.resolve(sourceFilePaths, encodings, bindingKeys, requestor, apiLevel, compilerOptions, classpath, flags, monitor); + } + + @Override + public void parse(ICompilationUnit[] compilationUnits, ASTRequestor requestor, int apiLevel, + Map compilerOptions, int flags, IProgressMonitor monitor) { + CompilationUnitResolver.parse(compilationUnits, requestor, apiLevel, compilerOptions, flags, monitor); + } + + @Override + public void parse(String[] sourceFilePaths, String[] encodings, FileASTRequestor requestor, int apiLevel, + Map compilerOptions, int flags, IProgressMonitor monitor) { + CompilationUnitResolver.parse(sourceFilePaths, encodings, requestor, apiLevel, compilerOptions, flags, monitor); + } + + @Override + public void resolve(ICompilationUnit[] compilationUnits, String[] bindingKeys, ASTRequestor requestor, + int apiLevel, Map compilerOptions, IJavaProject project, + WorkingCopyOwner workingCopyOwner, int flags, IProgressMonitor monitor) { + CompilationUnitResolver.resolve(compilationUnits, bindingKeys, requestor, apiLevel, compilerOptions, project, workingCopyOwner, flags, monitor); + } + + @Override + public CompilationUnit toCompilationUnit(org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit, final boolean initialNeedsToResolveBinding, IJavaProject project, List classpaths, int focalPosition, + int apiLevel, Map compilerOptions, WorkingCopyOwner parsedUnitWorkingCopyOwner, WorkingCopyOwner typeRootWorkingCopyOwner, int flags, IProgressMonitor monitor) { + return CompilationUnitResolver.toCompilationUnit(sourceUnit, initialNeedsToResolveBinding, project, + classpaths, focalPosition == -1 ? null : new NodeSearcher(focalPosition), apiLevel, compilerOptions, parsedUnitWorkingCopyOwner, typeRootWorkingCopyOwner, flags, monitor); + } + } + + private static ECJCompilationUnitResolver FACADE; + public static synchronized ICompilationUnitResolver getInstance() { + if (FACADE == null) { + FACADE = new ECJCompilationUnitResolver(); + } + return FACADE; + } + + public static final int RESOLVE_BINDING = 0x1; public static final int PARTIAL = 0x2; public static final int STATEMENT_RECOVERY = 0x4; @@ -1408,6 +1455,62 @@ public CompilationUnitDeclaration resolve( generateCode); } + public static CompilationUnit toCompilationUnit(org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit, final boolean initialNeedsToResolveBinding, IJavaProject project, List classpaths, NodeSearcher nodeSearcher, + int apiLevel, Map compilerOptions, WorkingCopyOwner parsedUnitWorkingCopyOwner, WorkingCopyOwner typeRootWorkingCopyOwner, int flags, IProgressMonitor monitor) { + // this -> astParser, pass as args + CompilationUnitDeclaration compilationUnitDeclaration = null; + boolean needsToResolveBindingsState = initialNeedsToResolveBinding; + try { + if (initialNeedsToResolveBinding) { + try { + // parse and resolve + compilationUnitDeclaration = + CompilationUnitResolver.resolve( + sourceUnit, + project, + classpaths, + nodeSearcher, + compilerOptions, + parsedUnitWorkingCopyOwner, + flags, + monitor); + } catch (JavaModelException e) { + flags &= ~ICompilationUnit.ENABLE_BINDINGS_RECOVERY; + compilationUnitDeclaration = CompilationUnitResolver.parse( + sourceUnit, + nodeSearcher, + compilerOptions, + flags); + needsToResolveBindingsState = false; + } + } else { + compilationUnitDeclaration = CompilationUnitResolver.parse( + sourceUnit, + nodeSearcher, + compilerOptions, + flags, + project); + needsToResolveBindingsState = false; + } + return CompilationUnitResolver.convert( + compilationUnitDeclaration, + sourceUnit.getContents(), + apiLevel, + compilerOptions, + needsToResolveBindingsState, + typeRootWorkingCopyOwner, + needsToResolveBindingsState ? new DefaultBindingResolver.BindingTables() : null, + flags, + monitor, + project != null, + project); + } finally { + if (compilationUnitDeclaration != null && initialNeedsToResolveBinding) { + compilationUnitDeclaration.cleanUp(); + } + } + } + private void worked(int work) { if (this.monitor != null) { if (this.monitor.isCanceled()) diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolverDiscovery.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolverDiscovery.java new file mode 100644 index 00000000000..84ac7afcdf6 --- /dev/null +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/CompilationUnitResolverDiscovery.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jdt.core.dom; + +import java.util.Objects; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExtension; +import org.eclipse.core.runtime.IExtensionPoint; +import org.eclipse.core.runtime.ILog; +import org.eclipse.core.runtime.Platform; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.internal.core.dom.ICompilationUnitResolver; + +class CompilationUnitResolverDiscovery { + private static final String SELECTED_SYSPROP = "ICompilationUnitResolver"; //$NON-NLS-1$ + private static final String COMPILATION_UNIT_RESOLVER_EXTPOINT_ID = "compilationUnitResolver" ; //$NON-NLS-1$ + private static boolean ERROR_LOGGED = false; + + private static String lastId; + private static IConfigurationElement lastExtension; + + static ICompilationUnitResolver getInstance() { + String id = System.getProperty(SELECTED_SYSPROP); + IConfigurationElement configElement = getConfigurationElement(id); + lastId = id; + lastExtension = configElement; + if (configElement != null) { + try { + // We do prefer creating a new instance on each call, as it can allow the extension + // to store some state more easily than by using a singleton. + Object executableExtension = configElement.createExecutableExtension("class"); //$NON-NLS-1$ + if (executableExtension instanceof ICompilationUnitResolver icur) { + return icur; + } + } catch (CoreException e) { + if (!setErrorLogged()) { + ILog.get().error("Could not instantiate ICompilationUnitResolver: '" + id + "' with class: " + configElement.getAttribute("class"), e); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + } + } + return CompilationUnitResolver.getInstance(); + } + + /** + * + * @param id + * @return The extension element with the given id or null if not found + */ + private static IConfigurationElement getConfigurationElement(String id) { + if (id == null || id.isBlank()) { + return null; + } + if (lastExtension != null && Objects.equals(id, lastId)) { + return lastExtension; + } + IExtensionPoint extension = Platform.getExtensionRegistry().getExtensionPoint(JavaCore.PLUGIN_ID, COMPILATION_UNIT_RESOLVER_EXTPOINT_ID); + if (extension != null) { + IExtension[] extensions = extension.getExtensions(); + for (IExtension ext : extensions) { + IConfigurationElement[] configElements = ext.getConfigurationElements(); + for (final IConfigurationElement configElement : configElements) { + String elementId = configElement.getAttribute("id"); //$NON-NLS-1$ + if (id.equals(elementId) && "resolver".equals(configElement.getName())) { //$NON-NLS-1$ + return configElement; + } + } + } + } + return null; + } + + /** + * Set the ERROR_LOGGED field to true. + * @return the previous value of ERROR_LOGGED. + */ + private static synchronized boolean setErrorLogged() { + boolean prev = ERROR_LOGGED; + ERROR_LOGGED = true; + return prev; + } +} diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/ICompilationUnitResolver.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/ICompilationUnitResolver.java new file mode 100644 index 00000000000..38889892284 --- /dev/null +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/internal/core/dom/ICompilationUnitResolver.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jdt.internal.core.dom; + +import java.util.List; +import java.util.Map; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.WorkingCopyOwner; +import org.eclipse.jdt.core.dom.ASTRequestor; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.FileASTRequestor; +import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath; + +/** + * This interface is used to resolve a jdt dom tree from source files. + * It is contributed to via the compilationUnitResolver extension point. + * This interface is currently internal only, and is not considered API. + * This interface may be modified, changed, or removed at any time. + * +*

+* EXPERIMENTAL. This class or interface has been added as +* part of a work in progress. There is no guarantee that this API will +* work or that it will remain the same. Please do not use this API without +* consulting with the Red Hat team. +*

+* +* See https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2641 for discussion on possible +* changes to this interface +*/ +public interface ICompilationUnitResolver { + /** + * Parse the ASTs and resolve the bindings for the given source files using the following options. + * + * @param sourceFilePaths the compilation units to create ASTs for + * @param encodings the given encoding for the source units + * @param bindingKeys the binding keys to create bindings for + * @param requestor the AST requestor that collects abstract syntax trees and bindings + * @param apiLevel Level of AST API desired. + * @param compilerOptions Compiler options. Defaults to JavaCore.getOptions(). + * @param classpathList A list of classpaths to use during this operation + * @param flags Flags to to be used during this operation + * @param monitor A progress monitor + */ + void resolve(String[] sourceFilePaths, String[] encodings, String[] bindingKeys, FileASTRequestor requestor, + int apiLevel, Map compilerOptions, List classpathList, int flags, + IProgressMonitor monitor); + + /** + * Parse the ASTs for the given source units using the following options. + * + * @param compilationUnits the compilation units to create ASTs for + * @param requestor the AST requestor that collects abstract syntax trees and bindings + * @param apiLevel Level of AST API desired. + * @param compilerOptions Compiler options. Defaults to JavaCore.getOptions(). + * @param flags Flags to to be used during this operation + * @param monitor A progress monitor + */ + void parse(ICompilationUnit[] compilationUnits, ASTRequestor requestor, int apiLevel, + Map compilerOptions, int flags, IProgressMonitor monitor); + + /** + * Parse the given source paths with the following options. + * + * @param sourceFilePaths the compilation units to create ASTs for + * @param encodings the given encoding for the source units + * @param requestor the AST requester that collects abstract syntax trees and bindings + * @param apiLevel Level of AST API desired. + * @param compilerOptions Compiler options. Defaults to JavaCore.getOptions(). + * @param flags Flags to to be used during this operation + * @param monitor A progress monitor + */ + void parse(String[] sourceFilePaths, String[] encodings, FileASTRequestor requestor, int apiLevel, + Map compilerOptions, int flags, IProgressMonitor monitor); + + /** + * Parse and resolve bindings for the given compilation units with the following options. + * + * @param compilationUnits the compilation units to create ASTs for + * @param bindingKeys the binding keys to create bindings for + * @param requestor the AST requester that collects abstract syntax trees and bindings + * @param apiLevel Level of AST API desired. + * @param compilerOptions Compiler options. Defaults to JavaCore.getOptions(). + * @param project The project providing the context of the resolution + * @param workingCopyOwner The owner of the working copy + * @param flags Flags to to be used during this operation + * @param monitor A progress monitor + */ + void resolve(ICompilationUnit[] compilationUnits, String[] bindingKeys, ASTRequestor requestor, int apiLevel, + Map compilerOptions, IJavaProject project, WorkingCopyOwner workingCopyOwner, int flags, + IProgressMonitor monitor); + + + + /** + * Convert the given source unit into a CompilationUnit using the following options. + * + * @param sourceUnit A source unit + * @param initialNeedsToResolveBinding Initial guess as to whether we need to resolve bindings + * @param project The project providing the context of the conversion + * @param classpaths A list of classpaths to use during this operation + * @param focalPosition a position to focus on, or -1 if N/A + * @param apiLevel Level of AST API desired. + * @param compilerOptions Compiler options. Defaults to JavaCore.getOptions(). + * @param parsedUnitWorkingCopyOwner The working copy owner of the unit + * @param typeRootWorkingCopyOwner The working copy owner of the type + * @param flags Flags to to be used during this operation + * @param monitor A progress monitor + * @return A CompilationUnit + */ + CompilationUnit toCompilationUnit(org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit, final boolean initialNeedsToResolveBinding, IJavaProject project, List classpaths, int focalPosition, + int apiLevel, Map compilerOptions, WorkingCopyOwner parsedUnitWorkingCopyOwner, WorkingCopyOwner typeRootWorkingCopyOwner, int flags, IProgressMonitor monitor); +} diff --git a/org.eclipse.jdt.core/plugin.properties b/org.eclipse.jdt.core/plugin.properties index 7611e97e290..f901126dae0 100644 --- a/org.eclipse.jdt.core/plugin.properties +++ b/org.eclipse.jdt.core/plugin.properties @@ -23,6 +23,7 @@ classpathVariableInitializersName=Classpath Variable Initializers classpathContainerInitializersName=Classpath Container Initializers codeFormattersName=Source Code Formatters compilationParticipantsName=Compilation Participants +compilationUnitResolverName=Compilation Unit Resolver annotationProcessorManagerName=Java 6 Annotation Processor Manager javaTaskName=Java Task javaPropertiesName=Java Properties File diff --git a/org.eclipse.jdt.core/plugin.xml b/org.eclipse.jdt.core/plugin.xml index 0569dff3f86..1a5c84cb82e 100644 --- a/org.eclipse.jdt.core/plugin.xml +++ b/org.eclipse.jdt.core/plugin.xml @@ -63,6 +63,14 @@ id="compilationParticipant" schema="schema/compilationParticipant.exsd"/> + + + + + + diff --git a/org.eclipse.jdt.core/schema/compilationUnitResolver.exsd b/org.eclipse.jdt.core/schema/compilationUnitResolver.exsd new file mode 100644 index 00000000000..32de89bd3ab --- /dev/null +++ b/org.eclipse.jdt.core/schema/compilationUnitResolver.exsd @@ -0,0 +1,121 @@ + + + + + + + + + This extension point provides the ability to override the default behavior of converting source files into the JDT DOM tree. The resolver will be instantiated on-demand based on the value of the system property `ICompilationUnitResolver`, which must be set to the id of an implementing extension. This extension point is not intended to be implemented by clients. This extension point is not considered API. This extension point may be modified or removed at any moment. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Definition of a compilation unit resolver. + + + + + + + The class that implements this compilation unit resolver. This class must implement the <code>org.eclipse.jdt.internal.core.dom.ICompilationUnitResolver</code> interface with a public 0-arg constructor. + + + + + + + + + + A unique identifier for this resolver. + + + + + + + + + + + + 3.38 + + + + + + + + + Example of a declaration of a <code>compilationUnitResolver</code>: <pre> +<extension + point="org.eclipse.jdt.core.compilationParticipant"> + <resolver + class="org.eclipse.jdt.core.CompilationUnitResolver1" + id="org.eclipse.jdt.core.MyCustomResolver"> + </resolver> +</extension> +</pre> + + + + + + + + + + + Copyright (c) 2024 Red Hat, Inc. and others.<br> + +This program and the accompanying materials +are made available under the terms of the Eclipse Public License 2.0 +which accompanies this distribution, and is available at +<a href="https://www.eclipse.org/legal/epl-2.0">https://www.eclipse.org/legal/epl-v20.html</a>/ + +SPDX-License-Identifier: EPL-2.0 + + + +