From 56600432d74278b85ec70a955181389453c215e0 Mon Sep 17 00:00:00 2001 From: Mickael Istria Date: Fri, 12 Jan 2024 20:20:13 +0100 Subject: [PATCH] Allow resolving compilation unit (DOM) with Javac Extracts an interface ICompilationUnitResolver out of CompilationUnitResolver. This interface is used by ASTParser to create DOM instance from a Java source. The default implementation remains CompilationUnitResolver and uses ECJ parser/resolver to create a DOM. A system property `ICompilationUnitResolver` can be used to force usage of alternative implementations. At the moment, those alternative implementations need to be in the same classloader (eg in a fragment). A new fragment org.eclipse.jdt.core.javac is introduced and provides an implementation of ICompilationUnitResolver which uses Javac "API"s to create a JDT DOM. It can be enabled by setting `-DICompilationUnitResolver=org.eclipse.jdt.core.dom.JavacCompilationUnitResolver`. --- .github/workflows/ci.yml | 4 +- org.eclipse.jdt.core.javac/.classpath | 12 + org.eclipse.jdt.core.javac/.project | 28 + .../org.eclipse.core.resources.prefs | 2 + .../.settings/org.eclipse.jdt.core.prefs | 15 + .../META-INF/MANIFEST.MF | 8 + org.eclipse.jdt.core.javac/README.md | 57 + org.eclipse.jdt.core.javac/build.properties | 4 + org.eclipse.jdt.core.javac/pom.xml | 51 + .../core/dom/FindNextJavadocableSibling.java | 33 + .../jdt/core/dom/JavacAnnotationBinding.java | 97 ++ .../jdt/core/dom/JavacBindingResolver.java | 218 ++++ .../dom/JavacCompilationUnitResolver.java | 323 +++++ .../eclipse/jdt/core/dom/JavacCompiler.java | 28 + .../eclipse/jdt/core/dom/JavacConverter.java | 1104 +++++++++++++++++ .../core/dom/JavacMemberValuePairBinding.java | 99 ++ .../jdt/core/dom/JavacMethodBinding.java | 291 +++++ .../jdt/core/dom/JavacPackageBinding.java | 110 ++ .../jdt/core/dom/JavacTypeBinding.java | 437 +++++++ .../jdt/core/dom/JavacVariableBinding.java | 158 +++ .../org/eclipse/jdt/core/dom/ASTParser.java | 267 ++-- .../jdt/core/dom/CompilationUnitResolver.java | 95 ++ .../core/dom/ICompilationUnitResolver.java | 60 + pom.xml | 8 + 24 files changed, 3356 insertions(+), 153 deletions(-) create mode 100644 org.eclipse.jdt.core.javac/.classpath create mode 100644 org.eclipse.jdt.core.javac/.project create mode 100644 org.eclipse.jdt.core.javac/.settings/org.eclipse.core.resources.prefs create mode 100644 org.eclipse.jdt.core.javac/.settings/org.eclipse.jdt.core.prefs create mode 100644 org.eclipse.jdt.core.javac/META-INF/MANIFEST.MF create mode 100644 org.eclipse.jdt.core.javac/README.md create mode 100644 org.eclipse.jdt.core.javac/build.properties create mode 100644 org.eclipse.jdt.core.javac/pom.xml create mode 100644 org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/FindNextJavadocableSibling.java create mode 100644 org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacAnnotationBinding.java create mode 100644 org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacBindingResolver.java create mode 100644 org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacCompilationUnitResolver.java create mode 100644 org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacCompiler.java create mode 100644 org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacConverter.java create mode 100644 org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacMemberValuePairBinding.java create mode 100644 org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacMethodBinding.java create mode 100644 org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacPackageBinding.java create mode 100644 org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacTypeBinding.java create mode 100644 org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacVariableBinding.java create mode 100644 org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ICompilationUnitResolver.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6bd7938d2ba..c9fcda97a1b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,11 +30,13 @@ jobs: java-version: | 8 17 + 20 21 mvn-toolchain-id: | JavaSE-1.8 JavaSE-17 JavaSE-20 + JavaSE-21 distribution: 'temurin' - name: Set up Maven uses: stCarolas/setup-maven@07fbbe97d97ef44336b7382563d66743297e442f # v4.5 @@ -43,7 +45,7 @@ jobs: - name: Build with Maven 🏗️ run: | mvn clean install --batch-mode -f org.eclipse.jdt.core.compiler.batch -DlocalEcjVersion=99.99 - mvn -U clean verify --batch-mode --fail-at-end -Ptest-on-javase-20 -Pbree-libs -Papi-check -Djava.io.tmpdir=$WORKSPACE/tmp -Dproject.build.sourceEncoding=UTF-8 -Dtycho.surefire.argLine="--add-modules ALL-SYSTEM -Dcompliance=1.8,11,17,20 -Djdt.performance.asserts=disabled" -Dcbi-ecj-version=99.99 + mvn -U clean verify --batch-mode --fail-at-end -Ptest-on-javase-21 -Pbree-libs -Papi-check -Djava.io.tmpdir=$WORKSPACE/tmp -Dproject.build.sourceEncoding=UTF-8 -Dtycho.surefire.argLine="--add-modules ALL-SYSTEM -Dcompliance=1.8,11,17,20,21 -Djdt.performance.asserts=disabled" -Dcbi-ecj-version=99.99 - name: Upload Test Results for Linux if: always() uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 diff --git a/org.eclipse.jdt.core.javac/.classpath b/org.eclipse.jdt.core.javac/.classpath new file mode 100644 index 00000000000..13afab0b974 --- /dev/null +++ b/org.eclipse.jdt.core.javac/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/org.eclipse.jdt.core.javac/.project b/org.eclipse.jdt.core.javac/.project new file mode 100644 index 00000000000..1b611ff9d02 --- /dev/null +++ b/org.eclipse.jdt.core.javac/.project @@ -0,0 +1,28 @@ + + + org.eclipse.jdt.core.javac + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.eclipse.jdt.core.javac/.settings/org.eclipse.core.resources.prefs b/org.eclipse.jdt.core.javac/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000000..99f26c0203a --- /dev/null +++ b/org.eclipse.jdt.core.javac/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/org.eclipse.jdt.core.javac/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jdt.core.javac/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..7b7aa558af6 --- /dev/null +++ b/org.eclipse.jdt.core.javac/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,15 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=21 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=21 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=21 diff --git a/org.eclipse.jdt.core.javac/META-INF/MANIFEST.MF b/org.eclipse.jdt.core.javac/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..ee91aaea4d6 --- /dev/null +++ b/org.eclipse.jdt.core.javac/META-INF/MANIFEST.MF @@ -0,0 +1,8 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Javac +Bundle-SymbolicName: org.eclipse.jdt.core.javac +Bundle-Version: 1.0.0.qualifier +Fragment-Host: org.eclipse.jdt.core +Automatic-Module-Name: org.eclipse.jdt.core.javac +Bundle-RequiredExecutionEnvironment: JavaSE-21 diff --git a/org.eclipse.jdt.core.javac/README.md b/org.eclipse.jdt.core.javac/README.md new file mode 100644 index 00000000000..64a67f9218c --- /dev/null +++ b/org.eclipse.jdt.core.javac/README.md @@ -0,0 +1,57 @@ +# JDT over Javac + +This branch is a work in progress experiment to leverage all higher-level JDT IDE features (DOM, IJavaElement, refactorings...) relying on Javac as underlying compiler/parser instead of ECJ. + +Why? Some background... +* These days, with more frequent and more features Java releases, it's becoming hard for JDT to **cope with new Java features on time** and **facilitate support for upcoming/preview features before Java is released so JDT can participate to consolidation of the spec**. Over recent releases, JDT has failed at providing the features on time. This is mostly because of the difficulty of maintaining the Eclipse compiler: compilers are difficult bits of code to maintain and it takes a lot of time to implement things well in them. There is no clear sign the situation can improve here. +* The Eclipse compiler has always suffered from occasional **inconsistencies with Javac** which end-users fail at understanding. Sometimes, ECJ is right, sometimes Javac is; but for end-users and for the ecosystem, Javac is the reference implementation and it's behavior is what they perceive as the actual specification +* JDT has a very strong ecosystem (JDT-LS, plugins) a tons of nice features, so it seems profitable to **keep relying higher-level JDT APIs, such as model or DOM** to remain compatible with the ecosystem + + +🎯 The technical proposal here mostly to **allow Javac to be used at the lowest-level of JDT**, under the hood, to populate higher-level models that are used in many operations; named the JDT DOM and IJavaElement models. It is expected that if we can create a good DOM and IJavaElement structure with another strategy (eg using Javac API), then all higher level operations will remain working as well without modification. + +▶️ **To test this**, you'll need to import the code of `org.eclipse.jdt.core` and `org.eclipse.jdt.core.javac` from this branch in your Eclipse workspace; and create a Launch Configuration of type "Eclipse Application" which does include the `org.eclipse.jdt.core` bundle. Go to _Arguments_ tab of this launch configuration, and add the following content to the _VM arguments_ list: + +> `--add-opens jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED -DICompilationUnitResolver=org.eclipse.jdt.core.dom.JavacCompilationUnitResolver -DCompilationUnit.DOM_BASED_OPERATIONS=true -DCompilationUnit.codeComplete.DOM_BASED_OPERATIONS=true` + +The `--add-opens` allow to access internal API of the JVM; the `ICompilationUnitResolver=org.eclipse.jdt.core.dom.JavacCompilationUnitResolver` and `CompilationUnit.DOM_BASED_OPERATIONS=true`/`-DCompilationUnit.codeComplete.DOM_BASED_OPERATIONS` system properties enables the experimental processing described below, respectfully controlling whether to build DOM using ECJ or Javac, and whether to process other operations using ECJ or DOM. Note that setting only one of those properties can be useful when developing one particular aspect of this proposal, which property to set depends on what you want to focus on. + + + +🥼 **This experiment** here currently mostly involves/support some IDE features thanks for the following design: +* Refactoring ASTParser to allow delegating parsing/resolution to Javac instead of ECJ (system property `ICompilationUnitResolver` defines which parser to use). The Javac-based implementation is defined in the separate `org.eclipse.jdt.core.javac` fragment (so `org.eclipse.jdt.core` has no particular extra dependency on Javac by default) and consists mainly of + * orchestrating Javac via its API and use its output in... + * ...a converter from Javac diagnostics to JDT problems (then attached to the compilation unit) + * ...a converter from Javac to JDT DOM (partial) and + * ...a JavacBindingResolver relying on Javac "Symbol" to resolve types and references (partial) +* Some methods of the higher-level JDT "IDE" model such as reconciling model with source, or `codeSelect` or populating the index can now process on top of a built DOM directly, without invoking ECJ to re-parse the source (`CompilationUnit.DOM_BASED_OPERATIONS` system property controls whether to parse with ECJ, or use DOM; `CompilationUnit.codeComplete.DOM_BASED_OPERATIONS` specifically controls the Code Completion strategy). It doesn't matter whether the DOM originates from Javac or ECJ conversion, both should lead to same output from those higher-level methods. So these changes are independent of Javac experiment, they're just providing an alternative "DOM-first" strategy for usual operations (where the only available strategy before was re-parsing/resolving with ECJ). + +❓ What is known to be **not yet tried** to consider this experiment capable of getting on par with ECJ-based IDE: +* Support for **annotation processing**, which hopefully will be mostly a matter of looping the `parse` and `attr` steps of compilation with annotation processors, before running (binding) resolver +* Support for **.class generation**, which ideally would start from the javac context of the project, so only `gen` phase would be necessary as other ones such as parsing, resolution... already happened as the code was being edited + + +🏗️ What works as a proof of concept with no strong design issue known/left, but still requires work to be generally usable: +* about DOM production (use Javac APIs to generate DOM) + * Complete Javac AST -> JDT DOM converter (estimated difficulty 💪💪💪) + * Complete Javac AST/Symbols -> IBinding resolver (estimated difficulty 💪💪💪💪) + * Map all Javac diagnostic types to JDT's IProblem (estimated difficulty 💪💪) + * Forward all JDT compilerOptions/project configuration to configure Javac execution -currently only source path/class path configured (estimated difficulty 💪💪) +* about DOM consumption (plain JDT) + * Complete DOM -> Index population (estimated difficulty 💪) + * More support completion based on DOM: filtering, priority, missing constructs (estimated difficulty 💪💪💪💪) + + + +🤔 What are the potential concerns: +* Currently, the AST is built more times than necessary, when we could just reuse the latest version. +* Memory cost of retaining Javac contexts (can we get rid of the context earlier? Can we share subparts of the concerns across multiple files in the project?...) +* The created `Javac` instances are not yet fully configured according to project settings. +* It seems hard to find reusable parts from the CompletionEngine, although many proposals shouldn't really depend on the parser (so should be reusable) + + +😧 What are the confirmed concerns: +* **Coupling with particular JDK version** the Javac "API" is not stable but changes between release. As a result a Javac-based JDT would become specific to 1 particular JDK version and would only run with this version (that why making it pluggable can really help). +* **Null analysis** seems to be tightly hooked in ECJ and cannot be used with Javac. Maybe let another analysis engine (eg SpotBugs) deal with it? +* At the moment, Javac cannot be configured to **generate .class despite CompilationError** in them like ECJ can do to allow updating the target application even when some code is not complete yet + * We may actually be capable of hacking something like this in Eclipse/Javac integration (although it would be best to provide this directly in Javac), but running a 1st attempt of compilation, collecting errors, and then alterating the working copy of the source passed to Javac in case of error. More or less `if (diagnostics.anyMatch(getKind() == "error") { removeBrokenAST(diagnostic); injectAST("throw new CompilationError(diagnostic.getMessage()")`. diff --git a/org.eclipse.jdt.core.javac/build.properties b/org.eclipse.jdt.core.javac/build.properties new file mode 100644 index 00000000000..34d2e4d2dad --- /dev/null +++ b/org.eclipse.jdt.core.javac/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/org.eclipse.jdt.core.javac/pom.xml b/org.eclipse.jdt.core.javac/pom.xml new file mode 100644 index 00000000000..d17d8858d28 --- /dev/null +++ b/org.eclipse.jdt.core.javac/pom.xml @@ -0,0 +1,51 @@ + + + + 4.0.0 + + eclipse.jdt.core + org.eclipse.jdt + 4.31.0-SNAPSHOT + + org.eclipse.jdt.core.javac + 1.0.0-SNAPSHOT + eclipse-plugin + + + + + org.eclipse.tycho + tycho-compiler-plugin + + false + + --add-exports + java.base/java.lang=ALL-UNNAMED + --add-exports + jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED + --add-exports + jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + --add-exports + jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED + --add-exports + jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED + --add-exports + jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED + --add-exports + jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED + --add-exports + jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED + --add-exports + jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED + + + + + + diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/FindNextJavadocableSibling.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/FindNextJavadocableSibling.java new file mode 100644 index 00000000000..26d344f51da --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/FindNextJavadocableSibling.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * 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.core.dom; + +class FindNextJavadocableSibling extends ASTVisitor { + ASTNode nextNode = null; + private int javadocOffsetEnd; + public FindNextJavadocableSibling(int javadocOffsetEnd) { + this.javadocOffsetEnd = javadocOffsetEnd; + } + @Override + public void preVisit(ASTNode node) { + if (node.getStartPosition() > this.javadocOffsetEnd && + isJavadocAble(node) && + (this.nextNode == null || this.nextNode.getStartPosition() > node.getStartPosition())) { + this.nextNode = node; + } + } + + private static boolean isJavadocAble(ASTNode node) { + return node instanceof AbstractTypeDeclaration || + node instanceof FieldDeclaration || + node instanceof MethodDeclaration; + } +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacAnnotationBinding.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacAnnotationBinding.java new file mode 100644 index 00000000000..4d3d6a6f085 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacAnnotationBinding.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * 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.jdt.core.IJavaElement; + +import com.sun.tools.javac.code.Attribute.Compound; + +public class JavacAnnotationBinding implements IAnnotationBinding { + + private JavacBindingResolver resolver; + private Compound annotation; + + public JavacAnnotationBinding(Compound ann, JavacBindingResolver resolver) { + this.resolver = resolver; + this.annotation = ann; + } + + @Override + public IAnnotationBinding[] getAnnotations() { + return new IAnnotationBinding[0]; + } + + @Override + public int getKind() { + return ANNOTATION; + } + + @Override + public int getModifiers() { + return getAnnotationType().getModifiers(); + } + + @Override + public boolean isDeprecated() { + return getAnnotationType().isDeprecated(); + } + + @Override + public boolean isRecovered() { + throw new UnsupportedOperationException("Unimplemented method 'isRecovered'"); + } + + @Override + public boolean isSynthetic() { + return getAnnotationType().isSynthetic(); + } + + @Override + public IJavaElement getJavaElement() { + return getAnnotationType().getJavaElement(); + } + + @Override + public String getKey() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getKey'"); + } + + @Override + public boolean isEqualTo(IBinding binding) { + return binding instanceof JavacAnnotationBinding other && Objects.equals(this.annotation, other.annotation); + } + + @Override + public IMemberValuePairBinding[] getAllMemberValuePairs() { + return this.annotation.getElementValues().entrySet().stream() + .map(entry -> new JavacMemberValuePairBinding(entry.getKey(), entry.getValue(), this.resolver)) + .toArray(MemberValuePairBinding[]::new); + } + + @Override + public ITypeBinding getAnnotationType() { + return new JavacTypeBinding(this.annotation.type, this.resolver); + } + + @Override + public IMemberValuePairBinding[] getDeclaredMemberValuePairs() { + return getAllMemberValuePairs(); + } + + @Override + public String getName() { + return getAnnotationType().getName(); + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacBindingResolver.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacBindingResolver.java new file mode 100644 index 00000000000..247a31b606c --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacBindingResolver.java @@ -0,0 +1,218 @@ +/******************************************************************************* + * 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.core.dom; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Queue; + +import org.eclipse.jdt.core.IJavaProject; + +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Symbol.MethodSymbol; +import com.sun.tools.javac.code.Symbol.PackageSymbol; +import com.sun.tools.javac.code.Symbol.TypeSymbol; +import com.sun.tools.javac.code.Symbol.VarSymbol; +import com.sun.tools.javac.comp.AttrContext; +import com.sun.tools.javac.comp.Env; +import com.sun.tools.javac.comp.Modules; +import com.sun.tools.javac.comp.Todo; +import com.sun.tools.javac.main.JavaCompiler; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCFieldAccess; +import com.sun.tools.javac.tree.JCTree.JCIdent; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; +import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.List; + +class JavacBindingResolver extends BindingResolver { + + private final JavaCompiler javac; // TODO evaluate memory cost of storing the instance + // it will probably be better to run the `Enter` and then only extract interesting + // date from it. + final Context context; + private Map symbolToDom; + final IJavaProject javaProject; + private JavacConverter converter; + + public JavacBindingResolver(JavaCompiler javac, IJavaProject javaProject, Context context, JavacConverter converter) { + this.javac = javac; + this.context = context; + this.javaProject = javaProject; + this.converter = converter; + } + + private void resolve() { + if (this.symbolToDom == null) { + java.util.List units = this.converter.domToJavac.values().stream() + .filter(JCCompilationUnit.class::isInstance) + .map(JCCompilationUnit.class::cast) + .toList(); + Modules.instance(this.context).initModules(List.from(units)); + Todo todo = Todo.instance(this.context); + this.javac.enterTrees(List.from(units)); + Queue> attribute = this.javac.attribute(todo); + this.javac.flow(attribute); + this.symbolToDom = new HashMap<>(); + this.converter.domToJavac.entrySet().forEach(entry -> + symbol(entry.getValue()).ifPresent(sym -> this.symbolToDom.put(sym, entry.getKey()))); + } + } + + ASTNode findNode(Symbol symbol) { + if (this.symbolToDom != null) { + return this.symbolToDom.get(symbol); + } + return null; + } + + private Optional symbol(JCTree value) { + if (value instanceof JCClassDecl jcClassDecl) { + return Optional.of(jcClassDecl.sym); + } + if (value instanceof JCFieldAccess jcFieldAccess) { + return Optional.of(jcFieldAccess.sym); + } + // TODO fields, methods, variables... + return Optional.empty(); + } + + @Override + ITypeBinding resolveType(Type type) { + resolve(); + JCTree jcTree = this.converter.domToJavac.get(type); + if (jcTree instanceof JCIdent ident && ident.sym instanceof TypeSymbol typeSymbol) { + return new JavacTypeBinding(typeSymbol, this); + } + if (jcTree instanceof JCFieldAccess access && access.sym instanceof TypeSymbol typeSymbol) { + return new JavacTypeBinding(typeSymbol, this); + } + if (jcTree instanceof JCPrimitiveTypeTree primitive) { + return new JavacTypeBinding(primitive.type, this); + } +// return this.flowResult.stream().map(env -> env.enclClass) +// .filter(Objects::nonNull) +// .map(decl -> decl.type) +// .map(javacType -> javacType.tsym) +// .filter(sym -> Objects.equals(type.toString(), sym.name.toString())) +// .findFirst() +// .map(symbol -> new JavacTypeBinding(symbol, this)) +// .orElse(null); +// } +// if (type instanceof QualifiedType qualifiedType) { +// JCTree jcTree = this.converter.domToJavac.get(qualifiedType); +// } + return super.resolveType(type); + } + + @Override + ITypeBinding resolveType(TypeDeclaration type) { + resolve(); + JCTree javacNode = this.converter.domToJavac.get(type); + if (javacNode instanceof JCClassDecl jcClassDecl) { + return new JavacTypeBinding(jcClassDecl.sym, this); + } + return null; + } + + IBinding getBinding(final Symbol owner) { + if (owner instanceof final PackageSymbol other) { + return new JavacPackageBinding(other, this); + } else if (owner instanceof final TypeSymbol other) { + return new JavacTypeBinding(other, this); + } else if (owner instanceof final MethodSymbol other) { + return new JavacMethodBinding(other, this); + } else if (owner instanceof final VarSymbol other) { + return new JavacVariableBinding(other, this); + } + return null; + } + + @Override + IVariableBinding resolveField(FieldAccess fieldAccess) { + resolve(); + JCTree javacElement = this.converter.domToJavac.get(fieldAccess); + if (javacElement instanceof JCFieldAccess javacFieldAccess && javacFieldAccess.sym instanceof VarSymbol varSymbol) { + return new JavacVariableBinding(varSymbol, this); + } + return null; + } + + @Override + IMethodBinding resolveMethod(MethodInvocation method) { + resolve(); + JCTree javacElement = this.converter.domToJavac.get(method); + if (javacElement instanceof JCMethodInvocation javacMethodInvocation) { + javacElement = javacMethodInvocation.getMethodSelect(); + } + if (javacElement instanceof JCIdent ident && ident.sym instanceof MethodSymbol methodSymbol) { + return new JavacMethodBinding(methodSymbol, this); + } + if (javacElement instanceof JCFieldAccess fieldAccess && fieldAccess.sym instanceof MethodSymbol methodSymbol) { + return new JavacMethodBinding(methodSymbol, this); + } + return null; + } + + @Override + IMethodBinding resolveMethod(MethodDeclaration method) { + resolve(); + JCTree javacElement = this.converter.domToJavac.get(method); + if (javacElement instanceof JCMethodDecl methodDecl) { + return new JavacMethodBinding(methodDecl.sym, this); + } + return null; + } + + @Override + IBinding resolveName(Name name) { + resolve(); + JCTree tree = this.converter.domToJavac.get(name); + if (tree == null) { + tree = this.converter.domToJavac.get(name.getParent()); + } + if (tree instanceof JCIdent ident && ident.sym != null) { + return getBinding(ident.sym); + } else if (tree instanceof JCFieldAccess fieldAccess && fieldAccess.sym != null) { + return getBinding(fieldAccess.sym); + } + return null; + } + + @Override + IVariableBinding resolveVariable(VariableDeclaration variable) { + resolve(); + return this.converter.domToJavac.get(variable) instanceof JCVariableDecl decl ? + new JavacVariableBinding(decl.sym, this) : null; + } + + @Override + public IPackageBinding resolvePackage(PackageDeclaration decl) { + resolve(); + return null; + } + + @Override + public ITypeBinding resolveExpressionType(Expression expr) { + resolve(); + return this.converter.domToJavac.get(expr) instanceof JCExpression jcExpr ? + new JavacTypeBinding(jcExpr.type, this) : + null; + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacCompilationUnitResolver.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacCompilationUnitResolver.java new file mode 100644 index 00000000000..70c3ba6c509 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacCompilationUnitResolver.java @@ -0,0 +1,323 @@ +/******************************************************************************* + * 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.core.dom; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Predicate; + +import javax.tools.Diagnostic; +import javax.tools.DiagnosticListener; +import javax.tools.FileObject; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import javax.tools.StandardLocation; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.ILog; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.Status; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaElement; +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.compiler.IProblem; +import org.eclipse.jdt.core.compiler.InvalidInputException; +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.JavaProject; + +import com.sun.tools.javac.comp.Todo; +import com.sun.tools.javac.file.JavacFileManager; +import com.sun.tools.javac.main.Option; +import com.sun.tools.javac.parser.JavadocTokenizer; +import com.sun.tools.javac.parser.Scanner; +import com.sun.tools.javac.parser.ScannerFactory; +import com.sun.tools.javac.parser.Tokens.Comment; +import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle; +import com.sun.tools.javac.parser.Tokens.TokenKind; +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.DiagnosticSource; +import com.sun.tools.javac.util.Options; + +class JavacCompilationUnitResolver implements ICompilationUnitResolver { + + @Override + public void resolve(String[] sourceFilePaths, String[] encodings, String[] bindingKeys, FileASTRequestor requestor, + int apiLevel, Map compilerOptions, List list, int flags, + IProgressMonitor monitor) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'resolve'"); + } + + @Override + public void parse(ICompilationUnit[] compilationUnits, ASTRequestor requestor, int apiLevel, + Map compilerOptions, int flags, IProgressMonitor monitor) { + // TODO ECJCompilationUnitResolver has support for dietParse and ignore method body + // is this something we need? + for (ICompilationUnit in : compilationUnits) { + if (in instanceof org.eclipse.jdt.internal.compiler.env.ICompilationUnit compilerUnit) { + requestor.acceptAST(in, parse(compilerUnit, apiLevel, compilerOptions, flags, null, monitor)); + } + } + } + + + @Override + public void parse(String[] sourceFilePaths, String[] encodings, FileASTRequestor requestor, int apiLevel, + Map compilerOptions, int flags, IProgressMonitor monitor) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'parse'"); + } + + @Override + public void resolve(ICompilationUnit[] compilationUnits, String[] bindingKeys, ASTRequestor requestor, int apiLevel, + Map compilerOptions, IJavaProject project, WorkingCopyOwner workingCopyOwner, int flags, + IProgressMonitor monitor) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'resolve'"); + } + + @Override + public IBinding[] resolve(IJavaElement[] elements, int apiLevel, Map compilerOptions, + IJavaProject project, WorkingCopyOwner workingCopyOwner, int flags, IProgressMonitor monitor) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'resolve'"); + } + + @Override + public CompilationUnit toCompilationUnit(org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit, + boolean initialNeedsToResolveBinding, IJavaProject project, List classpaths, + NodeSearcher nodeSearcher, int apiLevel, Map compilerOptions, + WorkingCopyOwner workingCopyOwner, WorkingCopyOwner typeRootWorkingCopyOwner, int flags, IProgressMonitor monitor) { + // TODO currently only parse + CompilationUnit res = parse(sourceUnit, apiLevel, compilerOptions, flags, project, monitor); + if (initialNeedsToResolveBinding) { + res.getPackage().resolveBinding(); + } + return res; + } + + public CompilationUnit parse(org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit, int apiLevel, Map compilerOptions, + int flags, IJavaProject javaProject, IProgressMonitor monitor) { + SimpleJavaFileObject fileObject = new SimpleJavaFileObject(new File(new String(sourceUnit.getFileName())).toURI(), JavaFileObject.Kind.SOURCE) { + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws java.io.IOException { + return new String(sourceUnit.getContents()); + } + }; + Context context = new Context(); + Options options = Options.instance(context); + options.put(Option.XLINT, Boolean.TRUE.toString()); // TODO refine according to compilerOptions + options.put(Option.XLINT_CUSTOM, "all"); // TODO refine according to compilerOptions + // TODO populate more from compilerOptions and/or project settings + AST ast = createAST(compilerOptions, apiLevel, context); + CompilationUnit res = ast.newCompilationUnit(); + context.put(DiagnosticListener.class, new DiagnosticListener() { + @Override + public void report(Diagnostic diagnostic) { + if (Objects.equals(diagnostic.getSource(), fileObject) || + diagnostic.getSource() instanceof DiagnosticSource source && Objects.equals(source.getFile(), fileObject)) { + IProblem[] previous = res.getProblems(); + IProblem[] newProblems = Arrays.copyOf(previous, previous.length + 1); + newProblems[newProblems.length - 1] = JavacConverter.convertDiagnostic(diagnostic); + res.setProblems(newProblems); + } + } + }); + JavacFileManager.preRegister(context); + if (javaProject instanceof JavaProject internal) { + configurePaths(internal, context); + } + Todo.instance(context); // initialize early + com.sun.tools.javac.main.JavaCompiler javac = new com.sun.tools.javac.main.JavaCompiler(context); + javac.keepComments = true; + javac.genEndPos = true; + javac.lineDebugInfo = true; + JCCompilationUnit javacCompilationUnit = javac.parse(fileObject); + int savedDefaultNodeFlag = ast.getDefaultNodeFlag(); + ast.setDefaultNodeFlag(ASTNode.ORIGINAL); + JavacConverter converter = new JavacConverter(ast, javacCompilationUnit, context); + converter.populateCompilationUnit(res, javacCompilationUnit); + attachComments(res, context, fileObject, converter, compilerOptions); + ast.setBindingResolver(new JavacBindingResolver(javac, javaProject, context, converter)); + // + ast.setOriginalModificationCount(ast.modificationCount()); // "un-dirty" AST so Rewrite can process it + ast.setDefaultNodeFlag(savedDefaultNodeFlag); + return res; + } + + private List classpathEntriesToFiles(JavaProject project, Predicate select) { + try { + IClasspathEntry[] selected = Arrays.stream(project.getRawClasspath()) + .filter(select) + .toArray(IClasspathEntry[]::new); + return Arrays.stream(project.resolveClasspath(selected)) + .map(IClasspathEntry::getPath) + .map(path -> { + File asFile = path.toFile(); + if (asFile.exists()) { + return asFile; + } + IResource asResource = project.getProject().getParent().findMember(path); + if (asResource != null) { + return asResource.getLocation().toFile(); + } + return null; + }).filter(Objects::nonNull) + .filter(File::exists) + .toList(); + } catch (JavaModelException ex) { + ILog.get().error(ex.getMessage(), ex); + return List.of(); + } + } + + private void configurePaths(JavaProject javaProject, Context context) { + JavacFileManager fileManager = (JavacFileManager)context.get(JavaFileManager.class); + try { + fileManager.setLocation(StandardLocation.CLASS_OUTPUT, List.of(javaProject.getProject().getParent().findMember(javaProject.getOutputLocation()).getLocation().toFile())); + fileManager.setLocation(StandardLocation.SOURCE_PATH, classpathEntriesToFiles(javaProject, entry -> entry.getEntryKind() == IClasspathEntry.CPE_SOURCE)); + fileManager.setLocation(StandardLocation.CLASS_PATH, classpathEntriesToFiles(javaProject, entry -> entry.getEntryKind() != IClasspathEntry.CPE_SOURCE)); + } catch (Exception ex) { + ILog.get().error(ex.getMessage(), ex); + } + } + + private AST createAST(Map options, int level, Context context) { + AST ast = AST.newAST(level, JavaCore.ENABLED.equals(options.get(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES))); + String sourceModeSetting = options.get(JavaCore.COMPILER_SOURCE); + long sourceLevel = CompilerOptions.versionToJdkLevel(sourceModeSetting); + if (sourceLevel == 0) { + // unknown sourceModeSetting + sourceLevel = ClassFileConstants.JDK21; // TODO latest + } + ast.scanner.sourceLevel = sourceLevel; + String compliance = options.get(JavaCore.COMPILER_COMPLIANCE); + long complianceLevel = CompilerOptions.versionToJdkLevel(compliance); + if (complianceLevel == 0) { + // unknown sourceModeSetting + complianceLevel = sourceLevel; + } + ast.scanner.complianceLevel = complianceLevel; + ast.scanner.previewEnabled = JavaCore.ENABLED.equals(options.get(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES)); +// int savedDefaultNodeFlag = ast.getDefaultNodeFlag(); +// BindingResolver resolver = null; +// if (isResolved) { +// resolver = new DefaultBindingResolver(compilationUnitDeclaration.scope, workingCopy.owner, new DefaultBindingResolver.BindingTables(), false, true); +// ((DefaultBindingResolver) resolver).isRecoveringBindings = (reconcileFlags & ICompilationUnit.ENABLE_BINDINGS_RECOVERY) != 0; +// ast.setFlag(AST.RESOLVED_BINDINGS); +// } else { +// resolver = new BindingResolver(); +// } +// ast.setFlag(reconcileFlags); +// ast.setBindingResolver(resolver); +// +// CompilationUnit unit = converter.convert(compilationUnitDeclaration, workingCopy.getContents()); +// unit.setLineEndTable(compilationUnitDeclaration.compilationResult.getLineSeparatorPositions()); +// unit.setTypeRoot(workingCopy.originalFromClone()); + return ast; + } + + private class JavadocTokenizerFeedingComments extends JavadocTokenizer { + public final List comments = new ArrayList<>(); + private final JavacConverter converter; + + public JavadocTokenizerFeedingComments(ScannerFactory factory, char[] content, JavacConverter converter) { + super(factory, content, content.length); + this.converter = converter; + } + + @Override + protected Comment processComment(int pos, int endPos, CommentStyle style) { + Comment res = super.processComment(pos, endPos, style); + this.comments.add(this.converter.convert(res, pos, endPos)); + return res; + } + } + + /** + * Currently re-scans the doc to build the list of comments and then + * attach them to the already built AST. + * @param res + * @param context + * @param fileObject + * @param converter + * @param compilerOptions + */ + private void attachComments(CompilationUnit res, Context context, FileObject fileObject, JavacConverter converter, Map compilerOptions) { + try { + char[] content = fileObject.getCharContent(false).toString().toCharArray(); + ScannerFactory scannerFactory = ScannerFactory.instance(context); + JavadocTokenizerFeedingComments commentTokenizer = new JavadocTokenizerFeedingComments(scannerFactory, content, converter); + Scanner javacScanner = new Scanner(scannerFactory, commentTokenizer) { + // subclass just to access constructor + // TODO DefaultCommentMapper.this.scanner.linePtr == -1? + }; + do { // consume all tokens to populate comments + javacScanner.nextToken(); + } while (javacScanner.token() != null && javacScanner.token().kind != TokenKind.EOF); +// commentTokenizer.comments.forEach(comment -> comment.setAlternateRoot(res)); + res.setCommentTable(commentTokenizer.comments.toArray(org.eclipse.jdt.core.dom.Comment[]::new)); + org.eclipse.jdt.internal.compiler.parser.Scanner ecjScanner = new ASTConverter(compilerOptions, false, null).scanner; + ecjScanner.recordLineSeparator = true; + ecjScanner.skipComments = false; + try { + ecjScanner.setSource(content); + do { + ecjScanner.getNextToken(); + } while (!ecjScanner.atEnd()); + } catch (InvalidInputException ex) { + JavaCore.getPlugin().getLog().log(Status.error(ex.getMessage(), ex)); + } + + // need to scan with ecjScanner first to populate some line indexes used by the CommentMapper + // on longer-term, implementing an alternative comment mapper based on javac scanner might be best + res.initCommentMapper(ecjScanner); + res.setCommentTable(commentTokenizer.comments.toArray(org.eclipse.jdt.core.dom.Comment[]::new)); // TODO only javadoc comments are in; need to add regular comments + if (res.optionalCommentTable != null) { + Arrays.stream(res.optionalCommentTable) + .filter(Javadoc.class::isInstance) + .map(Javadoc.class::cast) + .forEach(doc -> attachToSibling(doc, res)); + } + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + private void attachToSibling(Javadoc javadoc, CompilationUnit unit) { + FindNextJavadocableSibling finder = new FindNextJavadocableSibling(javadoc.getStartPosition() + javadoc.getLength()); + unit.accept(finder); + if (finder.nextNode != null) { + if (finder.nextNode instanceof AbstractTypeDeclaration typeDecl) { + typeDecl.setJavadoc(javadoc); + } else if (finder.nextNode instanceof FieldDeclaration fieldDecl) { + fieldDecl.setJavadoc(javadoc); + } else if (finder.nextNode instanceof BodyDeclaration methodDecl) { + methodDecl.setJavadoc(javadoc); + } + int endOffset = finder.nextNode.getStartPosition() + finder.nextNode.getLength(); + finder.nextNode.setSourceRange(javadoc.getStartPosition(), endOffset - javadoc.getStartPosition()); + } + } +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacCompiler.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacCompiler.java new file mode 100644 index 00000000000..02158d726ff --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacCompiler.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * 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 org.eclipse.jdt.internal.compiler.Compiler; +import org.eclipse.jdt.internal.compiler.ICompilerRequestor; +import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy; +import org.eclipse.jdt.internal.compiler.IProblemFactory; +import org.eclipse.jdt.internal.compiler.env.INameEnvironment; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; + +public class JavacCompiler extends Compiler { + + public JavacCompiler(INameEnvironment environment, IErrorHandlingPolicy policy, CompilerOptions options, + ICompilerRequestor requestor, IProblemFactory problemFactory) { + super(environment, policy, options, requestor, problemFactory); + //TODO Auto-generated constructor stub + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacConverter.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacConverter.java new file mode 100644 index 00000000000..4bb472b5c30 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacConverter.java @@ -0,0 +1,1104 @@ +/******************************************************************************* + * 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.core.dom; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.PriorityQueue; +import java.util.function.Predicate; + +import javax.lang.model.type.TypeKind; +import javax.tools.Diagnostic; +import javax.tools.JavaFileObject; + +import org.eclipse.core.runtime.ILog; +import org.eclipse.jdt.core.compiler.IProblem; +import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword; +import org.eclipse.jdt.core.dom.PrimitiveType.Code; +import org.eclipse.jdt.internal.compiler.problem.DefaultProblem; +import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; + +import com.sun.source.tree.CaseTree.CaseKind; +import com.sun.tools.javac.parser.Tokens.Comment; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCAnyPattern; +import com.sun.tools.javac.tree.JCTree.JCArrayAccess; +import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree; +import com.sun.tools.javac.tree.JCTree.JCAssert; +import com.sun.tools.javac.tree.JCTree.JCAssign; +import com.sun.tools.javac.tree.JCTree.JCBinary; +import com.sun.tools.javac.tree.JCTree.JCBindingPattern; +import com.sun.tools.javac.tree.JCTree.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCBreak; +import com.sun.tools.javac.tree.JCTree.JCCase; +import com.sun.tools.javac.tree.JCTree.JCCatch; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.tree.JCTree.JCConditional; +import com.sun.tools.javac.tree.JCTree.JCContinue; +import com.sun.tools.javac.tree.JCTree.JCDoWhileLoop; +import com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop; +import com.sun.tools.javac.tree.JCTree.JCErroneous; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; +import com.sun.tools.javac.tree.JCTree.JCFieldAccess; +import com.sun.tools.javac.tree.JCTree.JCForLoop; +import com.sun.tools.javac.tree.JCTree.JCIdent; +import com.sun.tools.javac.tree.JCTree.JCIf; +import com.sun.tools.javac.tree.JCTree.JCImport; +import com.sun.tools.javac.tree.JCTree.JCInstanceOf; +import com.sun.tools.javac.tree.JCTree.JCLabeledStatement; +import com.sun.tools.javac.tree.JCTree.JCLambda; +import com.sun.tools.javac.tree.JCTree.JCLiteral; +import com.sun.tools.javac.tree.JCTree.JCMemberReference; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; +import com.sun.tools.javac.tree.JCTree.JCModifiers; +import com.sun.tools.javac.tree.JCTree.JCNewArray; +import com.sun.tools.javac.tree.JCTree.JCNewClass; +import com.sun.tools.javac.tree.JCTree.JCPackageDecl; +import com.sun.tools.javac.tree.JCTree.JCParens; +import com.sun.tools.javac.tree.JCTree.JCPattern; +import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; +import com.sun.tools.javac.tree.JCTree.JCReturn; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCSwitch; +import com.sun.tools.javac.tree.JCTree.JCSynchronized; +import com.sun.tools.javac.tree.JCTree.JCThrow; +import com.sun.tools.javac.tree.JCTree.JCTry; +import com.sun.tools.javac.tree.JCTree.JCTypeApply; +import com.sun.tools.javac.tree.JCTree.JCTypeCast; +import com.sun.tools.javac.tree.JCTree.JCTypeIntersection; +import com.sun.tools.javac.tree.JCTree.JCTypeUnion; +import com.sun.tools.javac.tree.JCTree.JCUnary; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.tree.JCTree.JCWhileLoop; +import com.sun.tools.javac.tree.JCTree.JCWildcard; +import com.sun.tools.javac.tree.JCTree.JCYield; +import com.sun.tools.javac.tree.JCTree.Tag; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Names; +import com.sun.tools.javac.util.Position.LineMap; + +/** + * Deals with conversion of Javac domain into JDT DOM domain + */ +@SuppressWarnings("unchecked") +class JavacConverter { + + public final AST ast; + private final JCCompilationUnit javacCompilationUnit; + private final Context context; + final Map domToJavac = new HashMap<>(); + //private final Map javacToDom = new HashMap<>(); + + public JavacConverter(AST ast, JCCompilationUnit javacCompilationUnit, Context context) { + this.ast = ast; + this.javacCompilationUnit = javacCompilationUnit; + this.context = context; + } + + CompilationUnit convertCompilationUnit(JCCompilationUnit javacCompilationUnit) { + CompilationUnit res = this.ast.newCompilationUnit(); + populateCompilationUnit(res, javacCompilationUnit); + return res; + } + + void populateCompilationUnit(CompilationUnit res, JCCompilationUnit javacCompilationUnit) { + commonSettings(res, javacCompilationUnit); + res.setLineEndTable(toLineEndPosTable(javacCompilationUnit.getLineMap(), res.getLength())); + if (javacCompilationUnit.getPackage() != null) { + res.setPackage(convert(javacCompilationUnit.getPackage())); + } + javacCompilationUnit.getImports().stream().map(jc -> convert(jc)).forEach(res.imports()::add); + javacCompilationUnit.getTypeDecls().stream() + .map(this::convertBodyDeclaration) + .forEach(res.types()::add); + res.accept(new FixPositions()); + } + + private int[] toLineEndPosTable(LineMap lineMap, int fileLength) { + List lineEnds = new ArrayList<>(); + int line = 1; + try { + do { + lineEnds.add(lineMap.getStartPosition(line + 1) - 1); + line++; + } while (true); + } catch (ArrayIndexOutOfBoundsException ex) { + // expected + } + lineEnds.add(fileLength - 1); + return lineEnds.stream().mapToInt(Integer::intValue).toArray(); + } + + private PackageDeclaration convert(JCPackageDecl javac) { + PackageDeclaration res = this.ast.newPackageDeclaration(); + res.setName(toName(javac.getPackageName())); + commonSettings(res, javac); + return res; + } + + private ImportDeclaration convert(JCImport javac) { + ImportDeclaration res = this.ast.newImportDeclaration(); + commonSettings(res, javac); + if (javac.isStatic()) { + res.setStatic(true); + } + var select = javac.getQualifiedIdentifier(); + if (select.getIdentifier().contentEquals("*")) { + res.setOnDemand(true); + res.setName(toName(select.getExpression())); + } else { + res.setName(toName(select)); + } + return res; + } + + private void commonSettings(ASTNode res, JCTree javac) { + if (javac.getStartPosition() >= 0) { + int length = javac.getEndPosition(this.javacCompilationUnit.endPositions) - javac.getStartPosition(); + res.setSourceRange(javac.getStartPosition(), Math.max(0, length)); + } + this.domToJavac.put(res, javac); + } + + private Name toName(JCTree expression) { + if (expression instanceof JCIdent ident) { + Name res = convert(ident.getName()); + commonSettings(res, ident); + return res; + } + if (expression instanceof JCFieldAccess fieldAccess) { + QualifiedName res = this.ast.newQualifiedName(toName(fieldAccess.getExpression()), (SimpleName)convert(fieldAccess.getIdentifier())); + commonSettings(res, fieldAccess); + return res; + } + throw new UnsupportedOperationException("toName for " + expression + " (" + expression.getClass().getName() + ")"); + } + + private AbstractTypeDeclaration convertClassDecl(JCClassDecl javacClassDecl) { + AbstractTypeDeclaration res = switch (javacClassDecl.getKind()) { + case ANNOTATED_TYPE -> this.ast.newAnnotationTypeDeclaration(); + case ENUM -> this.ast.newEnumDeclaration(); + case RECORD -> this.ast.newRecordDeclaration(); + case INTERFACE -> { + TypeDeclaration decl = this.ast.newTypeDeclaration(); + decl.setInterface(true); + yield decl; + } + case CLASS -> this.ast.newTypeDeclaration(); + default -> throw new IllegalStateException(); + }; + commonSettings(res, javacClassDecl); + res.setName((SimpleName)convert(javacClassDecl.getSimpleName())); + res.modifiers().addAll(convert(javacClassDecl.mods)); + if (res instanceof TypeDeclaration typeDeclaration) { + if (javacClassDecl.getExtendsClause() != null) { + typeDeclaration.setSuperclassType(convertToType(javacClassDecl.getExtendsClause())); + } + if (javacClassDecl.getImplementsClause() != null) { + javacClassDecl.getImplementsClause().stream() + .map(this::convertToType) + .forEach(typeDeclaration.superInterfaceTypes()::add); + } + if (javacClassDecl.getPermitsClause() != null) { + javacClassDecl.getPermitsClause().stream() + .map(this::convertToType) + .forEach(typeDeclaration.permittedTypes()::add); + } + if (javacClassDecl.getMembers() != null) { + javacClassDecl.getMembers().stream() + .map(this::convertBodyDeclaration) + .forEach(typeDeclaration.bodyDeclarations()::add); + } +// +// Javadoc doc = this.ast.newJavadoc(); +// TagElement tagElement = this.ast.newTagElement(); +// TextElement textElement = this.ast.newTextElement(); +// textElement.setText("Hello"); +// tagElement.fragments().add(textElement); +// doc.tags().add(tagElement); +// res.setJavadoc(doc); + } + // TODO Javadoc + return res; + } + + private ASTNode convertBodyDeclaration(JCTree tree) { + if (tree instanceof JCMethodDecl methodDecl) { + return convertMethodDecl(methodDecl); + } + if (tree instanceof JCClassDecl jcClassDecl) { + return convertClassDecl(jcClassDecl); + } + if (tree instanceof JCVariableDecl jcVariableDecl) { + return convertFieldDeclaration(jcVariableDecl); + } + if (tree instanceof JCBlock block) { + Initializer res = this.ast.newInitializer(); + commonSettings(res, tree); + res.setBody(convertBlock(block)); + return res; + } + throw new UnsupportedOperationException("Unsupported " + tree + " of type" + tree.getClass()); + } + + private MethodDeclaration convertMethodDecl(JCMethodDecl javac) { + MethodDeclaration res = this.ast.newMethodDeclaration(); + commonSettings(res, javac); + res.modifiers().addAll(convert(javac.getModifiers())); + res.setConstructor(Objects.equals(javac.getName(), Names.instance(this.context).init)); + if (!res.isConstructor()) { + res.setName((SimpleName)convert(javac.getName())); + } + if (javac.getReturnType() != null) { + //res.setConstructor(javac.); + res.setReturnType2(convertToType(javac.getReturnType())); + } + javac.getParameters().stream().map(this::convertVariableDeclaration).forEach(res.parameters()::add); + if (javac.getBody() != null) { + res.setBody(convertBlock(javac.getBody())); + } + return res; + } + + private VariableDeclaration convertVariableDeclaration(JCVariableDecl javac) { + // if (singleDecl) { + SingleVariableDeclaration res = this.ast.newSingleVariableDeclaration(); + commonSettings(res, javac); + if (convert(javac.getName()) instanceof SimpleName simpleName) { + res.setName(simpleName); + } + res.modifiers().addAll(convert(javac.getModifiers())); + if (javac.getType() != null) { + res.setType(convertToType(javac.getType())); + } + if (javac.getInitializer() != null) { + res.setInitializer(convertExpression(javac.getInitializer())); + } + return res; + } + + private FieldDeclaration convertFieldDeclaration(JCVariableDecl javac) { + // if (singleDecl) { + VariableDeclarationFragment fragment = this.ast.newVariableDeclarationFragment(); + commonSettings(fragment, javac); + if (convert(javac.getName()) instanceof SimpleName simpleName) { + fragment.setName(simpleName); + } + if (javac.getInitializer() != null) { + fragment.setInitializer(convertExpression(javac.getInitializer())); + } + FieldDeclaration res = this.ast.newFieldDeclaration(fragment); + commonSettings(res, javac); + res.modifiers().addAll(convert(javac.getModifiers())); + res.setType(convertToType(javac.getType())); + return res; + } + + private Expression convertExpression(JCExpression javac) { + if (javac instanceof JCIdent ident) { + if (Objects.equals(ident.name, Names.instance(this.context)._this)) { + ThisExpression res = this.ast.newThisExpression(); + commonSettings(res, javac); + return res; + } + return toName(ident); + } + if (javac instanceof JCLiteral literal) { + return convertLiteral(literal); + } + if (javac instanceof JCFieldAccess fieldAccess) { + if (Objects.equals(Names.instance(this.context)._class, fieldAccess.getIdentifier())) { + TypeLiteral res = this.ast.newTypeLiteral(); + commonSettings(res, javac); + res.setType(convertToType(fieldAccess.getExpression())); + return res; + } + if (fieldAccess.getExpression() instanceof JCFieldAccess parentFieldAccess && Objects.equals(Names.instance(this.context)._super, parentFieldAccess.getIdentifier())) { + SuperFieldAccess res = this.ast.newSuperFieldAccess(); + commonSettings(res, javac); + res.setQualifier(toName(parentFieldAccess.getExpression())); + res.setName((SimpleName)convert(fieldAccess.getIdentifier())); + return res; + } + FieldAccess res = this.ast.newFieldAccess(); + commonSettings(res, javac); + res.setExpression(convertExpression(fieldAccess.getExpression())); + if (convert(fieldAccess.getIdentifier()) instanceof SimpleName name) { + res.setName(name); + } + return res; + } + if (javac instanceof JCMethodInvocation methodInvocation) { + MethodInvocation res = this.ast.newMethodInvocation(); + commonSettings(res, methodInvocation); + JCExpression nameExpr = methodInvocation.getMethodSelect(); + if (nameExpr instanceof JCIdent ident) { + if (Objects.equals(ident.getName(), Names.instance(this.context)._super)) { + return convertSuperMethodInvocation(methodInvocation); + } + SimpleName name = (SimpleName)convert(ident.getName()); + commonSettings(name, ident); + res.setName(name); + } else if (nameExpr instanceof JCFieldAccess access) { + if (access.getExpression() instanceof JCFieldAccess parentAccess && Objects.equals(Names.instance(this.context)._super, parentAccess.getIdentifier())) { + SuperMethodInvocation res2 = this.ast.newSuperMethodInvocation(); + commonSettings(res2, javac); + methodInvocation.getArguments().stream().map(this::convertExpression).forEach(res.arguments()::add); + methodInvocation.getTypeArguments().stream().map(this::convertToType).forEach(res.typeArguments()::add); + res2.setQualifier(toName(parentAccess.getExpression())); + res2.setName((SimpleName)convert(access.getIdentifier())); + return res2; + } + res.setName((SimpleName)convert(access.getIdentifier())); + res.setExpression(convertExpression(access.getExpression())); + } + if (methodInvocation.getArguments() != null) { + methodInvocation.getArguments().stream() + .map(this::convertExpression) + .forEach(res.arguments()::add); + } + if (methodInvocation.getTypeArguments() != null) { + methodInvocation.getTypeArguments().stream().map(this::convertToType).forEach(res.typeArguments()::add); + } + return res; + } + if (javac instanceof JCNewClass newClass) { + ClassInstanceCreation res = this.ast.newClassInstanceCreation(); + commonSettings(res, javac); + res.setType(convertToType(newClass.getIdentifier())); + if (newClass.getClassBody() != null) { + res.setAnonymousClassDeclaration(null); // TODO + } + if (newClass.getArguments() != null) { + newClass.getArguments().stream() + .map(this::convertExpression) + .forEach(res.arguments()::add); + } + return res; + } + if (javac instanceof JCErroneous error) { + if (error.getErrorTrees().size() == 1) { + JCTree tree = error.getErrorTrees().get(0); + if (tree instanceof JCExpression nestedExpr) { + return convertExpression(nestedExpr); + } + } else { + ParenthesizedExpression substitute = this.ast.newParenthesizedExpression(); + commonSettings(substitute, error); + return substitute; + } + return null; + } + if (javac instanceof JCBinary binary) { + InfixExpression res = this.ast.newInfixExpression(); + commonSettings(res, javac); + Expression left = convertExpression(binary.getLeftOperand()); + if (left != null) { + res.setLeftOperand(left); + } + Expression right = convertExpression(binary.getRightOperand()); + if (right != null) { + res.setRightOperand(right); + } + res.setOperator(switch (binary.getTag()) { + case OR -> InfixExpression.Operator.CONDITIONAL_OR; + case AND -> InfixExpression.Operator.CONDITIONAL_AND; + case BITOR -> InfixExpression.Operator.OR; + case BITXOR -> InfixExpression.Operator.XOR; + case BITAND -> InfixExpression.Operator.AND; + case EQ -> InfixExpression.Operator.EQUALS; + case NE -> InfixExpression.Operator.NOT_EQUALS; + case LT -> InfixExpression.Operator.LESS; + case GT -> InfixExpression.Operator.GREATER; + case LE -> InfixExpression.Operator.LESS_EQUALS; + case GE -> InfixExpression.Operator.GREATER_EQUALS; + case SL -> InfixExpression.Operator.LEFT_SHIFT; + case SR -> InfixExpression.Operator.RIGHT_SHIFT_SIGNED; + case USR -> InfixExpression.Operator.RIGHT_SHIFT_UNSIGNED; + case PLUS -> InfixExpression.Operator.PLUS; + case MINUS -> InfixExpression.Operator.MINUS; + case MUL -> InfixExpression.Operator.TIMES; + case DIV -> InfixExpression.Operator.DIVIDE; + case MOD -> InfixExpression.Operator.REMAINDER; + default -> null; + }); + return res; + } + if (javac instanceof JCUnary unary) { + if (unary.getTag() != Tag.POSTINC && unary.getTag() != Tag.POSTDEC) { + PrefixExpression res = this.ast.newPrefixExpression(); + commonSettings(res, javac); + res.setOperand(convertExpression(unary.getExpression())); + res.setOperator(switch (unary.getTag()) { + case POS -> PrefixExpression.Operator.PLUS; + case NEG -> PrefixExpression.Operator.MINUS; + case NOT -> PrefixExpression.Operator.NOT; + case COMPL -> PrefixExpression.Operator.COMPLEMENT; + case PREINC -> PrefixExpression.Operator.INCREMENT; + case PREDEC -> PrefixExpression.Operator.DECREMENT; + default -> null; + }); + return res; + } else { + PostfixExpression res = this.ast.newPostfixExpression(); + commonSettings(res, javac); + res.setOperand(convertExpression(unary.getExpression())); + res.setOperator(switch (unary.getTag()) { + case POSTINC -> PostfixExpression.Operator.INCREMENT; + case POSTDEC -> PostfixExpression.Operator.DECREMENT; + default -> null; + }); + return res; + } + } + if (javac instanceof JCParens parens) { + ParenthesizedExpression res = this.ast.newParenthesizedExpression(); + commonSettings(res, javac); + res.setExpression(convertExpression(parens.getExpression())); + return res; + } + if (javac instanceof JCAssign assign) { + Assignment res = this.ast.newAssignment(); + commonSettings(res, javac); + res.setLeftHandSide(convertExpression(assign.getVariable())); + res.setRightHandSide(convertExpression(assign.getExpression())); + return res; + } + if (javac instanceof JCInstanceOf jcInstanceOf) { + if (jcInstanceOf.getType() != null) { + InstanceofExpression res = this.ast.newInstanceofExpression(); + commonSettings(res, javac); + res.setLeftOperand(convertExpression(jcInstanceOf.getExpression())); + res.setRightOperand(convertToType(jcInstanceOf.getType())); + return res; + } + JCPattern jcPattern = jcInstanceOf.getPattern(); + if (jcPattern instanceof JCAnyPattern) { + InstanceofExpression res = this.ast.newInstanceofExpression(); + commonSettings(res, javac); + res.setLeftOperand(convertExpression(jcInstanceOf.getExpression())); + throw new UnsupportedOperationException("Right operand not supported yet"); +// return res; + } + PatternInstanceofExpression res = this.ast.newPatternInstanceofExpression(); + commonSettings(res, javac); + res.setLeftOperand(convertExpression(jcInstanceOf.getExpression())); + if (jcPattern instanceof JCBindingPattern jcBindingPattern) { + TypePattern jdtPattern = this.ast.newTypePattern(); + commonSettings(jdtPattern, jcBindingPattern); + jdtPattern.setPatternVariable((SingleVariableDeclaration)convertVariableDeclaration(jcBindingPattern.var)); + res.setPattern(jdtPattern); + } else { + throw new UnsupportedOperationException("Missing support to convert '" + jcPattern + "' of type " + javac.getClass().getSimpleName()); + } + return res; + } + if (javac instanceof JCArrayAccess jcArrayAccess) { + ArrayAccess res = this.ast.newArrayAccess(); + commonSettings(res, javac); + res.setArray(convertExpression(jcArrayAccess.getExpression())); + res.setIndex(convertExpression(jcArrayAccess.getIndex())); + return res; + } + if (javac instanceof JCTypeCast jcCast) { + CastExpression res = this.ast.newCastExpression(); + commonSettings(res, javac); + res.setExpression(convertExpression(jcCast.getExpression())); + res.setType(convertToType(jcCast.getType())); + return res; + } + if (javac instanceof JCMemberReference jcMemberReference) { + if (Objects.equals(Names.instance(this.context).init, jcMemberReference.getName())) { + CreationReference res = this.ast.newCreationReference(); + commonSettings(res, javac); + res.setType(convertToType(jcMemberReference.getQualifierExpression())); + if (jcMemberReference.getTypeArguments() != null) { + jcMemberReference.getTypeArguments().map(this::convertToType).forEach(res.typeArguments()::add); + } + return res; + } else { + ExpressionMethodReference res = this.ast.newExpressionMethodReference(); + commonSettings(res, javac); + res.setExpression(convertExpression(jcMemberReference.getQualifierExpression())); + res.setName((SimpleName)convert(jcMemberReference.getName())); + if (jcMemberReference.getTypeArguments() != null) { + jcMemberReference.getTypeArguments().map(this::convertToType).forEach(res.typeArguments()::add); + } + return res; + } + } + if (javac instanceof JCConditional jcCondition) { + ConditionalExpression res = this.ast.newConditionalExpression(); + commonSettings(res, javac); + res.setExpression(convertExpression(jcCondition.getCondition())); + res.setThenExpression(convertExpression(jcCondition.getTrueExpression())); + res.setElseExpression(convertExpression(jcCondition.getFalseExpression())); + return res; + } + if (javac instanceof JCLambda jcLambda) { + LambdaExpression res = this.ast.newLambdaExpression(); + jcLambda.getParameters().stream() + .filter(JCVariableDecl.class::isInstance) + .map(JCVariableDecl.class::cast) + .map(this::convertVariableDeclaration) + .forEach(res.parameters()::add); + res.setBody( + jcLambda.getBody() instanceof JCExpression expr ? convertExpression(expr) : + jcLambda.getBody() instanceof JCStatement stmt ? convertStatement(stmt) : + null); + // TODO set parenthesis looking at the next non-whitespace char after the last parameter + return res; + } + if (javac instanceof JCNewArray jcNewArray) { + ArrayCreation res = this.ast.newArrayCreation(); + commonSettings(res, javac); + if (jcNewArray.getType() != null) { + Type type = convertToType(jcNewArray.getType()); + ArrayType arrayType = this.ast.newArrayType(type); + commonSettings(arrayType, jcNewArray.getType()); + res.setType(arrayType); + } + jcNewArray.getDimensions().map(this::convertExpression).forEach(res.dimensions()::add); + if (jcNewArray.getInitializers() != null) { + ArrayInitializer initializer = this.ast.newArrayInitializer(); + commonSettings(initializer, javac); + jcNewArray.getInitializers().stream().map(this::convertExpression).forEach(initializer.expressions()::add); + } + return res; + } + // TODO instanceof, lambdas + throw new UnsupportedOperationException("Missing support to convert '" + javac + "' of type " + javac.getClass().getSimpleName()); + } + + private SuperMethodInvocation convertSuperMethodInvocation(JCMethodInvocation javac) { + SuperMethodInvocation res = this.ast.newSuperMethodInvocation(); + commonSettings(res, javac); + javac.getArguments().stream().map(this::convertExpression).forEach(res.arguments()::add); + javac.getTypeArguments().stream().map(this::convertToType).forEach(res.typeArguments()::add); + return res; + } + + private ConstructorInvocation convertThisConstructorInvocation(JCMethodInvocation javac) { + ConstructorInvocation res = this.ast.newConstructorInvocation(); + commonSettings(res, javac); + javac.getArguments().stream().map(this::convertExpression).forEach(res.arguments()::add); + javac.getTypeArguments().stream().map(this::convertToType).forEach(res.typeArguments()::add); + return res; + } + + private Expression convertLiteral(JCLiteral literal) { + Object value = literal.getValue(); + if (value instanceof Number number) { + NumberLiteral res = this.ast.newNumberLiteral(); + commonSettings(res, literal); + res.setToken(literal.value.toString()); // TODO: we want the token here + return res; + } + if (value instanceof String string) { + StringLiteral res = this.ast.newStringLiteral(); + commonSettings(res, literal); + res.setLiteralValue(string); // TODO: we want the token here + return res; + } + if (value instanceof Boolean string) { + BooleanLiteral res = this.ast.newBooleanLiteral(string.booleanValue()); + commonSettings(res, literal); + return res; + } + if (value == null) { + NullLiteral res = this.ast.newNullLiteral(); + commonSettings(res, literal); + return res; + } + if (value instanceof Character) { + CharacterLiteral res = this.ast.newCharacterLiteral(); + commonSettings(res, literal); + res.setCharValue(res.charValue()); + return res; + } + throw new UnsupportedOperationException("Not supported yet " + literal + "\n of type" + literal.getClass().getName()); + } + + private Statement convertStatement(JCStatement javac) { + if (javac instanceof JCReturn returnStatement) { + ReturnStatement res = this.ast.newReturnStatement(); + commonSettings(res, javac); + if (returnStatement.getExpression() != null) { + res.setExpression(convertExpression(returnStatement.getExpression())); + } + return res; + } + if (javac instanceof JCBlock block) { + return convertBlock(block); + } + if (javac instanceof JCExpressionStatement jcExpressionStatement) { + JCExpression jcExpression = jcExpressionStatement.getExpression(); + if (jcExpression instanceof JCMethodInvocation jcMethodInvocation + && jcMethodInvocation.getMethodSelect() instanceof JCIdent methodName + && Objects.equals(methodName.getName(), Names.instance(this.context)._this)) { + return convertThisConstructorInvocation(jcMethodInvocation); + } + if (jcExpressionStatement.getExpression() == null) { + return null; + } + if (jcExpressionStatement.getExpression() instanceof JCErroneous jcError) { + if (jcError.getErrorTrees().size() == 1) { + JCTree tree = jcError.getErrorTrees().get(0); + if (tree instanceof JCStatement nestedStmt) { + return convertStatement(nestedStmt); + } + } else { + Block substitute = this.ast.newBlock(); + commonSettings(substitute, jcError); + return substitute; + } + } + ExpressionStatement res = this.ast.newExpressionStatement(convertExpression(jcExpressionStatement.getExpression())); + commonSettings(res, javac); + return res; + } + if (javac instanceof JCVariableDecl jcVariableDecl) { + VariableDeclarationFragment fragment = this.ast.newVariableDeclarationFragment(); + commonSettings(fragment, javac); + fragment.setName((SimpleName)convert(jcVariableDecl.getName())); + if (jcVariableDecl.getInitializer() != null) { + fragment.setInitializer(convertExpression(jcVariableDecl.getInitializer())); + } + VariableDeclarationStatement res = this.ast.newVariableDeclarationStatement(fragment); + commonSettings(res, javac); + return res; + } + if (javac instanceof JCIf ifStatement) { + return convertIfStatement(ifStatement); + } + if (javac instanceof JCThrow throwStatement) { + ThrowStatement res = this.ast.newThrowStatement(); + commonSettings(res, javac); + res.setExpression(convertExpression(throwStatement.getExpression())); + return res; + } + if (javac instanceof JCTry tryStatement) { + return convertTryStatement(tryStatement); + } + if (javac instanceof JCSynchronized jcSynchronized) { + SynchronizedStatement res = this.ast.newSynchronizedStatement(); + commonSettings(res, javac); + res.setExpression(convertExpression(jcSynchronized.getExpression())); + res.setBody(convertBlock(jcSynchronized.getBlock())); + return res; + } + if (javac instanceof JCForLoop jcForLoop) { + ForStatement res = this.ast.newForStatement(); + commonSettings(res, javac); + res.setBody(convertStatement(jcForLoop.getStatement())); + jcForLoop.getInitializer().stream().map(this::convertStatementToExpression).forEach(res.initializers()::add); + res.setExpression(convertExpression(jcForLoop.getCondition())); + jcForLoop.getUpdate().stream().map(this::convertStatementToExpression).forEach(res.updaters()::add); + return res; + } + if (javac instanceof JCEnhancedForLoop jcEnhancedForLoop) { + EnhancedForStatement res = this.ast.newEnhancedForStatement(); + commonSettings(res, javac); + res.setParameter((SingleVariableDeclaration)convertVariableDeclaration(jcEnhancedForLoop.getVariable())); + res.setExpression(convertExpression(jcEnhancedForLoop.getExpression())); + res.setBody(convertStatement(jcEnhancedForLoop.getStatement())); + return res; + } + if (javac instanceof JCBreak jcBreak) { + BreakStatement res = this.ast.newBreakStatement(); + commonSettings(res, javac); + if (jcBreak.getLabel() != null) { + res.setLabel((SimpleName)convert(jcBreak.getLabel())); + } + return res; + } + if (javac instanceof JCSwitch jcSwitch) { + SwitchStatement res = this.ast.newSwitchStatement(); + commonSettings(res, javac); + res.setExpression(convertExpression(jcSwitch.getExpression())); + jcSwitch.getCases().stream() + .flatMap(switchCase -> { + List stmts = new ArrayList<>(switchCase.getStatements().size() + 1); + stmts.add(switchCase); + stmts.addAll(switchCase.getStatements()); + return stmts.stream(); + }).map(this::convertStatement) + .forEach(res.statements()::add); + return res; + } + if (javac instanceof JCCase jcCase) { + SwitchCase res = this.ast.newSwitchCase(); + commonSettings(res, javac); + res.setSwitchLabeledRule(jcCase.getCaseKind() == CaseKind.RULE); + jcCase.getExpressions().stream().map(this::convertExpression).forEach(res.expressions()::add); + // jcCase.getStatements is processed as part of JCSwitch conversion + return res; + } + if (javac instanceof JCWhileLoop jcWhile) { + WhileStatement res = this.ast.newWhileStatement(); + commonSettings(res, javac); + res.setExpression(convertExpression(jcWhile.getCondition())); + res.setBody(convertStatement(jcWhile.getStatement())); + return res; + } + if (javac instanceof JCDoWhileLoop jcDoWhile) { + DoStatement res = this.ast.newDoStatement(); + commonSettings(res, javac); + res.setExpression(convertExpression(jcDoWhile.getCondition())); + res.setBody(convertStatement(jcDoWhile.getStatement())); + return res; + } + if (javac instanceof JCYield jcYield) { + YieldStatement res = this.ast.newYieldStatement(); + commonSettings(res, javac); + res.setExpression(convertExpression(jcYield.getValue())); + return res; + } + if (javac instanceof JCContinue jcContinue) { + ContinueStatement res = this.ast.newContinueStatement(); + commonSettings(res, javac); + if (jcContinue.getLabel() != null) { + res.setLabel((SimpleName)convert(jcContinue.getLabel())); + } + return res; + } + if (javac instanceof JCLabeledStatement jcLabel) { + LabeledStatement res = this.ast.newLabeledStatement(); + commonSettings(res, javac); + res.setLabel((SimpleName)convert(jcLabel.getLabel())); + return res; + } + if (javac instanceof JCAssert jcAssert) { + AssertStatement res =this.ast.newAssertStatement(); + commonSettings(res, javac); + res.setExpression(convertExpression(jcAssert.getCondition())); + return res; + } + throw new UnsupportedOperationException("Missing support to convert " + javac + "of type " + javac.getClass().getName()); + } + + private Expression convertStatementToExpression(JCStatement javac) { + if (javac instanceof JCExpressionStatement jcExpressionStatement) { + return convertExpression(jcExpressionStatement.getExpression()); + } + Statement javacStatement = convertStatement(javac); + if (javacStatement instanceof VariableDeclarationStatement decl && decl.fragments().size() == 1) { + javacStatement.delete(); + VariableDeclarationFragment fragment = (VariableDeclarationFragment)decl.fragments().get(0); + fragment.delete(); + VariableDeclarationExpression jdtVariableDeclarationExpression = this.ast.newVariableDeclarationExpression(fragment); + commonSettings(jdtVariableDeclarationExpression, javac); + return jdtVariableDeclarationExpression; + } + throw new UnsupportedOperationException(javac + " of type" + javac.getClass()); + } + + private Block convertBlock(JCBlock javac) { + Block res = this.ast.newBlock(); + commonSettings(res, javac); + if (javac.getStatements() != null) { + javac.getStatements().stream() + .map(this::convertStatement) + .forEach(res.statements()::add); + } + return res; + } + + private TryStatement convertTryStatement(JCTry javac) { + TryStatement res = this.ast.newTryStatement(); + commonSettings(res, javac); + res.setBody(convertBlock(javac.getBlock())); + if (javac.finalizer != null) { + res.setFinally(convertBlock(javac.getFinallyBlock())); + } + javac.getResources().stream().map(this::convertTryResource).forEach(res.resources()::add); + javac.getCatches().stream().map(this::convertCatcher).forEach(res.catchClauses()::add); + return res; + } + + private ASTNode /*VariableDeclarationExpression or Name*/ convertTryResource(JCTree javac) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + private CatchClause convertCatcher(JCCatch javac) { + CatchClause res = this.ast.newCatchClause(); + commonSettings(res, javac); + res.setBody(convertBlock(javac.getBlock())); + res.setException((SingleVariableDeclaration)convertVariableDeclaration(javac.getParameter())); + return res; + } + + private IfStatement convertIfStatement(JCIf javac) { + IfStatement res = this.ast.newIfStatement(); + commonSettings(res, javac); + if (javac.getCondition() != null) { + res.setExpression(convertExpression(javac.getCondition())); + } + if (javac.getThenStatement() != null) { + res.setThenStatement(convertStatement(javac.getThenStatement())); + } + if (javac.getElseStatement() != null) { + res.setElseStatement(convertStatement(javac.getElseStatement())); + } + return res; + } + + private Type convertToType(JCTree javac) { + if (javac instanceof JCIdent ident) { + SimpleType res = this.ast.newSimpleType(convert(ident.name)); + commonSettings(res, ident); + return res; + } + if (javac instanceof JCFieldAccess qualified) { + QualifiedType res = this.ast.newQualifiedType(convertToType(qualified.getExpression()), (SimpleName)convert(qualified.name)); + commonSettings(res, qualified); + return res; + } + if (javac instanceof JCPrimitiveTypeTree primitiveTypeTree) { + PrimitiveType res = this.ast.newPrimitiveType(convert(primitiveTypeTree.getPrimitiveTypeKind())); + commonSettings(res, primitiveTypeTree); + return res; + } + if (javac instanceof JCTypeUnion union) { + UnionType res = this.ast.newUnionType(); + commonSettings(res, javac); + union.getTypeAlternatives().stream().map(this::convertToType).forEach(res.types()::add); + return res; + } + if (javac instanceof JCArrayTypeTree jcArrayType) { + ArrayType res = this.ast.newArrayType(convertToType(jcArrayType.getType())); + commonSettings(res, javac); + return res; + } + if (javac instanceof JCTypeApply jcTypeApply) { + ParameterizedType res = this.ast.newParameterizedType(convertToType(jcTypeApply.getType())); + commonSettings(res, javac); + jcTypeApply.getTypeArguments().stream().map(this::convertToType).forEach(res.typeArguments()::add); + return res; + } + if (javac instanceof JCWildcard) { + WildcardType res = this.ast.newWildcardType(); + commonSettings(res, javac); + return res; + } + if (javac instanceof JCTypeIntersection jcTypeIntersection) { + IntersectionType res = this.ast.newIntersectionType(); + commonSettings(res, javac); + jcTypeIntersection.getBounds().stream().map(this::convertToType).forEach(res.types()::add); + return res; + } + throw new UnsupportedOperationException("Not supported yet, type " + javac + " of class" + javac.getClass()); + } + + private List convert(JCModifiers modifiers) { + List res = new ArrayList<>(); + modifiers.getFlags().stream().map(this::convert).forEach(res::add); + modifiers.getAnnotations().stream().map(this::convert).forEach(res::add); + return res; + } + + private Code convert(TypeKind javac) { + return switch(javac) { + case BOOLEAN -> PrimitiveType.INT; + case BYTE -> PrimitiveType.BYTE; + case SHORT -> PrimitiveType.SHORT; + case INT -> PrimitiveType.INT; + case LONG -> PrimitiveType.LONG; + case CHAR -> PrimitiveType.CHAR; + case FLOAT -> PrimitiveType.FLOAT; + case DOUBLE -> PrimitiveType.DOUBLE; + case VOID -> PrimitiveType.VOID; + default -> throw new IllegalArgumentException(javac.toString()); + }; + } + + private Annotation convert(JCAnnotation javac) { + Annotation res = this.ast.newNormalAnnotation(); + commonSettings(res, javac); + res.setTypeName(toName(javac.getAnnotationType())); + // TODO member values/arguments + return res; + } + + private Modifier convert(javax.lang.model.element.Modifier javac) { + Modifier res = this.ast.newModifier(switch (javac) { + case PUBLIC -> ModifierKeyword.PUBLIC_KEYWORD; + case PROTECTED -> ModifierKeyword.PROTECTED_KEYWORD; + case PRIVATE -> ModifierKeyword.PRIVATE_KEYWORD; + case ABSTRACT -> ModifierKeyword.ABSTRACT_KEYWORD; + case DEFAULT -> ModifierKeyword.DEFAULT_KEYWORD; + case STATIC -> ModifierKeyword.STATIC_KEYWORD; + case SEALED -> ModifierKeyword.SEALED_KEYWORD; + case NON_SEALED -> ModifierKeyword.NON_SEALED_KEYWORD; + case FINAL -> ModifierKeyword.FINAL_KEYWORD; + case TRANSIENT -> ModifierKeyword.TRANSIENT_KEYWORD; + case VOLATILE -> ModifierKeyword.VOLATILE_KEYWORD; + case SYNCHRONIZED -> ModifierKeyword.SYNCHRONIZED_KEYWORD; + case NATIVE -> ModifierKeyword.NATIVE_KEYWORD; + case STRICTFP -> ModifierKeyword.STRICTFP_KEYWORD; + }); + // TODO set positions + return res; + } + + private Name convert(com.sun.tools.javac.util.Name javac) { + if (javac == null || Objects.equals(javac, Names.instance(this.context).error) || Objects.equals(javac, Names.instance(this.context).empty)) { + return null; + } + int lastDot = javac.lastIndexOf((byte)'.'); + if (lastDot < 0) { + return this.ast.newSimpleName(javac.toString()); + } else { + return this.ast.newQualifiedName(convert(javac.subName(0, lastDot)), (SimpleName)convert(javac.subName(lastDot + 1, javac.length() - 1))); + } + // position is set later, in FixPositions, as computing them depends on the sibling + } + + public org.eclipse.jdt.core.dom.Comment convert(Comment javac, int pos, int endPos) { + org.eclipse.jdt.core.dom.Comment jdt = switch (javac.getStyle()) { + case LINE -> this.ast.newLineComment(); + case BLOCK -> this.ast.newBlockComment(); + case JAVADOC -> this.ast.newJavadoc(); + }; + jdt.setSourceRange(pos, endPos - pos); + return jdt; + } + + static IProblem convertDiagnostic(Diagnostic javacDiagnostic) { + // TODO use a problem factory? Map code to category...? + return new DefaultProblem( + javacDiagnostic.getSource().getName().toCharArray(), + javacDiagnostic.getMessage(Locale.getDefault()), + toProblemId(javacDiagnostic.getCode()), // TODO probably use `getCode()` here + null, + switch (javacDiagnostic.getKind()) { + case ERROR -> ProblemSeverities.Error; + case WARNING, MANDATORY_WARNING -> ProblemSeverities.Warning; + case NOTE -> ProblemSeverities.Info; + default -> ProblemSeverities.Error; + }, + (int)Math.min(javacDiagnostic.getPosition(), javacDiagnostic.getStartPosition()), + (int)javacDiagnostic.getEndPosition(), + (int)javacDiagnostic.getLineNumber(), + (int)javacDiagnostic.getColumnNumber()); + } + + private static int toProblemId(String javacDiagnosticCode) { + // better use a Map if there is a 1->0..1 mapping + return switch (javacDiagnosticCode) { + case "compiler.warn.raw.class.use" -> IProblem.RawTypeReference; + // TODO complete mapping list; dig in https://github.com/openjdk/jdk/blob/master/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties + // for an exhaustive (but polluted) list, unless a better source can be found (spec?) + default -> 0; + }; + } + + class FixPositions extends ASTVisitor { + private final String contents; + + FixPositions() { + String s = null; + try { + s = JavacConverter.this.javacCompilationUnit.getSourceFile().getCharContent(true).toString(); + } catch (IOException ex) { + ILog.get().error(ex.getMessage(), ex); + } + this.contents = s; + } + + @Override + public boolean visit(QualifiedName node) { + if (node.getStartPosition() < 0) { + int foundOffset = findPositionOfText(node.getFullyQualifiedName(), node.getParent(), siblings(node)); + if (foundOffset >= 0) { + node.setSourceRange(foundOffset, node.getFullyQualifiedName().length()); + } + } + return true; + } + + @Override + public void endVisit(QualifiedName node) { + if (node.getName().getStartPosition() >= 0) { + node.setSourceRange(node.getQualifier().getStartPosition(), node.getName().getStartPosition() + node.getName().getLength() - node.getQualifier().getStartPosition()); + } + } + + @Override + public boolean visit(SimpleName name) { + if (name.getStartPosition() < 0) { + int foundOffset = findPositionOfText(name.getIdentifier(), name.getParent(), siblings(name)); + if (foundOffset >= 0) { + name.setSourceRange(foundOffset, name.getIdentifier().length()); + } + } + return false; + } + + @Override + public boolean visit(Modifier modifier) { + int parentStart = modifier.getParent().getStartPosition(); + int relativeStart = this.contents.substring(parentStart, parentStart + modifier.getParent().getLength()).indexOf(modifier.getKeyword().toString()); + if (relativeStart >= 0) { + modifier.setSourceRange(parentStart + relativeStart, modifier.getKeyword().toString().length()); + } else { + ILog.get().warn("Couldn't compute position of " + modifier); + } + return true; + } + + private static List siblings(ASTNode node) { + return ((Collection)node.getParent().properties().values()).stream() + .filter(ASTNode.class::isInstance) + .map(ASTNode.class::cast) + .filter(Predicate.not(node::equals)) + .toList(); + } + + private int findPositionOfText(String text, ASTNode in, List excluding) { + int current = in.getStartPosition(); + PriorityQueue excluded = new PriorityQueue<>(Comparator.comparing(ASTNode::getStartPosition)); + if (excluded.isEmpty()) { + String subText = this.contents.substring(current, current + in.getLength()); + int foundInSubText = subText.indexOf(text); + if (foundInSubText >= 0) { + return current + foundInSubText; + } + } else { + ASTNode currentExclusion = null; + while ((currentExclusion = excluded.poll()) != null) { + if (currentExclusion.getStartPosition() >= current) { + int rangeEnd = currentExclusion.getStartPosition(); + String subText = this.contents.substring(current, rangeEnd); + int foundInSubText = subText.indexOf(text); + if (foundInSubText >= 0) { + return current + foundInSubText; + } + current = rangeEnd + currentExclusion.getLength(); + } + } + } + return -1; + } + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacMemberValuePairBinding.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacMemberValuePairBinding.java new file mode 100644 index 00000000000..ae12d07fcdc --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacMemberValuePairBinding.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * 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.jdt.core.IJavaElement; + +import com.sun.tools.javac.code.Attribute; +import com.sun.tools.javac.code.Symbol.MethodSymbol; + +public class JavacMemberValuePairBinding implements IMemberValuePairBinding { + + private JavacMethodBinding method; + private Attribute value; + + public JavacMemberValuePairBinding(MethodSymbol key, Attribute value, JavacBindingResolver resolver) { + this.method = new JavacMethodBinding(key, resolver); + this.value = value; + } + + @Override + public IAnnotationBinding[] getAnnotations() { + return new IAnnotationBinding[0]; + } + + @Override + public int getKind() { + return MEMBER_VALUE_PAIR; + } + + @Override + public int getModifiers() { + return method.getModifiers(); + } + + @Override + public boolean isDeprecated() { + return method.isDeprecated(); + } + + @Override + public boolean isRecovered() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isRecovered'"); + } + + @Override + public boolean isSynthetic() { + return method.isSynthetic(); + } + + @Override + public IJavaElement getJavaElement() { + return method.getJavaElement(); + } + + @Override + public String getKey() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getKey'"); + } + + @Override + public boolean isEqualTo(IBinding binding) { + return binding instanceof JavacMemberValuePairBinding other && this.method.isEqualTo(other.method) + && Objects.equals(this.value, other.value); + } + + @Override + public String getName() { + return this.method.getName(); + } + + @Override + public IMethodBinding getMethodBinding() { + return this.method; + } + + @Override + public Object getValue() { + throw new UnsupportedOperationException("Unimplemented method 'getValue'"); + } + + @Override + public boolean isDefault() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isDefault'"); + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacMethodBinding.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacMethodBinding.java new file mode 100644 index 00000000000..ee8132ac184 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacMethodBinding.java @@ -0,0 +1,291 @@ +/******************************************************************************* + * 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.core.dom; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IType; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Symbol.ClassSymbol; +import com.sun.tools.javac.code.Symbol.MethodSymbol; +import com.sun.tools.javac.code.Symbol.VarSymbol; + +class JavacMethodBinding implements IMethodBinding { + + final MethodSymbol methodSymbol; + final JavacBindingResolver resolver; + + public JavacMethodBinding(MethodSymbol sym, JavacBindingResolver resolver) { + this.methodSymbol = sym; + this.resolver = resolver; + } + + @Override + public IAnnotationBinding[] getAnnotations() { + return methodSymbol.getAnnotationMirrors().stream().map(ann -> new JavacAnnotationBinding(ann, this.resolver)).toArray(IAnnotationBinding[]::new); + } + + @Override + public int getKind() { + return METHOD; + } + + @Override + public int getModifiers() { + return toInt(this.methodSymbol.getModifiers()); + } + + static int toInt(Set javac) { + if (javac == null) { + return 0; + } + int[] res = new int[] { 0 }; + javac.forEach(mod -> res[0] |= toInt(mod)); + return res[0]; + } + + private static int toInt(javax.lang.model.element.Modifier javac) { + return switch (javac) { + case PUBLIC -> Modifier.PUBLIC; + case PROTECTED -> Modifier.PROTECTED; + case PRIVATE -> Modifier.PRIVATE; + case ABSTRACT -> Modifier.ABSTRACT; + case DEFAULT -> Modifier.DEFAULT; + case STATIC -> Modifier.STATIC; + case SEALED -> Modifier.SEALED; + case NON_SEALED -> Modifier.NON_SEALED; + case FINAL -> Modifier.FINAL; + case TRANSIENT -> Modifier.TRANSIENT; + case VOLATILE -> Modifier.VOLATILE; + case SYNCHRONIZED -> Modifier.SYNCHRONIZED; + case NATIVE -> Modifier.NATIVE; + case STRICTFP -> Modifier.STRICTFP; + }; + } + + @Override + public boolean isDeprecated() { + return this.methodSymbol.isDeprecated(); + } + + @Override + public boolean isRecovered() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isRecovered'"); + } + + @Override + public boolean isSynthetic() { + return (this.methodSymbol.flags() & Flags.SYNTHETIC) != 0; + } + + @Override + public IJavaElement getJavaElement() { + IJavaElement parent = this.resolver.getBinding(this.methodSymbol.owner).getJavaElement(); + if (parent instanceof IType type) { + return type.getMethod(this.methodSymbol.getSimpleName().toString(), + this.methodSymbol.params().stream() + .map(varSymbol -> varSymbol.type) + .map(t -> t.tsym.name.toString()) + .toArray(String[]::new)); + } + return null; + } + + @Override + public String getKey() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getKey'"); + } + + @Override + public boolean isEqualTo(IBinding binding) { + return binding instanceof JavacMethodBinding other && // + Objects.equals(this.methodSymbol, other.methodSymbol) && // + Objects.equals(this.resolver, other.resolver); + } + + @Override + public boolean isConstructor() { + return this.methodSymbol.isConstructor(); + } + + @Override + public boolean isCompactConstructor() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isCompactConstructor'"); + } + + @Override + public boolean isCanonicalConstructor() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isCanonicalConstructor'"); + } + + @Override + public boolean isDefaultConstructor() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isDefaultConstructor'"); + } + + @Override + public String getName() { + return this.methodSymbol.getSimpleName().toString(); + } + + @Override + public ITypeBinding getDeclaringClass() { + Symbol parentSymbol = this.methodSymbol.owner; + do { + if (parentSymbol instanceof ClassSymbol clazz) { + return new JavacTypeBinding(clazz, this.resolver); + } + parentSymbol = parentSymbol.owner; + } while (parentSymbol != null); + return null; + } + + @Override + public IBinding getDeclaringMember() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getDeclaringMember'"); + } + + @Override + public Object getDefaultValue() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getDefaultValue'"); + } + + @Override + public IAnnotationBinding[] getParameterAnnotations(int paramIndex) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getParameterAnnotations'"); + } + + @Override + public ITypeBinding[] getParameterTypes() { + return this.methodSymbol.params().stream() + .map(param -> param.type) + .map(type -> new JavacTypeBinding(type, this.resolver)) + .toArray(ITypeBinding[]::new); + } + + @Override + public ITypeBinding getDeclaredReceiverType() { + return new JavacTypeBinding(this.methodSymbol.getReceiverType(), this.resolver); + } + + @Override + public ITypeBinding getReturnType() { + return new JavacTypeBinding(this.methodSymbol.getReturnType(), this.resolver); + } + + @SuppressWarnings("unchecked") + @Override + public ITypeBinding[] getExceptionTypes() { + ASTNode node = this.resolver.findNode(this.methodSymbol); + if (node instanceof MethodDeclaration method) { + return ((List)method.thrownExceptionTypes()).stream() + .map(Type::resolveBinding) + .toArray(ITypeBinding[]::new); + } + return new ITypeBinding[0]; + } + + @Override + public ITypeBinding[] getTypeParameters() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getTypeParameters'"); + } + + @Override + public boolean isAnnotationMember() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isAnnotationMember'"); + } + + @Override + public boolean isGenericMethod() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isGenericMethod'"); + } + + @Override + public boolean isParameterizedMethod() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isParameterizedMethod'"); + } + + @Override + public ITypeBinding[] getTypeArguments() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getTypeArguments'"); + } + + @Override + public IMethodBinding getMethodDeclaration() { + return this; + } + + @Override + public boolean isRawMethod() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isRawMethod'"); + } + + @Override + public boolean isSubsignature(IMethodBinding otherMethod) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isSubsignature'"); + } + + @Override + public boolean isVarargs() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isVarargs'"); + } + + @Override + public boolean overrides(IMethodBinding method) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'overrides'"); + } + + @Override + public IVariableBinding[] getSyntheticOuterLocals() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getSyntheticOuterLocals'"); + } + + @Override + public boolean isSyntheticRecordMethod() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isSyntheticRecordMethod'"); + } + + @Override + public String[] getParameterNames() { + if (this.methodSymbol.getParameters() == null) { + return new String[0]; + } + return this.methodSymbol.getParameters().stream() // + .map(VarSymbol::getSimpleName) // + .map(Object::toString) // + .toArray(String[]::new); + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacPackageBinding.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacPackageBinding.java new file mode 100644 index 00000000000..69ae3fec22e --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacPackageBinding.java @@ -0,0 +1,110 @@ +/******************************************************************************* + * 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.core.dom; + +import java.util.Arrays; +import java.util.Objects; + +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.JavaModelException; + +import com.sun.tools.javac.code.Symbol.PackageSymbol; + +class JavacPackageBinding implements IPackageBinding { + + final PackageSymbol packageSymbol; + final JavacBindingResolver resolver; + + JavacPackageBinding(PackageSymbol packge, JavacBindingResolver resolver) { + this.packageSymbol = packge; + this.resolver = resolver; + } + + @Override + public IAnnotationBinding[] getAnnotations() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getAnnotations'"); + } + + @Override + public int getKind() { + return PACKAGE; + } + + @Override + public int getModifiers() { + return JavacMethodBinding.toInt(this.packageSymbol.getModifiers()); + } + + @Override + public boolean isDeprecated() { + return this.packageSymbol.isDeprecated(); + } + + @Override + public boolean isRecovered() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isRecovered'"); + } + + @Override + public boolean isSynthetic() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isSynthetic'"); + } + + @Override + public IJavaElement getJavaElement() { + System.err.println("Hardocded binding->IJavaElement to 1st package"); + try { + return Arrays.stream(this.resolver.javaProject.getAllPackageFragmentRoots()) + .map(root -> root.getPackageFragment(this.packageSymbol.getQualifiedName().toString())) + .filter(Objects::nonNull) + .filter(IPackageFragment::exists) + .findFirst() + .orElse(null); + } catch (JavaModelException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + } + + @Override + public String getKey() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getKey'"); + } + + @Override + public boolean isEqualTo(IBinding binding) { + return binding instanceof JavacPackageBinding other && // + Objects.equals(this.packageSymbol, other.packageSymbol) && // + Objects.equals(this.resolver, other.resolver); + } + + @Override + public String getName() { + return isUnnamed() ? "" : this.packageSymbol.getQualifiedName().toString(); //$NON-NLS-1$ + } + + @Override + public boolean isUnnamed() { + return this.packageSymbol.isUnnamed(); + } + + @Override + public String[] getNameComponents() { + return isUnnamed()? new String[0] : this.packageSymbol.getQualifiedName().toString().split("."); //$NON-NLS-1$ + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacTypeBinding.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacTypeBinding.java new file mode 100644 index 00000000000..2479b0387e1 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacTypeBinding.java @@ -0,0 +1,437 @@ +/******************************************************************************* + * 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.core.dom; + +import java.util.Objects; +import java.util.stream.StreamSupport; + +import org.eclipse.core.runtime.ILog; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaModelException; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Symbol.ClassSymbol; +import com.sun.tools.javac.code.Symbol.MethodSymbol; +import com.sun.tools.javac.code.Symbol.TypeSymbol; +import com.sun.tools.javac.code.Symbol.VarSymbol; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.Type.ArrayType; +import com.sun.tools.javac.code.Types; + +class JavacTypeBinding implements ITypeBinding { + + final JavacBindingResolver resolver; + final TypeSymbol typeSymbol; + private final Types types; + + public JavacTypeBinding(final TypeSymbol classSymbol, final JavacBindingResolver resolver) { + this.typeSymbol = classSymbol; + this.resolver = resolver; + this.types = this.resolver.context.get(Types.class); + } + + JavacTypeBinding(final Type type, final JavacBindingResolver resolver) { + this(type.tsym, resolver); + } + + @Override + public IAnnotationBinding[] getAnnotations() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getAnnotations'"); + } + + @Override + public int getKind() { + return TYPE; + } + + @Override + public boolean isDeprecated() { + return this.typeSymbol.isDeprecated(); + } + + @Override + public boolean isRecovered() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isRecovered'"); + } + + @Override + public boolean isSynthetic() { + return (this.typeSymbol.flags() & Flags.SYNTHETIC) != 0; + } + + @Override + public IType getJavaElement() { + if (this.typeSymbol instanceof final ClassSymbol classSymbol) { + try { + return this.resolver.javaProject.findType(classSymbol.className()); + } catch (JavaModelException ex) { + ILog.get().error(ex.getMessage(), ex); + } + } + return null; + } + + @Override + public String getKey() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getKey'"); + } + + @Override + public boolean isEqualTo(final IBinding binding) { + return binding instanceof final JavacTypeBinding other && // + Objects.equals(this.resolver, other.resolver) && // + Objects.equals(this.typeSymbol, other.typeSymbol); + } + + @Override + public ITypeBinding createArrayType(final int dimension) { + Type type = this.typeSymbol.type; + for (int i = 0; i < dimension; i++) { + type = this.types.makeArrayType(type); + } + return new JavacTypeBinding(type, this.resolver); + } + + @Override + public String getBinaryName() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getBinaryName'"); + } + + @Override + public ITypeBinding getBound() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getBound'"); + } + + @Override + public ITypeBinding getGenericTypeOfWildcardType() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getGenericTypeOfWildcardType'"); + } + + @Override + public int getRank() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getRank'"); + } + + @Override + public ITypeBinding getComponentType() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getComponentType'"); + } + + @Override + public IVariableBinding[] getDeclaredFields() { + return StreamSupport.stream(this.typeSymbol.members().getSymbols().spliterator(), false) + .filter(VarSymbol.class::isInstance) + .map(VarSymbol.class::cast) + .map(sym -> new JavacVariableBinding(sym, this.resolver)) + .toArray(IVariableBinding[]::new); + } + + @Override + public IMethodBinding[] getDeclaredMethods() { + return StreamSupport.stream(this.typeSymbol.members().getSymbols().spliterator(), false) + .filter(MethodSymbol.class::isInstance) + .map(MethodSymbol.class::cast) + .map(sym -> new JavacMethodBinding(sym, this.resolver)) + .toArray(IMethodBinding[]::new); + } + + @Override + public int getDeclaredModifiers() { + return this.resolver.findNode(this.typeSymbol) instanceof TypeDeclaration typeDecl ? + typeDecl.getModifiers() : + 0; + } + + @Override + public ITypeBinding[] getDeclaredTypes() { + return StreamSupport.stream(this.typeSymbol.members().getSymbols().spliterator(), false) + .filter(TypeSymbol.class::isInstance) + .map(TypeSymbol.class::cast) + .map(sym -> new JavacTypeBinding(sym, this.resolver)) + .toArray(ITypeBinding[]::new); + } + + @Override + public ITypeBinding getDeclaringClass() { + Symbol parentSymbol = this.typeSymbol.owner; + do { + if (parentSymbol instanceof final ClassSymbol clazz) { + return new JavacTypeBinding(clazz, this.resolver); + } + parentSymbol = parentSymbol.owner; + } while (parentSymbol != null); + return null; + } + + @Override + public IMethodBinding getDeclaringMethod() { + Symbol parentSymbol = this.typeSymbol.owner; + do { + if (parentSymbol instanceof final MethodSymbol method) { + return new JavacMethodBinding(method, this.resolver); + } + parentSymbol = parentSymbol.owner; + } while (parentSymbol != null); + return null; + } + + @Override + public IBinding getDeclaringMember() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getDeclaringMember'"); + } + + @Override + public int getDimensions() { + return this.types.dimensions(this.typeSymbol.type); + } + + @Override + public ITypeBinding getElementType() { + return new JavacTypeBinding(this.types.elemtype(this.typeSymbol.type), this.resolver); + } + + @Override + public ITypeBinding getErasure() { + return new JavacTypeBinding(this.types.erasure(this.typeSymbol.type), this.resolver); + } + + @Override + public IMethodBinding getFunctionalInterfaceMethod() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getFunctionalInterfaceMethod'"); + } + + @Override + public ITypeBinding[] getInterfaces() { + return this.typeSymbol instanceof final ClassSymbol classSymbol && classSymbol.getInterfaces() != null ? + classSymbol.getInterfaces().map(t -> new JavacTypeBinding(t, this.resolver)).toArray(ITypeBinding[]::new) : + null; + } + + @Override + public int getModifiers() { + return JavacMethodBinding.toInt(this.typeSymbol.getModifiers()); + } + + @Override + public String getName() { + return this.typeSymbol.getSimpleName().toString(); + } + + @Override + public IPackageBinding getPackage() { + return this.typeSymbol.packge() != null ? + new JavacPackageBinding(this.typeSymbol.packge(), this.resolver) : + null; + } + + @Override + public String getQualifiedName() { + return this.typeSymbol.getQualifiedName().toString(); + } + + @Override + public ITypeBinding getSuperclass() { + if (this.typeSymbol instanceof final ClassSymbol classSymbol && classSymbol.getSuperclass() != null && classSymbol.getSuperclass().tsym != null) { + return new JavacTypeBinding(classSymbol.getSuperclass().tsym, this.resolver); + } + return null; + } + + @Override + public IAnnotationBinding[] getTypeAnnotations() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getTypeAnnotations'"); + } + + @Override + public ITypeBinding[] getTypeArguments() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getTypeBounds'"); + } + + @Override + public ITypeBinding[] getTypeBounds() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getTypeBounds'"); + } + + @Override + public ITypeBinding getTypeDeclaration() { + return this; + } + + @Override + public ITypeBinding[] getTypeParameters() { + return this.typeSymbol.getTypeParameters().stream() + .map(symbol -> new JavacTypeBinding(symbol, this.resolver)) + .toArray(ITypeBinding[]::new); + } + + @Override + public ITypeBinding getWildcard() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getWildcard'"); + } + + @Override + public boolean isAnnotation() { + return this.typeSymbol.isAnnotationType(); + } + + @Override + public boolean isAnonymous() { + return this.typeSymbol.isAnonymous(); + } + + @Override + public boolean isArray() { + return this.typeSymbol.type instanceof ArrayType; + } + + @Override + public boolean isAssignmentCompatible(final ITypeBinding variableType) { + if (variableType instanceof JavacTypeBinding other) { + return this.types.isAssignable(other.typeSymbol.type, this.typeSymbol.type); + } + throw new UnsupportedOperationException("Cannot mix with non Javac binding"); //$NON-NLS-1$ + } + + @Override + public boolean isCapture() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isCapture'"); + } + + @Override + public boolean isCastCompatible(final ITypeBinding type) { + if (type instanceof JavacTypeBinding other) { + return this.types.isCastable(this.typeSymbol.type, other.typeSymbol.type); + } + throw new UnsupportedOperationException("Cannot mix with non Javac binding"); //$NON-NLS-1$ + } + + @Override + public boolean isClass() { + return this.typeSymbol instanceof final ClassSymbol classSymbol && !( + classSymbol.isEnum() || classSymbol.isRecord()); + } + + @Override + public boolean isEnum() { + return this.typeSymbol.isEnum(); + } + + @Override + public boolean isRecord() { + return this.typeSymbol instanceof final ClassSymbol classSymbol && classSymbol.isRecord(); + } + + @Override + public boolean isFromSource() { + return this.resolver.findDeclaringNode(this) != null; + } + + @Override + public boolean isGenericType() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isGenericType'"); + } + + @Override + public boolean isInterface() { + return this.typeSymbol.isInterface(); + } + + @Override + public boolean isIntersectionType() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isIntersectionType'"); + } + + @Override + public boolean isLocal() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isLocal'"); + } + + @Override + public boolean isMember() { + return this.typeSymbol.owner instanceof TypeSymbol; + } + + @Override + public boolean isNested() { + return getDeclaringClass() != null; + } + + @Override + public boolean isNullType() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isNullType'"); + } + + @Override + public boolean isParameterizedType() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isParameterizedType'"); + } + + @Override + public boolean isPrimitive() { + return this.typeSymbol.type.isPrimitive(); + } + + @Override + public boolean isRawType() { + return this.typeSymbol.type.isRaw(); + } + + @Override + public boolean isSubTypeCompatible(final ITypeBinding type) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isSubTypeCompatible'"); + } + + @Override + public boolean isTopLevel() { + return getDeclaringClass() == null; + } + + @Override + public boolean isTypeVariable() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isTypeVariable'"); + } + + @Override + public boolean isUpperbound() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isUpperbound'"); + } + + @Override + public boolean isWildcardType() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isWildcardType'"); + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacVariableBinding.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacVariableBinding.java new file mode 100644 index 00000000000..de93fc1f7f7 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacVariableBinding.java @@ -0,0 +1,158 @@ +/******************************************************************************* + * 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.core.dom; + +import java.util.Objects; + +import org.eclipse.jdt.core.IField; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Symbol.ClassSymbol; +import com.sun.tools.javac.code.Symbol.MethodSymbol; +import com.sun.tools.javac.code.Symbol.TypeSymbol; +import com.sun.tools.javac.code.Symbol.VarSymbol; + +class JavacVariableBinding implements IVariableBinding { + + private VarSymbol variableSymbol; + private JavacBindingResolver resolver; + + public JavacVariableBinding(VarSymbol sym, JavacBindingResolver resolver) { + this.variableSymbol = sym; + this.resolver = resolver; + } + + @Override + public IAnnotationBinding[] getAnnotations() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getAnnotations'"); + } + + @Override + public int getKind() { + return VARIABLE; + } + + @Override + public int getModifiers() { + return JavacMethodBinding.toInt(this.variableSymbol.getModifiers()); + } + + @Override + public boolean isDeprecated() { + return this.variableSymbol.isDeprecated(); + } + + @Override + public boolean isRecovered() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isRecovered'"); + } + + @Override + public boolean isSynthetic() { + return (this.variableSymbol.flags() & Flags.SYNTHETIC) != 0; + } + + @Override + public IField getJavaElement() { + if (this.variableSymbol.owner instanceof TypeSymbol parentType) {//field + return new JavacTypeBinding(parentType, this.resolver).getJavaElement().getField(this.variableSymbol.name.toString()); + } + return null; + } + + @Override + public String getKey() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getKey'"); + } + + @Override + public boolean isEqualTo(IBinding binding) { + return binding instanceof JavacVariableBinding other && // + Objects.equals(this.variableSymbol, other.variableSymbol) && // + Objects.equals(this.resolver, other.resolver); + } + + @Override + public boolean isField() { + return this.variableSymbol.owner instanceof ClassSymbol; + } + + @Override + public boolean isEnumConstant() { + return this.variableSymbol.isEnum(); + } + + @Override + public boolean isParameter() { + return this.variableSymbol.owner instanceof MethodSymbol; + } + + @Override + public String getName() { + return this.variableSymbol.getSimpleName().toString(); + } + + @Override + public ITypeBinding getDeclaringClass() { + Symbol parentSymbol = this.variableSymbol.owner; + do { + if (parentSymbol instanceof ClassSymbol clazz) { + return new JavacTypeBinding(clazz, this.resolver); + } + parentSymbol = parentSymbol.owner; + } while (parentSymbol != null); + return null; + } + + @Override + public ITypeBinding getType() { + return new JavacTypeBinding(this.variableSymbol.type, this.resolver); + } + + @Override + public int getVariableId() { + return variableSymbol.adr; // ? + } + + @Override + public Object getConstantValue() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getConstantValue'"); + } + + @Override + public IMethodBinding getDeclaringMethod() { + Symbol parentSymbol = this.variableSymbol.owner; + do { + if (parentSymbol instanceof MethodSymbol method) { + return new JavacMethodBinding(method, this.resolver); + } + parentSymbol = parentSymbol.owner; + } while (parentSymbol != null); + return null; + } + + @Override + public IVariableBinding getVariableDeclaration() { + return this; + } + + @Override + public boolean isEffectivelyFinal() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isEffectivelyFinal'"); + } + +} 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 a582c04cb1d..c5da0918764 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 @@ -33,7 +33,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; @@ -221,6 +220,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 +234,7 @@ public static ASTParser newParser(int level) { ASTParser(int level) { DOMASTUtil.checkASTLevel(level); this.apiLevel = level; + this.unitResolver = ICompilationUnitResolver.getInstance(); initializeDefaults(); } @@ -245,17 +247,23 @@ 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) { for (int i = 0, max = this.classpaths.length; i < max; i++) { - main.processPathEntries( - Main.DEFAULT_SIZE_CLASSPATH, - allClasspaths, this.classpaths[i], null, false, false); + String classpath = this.classpaths[i]; + if (classpath != null) { + main.processPathEntries( + Main.DEFAULT_SIZE_CLASSPATH, + allClasspaths, classpath, null, false, false); + } } } ArrayList pendingErrors = main.pendingErrors; @@ -951,9 +959,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(compilationUnits, bindingKeys, requestor, this.apiLevel, this.compilerOptions, this.project, this.workingCopyOwner, flags, monitor); } else { - CompilationUnitResolver.parse(compilationUnits, requestor, this.apiLevel, this.compilerOptions, flags, monitor); + this.unitResolver.parse(compilationUnits, requestor, this.apiLevel, this.compilerOptions, flags, monitor); } } finally { // reset to defaults to allow reuse (and avoid leaking) @@ -1046,9 +1054,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(sourceFilePaths, encodings, bindingKeys, requestor, this.apiLevel, this.compilerOptions, getClasspath(), flags, monitor); } else { - CompilationUnitResolver.parse(sourceFilePaths, encodings, requestor, this.apiLevel, this.compilerOptions, flags, monitor); + this.unitResolver.parse(sourceFilePaths, encodings, requestor, this.apiLevel, this.compilerOptions, flags, monitor); } } finally { // reset to defaults to allow reuse (and avoid leaking) @@ -1109,7 +1117,7 @@ public IBinding[] createBindings(IJavaElement[] elements, IProgressMonitor monit if ((this.bits & CompilationUnitResolver.IGNORE_METHOD_BODIES) != 0) { flags |= ICompilationUnit.IGNORE_METHOD_BODIES; } - return CompilationUnitResolver.resolve(elements, this.apiLevel, this.compilerOptions, this.project, this.workingCopyOwner, flags, monitor); + return this.unitResolver.resolve(elements, this.apiLevel, this.compilerOptions, this.project, this.workingCopyOwner, flags, monitor); } finally { // reset to defaults to allow reuse (and avoid leaking) initializeDefaults(); @@ -1120,11 +1128,8 @@ private ASTNode internalCreateAST(IProgressMonitor monitor) { return JavaModelManager.cacheZipFiles(() -> internalCreateASTCached(monitor)); } private ASTNode internalCreateASTCached(IProgressMonitor monitor) { - boolean needToResolveBindings = (this.bits & CompilationUnitResolver.RESOLVE_BINDING) != 0; - switch(this.astKind) { - case K_CLASS_BODY_DECLARATIONS : - case K_EXPRESSION : - case K_STATEMENTS : + return switch(this.astKind) { + case K_CLASS_BODY_DECLARATIONS, K_EXPRESSION, K_STATEMENTS -> { if (this.rawSource == null) { if (this.typeRoot != null) { // get the source from the type root @@ -1149,145 +1154,101 @@ private ASTNode internalCreateASTCached(IProgressMonitor monitor) { if (this.sourceOffset + this.sourceLength > this.rawSource.length) { throw new IllegalStateException(); } - return internalCreateASTForKind(); + yield internalCreateASTForKind(); } - break; - case K_COMPILATION_UNIT : - CompilationUnitDeclaration compilationUnitDeclaration = null; - try { - NodeSearcher searcher = null; - org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit = null; - WorkingCopyOwner wcOwner = this.workingCopyOwner; - if (this.typeRoot instanceof ClassFileWorkingCopy) { - // special case: class file mimics as compilation unit, but that would use a wrong file name below, so better unwrap now: - this.typeRoot = ((ClassFileWorkingCopy) this.typeRoot).classFile; - } - if (this.typeRoot instanceof ICompilationUnit) { - /* - * this.compilationUnitSource is an instance of org.eclipse.jdt.internal.core.CompilationUnit that implements - * both org.eclipse.jdt.core.ICompilationUnit and org.eclipse.jdt.internal.compiler.env.ICompilationUnit - */ - sourceUnit = (org.eclipse.jdt.internal.compiler.env.ICompilationUnit) this.typeRoot; - /* - * use a BasicCompilation that caches the source instead of using the compilationUnitSource directly - * (if it is a working copy, the source can change between the parse and the AST convertion) - * (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=75632) - */ - sourceUnit = new BasicCompilationUnit(sourceUnit.getContents(), sourceUnit.getPackageName(), new String(sourceUnit.getFileName()), this.project); - wcOwner = ((ICompilationUnit) this.typeRoot).getOwner(); - } else if (this.typeRoot instanceof IClassFile) { - try { - String sourceString = this.typeRoot.getSource(); - if (sourceString == null) { - throw new IllegalStateException(); - } - PackageFragment packageFragment = (PackageFragment) this.typeRoot.getParent(); - BinaryType type = (BinaryType) this.typeRoot.findPrimaryType(); - String fileNameString = null; - if (type != null) { - IBinaryType binaryType = type.getElementInfo(); - // file name is used to recreate the Java element, so it has to be the toplevel .class file name - char[] fileName = binaryType.getFileName(); + throw new IllegalStateException(); + } + case K_COMPILATION_UNIT -> internalCreateCompilationUnit(monitor); + default -> throw new IllegalStateException(); + }; + } - int firstDollar = CharOperation.indexOf('$', fileName); - if (firstDollar != -1) { - char[] suffix = SuffixConstants.SUFFIX_class; - int suffixLength = suffix.length; - char[] newFileName = new char[firstDollar + suffixLength]; - System.arraycopy(fileName, 0, newFileName, 0, firstDollar); - System.arraycopy(suffix, 0, newFileName, firstDollar, suffixLength); - fileName = newFileName; - } - fileNameString = new String(fileName); - } else { - // assumed to be "module-info.class" (which has no type): - fileNameString = this.typeRoot.getElementName(); - } - sourceUnit = new BasicCompilationUnit(sourceString.toCharArray(), Util.toCharArrays(packageFragment.names), fileNameString, this.typeRoot); - } catch(JavaModelException e) { - // an error occured accessing the java element - CharSequence stackTrace = org.eclipse.jdt.internal.compiler.util.Util.getStackTrace(e); - throw new IllegalStateException(stackTrace.toString()); - } - } else if (this.rawSource != null) { - needToResolveBindings = - ((this.bits & CompilationUnitResolver.RESOLVE_BINDING) != 0) - && this.unitName != null - && (this.project != null - || this.classpaths != null - || this.sourcepaths != null - || ((this.bits & CompilationUnitResolver.INCLUDE_RUNNING_VM_BOOTCLASSPATH) != 0)) - && this.compilerOptions != null; - sourceUnit = new BasicCompilationUnit(this.rawSource, null, this.unitName == null ? "" : this.unitName, this.project); //$NON-NLS-1$ - } else { - throw new IllegalStateException(); - } - if ((this.bits & CompilationUnitResolver.PARTIAL) != 0) { - searcher = new NodeSearcher(this.focalPointPosition); - } - 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)) { - 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); - result.setTypeRoot(this.typeRoot); - return result; - } finally { - if (compilationUnitDeclaration != null - && ((this.bits & CompilationUnitResolver.RESOLVE_BINDING) != 0)) { - compilationUnitDeclaration.cleanUp(); + private CompilationUnit internalCreateCompilationUnit(IProgressMonitor monitor) { + boolean needToResolveBindings = (this.bits & CompilationUnitResolver.RESOLVE_BINDING) != 0; + NodeSearcher searcher = null; + org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit = null; + WorkingCopyOwner wcOwner = this.workingCopyOwner; + if (this.typeRoot instanceof ClassFileWorkingCopy) { + // special case: class file mimics as compilation unit, but that would use a wrong file name below, so better unwrap now: + this.typeRoot = ((ClassFileWorkingCopy) this.typeRoot).classFile; + } + if (this.typeRoot instanceof ICompilationUnit) { + /* + * this.compilationUnitSource is an instance of org.eclipse.jdt.internal.core.CompilationUnit that implements + * both org.eclipse.jdt.core.ICompilationUnit and org.eclipse.jdt.internal.compiler.env.ICompilationUnit + */ + sourceUnit = (org.eclipse.jdt.internal.compiler.env.ICompilationUnit) this.typeRoot; + /* + * use a BasicCompilation that caches the source instead of using the compilationUnitSource directly + * (if it is a working copy, the source can change between the parse and the AST convertion) + * (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=75632) + */ + sourceUnit = new BasicCompilationUnit(sourceUnit.getContents(), sourceUnit.getPackageName(), new String(sourceUnit.getFileName()), this.project); + wcOwner = ((ICompilationUnit) this.typeRoot).getOwner(); + } else if (this.typeRoot instanceof IClassFile) { + try { + String sourceString = this.typeRoot.getSource(); + if (sourceString == null) { + throw new IllegalStateException(); + } + PackageFragment packageFragment = (PackageFragment) this.typeRoot.getParent(); + BinaryType type = (BinaryType) this.typeRoot.findPrimaryType(); + String fileNameString = null; + if (type != null) { + IBinaryType binaryType = type.getElementInfo(); + // file name is used to recreate the Java element, so it has to be the toplevel .class file name + char[] fileName = binaryType.getFileName(); + + int firstDollar = CharOperation.indexOf('$', fileName); + if (firstDollar != -1) { + char[] suffix = SuffixConstants.SUFFIX_class; + int suffixLength = suffix.length; + char[] newFileName = new char[firstDollar + suffixLength]; + System.arraycopy(fileName, 0, newFileName, 0, firstDollar); + System.arraycopy(suffix, 0, newFileName, firstDollar, suffixLength); + fileName = newFileName; } + fileNameString = new String(fileName); + } else { + // assumed to be "module-info.class" (which has no type): + fileNameString = this.typeRoot.getElementName(); } + sourceUnit = new BasicCompilationUnit(sourceString.toCharArray(), Util.toCharArrays(packageFragment.names), fileNameString, this.typeRoot); + } catch(JavaModelException e) { + // an error occured accessing the java element + CharSequence stackTrace = org.eclipse.jdt.internal.compiler.util.Util.getStackTrace(e); + throw new IllegalStateException(stackTrace.toString()); + } + } else if (this.rawSource != null) { + needToResolveBindings = + ((this.bits & CompilationUnitResolver.RESOLVE_BINDING) != 0) + && this.unitName != null + && (this.project != null + || this.classpaths != null + || this.sourcepaths != null + || ((this.bits & CompilationUnitResolver.INCLUDE_RUNNING_VM_BOOTCLASSPATH) != 0)) + && this.compilerOptions != null; + sourceUnit = new BasicCompilationUnit(this.rawSource, null, this.unitName == null ? "" : this.unitName, this.project); //$NON-NLS-1$ + } else { + throw new IllegalStateException(); } - throw new IllegalStateException(); + if ((this.bits & CompilationUnitResolver.PARTIAL) != 0) { + searcher = new NodeSearcher(this.focalPointPosition); + } + 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)) { + flags |= ICompilationUnit.IGNORE_METHOD_BODIES; + } + if (needToResolveBindings && (this.bits & CompilationUnitResolver.BINDING_RECOVERY) != 0) { + flags |= ICompilationUnit.ENABLE_BINDINGS_RECOVERY; + } + + CompilationUnit result = this.unitResolver.toCompilationUnit(sourceUnit, needToResolveBindings, this.project, getClasspath(), searcher, this.apiLevel, this.compilerOptions, this.workingCopyOwner, wcOwner, flags, monitor); + result.setTypeRoot(this.typeRoot); + return result; } /** @@ -1362,6 +1323,7 @@ private ASTNode internalCreateASTCached(IProgressMonitor monitor) { * @see ASTNode#getLength() */ private ASTNode internalCreateASTForKind() { + // TODO make it independent from ECJ final ASTConverter converter = new ASTConverter(this.compilerOptions, false, null); converter.compilationUnitSource = this.rawSource; converter.compilationUnitSourceLength = this.rawSource.length; @@ -1546,4 +1508,5 @@ private void rootNodeToCompilationUnit(AST ast, CompilationUnit compilationUnit, } } } + } 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 7a6e5f24b84..2ff5ba9f32c 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 @@ -81,6 +81,101 @@ @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 IBinding[] resolve(IJavaElement[] elements, int apiLevel, Map compilerOptions, + IJavaProject project, WorkingCopyOwner workingCopyOwner, int flags, IProgressMonitor monitor) { + return CompilationUnitResolver.resolve(elements, 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, 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(); + } + } + } + } + + public static final ECJCompilationUnitResolver FACADE = new ECJCompilationUnitResolver(); + public static final int RESOLVE_BINDING = 0x1; public static final int PARTIAL = 0x2; public static final int STATEMENT_RECOVERY = 0x4; diff --git a/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ICompilationUnitResolver.java b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ICompilationUnitResolver.java new file mode 100644 index 00000000000..7798dd82d34 --- /dev/null +++ b/org.eclipse.jdt.core/dom/org/eclipse/jdt/core/dom/ICompilationUnitResolver.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * 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.core.dom; + +import java.util.List; +import java.util.Map; + +import org.eclipse.core.runtime.ILog; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.WorkingCopyOwner; +import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath; + +interface ICompilationUnitResolver { + + void resolve(String[] sourceFilePaths, String[] encodings, String[] bindingKeys, FileASTRequestor requestor, + int apiLevel, Map compilerOptions, List list, int flags, + IProgressMonitor monitor); + + void parse(ICompilationUnit[] compilationUnits, ASTRequestor requestor, int apiLevel, + Map compilerOptions, int flags, IProgressMonitor monitor); + + void parse(String[] sourceFilePaths, String[] encodings, FileASTRequestor requestor, int apiLevel, + Map compilerOptions, int flags, IProgressMonitor monitor); + + void resolve(ICompilationUnit[] compilationUnits, String[] bindingKeys, ASTRequestor requestor, int apiLevel, + Map compilerOptions, IJavaProject project, WorkingCopyOwner workingCopyOwner, int flags, + IProgressMonitor monitor); + + IBinding[] resolve(IJavaElement[] elements, int apiLevel, Map compilerOptions, IJavaProject project, + WorkingCopyOwner workingCopyOwner, int flags, IProgressMonitor monitor); + + 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); + + @SuppressWarnings("unchecked") + static ICompilationUnitResolver getInstance() { + String compilationUnitResolverClass = System.getProperty(ICompilationUnitResolver.class.getSimpleName()); + if (compilationUnitResolverClass != null) { + try { + Class clazz = (Class) Class.forName(compilationUnitResolverClass); + return clazz.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + ILog.get().error(e.getMessage(), e); + } + } + return CompilationUnitResolver.FACADE; + } + +} diff --git a/pom.xml b/pom.xml index 181684c7e2b..0d2a91f56e3 100644 --- a/pom.xml +++ b/pom.xml @@ -55,6 +55,7 @@ org.eclipse.jdt.annotation_v1 org.eclipse.jdt.core.compiler.batch org.eclipse.jdt.core + org.eclipse.jdt.core.javac org.eclipse.jdt.core.formatterapp org.eclipse.jdt.compiler.tool.tests org.eclipse.jdt.core.tests.builder @@ -72,6 +73,13 @@ + + org.eclipse.tycho + target-platform-configuration + + JavaSE-21 + + org.apache.maven.plugins maven-toolchains-plugin