diff --git a/.github/workflows/auto-author-assign.yml b/.github/workflows/auto-author-assign.yml new file mode 100644 index 00000000000..febd4f8d1fd --- /dev/null +++ b/.github/workflows/auto-author-assign.yml @@ -0,0 +1,14 @@ +name: Auto Author Assign + +on: + pull_request_target: + types: [ opened, reopened ] + +permissions: + pull-requests: write + +jobs: + assign-author: + runs-on: ubuntu-latest + steps: + - uses: toshimaru/auto-author-assign@v2.1.0 \ No newline at end of file diff --git a/.github/workflows/ci-dom-javac.yml b/.github/workflows/ci-dom-javac.yml new file mode 100644 index 00000000000..b5b07e4bb5b --- /dev/null +++ b/.github/workflows/ci-dom-javac.yml @@ -0,0 +1,55 @@ +name: Continuous Integration with DOM/Javac +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-dom + cancel-in-progress: true + +on: + push: + branches: [ 'dom-based-operations', 'dom-with-javac' ] + pull_request: + branches: [ 'dom-based-operations', 'dom-with-javac' ] + +jobs: + build-dom-javac: + runs-on: ubuntu-latest + steps: + - name: Install xmllint + shell: bash + run: | + sudo apt update + sudo apt install -y libxml2-utils + - uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + - name: Enable DOM-first and Javac + run: sed -i 's$$ -DCompilationUnit.DOM_BASED_OPERATIONS=true -DSourceIndexer.DOM_BASED_INDEXER=false -DICompilationUnitResolver=org.eclipse.jdt.core.dom.JavacCompilationUnitResolver -DAbstractImageBuilder.compiler=org.eclipse.jdt.internal.javac.JavacCompiler_$g' */pom.xml + - name: Set up JDKs ☕ + uses: actions/setup-java@v4 + with: + java-version: | + 8 + 17 + 21 + 23 + mvn-toolchain-id: | + JavaSE-1.8 + JavaSE-17 + JavaSE-21 + JavaSE-23 + distribution: 'temurin' + - name: Set up Maven + uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5 + with: + maven-version: 3.9.9 + - 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-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,20 -Djdt.performance.asserts=disabled" -Dcbi-ecj-version=99.99 + - name: Test Report + if: success() || failure() # run this step even if previous step failed + run: | + echo ▶️ TESTS RUN: $(xmllint --xpath 'string(/testsuite/@tests)' */target/surefire-reports/TEST-*.xml | awk '{n += $1}; END{print n}' -) + echo ❌ FAILURES: $(xmllint --xpath 'string(/testsuite/@failures)' */target/surefire-reports/TEST-*.xml | awk '{n += $1}; END{print n}' -) + echo 💥 ERRORS: $(xmllint --xpath 'string(/testsuite/@errors)' */target/surefire-reports/TEST-*.xml | awk '{n += $1}; END{print n}' -) + echo 🛑 SKIPPED: $(xmllint --xpath 'string(/testsuite/@skipped)' */target/surefire-reports/TEST-*.xml | awk '{n += $1}; END{print n}' -) \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fcd97246e3b..7e626d64f9a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ concurrency: on: push: - branches: '**' + branches: ['**'] jobs: event_file: @@ -31,15 +31,17 @@ jobs: 8 17 21 + 23 mvn-toolchain-id: | JavaSE-1.8 JavaSE-17 JavaSE-21 + JavaSE-23 distribution: 'temurin' - name: Set up Maven uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5 with: - maven-version: 3.9.8 + maven-version: 3.9.9 - name: Build with Maven 🏗️ run: | mvn clean install --batch-mode -f org.eclipse.jdt.core.compiler.batch -DlocalEcjVersion=99.99 diff --git a/Jenkinsfile b/Jenkinsfile index 6edcbc49282..232ffded789 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -13,7 +13,7 @@ pipeline { jdk 'openjdk-jdk23-latest' } stages { - stage('Build') { + stage('Build and Test') { steps { sh """#!/bin/bash -x @@ -29,8 +29,10 @@ pipeline { # export MAVEN_OPTS="-Xmx2G" mvn clean install -f org.eclipse.jdt.core.compiler.batch -DlocalEcjVersion=99.99 -Dmaven.repo.local=$WORKSPACE/.m2/repository -DcompilerBaselineMode=disable -DcompilerBaselineReplace=none - + + # Build and test without DOM-first to ensure no regression takes place mvn -U clean verify --batch-mode --fail-at-end -Dmaven.repo.local=$WORKSPACE/.m2/repository \ + -pl !org.eclipse.jdt.core.tests.javac \ -Ptest-on-javase-23 -Pbree-libs -Papi-check -Pjavadoc -Pp2-repo \ -Dmaven.test.failure.ignore=true \ -Dcompare-version-with-baselines.skip=false \ @@ -45,7 +47,6 @@ pipeline { } post { always { - archiveArtifacts artifacts: '*.log,*/target/work/data/.metadata/*.log,*/tests/target/work/data/.metadata/*.log,apiAnalyzer-workspace/.metadata/*.log,repository/target/repository/**,**/target/artifactcomparison/**', allowEmptyArchive: true // The following lines use the newest build on master that did not fail a reference // To not fail master build on failed test maven needs to be started with "-Dmaven.test.failure.ignore=true" it will then only marked unstable. // To not fail the build also "unstable: true" is used to only mark the build unstable instead of failing when qualityGates are missed @@ -55,8 +56,37 @@ pipeline { // The eclipse compiler name is changed because the logfile not only contains ECJ but also API warnings. // "pattern:" is used to collect warnings in dedicated files avoiding output of junit tests treated as warnings junit '**/target/surefire-reports/*.xml' - discoverGitReferenceBuild referenceJob: 'eclipse.jdt.core-github/master' - recordIssues publishAllIssues:false, ignoreQualityGate:true, tool: eclipse(name: 'Compiler and API Tools', pattern: '**/target/compilelogs/*.xml'), qualityGates: [[threshold: 1, type: 'DELTA', unstable: true]] + //discoverGitReferenceBuild referenceJob: 'eclipse.jdt.core-github/master' + //recordIssues publishAllIssues:false, ignoreQualityGate:true, tool: eclipse(name: 'Compiler and API Tools', pattern: '**/target/compilelogs/*.xml'), qualityGates: [[threshold: 1, type: 'DELTA', unstable: true]] + } + } + } + stage('javac specific tests') { + steps { + sh """#!/bin/bash -x + mkdir -p $WORKSPACE/tmp + + unset JAVA_TOOL_OPTIONS + unset _JAVA_OPTIONS + # force qualifier to start with `z` so we identify it more easily and it always seem more recent than upstrea + mvn install -DskipTests -Djava.io.tmpdir=$WORKSPACE/tmp \ + -Dtycho.buildqualifier.format="'z'yyyyMMdd-HHmm" \ + -Pp2-repo \ + -pl org.eclipse.jdt.core.compiler.batch,org.eclipse.jdt.core,org.eclipse.jdt.core.javac,org.eclipse.jdt.core.javac.feature,org.eclipse.jdt.core.tests.model,org.eclipse.jdt.core.tests.compiler,repository + + mvn verify --batch-mode -f org.eclipse.jdt.core.tests.javac \ + --fail-at-end -Ptest-on-javase-23 -Pbree-libs \ + -DfailIfNoTests=false -DexcludedGroups=org.junit.Ignore -DproviderHint=junit47 \ + -Papi-check -Djava.io.tmpdir=$WORKSPACE/tmp -Dproject.build.sourceEncoding=UTF-8 \ + -Dmaven.test.failure.ignore=true -Dmaven.test.error.ignore=true +""" + } + post { + always { + archiveArtifacts artifacts: '*.log,*/target/work/data/.metadata/*.log,*/tests/target/work/data/.metadata/*.log,apiAnalyzer-workspace/.metadata/*.log,repository/target/repository/**,**/target/artifactcomparison/**', allowEmptyArchive: true + junit 'org.eclipse.jdt.core.tests.javac/target/surefire-reports/*.xml' + discoverGitReferenceBuild referenceJob: 'jdt-core-incubator/dom-with-javac' + recordIssues ignoreQualityGate:true, tool: junitParser(pattern: 'org.eclipse.jdt.core.tests.javac/target/surefire-reports/*.xml'), qualityGates: [[threshold: 1, type: 'DELTA', unstable: true]] } } } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/classfmt/ClassFileConstants.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/classfmt/ClassFileConstants.java index 6f9495d5116..20bf090f052 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/classfmt/ClassFileConstants.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/classfmt/ClassFileConstants.java @@ -177,6 +177,10 @@ public interface ClassFileConstants { long JDK22 = ((long)ClassFileConstants.MAJOR_VERSION_22 << 16) + ClassFileConstants.MINOR_VERSION_0; long JDK23 = ((long)ClassFileConstants.MAJOR_VERSION_23 << 16) + ClassFileConstants.MINOR_VERSION_0; + /** + * + * @return The latest JDK level supported by ECJ (can be different from the latest known JDK level) + */ public static long getLatestJDKLevel() { return ((long)ClassFileConstants.MAJOR_LATEST_VERSION << 16) + ClassFileConstants.MINOR_VERSION_0; } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java index a0af5003843..292666f60f9 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java @@ -433,6 +433,11 @@ public class CompilerOptions { public long complianceLevel; /** Java source level, refers to a JDK version, e.g. {@link ClassFileConstants#JDK1_4} */ public long sourceLevel; + /** + * Use -release setting instead of -source to pass compilance version + * and checking also availability of system APIs for the compliance version. + */ + public boolean release; /** * Initially requested source version, not necessarily consistent with {@link #sourceLevel} as * sourceLevel forcibly contain a version that is compatible with ECJ. @@ -1410,7 +1415,7 @@ public Map getMap() { optionsMap.put(OPTION_ReportUnusedLabel, getSeverityString(UnusedLabel)); optionsMap.put(OPTION_ReportUnusedTypeArgumentsForMethodInvocation, getSeverityString(UnusedTypeArguments)); optionsMap.put(OPTION_Compliance, versionFromJdkLevel(this.complianceLevel)); - optionsMap.put(OPTION_Release, DISABLED); + optionsMap.put(OPTION_Release, this.release ? ENABLED : DISABLED); optionsMap.put(OPTION_Source, versionFromJdkLevel(this.sourceLevel)); optionsMap.put(OPTION_TargetPlatform, versionFromJdkLevel(this.targetJDK)); optionsMap.put(OPTION_FatalOptionalError, this.treatOptionalErrorAsFatal ? ENABLED : DISABLED); @@ -1776,6 +1781,7 @@ public void set(Map optionsMap) { long level = versionToJdkLevel(optionValue); if (level != 0) this.sourceLevel = level; } + this.release = ENABLED.equals(optionsMap.get(OPTION_Release)); if ((optionValue = optionsMap.get(OPTION_TargetPlatform)) != null) { long level = versionToJdkLevel(optionValue); if (level != 0) { diff --git a/org.eclipse.jdt.core.javac.feature/feature.xml b/org.eclipse.jdt.core.javac.feature/feature.xml new file mode 100644 index 00000000000..90c9f421c94 --- /dev/null +++ b/org.eclipse.jdt.core.javac.feature/feature.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/org.eclipse.jdt.core.javac/.classpath b/org.eclipse.jdt.core.javac/.classpath new file mode 100644 index 00000000000..d83ec0bc7c5 --- /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..ed20e434521 --- /dev/null +++ b/org.eclipse.jdt.core.javac/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,524 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.builder.annotationPath.allLocations=disabled +org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnull.secondary= +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary= +org.eclipse.jdt.core.compiler.annotation.notowning=org.eclipse.jdt.annotation.NotOwning +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullable.secondary= +org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled +org.eclipse.jdt.core.compiler.annotation.owning=org.eclipse.jdt.annotation.Owning +org.eclipse.jdt.core.compiler.annotation.resourceanalysis=disabled +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=23 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=23 +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.APILeak=warning +org.eclipse.jdt.core.compiler.problem.annotatedTypeArgumentToUnannotated=info +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning +org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompatibleOwningContract=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.insufficientResourceAnalysis=warning +org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning +org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore +org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore +org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.suppressWarningsNotFullyAnalysed=info +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore +org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning +org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled +org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedImport=error +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLambdaParameter=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=23 +org.eclipse.jdt.core.formatter.align_arrows_in_switch_on_columns=false +org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false +org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 +org.eclipse.jdt.core.formatter.align_selector_in_method_invocation_on_expression_first_line=true +org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns=false +org.eclipse.jdt.core.formatter.align_with_spaces=false +org.eclipse.jdt.core.formatter.alignment_for_additive_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter=0 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type=49 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_assertion_message=16 +org.eclipse.jdt.core.formatter.alignment_for_assignment=0 +org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_loops=16 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain=0 +org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_arrow=16 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_colon=16 +org.eclipse.jdt.core.formatter.alignment_for_logical_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_module_statements=16 +org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_permitted_types_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_record_components=16 +org.eclipse.jdt.core.formatter.alignment_for_relational_operator=0 +org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 +org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_shift_operator=0 +org.eclipse.jdt.core.formatter.alignment_for_string_concatenation=16 +org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_switch_case_with_arrow=20 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_type_annotations=0 +org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0 +org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0 +org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 +org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_field=0 +org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 +org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +org.eclipse.jdt.core.formatter.blank_lines_before_package=0 +org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch=0 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case_after_arrow=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_record_constructor=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_record_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped=true +org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false +org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=true +org.eclipse.jdt.core.formatter.comment.format_block_comments=true +org.eclipse.jdt.core.formatter.comment.format_header=false +org.eclipse.jdt.core.formatter.comment.format_html=true +org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true +org.eclipse.jdt.core.formatter.comment.format_line_comments=true +org.eclipse.jdt.core.formatter.comment.format_source_code=true +org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false +org.eclipse.jdt.core.formatter.comment.indent_root_tags=false +org.eclipse.jdt.core.formatter.comment.indent_tag_description=false +org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags=do not insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert +org.eclipse.jdt.core.formatter.comment.javadoc_do_not_separate_block_tags=false +org.eclipse.jdt.core.formatter.comment.line_length=80 +org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true +org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true +org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false +org.eclipse.jdt.core.formatter.compact_else_if=true +org.eclipse.jdt.core.formatter.continuation_indentation=2 +org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 +org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off +org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on +org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=false +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_empty_lines=false +org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false +org.eclipse.jdt.core.formatter.indentation.size=4 +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_additive_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default=insert +org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_permitted_types=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert +org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert +org.eclipse.jdt.core.formatter.insert_space_after_logical_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_not_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_record_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_relational_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert +org.eclipse.jdt.core.formatter.insert_space_after_shift_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation=insert +org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_additive_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case=insert +org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default=insert +org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_record_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_permitted_types=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert +org.eclipse.jdt.core.formatter.insert_space_before_logical_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_constructor=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_record_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert +org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_relational_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_shift_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation=insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.join_line_comments=false +org.eclipse.jdt.core.formatter.join_lines_in_comments=true +org.eclipse.jdt.core.formatter.join_wrapped_lines=true +org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_code_block_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false +org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false +org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_method_body_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line=false +org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line=false +org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line=false +org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line=false +org.eclipse.jdt.core.formatter.keep_switch_body_block_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_switch_case_with_arrow_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.lineSplit=120 +org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false +org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false +org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block=0 +org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 +org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines +org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true +org.eclipse.jdt.core.formatter.tabulation.char=tab +org.eclipse.jdt.core.formatter.tabulation.size=4 +org.eclipse.jdt.core.formatter.text_block_indentation=0 +org.eclipse.jdt.core.formatter.use_on_off_tags=true +org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false +org.eclipse.jdt.core.formatter.wrap_before_additive_operator=true +org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator=true +org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false +org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator=true +org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true +org.eclipse.jdt.core.formatter.wrap_before_logical_operator=true +org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator=true +org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true +org.eclipse.jdt.core.formatter.wrap_before_relational_operator=true +org.eclipse.jdt.core.formatter.wrap_before_shift_operator=true +org.eclipse.jdt.core.formatter.wrap_before_string_concatenation=true +org.eclipse.jdt.core.formatter.wrap_before_switch_case_arrow_operator=false +org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true +org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter diff --git a/org.eclipse.jdt.core.javac/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jdt.core.javac/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 00000000000..74de2ed42dc --- /dev/null +++ b/org.eclipse.jdt.core.javac/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,150 @@ +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +formatter_profile=org.eclipse.jdt.ui.default.eclipse_profile +formatter_settings_version=23 +sp_cleanup.add_all=false +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.also_simplify_lambda=true +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.array_with_curly=false +sp_cleanup.arrays_fill=false +sp_cleanup.bitwise_conditional_expression=false +sp_cleanup.boolean_literal=false +sp_cleanup.boolean_value_rather_than_comparison=false +sp_cleanup.break_loop=false +sp_cleanup.collection_cloning=false +sp_cleanup.comparing_on_criteria=false +sp_cleanup.comparison_statement=false +sp_cleanup.controlflow_merge=false +sp_cleanup.convert_functional_interfaces=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.convert_to_enhanced_for_loop_if_loop_var_used=false +sp_cleanup.convert_to_switch_expressions=false +sp_cleanup.correct_indentation=false +sp_cleanup.do_while_rather_than_while=false +sp_cleanup.double_negation=false +sp_cleanup.else_if=false +sp_cleanup.embedded_if=false +sp_cleanup.evaluate_nullable=false +sp_cleanup.extract_increment=false +sp_cleanup.format_source_code=false +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.hash=false +sp_cleanup.if_condition=false +sp_cleanup.insert_inferred_type_arguments=false +sp_cleanup.instanceof=false +sp_cleanup.instanceof_keyword=false +sp_cleanup.invert_equals=false +sp_cleanup.join=false +sp_cleanup.lazy_logical_operator=false +sp_cleanup.make_local_variable_final=true +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=false +sp_cleanup.map_cloning=false +sp_cleanup.merge_conditional_blocks=false +sp_cleanup.multi_catch=false +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.no_string_creation=false +sp_cleanup.no_super=false +sp_cleanup.number_suffix=false +sp_cleanup.objects_equals=false +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.one_if_rather_than_duplicate_blocks_that_fall_through=false +sp_cleanup.operand_factorization=false +sp_cleanup.organize_imports=true +sp_cleanup.overridden_assignment=false +sp_cleanup.overridden_assignment_move_decl=true +sp_cleanup.plain_replacement=false +sp_cleanup.precompile_regex=false +sp_cleanup.primitive_comparison=false +sp_cleanup.primitive_parsing=false +sp_cleanup.primitive_rather_than_wrapper=false +sp_cleanup.primitive_serialization=false +sp_cleanup.pull_out_if_from_if_else=false +sp_cleanup.pull_up_assignment=false +sp_cleanup.push_down_negation=false +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.reduce_indentation=false +sp_cleanup.redundant_comparator=false +sp_cleanup.redundant_falling_through_block_end=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_modifiers=false +sp_cleanup.remove_redundant_semicolons=false +sp_cleanup.remove_redundant_type_arguments=false +sp_cleanup.remove_trailing_whitespaces=false +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_array_creation=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_method_parameters=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.replace_deprecated_calls=false +sp_cleanup.return_expression=false +sp_cleanup.simplify_boolean_if_else=false +sp_cleanup.simplify_lambda_expression_and_method_ref=false +sp_cleanup.single_used_field=false +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.standard_comparison=false +sp_cleanup.static_inner_class=false +sp_cleanup.strictly_equal_or_different=false +sp_cleanup.stringbuffer_to_stringbuilder=false +sp_cleanup.stringbuilder=false +sp_cleanup.stringbuilder_for_local_vars=true +sp_cleanup.stringconcat_stringbuffer_stringbuilder=false +sp_cleanup.stringconcat_to_textblock=false +sp_cleanup.substring=false +sp_cleanup.switch=false +sp_cleanup.system_property=false +sp_cleanup.system_property_boolean=false +sp_cleanup.system_property_file_encoding=false +sp_cleanup.system_property_file_separator=false +sp_cleanup.system_property_javaspecversion=false +sp_cleanup.system_property_javaversion=false +sp_cleanup.system_property_line_separator=false +sp_cleanup.system_property_path_separator=false +sp_cleanup.ternary_operator=false +sp_cleanup.try_with_resource=false +sp_cleanup.unlooped_while=false +sp_cleanup.unreachable_block=false +sp_cleanup.use_anonymous_class_creation=false +sp_cleanup.use_autoboxing=false +sp_cleanup.use_blocks=false +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_directly_map_method=false +sp_cleanup.use_lambda=true +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_string_is_blank=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true +sp_cleanup.use_unboxing=false +sp_cleanup.use_var=false +sp_cleanup.useless_continue=false +sp_cleanup.useless_return=false +sp_cleanup.valueof_rather_than_instantiation=false 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..2d7eb4e61bb --- /dev/null +++ b/org.eclipse.jdt.core.javac/META-INF/MANIFEST.MF @@ -0,0 +1,12 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Javac backend for JDT Core +Bundle-Vendor: Eclipse JDT-LS +Bundle-SymbolicName: org.eclipse.jdt.core.javac;singleton:=true +Bundle-Version: 1.0.0.qualifier +Fragment-Host: org.eclipse.jdt.core +Automatic-Module-Name: org.eclipse.jdt.core.javac +Require-Capability: osgi.ee; filter:="(&(osgi.ee=JavaSE)(version=23))" +Import-Package: org.eclipse.jdt.core.dom +Export-Package: org.eclipse.jdt.internal.javac;x-friends:="org.eclipse.jdt.core.tests.javac", + org.eclipse.jdt.internal.javac.dom;x-friends:="org.eclipse.jdt.core.tests.javac" diff --git a/org.eclipse.jdt.core.javac/META-INF/p2.inf b/org.eclipse.jdt.core.javac/META-INF/p2.inf new file mode 100644 index 00000000000..65137b8cd81 --- /dev/null +++ b/org.eclipse.jdt.core.javac/META-INF/p2.inf @@ -0,0 +1,87 @@ +instructions.configure=org.eclipse.equinox.p2.touchpoint.eclipse.addJvmArg(jvmArg:\ +--add-opens\n\ +jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED\n\ +--add-opens\n\ +jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED\n\ +--add-opens\n\ +jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED\n\ +--add-opens\n\ +jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED\n\ +--add-opens\n\ +jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED\n\ +--add-opens\n\ +jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED\n\ +--add-opens\n\ +jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED\n\ +--add-opens\n\ +jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED\n\ +--add-opens\n\ +jdk.javadoc/jdk.javadoc.internal.doclets.formats.html.taglets.snippet=ALL-UNNAMED\n\ +--add-opens\n\ +jdk.javadoc/jdk.javadoc.internal.doclets.formats.html.taglets=ALL-UNNAMED\n\ +--add-opens\n\ +jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED\n\ +--add-opens\n\ +jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED\n\ +--add-opens\n\ +jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED\n\ +--add-opens\n\ +jdk.compiler/com.sun.tools.javac.platform=ALL-UNNAMED\n\ +--add-opens\n\ +jdk.compiler/com.sun.tools.javac.resources=ALL-UNNAMED\n\ +--add-opens\n\ +java.base/sun.nio.ch=ALL-UNNAMED\n\ +--add-opens\n\ +jdk.zipfs/jdk.nio.zipfs=ALL-UNNAMED\n\ +--add-opens\n\ +java.compiler/javax.tools=ALL-UNNAMED\n\ +-DICompilationUnitResolver=org.eclipse.jdt.core.dom.JavacCompilationUnitResolver\n\ +-DAbstractImageBuilder.compilerFactory=org.eclipse.jdt.internal.javac.JavacCompilerFactory\n\ +-DCompilationUnit.DOM_BASED_OPERATIONS=true\n\ +-DICompletionEngineProvider=org.eclipse.jdt.core.dom.DOMCompletionEngineProvider\n\ +-DSourceIndexer.DOM_BASED_INDEXER=true\ +); + +# See https://github.com/eclipse-equinox/p2/issues/572 about requirement for multiple instructions +instructions.unconfigure=\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.javadoc/jdk.javadoc.internal.doclets.formats.html.taglets.snippet=ALL-UNNAMED);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.javadoc/jdk.javadoc.internal.doclets.formats.html.taglets=ALL-UNNAMED);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.platform=ALL-UNNAMED);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.resources=ALL-UNNAMED);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:java.base/sun.nio.ch=ALL-UNNAMED);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.zipfs/jdk.nio.zipfs=ALL-UNNAMED);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:java.compiler/javax.tools=ALL-UNNAMED);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:-DICompilationUnitResolver=org.eclipse.jdt.core.dom.JavacCompilationUnitResolver);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:-DAbstractImageBuilder.compilerFactory=org.eclipse.jdt.internal.javac.JavacCompilerFactory);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:-DCompilationUnit.DOM_BASED_OPERATIONS=true);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:-DCompilationUnit.codeComplete.DOM_BASED_OPERATIONS=true_);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:-DSourceIndexer.DOM_BASED_INDEXER=true); \ No newline at end of file diff --git a/org.eclipse.jdt.core.javac/README.md b/org.eclipse.jdt.core.javac/README.md new file mode 100644 index 00000000000..216df184191 --- /dev/null +++ b/org.eclipse.jdt.core.javac/README.md @@ -0,0 +1,77 @@ +# Eclipse JDT over Javac + +This fragment contains a Javac backend (instead of ECJ) for JDT features: +* error reporting/reconciling +* build/compilation +* indexing +* code selection (Ctrl + click / hover) +* code completion (requires JDT fork bundle + opt-out flag) +* search/match (requires JDT fork bundle + opt-out flag) + +## ❓ 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. + +## 📥 Install on top of existing JDT in your Eclipse IDE + +Assuming you have Eclipse IDE well configured, just click the following link 👇 https://mickaelistria.github.io/redirctToEclipseIDECloneCommand/redirectToMarketplace.html?entryId=6444683 + +**OR** + +Using the _Help > Install New Software_ dialog and pointing to https://ci.eclipse.org/ls/job/jdt-core-incubator/job/dom-with-javac/lastSuccessfulBuild/artifact/repository/target/repository/ p2 repository, +install the _Javac backend for JDT_ artifact. Installing it will also tweak your `eclipse.ini` file to add the relevant options. + +Note that some required feature switches are not yet available in upstream JDT (see above) and thus will still default to ECJ. + +## ⌨️ Development + +### ⌨️ Contribute + +From a PDE-able IDE using a target platform that is suitable for JDT development (usually default case). + +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: + +``` +-DCompilationUnit.DOM_BASED_OPERATIONS=true -DICompletionProvider=org.eclipse.jdt.core.dom.DOMCompletionProvider -DSourceIndexer.DOM_BASED_INDEXER=true -DICompilationUnitResolver=org.eclipse.jdt.core.dom.JavacCompilationUnitResolver -DAbstractImageBuilder.compilerFactory=org.eclipse.jdt.internal.javac.JavacCompilerFactory --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 --add-opens jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.platform=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.platform=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.resources=ALL-UNNAMED --add-opens jdk.zipfs/jdk.nio.zipfs=ALL-UNNAMED --add-opens java.compiler/javax.tools=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED +``` + +Those arguments are automatically added to your eclipse.ini file when installing with p2. + +* `CompilationUnit.DOM_BASED_OPERATIONS=true`/`CompilationUnit.codeComplete.DOM_BASED_OPERATIONS` / `SourceIndexer.DOM_BASED_INDEXER=true` system properties enables some operations to use build and DOM instead of ECJ Parser (so if DOM comes from Javac, ECJ parser is not involved at all) +* `--add-opens ...` as visible in the `org.eclipse.jdt.core.javac/META-INF/p2.inf` file to allow to access internal API of the JVM, including Javac ones +* `ICompilationUnitResolver=org.eclipse.jdt.core.dom.JavacCompilationUnitResolver` system property enables using Javac instead of ECJ to create JDT DOM AST. +* `AbstractImageBuilder.compilerFactory=org.eclipse.jdt.internal.javac.JavacCompilerFactory` system property instruct the builder to use Javac instead of ECJ to generate the .class file during build. + +Those properties can be set separately, which can useful when developing one particular aspect of this proposal, which property to set depends on what you want to focus on. + +### 📈 Progress + +* Refactorings in upstream JDT got merged in order to allow alternative (non-ECJ strategies) for + * reconciling model/errors using DOM ✔DONE + * generating DOM from alternative parser ✔DONE + * codeSelect ✔DONE + * indexing ✔DONE + * completion ⌨️IN PROGRESS + * search/match ⌨️IN PROGRESS + * compiler ✔DONE +* Javac backend for + * producing Javac AST & Symbols ✔DONE (incl. annotation processing) + * Javac->JDT AST conversion ✔DONE (incl. annotation processing) + * binding conversion ✔DONE + * JavacCompiler ✔DONE + + +🤔 What are the potential concerns: +* **Memory cost** of retaining Javac contexts needs to be evaluated (can we get rid of the context earlier? Can we share subparts of the concerns across multiple files in the project?...) +* 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: +* **Null analysis** and some other **static analysis** are coded deep in ECJ and cannot be used with Javac. A solution can be to leverage another analysis engine (eg SpotBugs, SonarQube) deal with those features. +* 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..e3023e14e99 --- /dev/null +++ b/org.eclipse.jdt.core.javac/build.properties @@ -0,0 +1,5 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + fragment.xml diff --git a/org.eclipse.jdt.core.javac/fragment.xml b/org.eclipse.jdt.core.javac/fragment.xml new file mode 100644 index 00000000000..fc5459d9769 --- /dev/null +++ b/org.eclipse.jdt.core.javac/fragment.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/org.eclipse.jdt.core.javac/pom.xml b/org.eclipse.jdt.core.javac/pom.xml new file mode 100644 index 00000000000..f4897da35f4 --- /dev/null +++ b/org.eclipse.jdt.core.javac/pom.xml @@ -0,0 +1,64 @@ + + + + 4.0.0 + + eclipse.jdt.core + org.eclipse.jdt + 4.35.0-SNAPSHOT + + org.eclipse.jdt.core.javac + 1.0.0-SNAPSHOT + eclipse-plugin + + + + + org.eclipse.tycho + tycho-compiler-plugin + + false + javac + + --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 + --add-exports + jdk.compiler/com.sun.tools.javac.platform=ALL-UNNAMED + --add-exports + jdk.compiler/com.sun.tools.javac.resources=ALL-UNNAMED + --add-exports + jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED + --add-exports + jdk.javadoc/jdk.javadoc.internal.doclets.formats.html.taglets.snippet=ALL-UNNAMED + --add-exports + jdk.javadoc/jdk.javadoc.internal.doclets.formats.html.taglets=ALL-UNNAMED + --add-exports + java.base/sun.nio.ch=ALL-UNNAMED + + + + + + diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/DOMCompletionEngineProvider.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/DOMCompletionEngineProvider.java new file mode 100644 index 00000000000..4279ffd1f9c --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/DOMCompletionEngineProvider.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2025 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.Map; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.core.CompletionRequestor; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.WorkingCopyOwner; +import org.eclipse.jdt.internal.codeassist.DOMCompletionEngine; +import org.eclipse.jdt.internal.codeassist.ICompletionEngine; +import org.eclipse.jdt.internal.codeassist.ICompletionEngineProvider; +import org.eclipse.jdt.internal.core.SearchableEnvironment; + +public class DOMCompletionEngineProvider implements ICompletionEngineProvider { + + @Override + public ICompletionEngine newCompletionEngine(SearchableEnvironment nameEnvironment, CompletionRequestor requestor, + Map settings, IJavaProject javaProject, WorkingCopyOwner owner, IProgressMonitor monitor) { + return new DOMCompletionEngine(nameEnvironment, requestor, settings, javaProject, owner, monitor); + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/GenericRecoveredTypeBinding.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/GenericRecoveredTypeBinding.java new file mode 100644 index 00000000000..4ca30db827e --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/GenericRecoveredTypeBinding.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * 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; + +public class GenericRecoveredTypeBinding extends RecoveredTypeBinding { + + private ITypeBinding from; + + public GenericRecoveredTypeBinding(BindingResolver resolver, Type type, ITypeBinding from) { + super(resolver, type); + this.from = from; + } + + @Override + public boolean isParameterizedType() { + return false; + } + + @Override + public boolean isGenericType() { + return super.isParameterizedType(); + } + + @Override + public ITypeBinding[] getTypeParameters() { + return TypeBinding.NO_TYPE_BINDINGS; + } + + @Override + public ITypeBinding[] getTypeArguments() { + return super.getTypeParameters(); + } + + @Override + public IPackageBinding getPackage() { + return this.from.getPackage(); + } + +} 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..c9d6c0d4bce --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacBindingResolver.java @@ -0,0 +1,1828 @@ +/******************************************************************************* + * Copyright (c) 2023,2025 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.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Stream; + +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeKind; + +import org.eclipse.core.runtime.ILog; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.WorkingCopyOwner; +import org.eclipse.jdt.internal.javac.dom.JavacAnnotationBinding; +import org.eclipse.jdt.internal.javac.dom.JavacErrorMethodBinding; +import org.eclipse.jdt.internal.javac.dom.JavacLambdaBinding; +import org.eclipse.jdt.internal.javac.dom.JavacMemberValuePairBinding; +import org.eclipse.jdt.internal.javac.dom.JavacMethodBinding; +import org.eclipse.jdt.internal.javac.dom.JavacModuleBinding; +import org.eclipse.jdt.internal.javac.dom.JavacPackageBinding; +import org.eclipse.jdt.internal.javac.dom.JavacTypeBinding; +import org.eclipse.jdt.internal.javac.dom.JavacTypeVariableBinding; +import org.eclipse.jdt.internal.javac.dom.JavacVariableBinding; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.Tree; +import com.sun.source.util.DocTreePath; +import com.sun.source.util.JavacTask; +import com.sun.source.util.TreePath; +import com.sun.tools.javac.api.JavacTaskImpl; +import com.sun.tools.javac.api.JavacTrees; +import com.sun.tools.javac.code.Attribute; +import com.sun.tools.javac.code.Attribute.Compound; +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.ModuleSymbol; +import com.sun.tools.javac.code.Symbol.PackageSymbol; +import com.sun.tools.javac.code.Symbol.RootPackageSymbol; +import com.sun.tools.javac.code.Symbol.TypeSymbol; +import com.sun.tools.javac.code.Symbol.TypeVariableSymbol; +import com.sun.tools.javac.code.Symbol.VarSymbol; +import com.sun.tools.javac.code.Symtab; +import com.sun.tools.javac.code.Type.ArrayType; +import com.sun.tools.javac.code.Type.ClassType; +import com.sun.tools.javac.code.Type.ErrorType; +import com.sun.tools.javac.code.Type.ForAll; +import com.sun.tools.javac.code.Type.JCNoType; +import com.sun.tools.javac.code.Type.JCPrimitiveType; +import com.sun.tools.javac.code.Type.JCVoidType; +import com.sun.tools.javac.code.Type.MethodType; +import com.sun.tools.javac.code.Type.ModuleType; +import com.sun.tools.javac.code.Type.PackageType; +import com.sun.tools.javac.code.Type.TypeVar; +import com.sun.tools.javac.code.TypeTag; +import com.sun.tools.javac.code.Types; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCAnnotatedType; +import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree; +import com.sun.tools.javac.tree.JCTree.JCAssign; +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.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.JCModuleDecl; +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.JCPrimitiveTypeTree; +import com.sun.tools.javac.tree.JCTree.JCTypeApply; +import com.sun.tools.javac.tree.JCTree.JCTypeCast; +import com.sun.tools.javac.tree.JCTree.JCTypeParameter; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.tree.JCTree.JCWildcard; +import com.sun.tools.javac.tree.TreeInfo; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Names; + +/** + * Deals with creation of binding model, using the Symbols from Javac. + * @implNote Cannot move to another package because parent class is package visible only + */ +public class JavacBindingResolver extends BindingResolver { + + private JavacTask javacTask; // 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. + public final Context context; + public Map symbolToDeclaration; + public final IJavaProject javaProject; + private JavacConverter converter; + boolean isRecoveringBindings = false; + + public static class BindingKeyException extends Exception { + private static final long serialVersionUID = -4468681148041117634L; + public BindingKeyException(Throwable t) { + super(t); + } + public BindingKeyException(String message, Throwable cause) { + super(message, cause); + } + } + + public class Bindings { + private Map annotationBindings = new HashMap<>(); + public JavacAnnotationBinding getAnnotationBinding(Compound ann, IBinding recipient) { + JavacAnnotationBinding newInstance = new JavacAnnotationBinding(ann, JavacBindingResolver.this, recipient) { }; + String k = newInstance.getKey(); + if( k != null ) { + annotationBindings.putIfAbsent(k, newInstance); + return annotationBindings.get(k); + } + return null; + } + // + private Map memberValuePairBindings = new HashMap<>(); + public JavacMemberValuePairBinding getMemberValuePairBinding(MethodSymbol key, Attribute value) { + JavacMemberValuePairBinding newInstance = new JavacMemberValuePairBinding(key, value, JavacBindingResolver.this) { }; + String k = newInstance.getKey(); + if( k != null ) { + memberValuePairBindings.putIfAbsent(k, newInstance); + return memberValuePairBindings.get(k); + } + return null; + } + // + private Map methodBindings = new HashMap<>(); + public JavacMethodBinding getMethodBinding(MethodType methodType, MethodSymbol sym, com.sun.tools.javac.code.Type type, + boolean isSynthetic, boolean isDeclaration) { + if( isSynthetic ) { + return getSyntheticMethodBinding(methodType, sym, type); + } else { + return getMethodBinding(methodType, sym, type, isDeclaration); + } + } + + public JavacMethodBinding getMethodBinding(MethodType methodType, MethodSymbol methodSymbol, com.sun.tools.javac.code.Type parentType, boolean isDeclaration) { + JavacMethodBinding newInstance = new JavacMethodBinding(methodType, methodSymbol, parentType, JavacBindingResolver.this, false, isDeclaration) { }; + return insertAndReturn(newInstance); + } + public JavacMethodBinding getSyntheticMethodBinding(MethodType methodType, MethodSymbol methodSymbol, com.sun.tools.javac.code.Type parentType) { + JavacMethodBinding newInstance = new JavacMethodBinding(methodType, methodSymbol, parentType, JavacBindingResolver.this, true, false) { }; + return insertAndReturn(newInstance); + } + public JavacMethodBinding getErrorMethodBinding(MethodType methodType, Symbol originatingSymbol) { + JavacMethodBinding newInstance = new JavacErrorMethodBinding(originatingSymbol, methodType, JavacBindingResolver.this) { }; + return insertAndReturn(newInstance); + } + private JavacMethodBinding insertAndReturn(JavacMethodBinding newInstance) { + methodBindings.putIfAbsent(newInstance, newInstance); + return methodBindings.get(newInstance); + } + // + private Map moduleBindings = new HashMap<>(); + public JavacModuleBinding getModuleBinding(ModuleType moduleType) { + JavacModuleBinding newInstance = new JavacModuleBinding(moduleType, JavacBindingResolver.this) { }; + String k = newInstance.getKey(); + if( k != null ) { + moduleBindings.putIfAbsent(k, newInstance); + return moduleBindings.get(k); + } + return null; + } + public JavacModuleBinding getModuleBinding(ModuleSymbol moduleSymbol) { + JavacModuleBinding newInstance = new JavacModuleBinding(moduleSymbol, JavacBindingResolver.this) { }; + String k = newInstance.getKey(); + if( k != null ) { + moduleBindings.putIfAbsent(k, newInstance); + return moduleBindings.get(k); + } + return null; + } + public JavacModuleBinding getModuleBinding(JCModuleDecl moduleDecl) { + JavacModuleBinding newInstance = new JavacModuleBinding(moduleDecl, JavacBindingResolver.this) { }; + // Overwrite existing + String k = newInstance.getKey(); + if( k != null ) { + moduleBindings.put(k, newInstance); + return moduleBindings.get(k); + } + return null; + } + + // + private Map packageBindings = new HashMap<>(); + public JavacPackageBinding getPackageBinding(PackageSymbol packageSymbol) { + if( packageSymbol.owner instanceof PackageSymbol parentPack) { + if( !(parentPack instanceof RootPackageSymbol) ) + getPackageBinding(parentPack); + } + JavacPackageBinding newInstance = new JavacPackageBinding(packageSymbol, JavacBindingResolver.this) { }; + return preferentiallyInsertPackageBinding(newInstance); + } + public JavacPackageBinding getPackageBinding(Name name) { + String n = packageNameToString(name); + if( n == null ) + return null; + JavacPackageBinding newInstance = new JavacPackageBinding(n, JavacBindingResolver.this) {}; + return preferentiallyInsertPackageBinding(newInstance); + } + + public JavacPackageBinding findExistingPackageBinding(Name name) { + String n = name == null ? null : name.toString(); + if( n == null ) + return null; + JavacPackageBinding newInstance = new JavacPackageBinding(n, JavacBindingResolver.this) {}; + String k = newInstance == null ? null : newInstance.getKey(); + if( k != null ) { + JavacPackageBinding current = packageBindings.get(k); + return current; + } + return null; + } + + private String packageNameToString(Name name) { + String n = null; + if( name instanceof QualifiedName ) + n = name.toString(); + else if( name instanceof SimpleName) { + if( name.getParent() instanceof QualifiedName qn) { + if( qn.getName() == name ) { + n = qn.toString(); + } else if( qn.getQualifier() == name) { + n = name.toString(); + } + } + } + return n; + } + + private JavacPackageBinding preferentiallyInsertPackageBinding(JavacPackageBinding newest) { + // A package binding may be created while traversing something as simple as a name. + // The binding using name-only logic should be instantiated, but + // when a proper symbol is found, it should be added to that object. + String k = newest == null ? null : newest.getKey(); + if( k != null ) { + JavacPackageBinding current = packageBindings.get(k); + if( current == null ) { + packageBindings.putIfAbsent(k, newest); + } else if( current.getPackageSymbol() == null && newest.getPackageSymbol() != null) { + current.setPackageSymbol(newest.getPackageSymbol()); + } + return packageBindings.get(k); + } + return null; + } + // + private Map typeBinding = new HashMap<>(); + public JavacTypeBinding getTypeBinding(JCTree tree, com.sun.tools.javac.code.Type type) { + return getTypeBinding(type, tree instanceof JCClassDecl); + } + + + public JavacTypeBinding getTypeBinding(com.sun.tools.javac.code.Type type) { + if (type == null) { + return null; + } + return getTypeBinding(type.baseType() /* remove metadata for constant values */, false); + } + public JavacTypeBinding getTypeBinding(com.sun.tools.javac.code.Type type, boolean isDeclaration) { + if (type instanceof com.sun.tools.javac.code.Type.TypeVar typeVar) { + return getTypeVariableBinding(typeVar); + } + if (type == null || type == com.sun.tools.javac.code.Type.noType) { + return null; + } + if (type instanceof ErrorType errorType) { + var originalType = errorType.getOriginalType(); + if (originalType != com.sun.tools.javac.code.Type.noType + && !(originalType instanceof com.sun.tools.javac.code.Type.MethodType) + && !(originalType instanceof com.sun.tools.javac.code.Type.ForAll) + && !(originalType instanceof com.sun.tools.javac.code.Type.ErrorType)) { + JavacTypeBinding newInstance = new JavacTypeBinding(originalType, type.tsym, isDeclaration, JavacBindingResolver.this) { }; + typeBinding.putIfAbsent(newInstance, newInstance); + JavacTypeBinding jcb = typeBinding.get(newInstance); + jcb.setRecovered(true); + return jcb; + } else if (errorType.tsym instanceof ClassSymbol classErrorSymbol && + Character.isJavaIdentifierStart(classErrorSymbol.getSimpleName().charAt(0))) { + // non usable original type: try symbol + JavacTypeBinding newInstance = new JavacTypeBinding(classErrorSymbol.type, classErrorSymbol, isDeclaration, JavacBindingResolver.this) { }; + typeBinding.putIfAbsent(newInstance, newInstance); + JavacTypeBinding jcb = typeBinding.get(newInstance); + jcb.setRecovered(true); + return jcb; + } + // no type information we could recover from + return null; + } + if (!type.isParameterized() && !type.isRaw() && type instanceof ClassType classType + && classType.interfaces_field == null) { + // workaround faulty case of TypeMismatchQuickfixText.testMismatchingReturnTypeOnGenericMethod + // interfaces/supertypes are not set which seem to imply that the compiler generated + // a dummy type object that's not suitable for a binding. + // Fail back to an hopefully better type + type = type.tsym.type; + } + JavacTypeBinding newInstance = new JavacTypeBinding(type, type.tsym, isDeclaration, JavacBindingResolver.this) { }; + typeBinding.putIfAbsent(newInstance, newInstance); + return typeBinding.get(newInstance); + } + // + private Map typeVariableBindings = new HashMap<>(); + public JavacTypeVariableBinding getTypeVariableBinding(TypeVar typeVar) { + JavacTypeVariableBinding newInstance = new JavacTypeVariableBinding(typeVar, (TypeVariableSymbol)typeVar.tsym, JavacBindingResolver.this) { }; + typeVariableBindings.putIfAbsent(newInstance, newInstance); + return typeVariableBindings.get(newInstance); + } + // + private Map variableBindings = new HashMap<>(); + public JavacVariableBinding getVariableBinding(VarSymbol varSymbol) { + if (varSymbol == null) { + return null; + } + JavacVariableBinding newInstance = new JavacVariableBinding(varSymbol, JavacBindingResolver.this) { }; + String k = newInstance.getKey(); + if( k != null ) { + variableBindings.putIfAbsent(k, newInstance); + return variableBindings.get(k); + } + return null; + } + // + private Map lambdaBindings = new HashMap<>(); + public JavacLambdaBinding getLambdaBinding(JavacMethodBinding javacMethodBinding, LambdaExpression lambda) { + JavacLambdaBinding newInstance = new JavacLambdaBinding(javacMethodBinding, lambda); + String k = newInstance.getKey(); + if( k != null ) { + lambdaBindings.putIfAbsent(k, newInstance); + return lambdaBindings.get(k); + } + return null; + } + + public IBinding getBinding(final Symbol owner, final com.sun.tools.javac.code.Type type) { + Symbol recoveredSymbol = getRecoveredSymbol(type); + if (recoveredSymbol != null) { + return getBinding(recoveredSymbol, recoveredSymbol.type); + } + if (type instanceof ErrorType || owner.owner == null || owner.owner.type == com.sun.tools.javac.code.Type.noType) { + if (type.getOriginalType() instanceof MethodType missingMethodType) { + return getErrorMethodBinding(missingMethodType, owner); + } + } + if (owner instanceof final PackageSymbol other) { + return getPackageBinding(other); + } else if (owner instanceof ModuleSymbol typeSymbol) { + return getModuleBinding(typeSymbol); + } else if (owner instanceof Symbol.TypeVariableSymbol typeVariableSymbol) { + if (type instanceof TypeVar typeVar) { + return getTypeVariableBinding(typeVar); + } else if (typeVariableSymbol.type instanceof TypeVar typeVar) { + return getTypeVariableBinding(typeVar); + } + // without the type there is not much we can do; fallthrough to null + } else if (owner instanceof TypeSymbol typeSymbol) { + return getTypeBinding(isTypeOfType(type) ? type : typeSymbol.type); + } else if (owner instanceof final MethodSymbol other) { + var methodType = type instanceof com.sun.tools.javac.code.Type.MethodType aMethodType ? aMethodType : + owner.type != null ? owner.type.asMethodType() : + null; + if (methodType != null) { + return getMethodBinding(methodType, other, null, false); + } + } else if (owner instanceof final VarSymbol other) { + return getVariableBinding(other); + } + return null; + } + public IBinding getBinding(String key) { + IBinding binding; + binding = this.annotationBindings.get(key); + if (binding != null) { + return binding; + } + binding = this.memberValuePairBindings.get(key); + if (binding != null) { + return binding; + } + binding = this.methodBindings.values() + .stream() + .filter(methodBindings -> key.equals(methodBindings.getKey())) + .findAny() + .orElse(null); + if (binding != null) { + return binding; + } + binding = this.moduleBindings.get(key); + if (binding != null) { + return binding; + } + binding = this.packageBindings.get(key); + if (binding != null) { + return binding; + } + binding = this.typeBinding.values() + .stream() + .filter(typeBinding -> key.equals(typeBinding.getKey())) + .findAny() + .orElse(null); + if (binding != null) { + return binding; + } + return this.variableBindings.get(key); + } + } + public final Bindings bindings = new Bindings(); + private WorkingCopyOwner owner; + private HashMap resolvedBindingsCache = new HashMap<>(); + private List javacCompilationUnits; + + public JavacBindingResolver(IJavaProject javaProject, JavacTask javacTask, Context context, JavacConverter converter, WorkingCopyOwner owner, List javacCompilationUnits) { + this.javacTask = javacTask; + this.context = context; + this.javaProject = javaProject; + this.converter = converter; + this.owner = owner; + this.javacCompilationUnits = javacCompilationUnits; + } + + private void resolve() { + if (this.symbolToDeclaration != null) { + // already done and ready + return; + } + final JavacTask tmpTask = this.javacTask; + if( tmpTask == null ) { + return; + } + synchronized (tmpTask) { // prevents from multiple `analyze` for the same task + if( this.javacTask == null ) { + return; + } + boolean alreadyAnalyzed = this.converter.domToJavac.values().stream().map(TreeInfo::symbolFor).anyMatch(Objects::nonNull); + if (!alreadyAnalyzed) { + // symbols not already present: analyze + try { + Iterable elements; + // long start = System.currentTimeMillis(); + if (this.javacTask instanceof JavacTaskImpl javacTaskImpl) { + if (javacCompilationUnits != null && !javacCompilationUnits.isEmpty()) { + Iterable trees = javacCompilationUnits; + elements = javacTaskImpl.enter(trees); + } else { + elements = javacTaskImpl.enter(); + } + elements = javacTaskImpl.analyze(elements); +// long count = StreamSupport.stream(elements.spliterator(), false).count(); +// String name = elements.iterator().hasNext() +// ? elements.iterator().next().getSimpleName().toString() +// : ""; +// ILog.get().info("enter/analyze elements=" + count + ", took: " +// + (System.currentTimeMillis() - start) + ", first=" + name); + } else { + elements = this.javacTask.analyze(); +// long count = StreamSupport.stream(elements.spliterator(), false).count(); +// String name = elements.iterator().hasNext() +// ? elements.iterator().next().getSimpleName().toString() +// : ""; +// ILog.get().info("analyze elements=" + count + ", took: " + (System.currentTimeMillis() - start) +// + ", first=" + name); + } + } catch (IOException | Error | RuntimeException e) { + ILog.get().error(e.getMessage(), e); + } + } + // some cleanups to encourage garbage collection + JavacCompilationUnitResolver.cleanup(context); + } + this.javacTask = null; + synchronized (this) { + if (this.symbolToDeclaration == null) { + Map wipSymbolToDeclaration = new HashMap<>(); + this.converter.domToJavac.forEach((jdt, javac) -> { + // We don't want FieldDeclaration (ref ASTConverterTest2.test0433) + if (jdt instanceof MethodDeclaration || + jdt instanceof VariableDeclaration || + jdt instanceof EnumConstantDeclaration || + jdt instanceof AnnotationTypeMemberDeclaration || + jdt instanceof AbstractTypeDeclaration || + jdt instanceof AnonymousClassDeclaration || + jdt instanceof TypeParameter) { + var symbol = TreeInfo.symbolFor(javac); + if (symbol != null) { + wipSymbolToDeclaration.put(symbol, jdt); + } + } + }); + // prefill the binding so that they're already searchable by key + wipSymbolToDeclaration.keySet().forEach(sym -> this.bindings.getBinding(sym, null)); + this.symbolToDeclaration = wipSymbolToDeclaration; + } + } + } + + @Override + public ASTNode findDeclaringNode(IBinding binding) { + return findNode(getJavacSymbol(binding)); + } + + @Override + public ASTNode findDeclaringNode(String bindingKey) { + resolve(); + IBinding binding = this.bindings.getBinding(bindingKey); + if (binding == null) { + return null; + } + return findDeclaringNode(binding); + } + + private Symbol getJavacSymbol(IBinding binding) { + if (binding instanceof JavacMemberValuePairBinding valuePair) { + return getJavacSymbol(valuePair.method); + } + if (binding instanceof JavacAnnotationBinding annotation) { + return getJavacSymbol(annotation.getAnnotationType()); + } + if (binding instanceof JavacMethodBinding method) { + return method.methodSymbol; + } + if (binding instanceof JavacPackageBinding packageBinding) { + return packageBinding.getPackageSymbol(); + } + if (binding instanceof JavacTypeBinding type) { + return type.typeSymbol; + } + if (binding instanceof JavacVariableBinding variable) { + return variable.variableSymbol; + } + return null; + } + + public ASTNode findNode(Symbol symbol) { + if (this.symbolToDeclaration != null) { + return this.symbolToDeclaration.get(symbol); + } + return null; + } + + @Override + public ITypeBinding resolveType(Type type) { + if (type.getParent() instanceof ParameterizedType parameterized + && type.getLocationInParent() == ParameterizedType.TYPE_PROPERTY) { + // use parent type for this as it keeps generics info + return resolveType(parameterized); + } + resolve(); + if (type.getParent() instanceof ArrayCreation arrayCreation) { + JCTree jcArrayCreation = this.converter.domToJavac.get(arrayCreation); + return this.bindings.getTypeBinding(((JCNewArray)jcArrayCreation).type); + } + JCTree jcTree = this.converter.domToJavac.get(type); + if (jcTree instanceof JCIdent ident && ident.type != null) { + if (ident.type instanceof PackageType) { + return null; + } + return this.bindings.getTypeBinding(ident.type); + } + if (jcTree instanceof JCFieldAccess access) { + IBinding b = this.getFieldAccessBinding(access); + return b instanceof ITypeBinding tb ? tb : null; + } + if (jcTree instanceof JCPrimitiveTypeTree primitive && primitive.type != null) { + return this.bindings.getTypeBinding(primitive.type); + } + if (jcTree instanceof JCArrayTypeTree arrayType && arrayType.type != null) { + return this.bindings.getTypeBinding(arrayType.type); + } + if (jcTree instanceof JCWildcard wcType && wcType.type != null) { + return this.bindings.getTypeBinding(wcType.type); + } + if (jcTree instanceof JCTypeApply jcta && jcta.type != null) { + var res = this.bindings.getTypeBinding(jcta.type); + if (res != null) { + return res; + } + if (jcta.getType().type instanceof ErrorType errorType) { + res = this.bindings.getTypeBinding(errorType.getOriginalType(), true); + if (res != null) { + return res; + } + } + if (jcta.getType().type != null) { + res = this.bindings.getTypeBinding(jcta.getType().type); + if (res != null) { + return res; + } + } + } + if (jcTree instanceof JCAnnotatedType annotated && annotated.type != null) { + return this.bindings.getTypeBinding(annotated.type); + } + +// 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); +// } + if (type instanceof PrimitiveType primitive) { // a type can be requested even if there is no token for it in JCTree + return resolveWellKnownType(primitive.getPrimitiveTypeCode().toString()); + } + if (type.getAST().apiLevel() >= AST.JLS10 && type.isVar()) { + if (type.getParent() instanceof VariableDeclaration varDecl) { + IVariableBinding varBinding = resolveVariable(varDecl); + if (varBinding != null) { + return varBinding.getType(); + } + } + if (type.getParent() instanceof VariableDeclarationStatement statement && + this.converter.domToJavac.get(statement) instanceof JCVariableDecl jcDecl && + jcDecl.type != null) { + return this.bindings.getTypeBinding(jcDecl.type); + } + } + // Recovery: sometime with Javac, there is no suitable type/symbol + // Workaround: use a RecoveredTypeBinding + // Caveats: cascade to other workarounds + return createRecoveredTypeBinding(type); + } + + private RecoveredTypeBinding createRecoveredTypeBinding(Type type) { + return new RecoveredTypeBinding(this, type) { + @Override + public ITypeBinding getTypeDeclaration() { + if (isParameterizedType()) { + return new GenericRecoveredTypeBinding(JavacBindingResolver.this, type, this); + } + return super.getTypeDeclaration(); + } + @Override + public IPackageBinding getPackage() { + if (type instanceof SimpleType simpleType && simpleType.getName() instanceof SimpleName) { + return JavacBindingResolver.this.converter.domToJavac + .values() + .stream() + .filter(CompilationUnit.class::isInstance) + .map(CompilationUnit.class::cast) + .map(CompilationUnit::getPackage) + .map(PackageDeclaration::resolveBinding) + .findAny() + .orElse(super.getPackage()); + } + return super.getPackage(); + } + }; + } + + @Override + ITypeBinding resolveType(AnnotationTypeDeclaration type) { + resolve(); + JCTree javacNode = this.converter.domToJavac.get(type); + if (javacNode instanceof JCClassDecl jcClassDecl && jcClassDecl.type != null) { + return this.bindings.getTypeBinding(jcClassDecl.type); + } + return null; + } + + @Override + ITypeBinding resolveType(RecordDeclaration type) { + resolve(); + JCTree javacNode = this.converter.domToJavac.get(type); + if (javacNode instanceof JCClassDecl jcClassDecl && jcClassDecl.type != null) { + return this.bindings.getTypeBinding(jcClassDecl.type); + } + return null; + } + + + @Override + ITypeBinding resolveType(TypeDeclaration type) { + resolve(); + JCTree javacNode = this.converter.domToJavac.get(type); + if (javacNode instanceof JCClassDecl jcClassDecl && jcClassDecl.type != null) { + return this.bindings.getTypeBinding(jcClassDecl.type, true); + } + if (javacNode instanceof JCClassDecl jcClassDecl && jcClassDecl.sym != null && jcClassDecl.sym.type != null) { + return this.bindings.getTypeBinding(jcClassDecl.sym.type, true); + } + return null; + } + + @Override + ITypeBinding resolveType(EnumDeclaration enumDecl) { + resolve(); + JCTree javacNode = this.converter.domToJavac.get(enumDecl); + if (javacNode instanceof JCClassDecl jcClassDecl && jcClassDecl.type != null) { + return this.bindings.getTypeBinding(jcClassDecl.type, true); + } + return null; + } + + @Override + ITypeBinding resolveType(AnonymousClassDeclaration anonymousClassDecl) { + resolve(); + JCTree javacNode = this.converter.domToJavac.get(anonymousClassDecl); + if (javacNode instanceof JCClassDecl jcClassDecl && jcClassDecl.type != null) { + return this.bindings.getTypeBinding(jcClassDecl.type, true); + } + return null; + } + @Override + ITypeBinding resolveTypeParameter(TypeParameter typeParameter) { + resolve(); + JCTree javacNode = this.converter.domToJavac.get(typeParameter); + if (javacNode instanceof JCTypeParameter jcClassDecl) { + return this.bindings.getTypeBinding(jcClassDecl.type); + } + 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 this.bindings.getVariableBinding(varSymbol); + } + return null; + } + + @Override + IVariableBinding resolveField(SuperFieldAccess fieldAccess) { + resolve(); + JCTree javacElement = this.converter.domToJavac.get(fieldAccess); + if (javacElement instanceof JCFieldAccess javacFieldAccess && javacFieldAccess.sym instanceof VarSymbol varSymbol) { + return this.bindings.getVariableBinding(varSymbol); + } + return null; + } + + @Override + IMethodBinding resolveMethod(MethodInvocation method) { + resolve(); + JCTree javacElement = this.converter.domToJavac.get(method); + List typeArgs = List.of(); + if (javacElement instanceof JCMethodInvocation javacMethodInvocation) { + javacElement = javacMethodInvocation.getMethodSelect(); + typeArgs = javacMethodInvocation.getTypeArguments().stream().map(jcExpr -> jcExpr.type).toList(); + } + var type = javacElement.type; + // next condition matches `localMethod(this::missingMethod)` + if (javacElement instanceof JCIdent ident && type == null) { + ASTNode node = method; + while (node != null && !(node instanceof AbstractTypeDeclaration)) { + node = node.getParent(); + } + if (node instanceof AbstractTypeDeclaration decl && + this.converter.domToJavac.get(decl) instanceof JCClassDecl javacClassDecl && + javacClassDecl.type instanceof ClassType classType && + !classType.isErroneous()) { + type = classType; + } + if (type != null && + type.tsym.members().findFirst(ident.getName(), MethodSymbol.class::isInstance) instanceof MethodSymbol methodSymbol && + methodSymbol.type instanceof MethodType methodType) { + var res = this.bindings.getMethodBinding(methodType, methodSymbol, null, false); + if (res != null) { + return res; + } + } + } + var sym = javacElement instanceof JCIdent ident ? ident.sym : + javacElement instanceof JCFieldAccess fieldAccess ? fieldAccess.sym : + null; + if (type instanceof MethodType methodType && sym instanceof MethodSymbol methodSymbol) { + com.sun.tools.javac.code.Type parentType = null; + if (methodSymbol.owner instanceof ClassSymbol ownerClass && isTypeOfType(ownerClass.type)) { + if (ownerClass.type.isParameterized() + && method.getExpression() != null + && resolveExpressionType(method.getExpression()) instanceof JavacTypeBinding exprType) { + parentType = exprType.type; + } else { + parentType = ownerClass.type; + } + } + return this.bindings.getMethodBinding(methodType, methodSymbol, parentType, false); + } + if (type instanceof ErrorType errorType && errorType.getOriginalType() instanceof MethodType methodType) { + if (sym.owner instanceof TypeSymbol typeSymbol) { + Iterator methods = typeSymbol.members().getSymbolsByName(sym.getSimpleName(), m -> m instanceof MethodSymbol && methodType.equals(m.type)).iterator(); + if (methods.hasNext()) { + return this.bindings.getMethodBinding(methodType, (MethodSymbol)methods.next(), null, false); + } + } + return this.bindings.getErrorMethodBinding(methodType, sym); + } + if (type == null && sym instanceof MethodSymbol methodSym && methodSym.type instanceof ForAll methodTemplateType) { + // build type from template + Map resolutionMapping = new HashMap<>(); + var templateParameters = methodTemplateType.getTypeVariables(); + for (int i = 0; i < typeArgs.size() && i < templateParameters.size(); i++) { + resolutionMapping.put(templateParameters.get(i), typeArgs.get(i)); + } + MethodType methodType = new MethodType( + methodTemplateType.asMethodType().getParameterTypes().map(t -> applyType(t, resolutionMapping)), + applyType(methodTemplateType.asMethodType().getReturnType(), resolutionMapping), + methodTemplateType.asMethodType().getThrownTypes().map(t -> applyType(t, resolutionMapping)), + methodTemplateType.tsym); + return this.bindings.getMethodBinding(methodType, methodSym, methodSym.owner.type, false); + } + if (type == null && sym != null && sym.type.isErroneous() + && sym.owner.type instanceof ClassType classType) { + var parentTypeBinding = this.bindings.getTypeBinding(classType); + return Arrays.stream(parentTypeBinding.getDeclaredMethods()) + .filter(binding -> binding.getName().equals(sym.getSimpleName().toString())) + .findAny() + .orElse(null); + } + if (type == null && sym instanceof MethodSymbol methodSymbol && methodSymbol.type instanceof MethodType + && javacElement instanceof JCFieldAccess selectedMethod + && selectedMethod.getExpression() != null + && selectedMethod.getExpression().type instanceof ClassType classType) { + // method is resolved, but type is not, probably because of invalid param + // workaround: check compatible method in selector + var parentTypeBinding = this.bindings.getTypeBinding(classType); + var res = Arrays.stream(parentTypeBinding.getDeclaredMethods()) + .filter(binding -> binding instanceof JavacMethodBinding javacMethodBinding && javacMethodBinding.methodSymbol == methodSymbol) + .findAny() + .orElse(null); + if (res != null) { + return res; + } + } + if (sym instanceof MethodSymbol && sym.type instanceof MethodType) { + return (IMethodBinding)this.bindings.getBinding(sym, sym.type); + } + return null; + } + + /** + * Derives an "applied" type replacing know TypeVar with their current value + * @param from The type to check for replacement of TypeVars + * @param resolutionMapping a dictionary defining which concrete type must replace TypeVar + * @return The derived "applied" type: recursively checks the type, replacing + * known {@link TypeVar} instances in those with their value defined in `resolutionMapping` + */ + private static com.sun.tools.javac.code.Type applyType(com.sun.tools.javac.code.Type from, Map resolutionMapping) { + if (from instanceof TypeVar typeVar) { + var directMapping = resolutionMapping.get(from); + if (directMapping != null) { + return directMapping; + } + return typeVar; + } + if (from instanceof JCNoType || from instanceof JCVoidType || + from instanceof JCPrimitiveType) { + return from; + } + if (from instanceof ClassType classType) { + var args = classType.getTypeArguments().map(typeArg -> applyType(typeArg, resolutionMapping)); + if (Objects.equals(args, classType.getTypeArguments())) { + return classType; + } + return new ClassType(classType.getEnclosingType(), args, classType.tsym); + } + if (from instanceof ArrayType arrayType) { + var targetElemType = applyType(arrayType.elemtype, resolutionMapping); + if (Objects.equals(targetElemType, arrayType.elemtype)) { + return arrayType; + } + return new ArrayType(targetElemType, arrayType.tsym); + } + return from; + } + + @Override + IMethodBinding resolveMethod(MethodDeclaration method) { + resolve(); + JCTree javacElement = this.converter.domToJavac.get(method); + if (javacElement instanceof JCMethodDecl methodDecl && !(methodDecl.type instanceof ErrorType)) { + if (methodDecl.type != null ) { + return this.bindings.getMethodBinding(methodDecl.type.asMethodType(), methodDecl.sym, null, true); + } + if (methodDecl.sym instanceof MethodSymbol methodSymbol && methodSymbol.type != null) { + return this.bindings.getMethodBinding(methodSymbol.type.asMethodType(), methodSymbol, null, true); + } + } + return null; + } + + @Override + IMethodBinding resolveMethod(LambdaExpression lambda) { + resolve(); + JCTree javacElement = this.converter.domToJavac.get(lambda); + if (javacElement instanceof JCLambda jcLambda) { + JavacTypeBinding typeBinding = this.bindings.getTypeBinding(jcLambda.type); + if (typeBinding != null && typeBinding.getFunctionalInterfaceMethod() instanceof JavacMethodBinding methodBinding) { + return this.bindings.getLambdaBinding(methodBinding, lambda); + } + } + return null; + } + + @Override + IMethodBinding resolveMethod(MethodReference methodReference) { + resolve(); + JCTree javacElement = this.converter.domToJavac.get(methodReference); + if (javacElement instanceof JCMemberReference memberRef && memberRef.sym instanceof MethodSymbol methodSymbol) { + return this.bindings.getMethodBinding(memberRef.referentType == null ? methodSymbol.type.asMethodType() : memberRef.referentType.asMethodType(), methodSymbol, null, false); + } + return null; + } + + @Override + IMethodBinding resolveMember(AnnotationTypeMemberDeclaration member) { + resolve(); + JCTree javacElement = this.converter.domToJavac.get(member); + if (javacElement instanceof JCMethodDecl methodDecl) { + return this.bindings.getMethodBinding(methodDecl.type.asMethodType(), methodDecl.sym, null, true); + } + return null; + } + + @Override + IMethodBinding resolveConstructor(EnumConstantDeclaration enumConstantDeclaration) { + resolve(); + JCTree javacElement = this.converter.domToJavac.get(enumConstantDeclaration); + if( javacElement instanceof JCVariableDecl jcvd ) { + javacElement = jcvd.init; + } + return javacElement instanceof JCNewClass jcExpr + && !jcExpr.constructor.type.isErroneous()? + this.bindings.getMethodBinding(jcExpr.constructor.type.asMethodType(), (MethodSymbol)jcExpr.constructor, null, true) : + null; + } + + @Override + IMethodBinding resolveConstructor(SuperConstructorInvocation expression) { + resolve(); + JCTree javacElement = this.converter.domToJavac.get(expression); + if (javacElement instanceof JCMethodInvocation javacMethodInvocation) { + javacElement = javacMethodInvocation.getMethodSelect(); + } + if (javacElement instanceof JCIdent ident && ident.sym instanceof MethodSymbol methodSymbol) { + if (ident.type != null && (ident.type instanceof MethodType || ident.type instanceof ForAll)) { + return this.bindings.getMethodBinding(ident.type.asMethodType(), methodSymbol, null, false); + } else if (methodSymbol.asType() instanceof MethodType || methodSymbol.asType() instanceof ForAll) { + return this.bindings.getMethodBinding(methodSymbol.asType().asMethodType(), methodSymbol, null, false); + } + } + if (javacElement instanceof JCFieldAccess fieldAccess && fieldAccess.sym instanceof MethodSymbol methodSymbol) { + return this.bindings.getMethodBinding(fieldAccess.type.asMethodType(), methodSymbol, null, false); + } + return null; + } + + @Override + IMethodBinding resolveMethod(SuperMethodInvocation 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 this.bindings.getMethodBinding(ident.type.asMethodType(), methodSymbol, null, false); + } + if (javacElement instanceof JCFieldAccess fieldAccess && fieldAccess.sym instanceof MethodSymbol methodSymbol + && fieldAccess.type != null /* when there are syntax errors */) { + return this.bindings.getMethodBinding(fieldAccess.type.asMethodType(), methodSymbol, null, false); + } + return null; + } + + IBinding resolveCached(ASTNode node, Function l) { + // Avoid using `computeIfAbsent` because it throws + // ConcurrentModificationException when nesting calls + var res = resolvedBindingsCache.get(node); + if (res == null) { + res = l.apply(node); + resolvedBindingsCache.put(node, res); + } + return res; + } + + @Override + IBinding resolveName(Name name) { + return resolveCached(name, (n) -> resolveNameImpl((Name)n)); + } + + private IBinding resolveNameImpl(Name name) { + resolve(); + + // first, prefer parent if appropriate + ASTNode parent = name.getParent(); + if (name.getLocationInParent() == QualifiedName.NAME_PROPERTY && parent instanceof QualifiedName qname && + qname.getParent() instanceof SimpleType simpleType && simpleType.getLocationInParent() == ParameterizedType.TYPE_PROPERTY) { + var typeBinding = resolveType((ParameterizedType)simpleType.getParent()); + if (typeBinding != null) { + return typeBinding; + } + } + if (name.getLocationInParent() == QualifiedType.NAME_PROPERTY && + parent.getLocationInParent() == QualifiedType.QUALIFIER_PROPERTY) { + var typeBinding = resolveType((QualifiedType)parent); + return typeBinding.getTypeDeclaration(); // exclude params + } + if (name.getLocationInParent() == SimpleType.NAME_PROPERTY + || name.getLocationInParent() == QualifiedType.NAME_PROPERTY + || name.getLocationInParent() == NameQualifiedType.NAME_PROPERTY) { // case of "var" + return resolveType((Type)parent); + } + + JCTree tree = this.converter.domToJavac.get(name); + if( tree != null ) { + var res = resolveNameToJavac(name, tree); + if (res != null) { + return res; + } + } + DocTreePath path = this.converter.findDocTreePath(name); + if (path != null) { + if (JavacTrees.instance(this.context).getElement(path) instanceof Symbol symbol) { + return this.bindings.getBinding(symbol, null); + } + } + + PackageSymbol ps = findPackageSymbol(name); + if( ps != null ) { + return this.bindings.getPackageBinding(ps); + } + if( isPackageName(name)) { + return this.bindings.getPackageBinding(name); + } + if( tree instanceof JCIdent jcid && jcid.sym instanceof ClassSymbol && jcid.type instanceof ErrorType) { + IBinding b = this.bindings.findExistingPackageBinding(name); + if( b != null ) + return b; + } + + if (tree == null && (name.getFlags() & ASTNode.ORIGINAL) != 0) { + tree = this.converter.domToJavac.get(parent); + if( tree instanceof JCFieldAccess jcfa) { + if( jcfa.selected instanceof JCIdent jcid && jcid.toString().equals(name.toString())) { + tree = jcfa.selected; + } + var grandParent = parent.getParent(); + if (grandParent instanceof ParameterizedType parameterized) { + var parameterizedType = resolveType(parameterized); + if (parameterizedType != null) { + return parameterizedType; + } + + } + } + } + if( tree != null ) { + // Looks duplicate to top of method, but is not. Must remain. + IBinding ret = resolveNameToJavac(name, tree); + if (ret != null) { + return ret; + } + } + if (parent instanceof ImportDeclaration importDecl && importDecl.getName() == name) { + return resolveImport(importDecl); + } + if (parent instanceof QualifiedName parentName && parentName.getName() == name) { + return resolveNameImpl(parentName); + } + if( parent instanceof MethodRef mref && mref.getName() == name) { + return resolveReference(mref); + } + if( parent instanceof MemberRef mref && mref.getName() == name) { + return resolveReference(mref); + } + if (parent instanceof MethodInvocation methodInvocation && methodInvocation.getName() == name) { + return resolveMethod(methodInvocation); + } + if (parent instanceof MethodDeclaration methodDeclaration && methodDeclaration.getName() == name) { + return resolveMethod(methodDeclaration); + } + if (parent instanceof ExpressionMethodReference methodRef && methodRef.getName() == name) { + return resolveMethod(methodRef); + } + if (parent instanceof TypeMethodReference methodRef && methodRef.getName() == name) { + return resolveMethod(methodRef); + } + if (parent instanceof SuperMethodReference methodRef && methodRef.getName() == name) { + return resolveMethod(methodRef); + } + if (parent instanceof VariableDeclaration decl && decl.getName() == name) { + return resolveVariable(decl); + } + return null; + } + + private boolean isPackageName(Name name) { + ASTNode working = name; + boolean insideQualifier = false; + while( working instanceof Name ) { + JCTree tree = this.converter.domToJavac.get(working); + if( tree instanceof JCFieldAccess jcfa) { + return jcfa.sym instanceof PackageSymbol; + } + if( working instanceof QualifiedName qnn) { + if( qnn.getQualifier() == working) { + insideQualifier = true; + } + } + working = working.getParent(); + } + return insideQualifier; + } + + private PackageSymbol findPackageSymbol(Name name) { + if( name instanceof SimpleName sn) { + ASTNode parent = sn.getParent(); + if( parent instanceof QualifiedName qn) { + JCTree tree = this.converter.domToJavac.get(parent); + if( tree instanceof JCFieldAccess jcfa) { + if( qn.getQualifier().equals(name)) { + if( jcfa.selected instanceof JCIdent jcid && jcid.sym instanceof PackageSymbol pss) + return pss; + } else if( qn.getName().equals(name)) { + return jcfa.sym instanceof PackageSymbol pss ? pss : null; + } + } + } + } + if( name instanceof QualifiedName qn ) { + JCTree tree = this.converter.domToJavac.get(qn); + if( tree instanceof JCFieldAccess jcfa) { + return jcfa.sym instanceof PackageSymbol pss ? pss : null; + } + } + return null; + } + + IBinding resolveNameToJavac(Name name, JCTree tree) { + boolean isTypeDeclaration = (name.getParent() instanceof AbstractTypeDeclaration typeDeclaration && typeDeclaration.getName() == name) + || (name.getParent() instanceof SimpleType type && type.getName() == name); + if( name.getParent() instanceof AnnotatableType st && st.getParent() instanceof ParameterizedType pt) { + if( st == pt.getType()) { + tree = this.converter.domToJavac.get(pt); + if (tree.type != null && !tree.type.isErroneous()) { + IBinding b = this.bindings.getTypeBinding(tree.type, isTypeDeclaration); + if( b != null ) { + return b; + } + } + } + } + + if (tree instanceof JCIdent ident && ident.sym != null) { + if (ident.type instanceof ErrorType errorType + && errorType.getOriginalType() instanceof ErrorType) { + return null; + } + if (isTypeDeclaration) { + return this.bindings.getTypeBinding(ident.type != null ? ident.type : ident.sym.type, true); + } + return this.bindings.getBinding(ident.sym, ident.type != null ? ident.type : ident.sym.type); + } + if (tree instanceof JCTypeApply variableDecl && variableDecl.type != null) { + return this.bindings.getTypeBinding(variableDecl.type); + } + if (tree instanceof JCFieldAccess fieldAccess) { + return this.getFieldAccessBinding(fieldAccess); + } + if (tree instanceof JCMethodInvocation methodInvocation && methodInvocation.meth.type != null) { + return this.bindings.getBinding(((JCFieldAccess)methodInvocation.meth).sym, methodInvocation.meth.type); + } + if (tree instanceof JCClassDecl classDecl && classDecl.sym != null) { + return this.bindings.getBinding(classDecl.sym, classDecl.type); + } + if (tree instanceof JCMethodDecl methodDecl && methodDecl.sym != null) { + return this.bindings.getBinding(methodDecl.sym, methodDecl.type); + } + if (tree instanceof JCVariableDecl variableDecl && variableDecl.sym != null) { + return this.bindings.getBinding(variableDecl.sym, variableDecl.type); + } + if (tree instanceof JCTypeParameter variableDecl && variableDecl.type != null && variableDecl.type.tsym != null) { + return this.bindings.getBinding(variableDecl.type.tsym, variableDecl.type); + } + if (tree instanceof JCModuleDecl variableDecl && variableDecl.sym != null && variableDecl.sym.type instanceof ModuleType) { + return this.bindings.getModuleBinding(variableDecl); + } + return null; + } + + @Override + IVariableBinding resolveVariable(EnumConstantDeclaration enumConstant) { + resolve(); + if (this.converter.domToJavac.get(enumConstant) instanceof JCVariableDecl decl) { + // the decl.type can be null when there are syntax errors + if ((decl.type != null && !decl.type.isErroneous()) || this.isRecoveringBindings()) { + return this.bindings.getVariableBinding(decl.sym); + } + } + return null; + } + + @Override + IVariableBinding resolveVariable(VariableDeclaration variable) { + resolve(); + if (this.converter.domToJavac.get(variable) instanceof JCVariableDecl decl) { + // the decl.type can be null when there are syntax errors + if ((decl.type != null && !decl.type.isErroneous()) || this.isRecoveringBindings()) { + if (decl.name != Names.instance(this.context).error) { // cannot recover if name is error + return this.bindings.getVariableBinding(decl.sym); + } + } + } + return null; + } + + @Override + public IPackageBinding resolvePackage(PackageDeclaration decl) { + resolve(); + if (this.converter.domToJavac.get(decl) instanceof JCPackageDecl jcPackageDecl) { + return this.bindings.getPackageBinding(jcPackageDecl.packge); + } + return null; + } + + @Override + public ITypeBinding resolveExpressionType(Expression expr) { + resolve(); + if (expr instanceof SimpleName name) { + IBinding binding = resolveName(name); + // binding can be null when the code has syntax errors + if (binding == null || (binding.isRecovered() && !this.isRecoveringBindings())) { + return null; + } + switch (binding) { + case IVariableBinding variableBinding: return variableBinding.getType(); + case ITypeBinding typeBinding: return typeBinding; + case IMethodBinding methodBinding: return methodBinding.getReturnType(); + default: + return null; + } + } + var jcTree = this.converter.domToJavac.get(expr); + if (jcTree instanceof JCExpression expression + && isTypeOfType(expression.type) + && !expression.type.isErroneous()) { + var res = this.bindings.getTypeBinding(expression.type); + if (res != null) { + return res; + } + } + if (jcTree instanceof JCMethodInvocation javacMethodInvocation) { + if (javacMethodInvocation.meth.type instanceof MethodType methodType) { + return this.bindings.getTypeBinding(methodType.getReturnType()); + } else if (javacMethodInvocation.meth.type instanceof ErrorType errorType) { + if (errorType.getOriginalType() instanceof MethodType methodType) { + return this.bindings.getTypeBinding(methodType.getReturnType()); + } + } + return null; + } + if (jcTree instanceof JCNewClass newClass + && newClass.type != null + && Symtab.instance(this.context).errSymbol == newClass.type.tsym) { + jcTree = newClass.getIdentifier(); + } + if (jcTree instanceof JCFieldAccess jcFieldAccess) { + if (jcFieldAccess.type instanceof PackageType) { + return null; + } + if (expr instanceof SuperFieldAccess) { + return this.bindings.getTypeBinding(jcFieldAccess.selected.type); + } + return this.bindings.getTypeBinding(jcFieldAccess.type == null || jcFieldAccess.type.isErroneous() ? jcFieldAccess.sym.type : jcFieldAccess.type); + } + if (jcTree instanceof JCVariableDecl jcVariableDecl) { + if (jcVariableDecl.type != null) { + return this.bindings.getTypeBinding(jcVariableDecl.type); + } else { + return null; + } + } + if (jcTree instanceof JCTypeCast jcCast && jcCast.getType() != null) { + return this.bindings.getTypeBinding(jcCast.getType().type); + } + if (jcTree instanceof JCLiteral jcLiteral && jcLiteral.type != null && jcLiteral.type.isErroneous()) { + if (jcLiteral.typetag == TypeTag.CLASS) { + return resolveWellKnownType("java.lang.String"); + } else if (jcLiteral.typetag == TypeTag.BOT) { + return this.bindings.getTypeBinding(com.sun.tools.javac.code.Symtab.instance(this.context).botType); + } + return resolveWellKnownType(jcLiteral.typetag.name().toLowerCase()); + } + if (jcTree instanceof JCExpression jcExpr) { + if (jcExpr.type instanceof PackageType) { + return null; + } + Symbol recoveredSymbol = getRecoveredSymbol(jcExpr.type); + if (recoveredSymbol != null) { + IBinding recoveredBinding = this.bindings.getBinding(recoveredSymbol, recoveredSymbol.type); + return switch (recoveredBinding) { + case IVariableBinding variableBinding -> variableBinding.getType(); + case ITypeBinding typeBinding -> typeBinding; + case IMethodBinding methodBinding -> methodBinding.getReturnType(); + default -> null; + }; + } + if (jcExpr.type != null) { + var res = this.bindings.getTypeBinding(jcExpr.type); + if (res != null) { + return res; + } + } + // workaround Javac missing bindings in some cases + if (expr instanceof ClassInstanceCreation classInstanceCreation) { + return createRecoveredTypeBinding(classInstanceCreation.getType()); + } + } + return null; + } + + @Override + IMethodBinding resolveConstructor(ClassInstanceCreation expression) { + return (IMethodBinding)resolveCached(expression, (n) -> resolveConstructorImpl((ClassInstanceCreation)n)); + } + + /** + * + * @param t the type to check + * @return whether this is actually a type (returns + * {@code false} for things like {@link PackageType}, + * {@link MethodType}... + */ + public static boolean isTypeOfType(com.sun.tools.javac.code.Type t) { + return t == null ? false : + switch (t.getKind()) { + case PACKAGE, MODULE, EXECUTABLE, OTHER -> false; + default -> true; + }; + } + + private IMethodBinding resolveConstructorImpl(ClassInstanceCreation expression) { + resolve(); + if (this.converter.domToJavac.get(expression) instanceof JCNewClass jcExpr) { + if (jcExpr.constructor != null && !jcExpr.constructor.type.isErroneous()) { + return this.bindings.getMethodBinding(jcExpr.constructor.type.asMethodType(), (MethodSymbol)jcExpr.constructor, jcExpr.type, false); + } + } + ITypeBinding type = resolveType(expression.getType()); + if (type != null) { + List givenTypes = ((List)expression.arguments()).stream() + .map(this::resolveExpressionType) + .toList(); + boolean hasTrailingNull; + boolean matchExactParamCount = false; + do { + hasTrailingNull = !givenTypes.isEmpty() && givenTypes.getLast() == null; + // try just checking by known args + // first filter by args count + var matchExactParamCountFinal = matchExactParamCount; + var finalGivenTypes = givenTypes; + var candidates = Arrays.stream(type.getDeclaredMethods()) + .filter(IMethodBinding::isConstructor) + .filter(other -> matchExactParamCountFinal ? other.getParameterTypes().length == finalGivenTypes.size() : other.getParameterTypes().length >= finalGivenTypes.size()) + .toList(); + if (candidates.size() == 1) { + return candidates.get(0); + } + if (candidates.size() > 1 && expression.arguments().size() > 0) { + // then try filtering by arg types + var typeFilteredCandidates = candidates.stream() + .filter(other -> matchTypes(finalGivenTypes, other.getParameterTypes())) + .toList(); + if (typeFilteredCandidates.size() == 1) { + return typeFilteredCandidates.get(0); + } + } + if (hasTrailingNull) { + givenTypes = givenTypes.subList(0, givenTypes.size() - 1); + matchExactParamCount = true; + } + } while (hasTrailingNull); + } + return null; + } + + private boolean matchTypes(List givenTypes, ITypeBinding[] expectedTypes) { + for (int i = 0; i < Math.min(givenTypes.size(), expectedTypes.length); i++) { + ITypeBinding givenType = givenTypes.get(i); + ITypeBinding expectedType = expectedTypes[i]; + if (givenType != null) { + if (!givenType.isAssignmentCompatible(expectedType)) { + return false; + } + } + } + return true; + } + + @Override + IMethodBinding resolveConstructor(ConstructorInvocation invocation) { + return (IMethodBinding)resolveCached(invocation, (n) -> resolveConstructorImpl((ConstructorInvocation)n)); + } + + private IMethodBinding resolveConstructorImpl(ConstructorInvocation invocation) { + resolve(); + JCTree javacElement = this.converter.domToJavac.get(invocation); + if (javacElement instanceof JCMethodInvocation javacMethodInvocation) { + javacElement = javacMethodInvocation.getMethodSelect(); + } + if (javacElement instanceof JCIdent ident && ident.sym instanceof MethodSymbol methodSymbol) { + return this.bindings.getMethodBinding(ident.type != null && ident.type.getKind() == TypeKind.EXECUTABLE ? ident.type.asMethodType() : methodSymbol.type.asMethodType(), methodSymbol, null, false); + } + if (javacElement instanceof JCFieldAccess fieldAccess && fieldAccess.sym instanceof MethodSymbol methodSymbol) { + return this.bindings.getMethodBinding(fieldAccess.type.asMethodType(), methodSymbol, null, false); + } + return null; + } + + public Types getTypes() { + return Types.instance(this.context); + } + + @Override + IModuleBinding resolveModule(ModuleDeclaration module) { + return (IModuleBinding)resolveCached(module, (n) -> resolveModuleImpl((ModuleDeclaration)n)); + } + + private IBinding resolveModuleImpl(ModuleDeclaration module) { + resolve(); + JCTree javacElement = this.converter.domToJavac.get(module); + if( javacElement instanceof JCModuleDecl jcmd) { + Object o = jcmd.sym.type; + if( o instanceof ModuleType mt ) { + return this.bindings.getModuleBinding(mt); + } + } + return null; + } + + /** + * Returns the constant value or the binding that a Javac attribute represents. + * + * See a detailed explanation of the returned value: {@link org.eclipse.jdt.core.dom.IMethodBinding#getDefaultValue()} + * + * @param attribute the javac attribute + * @return the constant value or the binding that a Javac attribute represents + */ + public Object getValueFromAttribute(Attribute attribute) { + if (attribute == null) { + return null; + } + if (attribute instanceof Attribute.Constant constant) { + return constant.value; + } else if (attribute instanceof Attribute.Class clazz) { + return this.bindings.getTypeBinding(clazz.classType); + } else if (attribute instanceof Attribute.Enum enumm) { + return this.bindings.getVariableBinding(enumm.value); + } else if (attribute instanceof Attribute.Array array) { + return Stream.of(array.values) // + .map(nestedAttr -> { + if (nestedAttr instanceof Attribute.Constant constant) { + return constant.value; + } else if (nestedAttr instanceof Attribute.Class clazz) { + return this.bindings.getTypeBinding(clazz.classType); + } else if (nestedAttr instanceof Attribute.Enum enumerable) { + return this.bindings.getVariableBinding(enumerable.value); + } + throw new IllegalArgumentException("Unexpected attribute type: " + nestedAttr.getClass().getCanonicalName()); + }) // + .toArray(Object[]::new); + } + throw new IllegalArgumentException("Unexpected attribute type: " + attribute.getClass().getCanonicalName()); + } + + @Override + IBinding resolveImport(ImportDeclaration importDeclaration) { + return resolveCached(importDeclaration, (n) -> resolveImportImpl((ImportDeclaration)n)); + } + + private IBinding resolveImportImpl(ImportDeclaration importDeclaration) { + var javac = this.converter.domToJavac.get(importDeclaration.getName()); + if (javac instanceof JCFieldAccess fieldAccess) { + if (fieldAccess.sym != null) { + return this.bindings.getBinding(fieldAccess.sym, null); + } + if (importDeclaration.isStatic()) { + com.sun.tools.javac.code.Type type = fieldAccess.getExpression().type; + if (type != null) { + IBinding binding = Arrays.stream(this.bindings.getTypeBinding(type).getDeclaredMethods()) + .filter(method -> Objects.equals(fieldAccess.getIdentifier().toString(), method.getName())) + .findAny() + .orElse(null); + if (binding == null) { + binding = Arrays.stream(this.bindings.getTypeBinding(type).getDeclaredFields()).filter( + field -> Objects.equals(fieldAccess.getIdentifier().toString(), field.getName())) + .findAny().orElse(null); + } + return binding; + } + } + } + return null; + } + + @Override + public ITypeBinding resolveWellKnownType(String typeName) { + resolve(); // could be skipped, but this method is used by ReconcileWorkingCopyOperation to generate errors + com.sun.tools.javac.code.Symtab symtab = com.sun.tools.javac.code.Symtab.instance(this.context); + com.sun.tools.javac.code.Type type = switch (typeName) { + case "byte", "java.lang.Byte" -> symtab.byteType; + case "char", "java.lang.Character" -> symtab.charType; + case "double", "java.lang.Double" -> symtab.doubleType; + case "float", "java.lang.Float" -> symtab.floatType; + case "int", "java.lang.Integer" -> symtab.intType; + case "long", "java.lang.Long" -> symtab.longType; + case "short", "java.lang.Short" -> symtab.shortType; + case "boolean", "java.lang.Boolean" -> symtab.booleanType; + case "void", "java.lang.Void" -> symtab.voidType; + case "java.lang.Object" -> symtab.objectType; + case "java.lang.String" -> symtab.stringType; + case "java.lang.StringBuffer" -> symtab.stringBufferType; + case "java.lang.Throwable" -> symtab.throwableType; + case "java.lang.Exception" -> symtab.exceptionType; + case "java.lang.RuntimeException" -> symtab.runtimeExceptionType; + case "java.lang.Error" -> symtab.errorType; + case "java.lang.Class" -> symtab.classType; + case "java.lang.Cloneable" -> symtab.cloneableType; + case "java.io.Serializable" -> symtab.serializableType; + default -> null; + }; + if (type == null) { + return null; + } + return this.bindings.getTypeBinding(type, true); + } + + @Override + IAnnotationBinding resolveAnnotation(Annotation annotation) { + return (IAnnotationBinding)resolveCached(annotation, (n) -> resolveAnnotationImpl((Annotation)n)); + } + + IAnnotationBinding resolveAnnotationImpl(Annotation annotation) { + resolve(); + IBinding recipient = null; + if (annotation.getParent() instanceof AnnotatableType annotatable) { + recipient = annotatable.resolveBinding(); + } else if (annotation.getParent() instanceof FieldDeclaration fieldDeclaration) { + recipient = ((VariableDeclarationFragment)fieldDeclaration.fragments().get(0)).resolveBinding(); + } else if (annotation.getParent() instanceof TypeDeclaration td) { + recipient = td.resolveBinding(); + } + var javac = this.converter.domToJavac.get(annotation); + if (javac instanceof JCAnnotation jcAnnotation) { + return this.bindings.getAnnotationBinding(jcAnnotation.attribute, recipient); + } + return null; + } + @Override + IMemberValuePairBinding resolveMemberValuePair(MemberValuePair memberValuePair) { + resolve(); + if (this.converter.domToJavac.get(memberValuePair) instanceof JCAssign assign && + assign.lhs instanceof JCIdent ident && ident.sym instanceof MethodSymbol methodSymbol) { + return this.bindings.getMemberValuePairBinding(methodSymbol, null); + } + return null; + } + + @Override + IBinding resolveReference(MethodRef ref) { + return resolveCached(ref, (n) -> resolveReferenceImpl((MethodRef)n)); + } + + private IBinding resolveReferenceImpl(MethodRef ref) { + resolve(); + DocTreePath path = this.converter.findDocTreePath(ref); + if (path != null ) { + Element e = JavacTrees.instance(this.context).getElement(path); + if(e instanceof Symbol symbol) { + IBinding r1 = this.bindings.getBinding(symbol, null); + return r1; + } + TreePath dt = path.getTreePath(); + if( dt != null) { + Tree t = dt.getLeaf(); + if( t instanceof JCMethodDecl jcmd) { + MethodSymbol ms = jcmd.sym; + IBinding r1 = ms == null ? null : this.bindings.getBinding(ms, jcmd.type); + return r1; + } + } + } + if( ref.parameters() != null && ref.parameters().size() == 0) { + // exhaustively search for a similar method ref + DocTreePath[] possible = this.converter.searchRelatedDocTreePath(ref); + if( possible != null ) { + for( int i = 0; i < possible.length; i++ ) { + Element e = JavacTrees.instance(this.context).getElement(possible[i]); + if(e instanceof Symbol symbol) { + IBinding r1 = this.bindings.getBinding(symbol, null); + if( r1 != null ) + return r1; + } + } + } + } + // + return null; + } + + private IBinding getFieldAccessBinding(JCFieldAccess fieldAccess) { + JCFieldAccess jcfa2 = (fieldAccess.sym == null && fieldAccess.selected instanceof JCFieldAccess jcfa3) ? jcfa3 : fieldAccess; + if( jcfa2.sym != null ) { + com.sun.tools.javac.code.Type typeToUse = jcfa2.type; + if(jcfa2.selected instanceof JCTypeApply) { + typeToUse = jcfa2.sym.type; + } + IBinding bRet = this.bindings.getBinding(jcfa2.sym, typeToUse); + if( jcfa2 != fieldAccess && bRet instanceof ITypeBinding itb ) { + String fieldAccessIdentifier = fieldAccess.getIdentifier().toString(); + // If we changed the field access, we need to go one generation lower + Function func = bindings -> { + for( int i = 0; i < bindings.length; i++ ) { + String childName = bindings[i].getName(); + if( childName.equals(fieldAccessIdentifier)) { + return bindings[i]; + } + } + return null; + }; + IBinding ret = func.apply(itb.getDeclaredTypes()); + if( ret != null ) + return ret; + ret = func.apply(itb.getDeclaredFields()); + if( ret != null ) + return ret; + ret = func.apply(itb.getDeclaredMethods()); + if( ret != null ) + return ret; + } + return bRet; + } + return null; + } + @Override + IBinding resolveReference(MemberRef ref) { + return resolveCached(ref, (n) -> resolveReferenceImpl((MemberRef)n)); + } + + private IBinding resolveReferenceImpl(MemberRef ref) { + resolve(); + DocTreePath path = this.converter.findDocTreePath(ref); + if (path != null && JavacTrees.instance(this.context).getElement(path) instanceof Symbol symbol) { + return this.bindings.getBinding(symbol, null); + } + return null; + } + private static Symbol getRecoveredSymbol(com.sun.tools.javac.code.Type type) { + if (type instanceof ErrorType) { + try { + Field candidateSymbolField = type.getClass().getField("candidateSymbol"); + candidateSymbolField.setAccessible(true); + + Object symbolFieldValue = candidateSymbolField.get(type); + if (symbolFieldValue instanceof Symbol symbol) { + return symbol; + } + } catch (NoSuchFieldException | IllegalAccessException unused) { + // fall through to null + } + } + return null; + } + + @Override + public WorkingCopyOwner getWorkingCopyOwner() { + return this.owner; + } + + @Override + Object resolveConstantExpressionValue(Expression expression) { + JCTree jcTree = this.converter.domToJavac.get(expression); + if (jcTree instanceof JCLiteral literal) { + return literal.getValue(); + } + return TreeInfo.symbolFor(jcTree) instanceof VarSymbol varSymbol ? varSymbol.getConstantValue() : null; + } + @Override + boolean resolveBoxing(Expression expression) { + // TODO need to handle many different things here, very rudimentary + if( expression.getParent() instanceof MethodInvocation mi) { + IMethodBinding mb = resolveMethod(mi); + int foundArg = -1; + if( mb != null ) { + for( int i = 0; i < mi.arguments().size() && foundArg == -1; i++ ) { + if( mi.arguments().get(i) == expression) { + foundArg = i; + } + } + if( foundArg != -1 ) { + ITypeBinding[] tbs = mb.getParameterTypes(); + if( tbs.length > foundArg) { + ITypeBinding foundType = tbs[foundArg]; + if( expression instanceof NumberLiteral nl) { + if( isBoxedVersion(nl, foundType)) { + return true; + } + } else { + if( expression instanceof MethodInvocation inner) { + JavacMethodBinding mbInner = (JavacMethodBinding)resolveMethod(inner); + ITypeBinding retTypeInner = mbInner == null ? null : mbInner.getReturnType(); + if( isBoxedVersion(retTypeInner, foundType)) { + return true; + } + } + } + } + } + } + } + return false; + } + + private boolean isBoxedVersion(NumberLiteral unboxed, ITypeBinding boxed) { + if( boxed instanceof JavacTypeBinding boxedBind ) { + String boxedString = boxedBind.typeSymbol == null ? null : boxedBind.typeSymbol.toString(); + if("java.lang.Integer".equals(boxedString) + || "java.lang.Float".equals(boxedString) + || "java.lang.Double".equals(boxedString)) { + return true; + } + } + return false; + } + + private Map boxingMap = null; + private boolean isBoxedVersion(ITypeBinding unboxed, ITypeBinding boxed) { + if( boxingMap == null ) { + Map m = new HashMap(); + m.put("java.lang.Boolean", "boolean"); + m.put("java.lang.Byte", "byte"); + m.put("java.lang.Character", "char"); + m.put("java.lang.Float", "float"); + m.put("java.lang.Integer", "int"); + m.put("java.lang.Long", "long"); + m.put("java.lang.Short", "short"); + m.put("java.lang.Double", "double"); + boxingMap = m; + } + if( boxed instanceof JavacTypeBinding boxedBind && unboxed instanceof JavacTypeBinding unboxedBind) { + String boxedString = boxedBind.typeSymbol == null ? null : boxedBind.typeSymbol.toString(); + String unboxedString = unboxedBind.typeSymbol == null ? null : unboxedBind.typeSymbol.toString(); + // TODO very rudimentary, fix it, add more + if( boxingMap.get(boxedString) != null ) { + if( unboxedString.equals(boxingMap.get(boxedString))) { + return true; + } + } + // Alternate case, they might be converting some types + if( boxingMap.keySet().contains(boxedString) && boxingMap.values().contains(unboxedString)) { + return true; + } + } + return false; + } + @Override + boolean resolveUnboxing(Expression expression) { + Type t = null; + if( expression instanceof ClassInstanceCreation cic ) { + t = cic.getType(); + } + if( t != null && expression.getParent() instanceof MethodInvocation mi) { + int foundArg = -1; + if( mi != null ) { + for( int i = 0; i < mi.arguments().size() && foundArg == -1; i++ ) { + if( mi.arguments().get(i) == expression) { + foundArg = i; + } + } + if( foundArg != -1 ) { + IMethodBinding mb = resolveMethod(mi); + ITypeBinding[] tbs = mb.getParameterTypes(); + if( tbs.length > foundArg) { + ITypeBinding unboxed = tbs[foundArg]; + ITypeBinding boxed = resolveType(t); + if( isBoxedVersion(unboxed, boxed)) { + return true; + } + } else if( tbs.length > 0 && mb.isVarargs()) { + ITypeBinding lastArg = tbs[tbs.length - 1]; + if( lastArg.isArray()) { + ITypeBinding el = lastArg.getElementType(); + if( el.isPrimitive()) { + if( isBoxedVersion(el, resolveType(t))) { + return true; + } + } + } + } + } + } + + } + return false; + } + public boolean isRecoveringBindings() { + return isRecoveringBindings; + } +} 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..62a8379077b --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacCompilationUnitResolver.java @@ -0,0 +1,1136 @@ +/******************************************************************************* + * 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.nio.CharBuffer; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; +import javax.tools.DiagnosticListener; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.StandardLocation; +import javax.tools.ToolProvider; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.ILog; +import org.eclipse.core.runtime.IProduct; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.Platform; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.Signature; +import org.eclipse.jdt.core.WorkingCopyOwner; +import org.eclipse.jdt.core.compiler.CategorizedProblem; +import org.eclipse.jdt.core.compiler.CharOperation; +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.env.AccessRestriction; +import org.eclipse.jdt.internal.compiler.env.IBinaryType; +import org.eclipse.jdt.internal.compiler.env.INameEnvironment; +import org.eclipse.jdt.internal.compiler.env.ISourceType; +import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; +import org.eclipse.jdt.internal.compiler.impl.ITypeRequestor; +import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding; +import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment; +import org.eclipse.jdt.internal.compiler.lookup.PackageBinding; +import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; +import org.eclipse.jdt.internal.compiler.util.Util; +import org.eclipse.jdt.internal.core.CancelableNameEnvironment; +import org.eclipse.jdt.internal.core.JavaModelManager; +import org.eclipse.jdt.internal.core.JavaProject; +import org.eclipse.jdt.internal.core.dom.ICompilationUnitResolver; +import org.eclipse.jdt.internal.core.util.BindingKeyParser; +import org.eclipse.jdt.internal.javac.CachingJDKPlatformArguments; +import org.eclipse.jdt.internal.javac.CachingJarsJavaFileManager; +import org.eclipse.jdt.internal.javac.JavacProblemConverter; +import org.eclipse.jdt.internal.javac.JavacUtils; +import org.eclipse.jdt.internal.javac.UnusedProblemFactory; +import org.eclipse.jdt.internal.javac.UnusedTreeScanner; + +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.Tree; +import com.sun.source.util.JavacTask; +import com.sun.source.util.TaskEvent; +import com.sun.source.util.TaskListener; +import com.sun.tools.javac.api.JavacTool; +import com.sun.tools.javac.api.MultiTaskListener; +import com.sun.tools.javac.code.Symbol.PackageSymbol; +import com.sun.tools.javac.comp.CompileStates.CompileState; +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.CommentStyle; +import com.sun.tools.javac.parser.Tokens.TokenKind; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +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; + +/** + * Allows to create and resolve DOM ASTs using Javac + * @implNote Cannot move to another package because parent class is package visible only + */ +public class JavacCompilationUnitResolver implements ICompilationUnitResolver { + + private final class ForwardDiagnosticsAsDOMProblems implements DiagnosticListener { + public final Map filesToUnits; + private final JavacProblemConverter problemConverter; + + private ForwardDiagnosticsAsDOMProblems(Map filesToUnits, + JavacProblemConverter problemConverter) { + this.filesToUnits = filesToUnits; + this.problemConverter = problemConverter; + } + + @Override + public void report(Diagnostic diagnostic) { + findTargetDOM(filesToUnits, diagnostic).ifPresent(dom -> { + var newProblem = problemConverter.createJavacProblem(diagnostic); + if (newProblem != null) { + IProblem[] previous = dom.getProblems(); + IProblem[] newProblems = Arrays.copyOf(previous, previous.length + 1); + newProblems[newProblems.length - 1] = newProblem; + dom.setProblems(newProblems); + } + }); + } + + private static Optional findTargetDOM(Map filesToUnits, Object obj) { + if (obj == null) { + return Optional.empty(); + } + if (obj instanceof JavaFileObject o) { + return Optional.ofNullable(filesToUnits.get(o)); + } + if (obj instanceof DiagnosticSource source) { + return findTargetDOM(filesToUnits, source.getFile()); + } + if (obj instanceof Diagnostic diag) { + return findTargetDOM(filesToUnits, diag.getSource()); + } + return Optional.empty(); + } + } + + private interface GenericRequestor { + public void acceptBinding(String bindingKey, IBinding binding); + } + + public JavacCompilationUnitResolver() { + // 0-arg constructor + } + + private List createSourceUnitList(String[] sourceFilePaths, String[] encodings) { + // make list of source unit + int length = sourceFilePaths.length; + List sourceUnitList = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + org.eclipse.jdt.internal.compiler.env.ICompilationUnit obj = createSourceUnit(sourceFilePaths[i], encodings[i]); + if( obj != null ) + sourceUnitList.add(obj); + } + return sourceUnitList; + } + + private org.eclipse.jdt.internal.compiler.env.ICompilationUnit createSourceUnit(String sourceFilePath, String encoding) { + char[] contents = null; + try { + contents = Util.getFileCharContent(new File(sourceFilePath), encoding); + } catch(IOException e) { + return null; + } + if (contents == null) { + return null; + } + return new org.eclipse.jdt.internal.compiler.batch.CompilationUnit(contents, sourceFilePath, encoding); + } + + + @Override + public void resolve(String[] sourceFilePaths, String[] encodings, String[] bindingKeys, FileASTRequestor requestor, + int apiLevel, Map compilerOptions, List classpaths, int flags, + IProgressMonitor monitor) { + List sourceUnitList = createSourceUnitList(sourceFilePaths, encodings); + JavacBindingResolver bindingResolver = null; + + // parse source units + Map res = + parse(sourceUnitList.toArray(org.eclipse.jdt.internal.compiler.env.ICompilationUnit[]::new), apiLevel, compilerOptions, true, flags, (IJavaProject)null, null, -1, monitor); + + for (var entry : res.entrySet()) { + CompilationUnit cu = entry.getValue(); + requestor.acceptAST(new String(entry.getKey().getFileName()), cu); + if (bindingResolver == null && (JavacBindingResolver)cu.ast.getBindingResolver() != null) { + bindingResolver = (JavacBindingResolver)cu.ast.getBindingResolver(); + } + } + + resolveRequestedBindingKeys(bindingResolver, bindingKeys, + (a,b) -> requestor.acceptBinding(a,b), + classpaths.stream().toArray(Classpath[]::new), + new CompilerOptions(compilerOptions), + res.values(), null, new HashMap<>(), monitor); + } + + @Override + public void resolve(ICompilationUnit[] compilationUnits, String[] bindingKeys, ASTRequestor requestor, int apiLevel, + Map compilerOptions, IJavaProject project, WorkingCopyOwner workingCopyOwner, int flags, + IProgressMonitor monitor) { + Map units = parse(compilationUnits, apiLevel, compilerOptions, true, flags, workingCopyOwner, monitor); + if (requestor != null) { + final JavacBindingResolver[] bindingResolver = new JavacBindingResolver[1]; + bindingResolver[0] = null; + + final Map bindingMap = new HashMap<>(); + { + INameEnvironment environment = null; + if (project instanceof JavaProject javaProject) { + try { + environment = new CancelableNameEnvironment(javaProject, workingCopyOwner, monitor); + } catch (JavaModelException e) { + // fall through + } + } + if (environment == null) { + environment = new NameEnvironmentWithProgress(new Classpath[0], null, monitor); + } + LookupEnvironment lu = new LookupEnvironment(new ITypeRequestor() { + + @Override + public void accept(IBinaryType binaryType, PackageBinding packageBinding, + AccessRestriction accessRestriction) { + // do nothing + } + + @Override + public void accept(org.eclipse.jdt.internal.compiler.env.ICompilationUnit unit, + AccessRestriction accessRestriction) { + // do nothing + } + + @Override + public void accept(ISourceType[] sourceType, PackageBinding packageBinding, + AccessRestriction accessRestriction) { + // do nothing + } + + }, new CompilerOptions(compilerOptions), null, environment); + requestor.additionalBindingResolver = javacAdditionalBindingCreator(bindingMap, environment, lu, bindingResolver); + } + + units.forEach((a,b) -> { + if (bindingResolver[0] == null && (JavacBindingResolver)b.ast.getBindingResolver() != null) { + bindingResolver[0] = (JavacBindingResolver)b.ast.getBindingResolver(); + } + requestor.acceptAST(a,b); + resolveBindings(b, bindingMap, apiLevel); + }); + + resolveRequestedBindingKeys(bindingResolver[0], bindingKeys, + (a,b) -> requestor.acceptBinding(a,b), + new Classpath[0], // TODO need some classpaths + new CompilerOptions(compilerOptions), + units.values(), project, bindingMap, monitor); + } else { + Iterator it = units.values().iterator(); + while(it.hasNext()) { + resolveBindings(it.next(), apiLevel); + } + } + } + + private void resolveRequestedBindingKeys(JavacBindingResolver bindingResolver, String[] bindingKeys, GenericRequestor requestor, + Classpath[] cp,CompilerOptions opts, + Collection units, + IJavaProject project, + Map bindingMap, + IProgressMonitor monitor) { + if (bindingResolver == null) { + var compiler = ToolProvider.getSystemJavaCompiler(); + var context = new Context(); + JavacTask task = (JavacTask) compiler.getTask(null, null, null, List.of(), List.of(), List.of()); + bindingResolver = new JavacBindingResolver(null, task, context, new JavacConverter(null, null, context, null, true, -1), null, null); + } + + for (CompilationUnit cu : units) { + cu.accept(new BindingBuilder(bindingMap)); + } + + INameEnvironment environment = null; + if (project instanceof JavaProject javaProject) { + try { + environment = new CancelableNameEnvironment(javaProject, null, monitor); + } catch (JavaModelException e) { + // do nothing + } + } + if (environment == null) { + environment = new NameEnvironmentWithProgress(cp, null, monitor); + } + + LookupEnvironment lu = new LookupEnvironment(new ITypeRequestor() { + + @Override + public void accept(IBinaryType binaryType, PackageBinding packageBinding, + AccessRestriction accessRestriction) { + // do nothing + } + + @Override + public void accept(org.eclipse.jdt.internal.compiler.env.ICompilationUnit unit, + AccessRestriction accessRestriction) { + // do nothing + } + + @Override + public void accept(ISourceType[] sourceType, PackageBinding packageBinding, + AccessRestriction accessRestriction) { + // do nothing + } + + }, opts, null, environment); + + // resolve the requested bindings + for (String bindingKey : bindingKeys) { + + int arrayCount = Signature.getArrayCount(bindingKey); + IBinding bindingFromMap = bindingMap.get(bindingKey); + if (bindingFromMap != null) { + // from parsed files + requestor.acceptBinding(bindingKey, bindingFromMap); + } else { + + if (arrayCount > 0) { + String elementKey = Signature.getElementType(bindingKey); + IBinding elementBinding = bindingMap.get(elementKey); + if (elementBinding instanceof ITypeBinding elementTypeBinding) { + requestor.acceptBinding(bindingKey, elementTypeBinding.createArrayType(arrayCount)); + continue; + } + } + + CustomBindingKeyParser bkp = new CustomBindingKeyParser(bindingKey); + bkp.parse(true); + char[][] name = bkp.compoundName; + +// // from ECJ +// char[] charArrayFQN = Signature.toCharArray(bindingKey.toCharArray()); +// char[][] twoDimensionalCharArrayFQN = Stream.of(new String(charArrayFQN).split("/")) // +// .map(myString -> myString.toCharArray()) // +// .toArray(char[][]::new); +// char[][] twoDimensionalCharArrayFQN = new char[][] {}; + NameEnvironmentAnswer answer = environment.findType(name); + if( answer != null ) { + IBinaryType binaryType = answer.getBinaryType(); + if (binaryType != null) { + BinaryTypeBinding binding = lu.cacheBinaryType(binaryType, null); + if( binding != null ) + requestor.acceptBinding(bindingKey, new TypeBinding(bindingResolver, binding)); + } + } + } + + } + + } + + private static class CustomBindingKeyParser extends BindingKeyParser { + + private char[] secondarySimpleName; + private char[][] compoundName; + + public CustomBindingKeyParser(String key) { + super(key); + } + + @Override + public void consumeSecondaryType(char[] simpleTypeName) { + this.secondarySimpleName = simpleTypeName; + } + + @Override + public void consumeFullyQualifiedName(char[] fullyQualifiedName) { + this.compoundName = CharOperation.splitOn('/', fullyQualifiedName); + } + } + + @Override + public void parse(ICompilationUnit[] compilationUnits, ASTRequestor requestor, int apiLevel, + Map compilerOptions, int flags, IProgressMonitor monitor) { + WorkingCopyOwner workingCopyOwner = Arrays.stream(compilationUnits) + .filter(ICompilationUnit.class::isInstance) + .map(ICompilationUnit.class::cast) + .map(ICompilationUnit::getOwner) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + Map units = parse(compilationUnits, apiLevel, compilerOptions, false, flags, workingCopyOwner, monitor); + if (requestor != null) { + units.forEach(requestor::acceptAST); + } + } + + private Map parse(ICompilationUnit[] compilationUnits, int apiLevel, + Map compilerOptions, boolean resolveBindings, int flags, WorkingCopyOwner workingCopyOwner, IProgressMonitor monitor) { + // TODO ECJCompilationUnitResolver has support for dietParse and ignore method body + // is this something we need? + if (compilationUnits.length > 0 + && Arrays.stream(compilationUnits).map(ICompilationUnit::getJavaProject).distinct().count() == 1 + && Arrays.stream(compilationUnits).allMatch(org.eclipse.jdt.internal.compiler.env.ICompilationUnit.class::isInstance)) { + // all in same project, build together + Map res = + parse(Arrays.stream(compilationUnits) + .map(org.eclipse.jdt.internal.compiler.env.ICompilationUnit.class::cast) + .toArray(org.eclipse.jdt.internal.compiler.env.ICompilationUnit[]::new), + apiLevel, compilerOptions, resolveBindings, flags, compilationUnits[0].getJavaProject(), workingCopyOwner, -1, monitor) + .entrySet().stream().collect(Collectors.toMap(entry -> (ICompilationUnit)entry.getKey(), entry -> entry.getValue())); + for (ICompilationUnit in : compilationUnits) { + CompilationUnit c = res.get(in); + if( c != null ) + c.setTypeRoot(in); + } + return res; + } + // build individually + Map res = new HashMap<>(compilationUnits.length, 1.f); + for (ICompilationUnit in : compilationUnits) { + if (in instanceof org.eclipse.jdt.internal.compiler.env.ICompilationUnit compilerUnit) { + res.put(in, parse(new org.eclipse.jdt.internal.compiler.env.ICompilationUnit[] { compilerUnit }, + apiLevel, compilerOptions, resolveBindings, flags, in.getJavaProject(), workingCopyOwner, -1, monitor).get(compilerUnit)); + res.get(in).setTypeRoot(in); + } + } + return res; + } + + @Override + public void parse(String[] sourceFilePaths, String[] encodings, FileASTRequestor requestor, int apiLevel, + Map compilerOptions, int flags, IProgressMonitor monitor) { + + for( int i = 0; i < sourceFilePaths.length; i++ ) { + org.eclipse.jdt.internal.compiler.env.ICompilationUnit ast = createSourceUnit(sourceFilePaths[i], encodings[i]); + Map res = + parse(new org.eclipse.jdt.internal.compiler.env.ICompilationUnit[] {ast}, apiLevel, compilerOptions, false, flags, (IJavaProject)null, null, -1, monitor); + CompilationUnit result = res.get(ast); + requestor.acceptAST(sourceFilePaths[i], result); + } + } + + + private void resolveBindings(CompilationUnit unit, int apiLevel) { + resolveBindings(unit, new HashMap<>(), apiLevel); + } + + private void resolveBindings(CompilationUnit unit, Map bindingMap, int apiLevel) { + try { + if (unit.getPackage() != null) { + IPackageBinding pb = unit.getPackage().resolveBinding(); + if (pb != null) { + bindingMap.put(pb.getKey(), pb); + } + } + if (!unit.types().isEmpty()) { + List types = unit.types(); + for( int i = 0; i < types.size(); i++ ) { + ITypeBinding tb = types.get(i).resolveBinding(); + if (tb != null) { + bindingMap.put(tb.getKey(), tb); + } + } + } + if( apiLevel >= AST.JLS9_INTERNAL) { + if (unit.getModule() != null) { + IModuleBinding mb = unit.getModule().resolveBinding(); + if (mb != null) { + bindingMap.put(mb.getKey(), mb); + } + } + } + } catch (Exception e) { + ILog.get().warn("Failed to resolve binding", e); + } + } + + @Override + public CompilationUnit toCompilationUnit(org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit, + boolean resolveBindings, IJavaProject project, List classpaths, + int focalPoint, int apiLevel, Map compilerOptions, + WorkingCopyOwner workingCopyOwner, WorkingCopyOwner typeRootWorkingCopyOwner, int flags, IProgressMonitor monitor) { + + // collect working copies + var workingCopies = JavaModelManager.getJavaModelManager().getWorkingCopies(workingCopyOwner, true); + if (workingCopies == null) { + workingCopies = new ICompilationUnit[0]; + } + Map pathToUnit = new HashMap<>(); + Arrays.stream(workingCopies) // + .filter(inMemoryCu -> { + try { + return inMemoryCu.hasUnsavedChanges() && (project == null || (inMemoryCu.getElementName() != null && !inMemoryCu.getElementName().contains("module-info")) || inMemoryCu.getJavaProject() == project); + } catch (JavaModelException e) { + return project == null || (inMemoryCu.getElementName() != null && !inMemoryCu.getElementName().contains("module-info")) || inMemoryCu.getJavaProject() == project; + } + }) + .map(org.eclipse.jdt.internal.compiler.env.ICompilationUnit.class::cast) // + .forEach(inMemoryCu -> { + pathToUnit.put(new String(inMemoryCu.getFileName()), inMemoryCu); + }); + + // `sourceUnit`'s path might contain only the last segment of the path. + // this presents a problem, since if there is a working copy of the class, + // we want to use `sourceUnit` instead of the working copy, + // and this is accomplished by replacing the working copy's entry in the path-to-CompilationUnit map + String pathOfClassUnderAnalysis = new String(sourceUnit.getFileName()); + if (!pathToUnit.keySet().contains(pathOfClassUnderAnalysis)) { + // try to find the project-relative path for the class under analysis by looking through the work copy paths + List potentialPaths = pathToUnit.keySet().stream() // + .filter(path -> path.endsWith(pathOfClassUnderAnalysis)) // + .toList(); + if (potentialPaths.isEmpty()) { + // there is no conflicting class in the working copies, + // so it's okay to use the 'broken' path + pathToUnit.put(pathOfClassUnderAnalysis, sourceUnit); + } else if (potentialPaths.size() == 1) { + // we know exactly which one is the duplicate, + // so replace it + pathToUnit.put(potentialPaths.get(0), sourceUnit); + } else { + // we don't know which one is the duplicate, + // so remove all potential duplicates + for (String potentialPath : potentialPaths) { + pathToUnit.remove(potentialPath); + } + pathToUnit.put(pathOfClassUnderAnalysis, sourceUnit); + } + } else { + // intentionally overwrite the existing working copy entry for the same file + pathToUnit.put(pathOfClassUnderAnalysis, sourceUnit); + } + + //CompilationUnit res2 = CompilationUnitResolver.getInstance().toCompilationUnit(sourceUnit, resolveBindings, project, classpaths, focalPoint, apiLevel, compilerOptions, typeRootWorkingCopyOwner, typeRootWorkingCopyOwner, flags, monitor); + CompilationUnit res = parse(pathToUnit.values().toArray(org.eclipse.jdt.internal.compiler.env.ICompilationUnit[]::new), + apiLevel, compilerOptions, resolveBindings, flags, project, workingCopyOwner, focalPoint, monitor).get(sourceUnit); + if (resolveBindings) { + resolveBindings(res, apiLevel); + } + return res; + } + + private Map parse(org.eclipse.jdt.internal.compiler.env.ICompilationUnit[] sourceUnits, int apiLevel, + Map compilerOptions, boolean resolveBindings, int flags, IJavaProject javaProject, WorkingCopyOwner workingCopyOwner, + int focalPoint, IProgressMonitor monitor) { + if (sourceUnits.length == 0) { + return Collections.emptyMap(); + } + var compiler = ToolProvider.getSystemJavaCompiler(); + Context context = new Context(); + CachingJarsJavaFileManager.preRegister(context); + CachingJDKPlatformArguments.preRegister(context); + Map result = new HashMap<>(sourceUnits.length, 1.f); + Map filesToUnits = new HashMap<>(); + final UnusedProblemFactory unusedProblemFactory = new UnusedProblemFactory(new DefaultProblemFactory(), compilerOptions); + var problemConverter = new JavacProblemConverter(compilerOptions, context); + DiagnosticListener diagnosticListener = new ForwardDiagnosticsAsDOMProblems(filesToUnits, problemConverter); + MultiTaskListener.instance(context).add(new TaskListener() { + @Override + public void finished(TaskEvent e) { + if (e.getCompilationUnit() instanceof JCCompilationUnit u) { + problemConverter.registerUnit(e.getSourceFile(), u); + } + + if (e.getKind() == TaskEvent.Kind.ANALYZE) { + final JavaFileObject file = e.getSourceFile(); + final CompilationUnit dom = filesToUnits.get(file); + if (dom == null) { + return; + } + + final TypeElement currentTopLevelType = e.getTypeElement(); + UnusedTreeScanner scanner = new UnusedTreeScanner<>() { + @Override + public Void visitClass(ClassTree node, Void p) { + if (node instanceof JCClassDecl classDecl) { + /** + * If a Java file contains multiple top-level types, it will + * trigger multiple ANALYZE taskEvents for the same compilation + * unit. Each ANALYZE taskEvent corresponds to the completion + * of analysis for a single top-level type. Therefore, in the + * ANALYZE task event listener, we only visit the class and nested + * classes that belong to the currently analyzed top-level type. + */ + if (Objects.equals(currentTopLevelType, classDecl.sym) + || !(classDecl.sym.owner instanceof PackageSymbol)) { + return super.visitClass(node, p); + } else { + return null; // Skip if it does not belong to the currently analyzed top-level type. + } + } + + return super.visitClass(node, p); + } + }; + final CompilationUnitTree unit = e.getCompilationUnit(); + try { + scanner.scan(unit, null); + } catch (Exception ex) { + ILog.get().error("Internal error when visiting the AST Tree. " + ex.getMessage(), ex); + } + + List unusedProblems = scanner.getUnusedPrivateMembers(unusedProblemFactory); + if (!unusedProblems.isEmpty()) { + addProblemsToDOM(dom, unusedProblems); + } + + List unusedImports = scanner.getUnusedImports(unusedProblemFactory); + List topTypes = unit.getTypeDecls(); + int typeCount = topTypes.size(); + // Once all top level types of this Java file have been resolved, + // we can report the unused import to the DOM. + if (typeCount <= 1) { + addProblemsToDOM(dom, unusedImports); + } else if (typeCount > 1 && topTypes.get(typeCount - 1) instanceof JCClassDecl lastType) { + if (Objects.equals(currentTopLevelType, lastType.sym)) { + addProblemsToDOM(dom, unusedImports); + } + } + } + } + }); + // must be 1st thing added to context + context.put(DiagnosticListener.class, diagnosticListener); + boolean docEnabled = JavaCore.ENABLED.equals(compilerOptions.get(JavaCore.COMPILER_DOC_COMMENT_SUPPORT)); + JavacUtils.configureJavacContext(context, compilerOptions, javaProject, JavacUtils.isTest(javaProject, sourceUnits)); + Options javacOptions = Options.instance(context); + javacOptions.put("allowStringFolding", Boolean.FALSE.toString()); // we need to keep strings as authored + if ((focalPoint >= 0 || !resolveBindings) && (flags & ICompilationUnit.FORCE_PROBLEM_DETECTION) == 0) { + // most likely no need for linting + // resolveBindings still seems requested for tests + javacOptions.remove(Option.XLINT.primaryName); + javacOptions.remove(Option.XLINT_CUSTOM.primaryName); + javacOptions.remove(Option.XDOCLINT.primaryName); + javacOptions.remove(Option.XDOCLINT_CUSTOM.primaryName); + } + javacOptions.put(Option.PROC, "only"); + Optional.ofNullable(Platform.getProduct()) + .map(IProduct::getApplication) + // if application is not a test runner (so we don't have regressions with JDT test suite because of too many problems + .or(() -> Optional.ofNullable(System.getProperty("eclipse.application"))) + .filter(name -> !name.contains("test") && !name.contains("junit")) + // continue as far as possible to get extra warnings about unused + .ifPresent(_ ->javacOptions.put("should-stop.ifError", CompileState.GENERATE.toString())); + var fileManager = (JavacFileManager)context.get(JavaFileManager.class); + List fileObjects = new ArrayList<>(); // we need an ordered list of them + for (var sourceUnit : sourceUnits) { + File unitFile; + if (javaProject != null && javaProject.getResource() != null) { + // path is relative to the workspace, make it absolute + IResource asResource = javaProject.getProject().getParent().findMember(new String(sourceUnit.getFileName())); + if (asResource != null) { + unitFile = asResource.getLocation().toFile(); + } else { + unitFile = new File(new String(sourceUnit.getFileName())); + } + } else { + unitFile = new File(new String(sourceUnit.getFileName())); + } + Path sourceUnitPath = null; + if (!unitFile.getName().endsWith(".java") || sourceUnit.getFileName() == null || sourceUnit.getFileName().length == 0) { + String uri1 = unitFile.toURI().toString().replaceAll("%7C", "/"); + if( uri1.endsWith(".class")) { + String[] split= uri1.split("/"); + String lastSegment = split[split.length-1].replace(".class", ".java"); + sourceUnitPath = Path.of(lastSegment); + } + if( sourceUnitPath == null ) + sourceUnitPath = Path.of(new File("whatever.java").toURI()); + } else { + sourceUnitPath = Path.of(unitFile.toURI()); + } + var fileObject = fileManager.getJavaFileObject(sourceUnitPath); + fileManager.cache(fileObject, CharBuffer.wrap(sourceUnit.getContents())); + AST ast = createAST(compilerOptions, apiLevel, context, flags); + CompilationUnit res = ast.newCompilationUnit(); + result.put(sourceUnit, res); + filesToUnits.put(fileObject, res); + fileObjects.add(fileObject); + } + + + JCCompilationUnit javacCompilationUnit = null; + Iterable options = configureAPIfNecessary(fileManager) ? null : Arrays.asList("-proc:none"); + JavacTask task = ((JavacTool)compiler).getTask(null, fileManager, null /* already added to context */, options, List.of() /* already set */, fileObjects, context); + { + // don't know yet a better way to ensure those necessary flags get configured + var javac = com.sun.tools.javac.main.JavaCompiler.instance(context); + javac.keepComments = true; + javac.genEndPos = true; + javac.lineDebugInfo = true; + } + + List javacCompilationUnits = new ArrayList<>(); + try { + var elements = task.parse().iterator(); + // after parsing, we already have the comments and we don't care about reading other comments + // during resolution + { + // The tree we have are complete and good enough for further processing. + // Disable extra features that can affect how other trees (source path elements) + // are parsed during resolution so we stick to the mininal useful data generated + // and stored during analysis + var javac = com.sun.tools.javac.main.JavaCompiler.instance(context); + javac.keepComments = false; + javac.genEndPos = false; + javac.lineDebugInfo = false; + } + var aptPath = fileManager.getLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH); + if ((flags & ICompilationUnit.FORCE_PROBLEM_DETECTION) != 0 + || (aptPath != null && aptPath.iterator().hasNext())) { + task.analyze(); + } + + Throwable cachedThrown = null; + + for (int i = 0 ; i < sourceUnits.length; i++) { + if (elements.hasNext() && elements.next() instanceof JCCompilationUnit u) { + javacCompilationUnit = u; + javacCompilationUnits.add(u); + if (sourceUnits.length == 1 && focalPoint >= 0) { + JavacUtils.trimUnvisibleContent(u, focalPoint, context); + } + } else { + return Map.of(); + } + try { + String rawText = null; + try { + rawText = fileObjects.get(i).getCharContent(true).toString(); + } catch( IOException ioe) { + ILog.get().error(ioe.getMessage(), ioe); + return null; + } + CompilationUnit res = result.get(sourceUnits[i]); + AST ast = res.ast; + JavacConverter converter = new JavacConverter(ast, javacCompilationUnit, context, rawText, docEnabled, focalPoint); + converter.populateCompilationUnit(res, javacCompilationUnit); + // javadoc problems explicitly set as they're not sent to DiagnosticListener (maybe find a flag to do it?) + var javadocProblems = converter.javadocDiagnostics.stream() + .map(problemConverter::createJavacProblem) + .filter(Objects::nonNull) + .toArray(IProblem[]::new); + if (javadocProblems.length > 0) { + int initialSize = res.getProblems().length; + var newProblems = Arrays.copyOf(res.getProblems(), initialSize + javadocProblems.length); + System.arraycopy(javadocProblems, 0, newProblems, initialSize, javadocProblems.length); + res.setProblems(newProblems); + } + List javadocComments = new ArrayList<>(); + res.accept(new ASTVisitor(true) { + @Override + public void postVisit(ASTNode node) { // fix some positions + if( node.getParent() != null ) { + int myStart = node.getStartPosition(); + int myEnd = myStart + node.getLength(); + int parentStart = node.getParent().getStartPosition(); + int parentEnd = parentStart + node.getParent().getLength(); + int newParentStart = parentStart; + int newParentEnd = parentEnd; + if( parentStart != -1 && myStart >= 0 && myStart < parentStart) { + newParentStart = myStart; + } + if( parentEnd != -1 && myStart >= 0 && myEnd > parentEnd) { + newParentEnd = myEnd; + } + if( newParentStart != -1 && newParentEnd != -1 && + parentStart != newParentStart || parentEnd != newParentEnd) { + node.getParent().setSourceRange(newParentStart, newParentEnd - newParentStart); + } + } + } + @Override + public boolean visit(Javadoc javadoc) { + javadocComments.add(javadoc); + return true; + } + }); + addCommentsToUnit(javadocComments, res); + addCommentsToUnit(converter.notAttachedComments, res); + attachMissingComments(res, context, rawText, converter, compilerOptions); + if ((flags & ICompilationUnit.ENABLE_STATEMENTS_RECOVERY) == 0) { + // remove all possible RECOVERED node + res.accept(new ASTVisitor(false) { + private boolean reject(ASTNode node) { + return (node.getFlags() & ASTNode.RECOVERED) != 0 + || (node instanceof FieldDeclaration field && field.fragments().isEmpty()) + || (node instanceof VariableDeclarationStatement decl && decl.fragments().isEmpty()); + } + + @Override + public boolean preVisit2(ASTNode node) { + if (reject(node)) { + StructuralPropertyDescriptor prop = node.getLocationInParent(); + if ((prop instanceof SimplePropertyDescriptor simple && !simple.isMandatory()) + || (prop instanceof ChildPropertyDescriptor child && !child.isMandatory()) + || (prop instanceof ChildListPropertyDescriptor)) { + node.delete(); + } else if (node.getParent() != null) { + node.getParent().setFlags(node.getParent().getFlags() | ASTNode.RECOVERED); + } + return false; // branch will be cut, no need to inspect deeper + } + return true; + } + + @Override + public void postVisit(ASTNode node) { + // repeat on postVisit so trimming applies bottom-up + preVisit2(node); + } + }); + } + if (resolveBindings) { + JavacBindingResolver resolver = new JavacBindingResolver(javaProject, task, context, converter, workingCopyOwner, javacCompilationUnits); + resolver.isRecoveringBindings = (flags & ICompilationUnit.ENABLE_BINDINGS_RECOVERY) != 0; + ast.setBindingResolver(resolver); + } + // + ast.setOriginalModificationCount(ast.modificationCount()); // "un-dirty" AST so Rewrite can process it + ast.setDefaultNodeFlag(ast.getDefaultNodeFlag() & ~ASTNode.ORIGINAL); + } catch (Throwable thrown) { + if (cachedThrown == null) { + cachedThrown = thrown; + } + ILog.get().error("Internal failure while parsing or converting AST for unit " + new String(sourceUnits[i].getFileName())); + ILog.get().error(thrown.getMessage(), thrown); + } + } + if (!resolveBindings) { + destroy(context); + } + if (cachedThrown != null) { + throw new RuntimeException(cachedThrown); + } + } catch (IOException ex) { + ILog.get().error(ex.getMessage(), ex); + } + + return result; + } + + /// cleans up context after analysis (nothing left to process) + /// but remain it usable by bindings by keeping filemanager available. + public static void cleanup(Context context) { + MultiTaskListener.instance(context).clear(); + if (context.get(DiagnosticListener.class) instanceof ForwardDiagnosticsAsDOMProblems listener) { + listener.filesToUnits.clear(); // no need to keep handle on generated ASTs in the context + } + // based on com.sun.tools.javac.api.JavacTaskImpl.cleanup() + var javac = com.sun.tools.javac.main.JavaCompiler.instance(context); + if (javac != null) { + javac.close(); + } + } + /// destroys the context, it's not usable at all after + public void destroy(Context context) { + cleanup(context); + try { + context.get(JavaFileManager.class).close(); + } catch (IOException e) { + ILog.get().error(e.getMessage(), e); + } + } + + private void addProblemsToDOM(CompilationUnit dom, Collection problems) { + if (problems == null) { + return; + } + IProblem[] previous = dom.getProblems(); + IProblem[] newProblems = Arrays.copyOf(previous, previous.length + problems.size()); + int start = previous.length; + for (CategorizedProblem problem : problems) { + newProblems[start] = problem; + start++; + } + dom.setProblems(newProblems); + } + + private AST createAST(Map options, int level, Context context, int flags) { + AST ast = AST.newAST(level, JavaCore.ENABLED.equals(options.get(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES))); + ast.setFlag(flags); + ast.setDefaultNodeFlag(ASTNode.ORIGINAL); + String sourceModeSetting = options.get(JavaCore.COMPILER_SOURCE); + long sourceLevel = CompilerOptions.versionToJdkLevel(sourceModeSetting); + if (sourceLevel == 0) { + // unknown sourceModeSetting + sourceLevel = ClassFileConstants.getLatestJDKLevel(); + } + 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)); + return ast; + } + +// + /** + * 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 attachMissingComments(CompilationUnit unit, Context context, String rawText, JavacConverter converter, Map compilerOptions) { + ScannerFactory scannerFactory = ScannerFactory.instance(context); + List missingComments = new ArrayList<>(); + JavadocTokenizer commentTokenizer = new JavadocTokenizer(scannerFactory, rawText.toCharArray(), rawText.length()) { + @Override + protected com.sun.tools.javac.parser.Tokens.Comment processComment(int pos, int endPos, CommentStyle style) { + // workaround Java bug 9077218 + if (style == CommentStyle.JAVADOC_BLOCK && endPos - pos <= 4) { + style = CommentStyle.BLOCK; + } + var res = super.processComment(pos, endPos, style); + if (noCommentAt(unit, pos)) { // not already processed + var comment = converter.convert(res, pos, endPos); + missingComments.add(comment); + } + return res; + } + }; + 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); + org.eclipse.jdt.internal.compiler.parser.Scanner ecjScanner = new ASTConverter(compilerOptions, false, null).scanner; + ecjScanner.recordLineSeparator = true; + ecjScanner.skipComments = false; + try { + ecjScanner.setSource(rawText.toCharArray()); + do { + ecjScanner.getNextToken(); + } while (!ecjScanner.atEnd()); + } catch (InvalidInputException ex) { + // Lexical errors are highly probably while editing + // don't log and just ignore them. + } + + // 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 + addCommentsToUnit(missingComments, unit); + unit.initCommentMapper(ecjScanner); + } + + static void addCommentsToUnit(Collection comments, CompilationUnit res) { + List before = res.getCommentList() == null ? new ArrayList<>() : new ArrayList<>(res.getCommentList()); + comments.stream().filter(comment -> comment.getStartPosition() >= 0 && JavacCompilationUnitResolver.noCommentAt(res, comment.getStartPosition())) + .forEach(before::add); + before.sort(Comparator.comparingInt(Comment::getStartPosition)); + res.setCommentTable(before.toArray(Comment[]::new)); + } + + private static boolean noCommentAt(CompilationUnit unit, int pos) { + if (unit.getCommentList() == null) { + return true; + } + return ((List)unit.getCommentList()).stream() + .allMatch(other -> pos < other.getStartPosition() || pos >= other.getStartPosition() + other.getLength()); + } + + private static class BindingBuilder extends ASTVisitor { + public Map bindingMap = new HashMap<>(); + + public BindingBuilder(Map bindingMap) { + this.bindingMap = bindingMap; + } + + @Override + public boolean visit(TypeDeclaration node) { + IBinding binding = node.resolveBinding(); + if (binding != null) { + bindingMap.putIfAbsent(binding.getKey(), binding); + } + return true; + } + + @Override + public boolean visit(MethodDeclaration node) { + IBinding binding = node.resolveBinding(); + if (binding != null) { + bindingMap.putIfAbsent(binding.getKey(), binding); + } + return true; + } + + @Override + public boolean visit(EnumDeclaration node) { + IBinding binding = node.resolveBinding(); + if (binding != null) { + bindingMap.putIfAbsent(binding.getKey(), binding); + } + return true; + } + + @Override + public boolean visit(RecordDeclaration node) { + IBinding binding = node.resolveBinding(); + if (binding != null) { + bindingMap.putIfAbsent(binding.getKey(), binding); + } + return true; + } + + @Override + public boolean visit(SingleVariableDeclaration node) { + IBinding binding = node.resolveBinding(); + if (binding != null) { + bindingMap.putIfAbsent(binding.getKey(), binding); + } + return true; + } + + @Override + public boolean visit(VariableDeclarationFragment node) { + IBinding binding = node.resolveBinding(); + if (binding != null) { + bindingMap.putIfAbsent(binding.getKey(), binding); + } + return true; + } + + @Override + public boolean visit(AnnotationTypeDeclaration node) { + IBinding binding = node.resolveBinding(); + if (binding != null) { + bindingMap.putIfAbsent(binding.getKey(), binding); + } + return true; + } + } + + private static Function javacAdditionalBindingCreator(Map bindingMap, INameEnvironment environment, LookupEnvironment lu, BindingResolver[] bindingResolverPointer) { + + return key -> { + + { + // check parsed files + IBinding binding = bindingMap.get(key); + if (binding != null) { + return binding; + } + } + + // if the requested type is an array type, + // check the parsed files for element type and create the array variant + int arrayCount = Signature.getArrayCount(key); + if (arrayCount > 0) { + String elementKey = Signature.getElementType(key); + IBinding elementBinding = bindingMap.get(elementKey); + if (elementBinding instanceof ITypeBinding elementTypeBinding) { + return elementTypeBinding.createArrayType(arrayCount); + } + } + + // check name environment + CustomBindingKeyParser bkp = new CustomBindingKeyParser(key); + bkp.parse(true); + char[][] name = bkp.compoundName; + NameEnvironmentAnswer answer = environment.findType(name); + if (answer != null) { + IBinaryType binaryType = answer.getBinaryType(); + if (binaryType != null) { + BinaryTypeBinding binding = lu.cacheBinaryType(binaryType, null); + return new TypeBinding(bindingResolverPointer[0], binding); + } + } + + return null; + }; + } + + private boolean configureAPIfNecessary(JavacFileManager fileManager) { + Iterable apPaths = fileManager.getLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH); + if (apPaths != null) { + return true; + } + + Iterable apModulePaths = fileManager.getLocation(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH); + if (apModulePaths != null) { + return true; + } + + Iterable classPaths = fileManager.getLocation(StandardLocation.CLASS_PATH); + if (classPaths != null) { + for(File cp : classPaths) { + String fileName = cp.getName(); + if (fileName != null && fileName.startsWith("lombok") && fileName.endsWith(".jar")) { + try { + fileManager.setLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH, List.of(cp)); + return true; + } catch (IOException ex) { + ILog.get().error(ex.getMessage(), ex); + } + } + } + } + + Iterable modulePaths = fileManager.getLocation(StandardLocation.MODULE_PATH); + if (modulePaths != null) { + for(File mp : modulePaths) { + String fileName = mp.getName(); + if (fileName != null && fileName.startsWith("lombok") && fileName.endsWith(".jar")) { + try { + fileManager.setLocation(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, List.of(mp)); + return true; + } catch (IOException ex) { + ILog.get().error(ex.getMessage(), ex); + } + } + } + } + + return false; + } +} 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..860f269ec41 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacConverter.java @@ -0,0 +1,3746 @@ +/******************************************************************************* + * Copyright (c) 2023, 2025 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 static com.sun.tools.javac.code.Flags.VARARGS; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.PriorityQueue; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Predicate; + +import javax.lang.model.type.TypeKind; + +import org.eclipse.core.runtime.ILog; +import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword; +import org.eclipse.jdt.core.dom.ModuleModifier.ModuleModifierKeyword; +import org.eclipse.jdt.core.dom.PrefixExpression.Operator; +import org.eclipse.jdt.core.dom.PrimitiveType.Code; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.parser.RecoveryScanner; + +import com.sun.source.tree.CaseTree.CaseKind; +import com.sun.source.tree.ModuleTree.ModuleKind; +import com.sun.source.tree.Tree.Kind; +import com.sun.source.util.DocTreePath; +import com.sun.source.util.TreePath; +import com.sun.tools.javac.code.BoundKind; +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.parser.ParserFactory; +import com.sun.tools.javac.parser.Tokens.Comment; +import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle; +import com.sun.tools.javac.tree.DCTree.DCDocComment; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCAnnotatedType; +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.JCAssignOp; +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.JCCaseLabel; +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.JCConstantCaseLabel; +import com.sun.tools.javac.tree.JCTree.JCContinue; +import com.sun.tools.javac.tree.JCTree.JCDefaultCaseLabel; +import com.sun.tools.javac.tree.JCTree.JCDirective; +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.JCExports; +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.JCModuleDecl; +import com.sun.tools.javac.tree.JCTree.JCModuleImport; +import com.sun.tools.javac.tree.JCTree.JCNewArray; +import com.sun.tools.javac.tree.JCTree.JCNewClass; +import com.sun.tools.javac.tree.JCTree.JCOpens; +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.JCPatternCaseLabel; +import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; +import com.sun.tools.javac.tree.JCTree.JCProvides; +import com.sun.tools.javac.tree.JCTree.JCRecordPattern; +import com.sun.tools.javac.tree.JCTree.JCRequires; +import com.sun.tools.javac.tree.JCTree.JCReturn; +import com.sun.tools.javac.tree.JCTree.JCSkip; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCSwitch; +import com.sun.tools.javac.tree.JCTree.JCSwitchExpression; +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.JCTypeParameter; +import com.sun.tools.javac.tree.JCTree.JCTypeUnion; +import com.sun.tools.javac.tree.JCTree.JCUnary; +import com.sun.tools.javac.tree.JCTree.JCUses; +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.tree.TreeInfo; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.JCDiagnostic; +import com.sun.tools.javac.util.Log; +import com.sun.tools.javac.util.Names; +import com.sun.tools.javac.util.Position; +import com.sun.tools.javac.util.Position.LineMap; + +/** + * Deals with conversion of Javac domain into JDT DOM domain + * @implNote Cannot move to another package as it uses some package protected methods + */ +@SuppressWarnings("unchecked") +class JavacConverter { + + private static final String ERROR = ""; + private static final String FAKE_IDENTIFIER = new String(RecoveryScanner.FAKE_IDENTIFIER); + public final AST ast; + final JCCompilationUnit javacCompilationUnit; + private final Context context; + final Map domToJavac = new HashMap<>(); + final String rawText; + final Set javadocDiagnostics = new HashSet<>(); + private final List javadocConverters = new ArrayList<>(); + final List notAttachedComments = new ArrayList<>(); + private boolean buildJavadoc; + private int focalPoint; + + private JavacConverter(AST ast, JCCompilationUnit javacCompilationUnit, Context context, String rawText, boolean buildJavadoc) { + this.ast = ast; + this.javacCompilationUnit = javacCompilationUnit; + this.context = context; + this.rawText = rawText; + this.buildJavadoc = buildJavadoc; + this.focalPoint = -1; + } + public JavacConverter(AST ast, JCCompilationUnit javacCompilationUnit, + Context context, String rawText, boolean buildJavadoc, int focalPoint) { + this(ast, javacCompilationUnit, context, rawText, buildJavadoc); + this.focalPoint = focalPoint; + } + + CompilationUnit convertCompilationUnit() { + return convertCompilationUnit(this.javacCompilationUnit); + } + + CompilationUnit convertCompilationUnit(JCCompilationUnit javacCompilationUnit) { + CompilationUnit res = this.ast.newCompilationUnit(); + populateCompilationUnit(res, javacCompilationUnit); + return res; + } + + void populateCompilationUnit(CompilationUnit res, JCCompilationUnit javacCompilationUnit) { + commonSettings(res, javacCompilationUnit); + res.setSourceRange(0, this.rawText.length()); + res.setLineEndTable(toLineEndPosTable(javacCompilationUnit.getLineMap(), res.getLength())); + if (javacCompilationUnit.getPackage() != null) { + res.setPackage(convert(javacCompilationUnit.getPackage())); + } else if( javacCompilationUnit.defs != null && javacCompilationUnit.defs.size() > 0 && javacCompilationUnit.defs.get(0) instanceof JCErroneous jcer) { + PackageDeclaration possible = convertMalformedPackageDeclaration(jcer); + if( possible != null ) { + res.setPackage(possible); + } + } + if (javacCompilationUnit.getModule() != null) { + res.setModule(convert(javacCompilationUnit.getModuleDecl())); + } + javacCompilationUnit.getImports().stream().filter(imp -> imp instanceof JCImport).map(jc -> convert((JCImport)jc)).forEach(res.imports()::add); + if (this.ast.apiLevel >= AST.JLS23_INTERNAL) { + javacCompilationUnit.getImports().stream().filter(imp -> imp instanceof JCModuleImport).map(jc -> convert((JCModuleImport)jc)).forEach(res.imports()::add); + } + javacCompilationUnit.getTypeDecls().stream() + .map(n -> convertBodyDeclaration(n, res)) + .filter(Objects::nonNull) + .forEach(res.types()::add); + res.accept(new FixPositions()); + } + + private PackageDeclaration convertMalformedPackageDeclaration(JCErroneous jcer) { + if( jcer.errs != null && jcer.errs.size() > 0 && jcer.errs.get(0) instanceof JCModifiers) { + // Legitimate chance this is a misplaced modifier, private package, etc + int errEndPos = jcer.getEndPosition(this.javacCompilationUnit.endPositions); + String possiblePackageDecl = this.rawText.length() > (errEndPos + 7) ? this.rawText.substring(errEndPos, errEndPos + 7) : null; + if( "package".equals(possiblePackageDecl)) { + int newLine = this.rawText.indexOf("\n", errEndPos); + String decl = null; + if( newLine != -1 ) { + decl = this.rawText.substring(errEndPos, newLine).trim(); + } else { + decl = this.rawText.substring(errEndPos); + } + String pkgName = decl.substring(7).trim(); + if( pkgName.endsWith(";")) { + pkgName = pkgName.substring(0,pkgName.length()-1); + } + PackageDeclaration res = this.ast.newPackageDeclaration(); + res.setName(toName(pkgName, 0, this.ast)); + setJavadocForNode(jcer, res); + res.setSourceRange(errEndPos, Math.max(0, pkgName.length())); + res.setFlags(res.getFlags() | ASTNode.MALFORMED); + return res; + } + } + return null; + } + + 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); + Iterator it = javac.annotations.iterator(); + while(it.hasNext()) { + res.annotations().add(convert(it.next())); + } + String raw = this.rawText.substring(res.getStartPosition(), res.getStartPosition() + res.getLength()); + if( !raw.trim().endsWith(";")) { + res.setFlags(res.getFlags() | ASTNode.MALFORMED); + } + return res; + } + + private ModuleDeclaration convert(JCModuleDecl javac) { + ModuleDeclaration res = this.ast.newModuleDeclaration(); + res.setName(toName(javac.getName())); + this.domToJavac.put(res.getName(), javac); + boolean isOpen = javac.getModuleType() == ModuleKind.OPEN; + res.setOpen(isOpen); + if (javac.getDirectives() != null) { + List directives = javac.getDirectives(); + for (int i = 0; i < directives.size(); i++) { + JCDirective jcDirective = directives.get(i); + res.moduleStatements().add(convert(jcDirective)); + } + } + commonSettings(res, javac); + if( isOpen ) { + int start = res.getStartPosition(); + if( !this.rawText.substring(start).trim().startsWith("open")) { + // we are open but we don't start with open... so... gotta look backwards + String prefix = this.rawText.substring(0,start); + if( prefix.trim().endsWith("open")) { + // previous token is open + int ind = new StringBuffer().append(prefix).reverse().toString().indexOf("nepo"); + if( ind != -1 ) { + int gap = ind + 4; + res.setSourceRange(res.getStartPosition() - gap, res.getLength() + gap); + } + } + } + } + List l = convertModifierAnnotations(javac.mods, res); + res.annotations().addAll(l); + return res; + } + + private ModuleDirective convert(JCDirective javac) { + return switch (javac.getKind()) { + case EXPORTS -> convert((JCExports)javac); + case OPENS -> convert((JCOpens)javac); + case PROVIDES -> convert((JCProvides)javac); + case REQUIRES -> convert((JCRequires)javac); + case USES -> convert((JCUses)javac); + default -> throw new IllegalStateException(); + }; + } + + private ExportsDirective convert(JCExports javac) { + ExportsDirective res = this.ast.newExportsStatement(); + res.setName(toName(javac.getPackageName())); + commonSettings(res, javac); + List mods = javac.getModuleNames(); + if (mods != null) { + Iterator it = mods.iterator(); + while(it.hasNext()) { + JCExpression jcpe = it.next(); + Expression e = convertExpression(jcpe); + if( e != null ) + res.modules().add(e); + } + } + return res; + } + + private OpensDirective convert(JCOpens javac) { + OpensDirective res = this.ast.newOpensDirective(); + res.setName(toName(javac.getPackageName())); + commonSettings(res, javac); + List mods = javac.getModuleNames(); + if (mods != null) { + Iterator it = mods.iterator(); + while (it.hasNext()) { + JCExpression jcpe = it.next(); + Expression e = convertExpression(jcpe); + if (e != null) + res.modules().add(e); + } + } + return res; + } + + private ProvidesDirective convert(JCProvides javac) { + ProvidesDirective res = this.ast.newProvidesDirective(); + res.setName(toName(javac.getServiceName())); + for (var jcName : javac.implNames) { + res.implementations().add(toName(jcName)); + } + commonSettings(res, javac); + return res; + } + + private RequiresDirective convert(JCRequires javac) { + RequiresDirective res = this.ast.newRequiresDirective(); + res.setName(toName(javac.getModuleName())); + int javacStart = javac.getStartPosition(); + List modifiersToAdd = new ArrayList<>(); + if (javac.isTransitive()) { + ModuleModifier trans = this.ast.newModuleModifier(ModuleModifierKeyword.TRANSITIVE_KEYWORD); + int transStart = this.rawText.substring(javacStart).indexOf(ModuleModifierKeyword.TRANSITIVE_KEYWORD.toString()); + if( transStart != -1 ) { + int trueStart = javacStart + transStart; + trans.setSourceRange(trueStart, ModuleModifierKeyword.TRANSITIVE_KEYWORD.toString().length()); + } + modifiersToAdd.add(trans); + } + if (javac.isStatic()) { + ModuleModifier stat = this.ast.newModuleModifier(ModuleModifierKeyword.STATIC_KEYWORD); + int statStart = this.rawText.substring(javacStart).indexOf(ModuleModifierKeyword.STATIC_KEYWORD.toString()); + if( statStart != -1 ) { + int trueStart = javacStart + statStart; + stat.setSourceRange(trueStart, ModuleModifierKeyword.STATIC_KEYWORD.toString().length()); + } + modifiersToAdd.add(stat); + } + modifiersToAdd.sort((a, b) -> a.getStartPosition() - b.getStartPosition()); + modifiersToAdd.stream().forEach(res.modifiers()::add); + commonSettings(res, javac); + return res; + } + + private UsesDirective convert(JCUses javac) { + UsesDirective res = this.ast.newUsesDirective(); + res.setName(toName(javac.getServiceName())); + commonSettings(res, javac); + return res; + } + + private ImportDeclaration convert(JCImport javac) { + ImportDeclaration res = this.ast.newImportDeclaration(); + commonSettings(res, javac); + if (javac.isStatic()) { + if (this.ast.apiLevel != AST.JLS2_INTERNAL) { + res.setStatic(true); + } + } + var select = javac.getQualifiedIdentifier(); + if (select.getIdentifier().contentEquals("*")) { + res.setOnDemand(true); + res.setName(toName(select.getExpression())); + } else if (this.ast.apiLevel >= AST.JLS23_INTERNAL && select.selected.toString().equals("module") && select.name.toString().equals("")) { + // it's a broken module import + var moduleModifier = this.ast.newModifier(ModifierKeyword.MODULE_KEYWORD); + res.modifiers().add(moduleModifier); + Name name = new SimpleName(this.ast); + name.setSourceRange(res.getStartPosition() + res.getLength() + 1, 0); + res.setName(name); + res.setSourceRange(res.getStartPosition(), res.getLength() + 1); + } else { + res.setName(toName(select)); + } + if (javac.isStatic() || javac.isModule()) { + if (this.ast.apiLevel == AST.JLS2_INTERNAL) { + res.setFlags(res.getFlags() | ASTNode.MALFORMED); + } else if (this.ast.apiLevel < AST.JLS23_INTERNAL) { + if (!javac.isStatic()) { + res.setFlags(res.getFlags() | ASTNode.MALFORMED); + } + } else { + ModifierKeyword keyword = null; + if (javac.isStatic()) { + keyword = ModifierKeyword.STATIC_KEYWORD; + } + if (javac.isModule()) { + keyword = ModifierKeyword.MODULE_KEYWORD; + } + if (keyword != null) { + Modifier newModifier = this.ast.newModifier(keyword); + newModifier.setSourceRange(javac.getStartPosition(), keyword.toString().length()); + res.modifiers().add(newModifier); + } else { + res.setFlags(res.getFlags() | ASTNode.MALFORMED); + } + } + } + return res; + } + + private ImportDeclaration convert(JCModuleImport javac) { + ImportDeclaration res = this.ast.newImportDeclaration(); + commonSettings(res, javac); + var moduleModifier = this.ast.newModifier(ModifierKeyword.MODULE_KEYWORD); + res.modifiers().add(moduleModifier); + if (javac.isStatic()) { + if( this.ast.apiLevel != AST.JLS2_INTERNAL) { + res.setStatic(true); + } + } + var select = javac.getQualifiedIdentifier(); + res.setName(toName(select)); + return res; + } + + void commonSettings(ASTNode res, JCTree javac) { + if( javac != null ) { + int length = commonSettingsGetLength(res, javac); + commonSettings(res, javac, length, true); + } + } + + int commonSettingsGetLength(ASTNode res, JCTree javac) { + int length = -1; + if( javac != null ) { + int start = javac.getStartPosition(); + if (start >= 0) { + int endPos = javac.getEndPosition(this.javacCompilationUnit.endPositions); + if( endPos < 0 ) { + endPos = start + javac.toString().length(); + } + // workaround: some types appear to not keep the trailing semicolon in source range + if (res instanceof Name || res instanceof FieldAccess || res instanceof SuperFieldAccess ) { + while (endPos > start && this.rawText.length()>= endPos && this.rawText.charAt(endPos - 1) == ';') { + endPos--; + } + } + length = endPos - start; + if (start + Math.max(0, length) > this.rawText.length()) { + length = this.rawText.length() - start; + } + } + return Math.max(0, length); + } + return length; + } + + void commonSettings(ASTNode res, JCTree javac, int length, boolean removeWhitespace) { + if( javac != null && length >= 0) { + if (javac.getStartPosition() >= 0) { + res.setSourceRange(javac.getStartPosition(), Math.max(0, length)); + } + if( removeWhitespace ) { + removeSurroundingWhitespaceFromRange(res); + } + this.domToJavac.put(res, javac); + setJavadocForNode(javac, res); + } + } + + private void nameSettings(SimpleName name, JCMethodDecl javac, String selector, boolean isConstructor) { + if ((selector.equals(ERROR) || selector.equals(FAKE_IDENTIFIER))) + return; + var start = javac.getPreferredPosition(); + if (start > -1) { + // handle constructor length using type name instead of selector. + var length = isConstructor ? name.toString().length() : selector.length(); + name.setSourceRange(start, length); + } + } + + private void nameSettings(SimpleName name, JCVariableDecl javac, String varName) { + if (varName.equals(ERROR) || varName.equals(FAKE_IDENTIFIER)) + return; + var start = javac.getPreferredPosition(); + if (start > -1) { + name.setSourceRange(start, varName.length()); + } + } + + private Name toName(JCTree expression) { + return toName(expression, null); + } + + Name toName(JCTree expression, BiConsumer extraSettings ) { + if (expression instanceof JCIdent ident) { + Name res = convertName(ident.getName()); + commonSettings(res, expression); + if( extraSettings != null ) + extraSettings.accept(res, ident); + return res; + } + if (expression instanceof JCFieldAccess fieldAccess) { + JCExpression faExpression = fieldAccess.getExpression(); + SimpleName n = (SimpleName)convertName(fieldAccess.getIdentifier()); + if (n == null) { + n = this.ast.newSimpleName(FAKE_IDENTIFIER); + n.setFlags(ASTNode.RECOVERED); + } + commonSettings(n, fieldAccess); + + Name qualifier = toName(faExpression, extraSettings); + QualifiedName res = this.ast.newQualifiedName(qualifier, n); + commonSettings(res, fieldAccess); + if( extraSettings != null ) + extraSettings.accept(res, fieldAccess); + // don't calculate source range if the identifier is not valid. + if (!fieldAccess.getIdentifier().contentEquals(FAKE_IDENTIFIER) + && !fieldAccess.getIdentifier().contentEquals(ERROR)) { + // fix name position according to qualifier position + int nameIndex = this.rawText.indexOf(fieldAccess.getIdentifier().toString(), + qualifier.getStartPosition() + qualifier.getLength()); + if (nameIndex >= 0) { + n.setSourceRange(nameIndex, fieldAccess.getIdentifier().toString().length()); + } + } + return res; + } + if (expression instanceof JCAnnotatedType jcat) { + Name n = toName(jcat.underlyingType, extraSettings); + commonSettings(n, jcat.underlyingType); + return n; + } + if (expression instanceof JCTypeApply jcta) { + Name n = toName(jcta.clazz, extraSettings); + commonSettings(n, jcta.clazz); + return n; + } + throw new UnsupportedOperationException("toName for " + expression + " (" + expression == null ? "null" : expression.getClass().getName() + ")"); + } + + private AbstractTypeDeclaration convertClassDecl(JCClassDecl javacClassDecl, ASTNode parent) { + if( javacClassDecl.getKind() == Kind.ANNOTATION_TYPE && + (this.ast.apiLevel <= AST.JLS2_INTERNAL || this.ast.scanner.complianceLevel < ClassFileConstants.JDK1_5)) { + return null; + } + if( javacClassDecl.getKind() == Kind.ENUM && + (this.ast.apiLevel <= AST.JLS2_INTERNAL || this.ast.scanner.complianceLevel < ClassFileConstants.JDK1_5)) { + return null; + } + if( javacClassDecl.getKind() == Kind.RECORD && + (this.ast.apiLevel < AST.JLS16_INTERNAL || this.ast.scanner.complianceLevel < ClassFileConstants.JDK16)) { + return null; + } + + AbstractTypeDeclaration res = switch (javacClassDecl.getKind()) { + case ANNOTATION_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 -> javacClassDecl.getModifiers() != null && (javacClassDecl.getModifiers().flags & Flags.IMPLICIT_CLASS) != 0 ? + new ImplicitTypeDeclaration(this.ast) : + this.ast.newTypeDeclaration(); + default -> throw new IllegalStateException(); + }; + return convertClassDecl(javacClassDecl, parent, res); + } + + private AbstractTypeDeclaration convertClassDecl(JCClassDecl javacClassDecl, ASTNode parent, AbstractTypeDeclaration res) { + commonSettings(res, javacClassDecl); + SimpleName simpName = (SimpleName)convertName(javacClassDecl.getSimpleName()); + if(!(res instanceof ImplicitTypeDeclaration) && simpName != null) { + res.setName(simpName); + int searchNameFrom = javacClassDecl.getPreferredPosition(); + if (javacClassDecl.getModifiers() != null) { + searchNameFrom = Math.max(searchNameFrom, TreeInfo.getEndPos(javacClassDecl.getModifiers(), this.javacCompilationUnit.endPositions)); + } + int namePosition = this.rawText.indexOf(simpName.getIdentifier(), searchNameFrom); + if (namePosition >= 0) { + simpName.setSourceRange(namePosition, simpName.getIdentifier().length()); + } + } + if( this.ast.apiLevel != AST.JLS2_INTERNAL) { + res.modifiers().addAll(convert(javacClassDecl.mods, res)); + } else { + int jls2Flags = getJLS2ModifiersFlags(javacClassDecl.mods); + jls2Flags &= ~Flags.INTERFACE; // remove AccInterface flags, see ASTConverter + res.internalSetModifiers(jls2Flags); + } + if (res instanceof TypeDeclaration typeDeclaration) { + if (javacClassDecl.getExtendsClause() != null) { + if( this.ast.apiLevel != AST.JLS2_INTERNAL) { + typeDeclaration.setSuperclassType(convertToType(javacClassDecl.getExtendsClause())); + } else { + JCExpression e = javacClassDecl.getExtendsClause(); + Name m = toName(e); + if( m != null ) { + typeDeclaration.setSuperclass(m); + } + } + } + if (javacClassDecl.getImplementsClause() != null) { + if( this.ast.apiLevel != AST.JLS2_INTERNAL) { + javacClassDecl.getImplementsClause().stream() + .map(this::convertToType) + .filter(Objects::nonNull) + .forEach(typeDeclaration.superInterfaceTypes()::add); + } else { + Iterator it = javacClassDecl.getImplementsClause().iterator(); + while(it.hasNext()) { + JCExpression next = it.next(); + Name m = toName(next); + if( m != null ) { + typeDeclaration.superInterfaces().add(m); + } + } + } + } + + if( javacClassDecl.getTypeParameters() != null ) { + if( this.ast.apiLevel != AST.JLS2_INTERNAL) { + Iterator i = javacClassDecl.getTypeParameters().iterator(); + while(i.hasNext()) { + JCTypeParameter next = i.next(); + typeDeclaration.typeParameters().add(convert(next)); + } + } + } + + if (javacClassDecl.getPermitsClause() != null) { + if( this.ast.apiLevel >= AST.JLS17_INTERNAL) { + javacClassDecl.getPermitsClause().stream() + .map(this::convertToType) + .filter(Objects::nonNull) + .forEach(typeDeclaration.permittedTypes()::add); + if (!javacClassDecl.getPermitsClause().isEmpty()) { + int permitsOffset = this.rawText.substring(javacClassDecl.pos).indexOf("permits") + javacClassDecl.pos; + typeDeclaration.setRestrictedIdentifierStartPosition(permitsOffset); + } + } + } + if (javacClassDecl.getMembers() != null) { + List members = javacClassDecl.getMembers(); + ASTNode previous = null; + for( int i = 0; i < members.size(); i++ ) { + ASTNode decl = convertBodyDeclaration(members.get(i), res); + if( decl != null ) { + typeDeclaration.bodyDeclarations().add(decl); + if (previous != null) { + int istart = decl.getStartPosition(); + int siblingEnds = previous.getStartPosition() + previous.getLength(); + if(previous.getStartPosition() >= 0 && siblingEnds > istart && istart > previous.getStartPosition()) { + previous.setSourceRange(previous.getStartPosition(), istart - previous.getStartPosition()-1); + } + } + previous = decl; + } + } + } + } else if (res instanceof EnumDeclaration enumDecl) { + List enumStatements= enumDecl.enumConstants(); + if (javacClassDecl.getMembers() != null) { + for(JCTree member : javacClassDecl.getMembers()) { + EnumConstantDeclaration dec1 = convertEnumConstantDeclaration(member, parent, enumDecl); + if( dec1 != null ) { + enumStatements.add(dec1); + } else { + // body declaration + ASTNode bodyDecl = convertBodyDeclaration(member, res); + if( bodyDecl != null ) { + res.bodyDeclarations().add(bodyDecl); + } + } + } + } + } else if (res instanceof AnnotationTypeDeclaration annotDecl) { + //setModifiers(annotationTypeMemberDeclaration2, annotationTypeMemberDeclaration); + final SimpleName name = new SimpleName(this.ast); + name.internalSetIdentifier(new String(annotDecl.typeName.toString())); + res.setName(name); + if( javacClassDecl.defs != null ) { + for( Iterator i = javacClassDecl.defs.iterator(); i.hasNext(); ) { + ASTNode converted = convertBodyDeclaration(i.next(), res); + if( converted != null ) { + res.bodyDeclarations.add(converted); + } + } + } + +// org.eclipse.jdt.internal.compiler.ast.TypeReference typeReference = annotDecl.get +// if (typeReference != null) { +// Type returnType = convertType(typeReference); +// setTypeForMethodDeclaration(annotationTypeMemberDeclaration2, returnType, 0); +// } +// int declarationSourceStart = annotationTypeMemberDeclaration.declarationSourceStart; +// int declarationSourceEnd = annotationTypeMemberDeclaration.bodyEnd; +// annotationTypeMemberDeclaration2.setSourceRange(declarationSourceStart, declarationSourceEnd - declarationSourceStart + 1); +// // The javadoc comment is now got from list store in compilation unit declaration +// convert(annotationTypeMemberDeclaration.javadoc, annotationTypeMemberDeclaration2); +// org.eclipse.jdt.internal.compiler.ast.Expression memberValue = annotationTypeMemberDeclaration.defaultValue; +// if (memberValue != null) { +// annotationTypeMemberDeclaration2.setDefault(convert(memberValue)); +// } + + } else if (res instanceof RecordDeclaration recordDecl) { + int start = javacClassDecl.getPreferredPosition(); + if( start != -1 ) { + recordDecl.setRestrictedIdentifierStartPosition(start); + } + for (JCTree node : javacClassDecl.getMembers()) { + if (node instanceof JCVariableDecl vd && !vd.getModifiers().getFlags().contains(javax.lang.model.element.Modifier.STATIC)) { + SingleVariableDeclaration vdd = (SingleVariableDeclaration)convertVariableDeclaration(vd); + // Records cannot have modifiers + vdd.modifiers().clear(); + // Add only annotation modifiers + vdd.modifiers().addAll(convertModifierAnnotations(vd.getModifiers(), vdd)); + recordDecl.recordComponents().add(vdd); + } else { + ASTNode converted = convertBodyDeclaration(node, res); + if( converted != null ) { + res.bodyDeclarations.add(converted); + } + } + } + } else if (res instanceof ImplicitTypeDeclaration) { + javacClassDecl.getMembers().stream() + .map(member -> convertBodyDeclaration(member, res)) + .filter(Objects::nonNull) + .forEach(res.bodyDeclarations()::add); + } + return res; + } + + private TypeParameter convert(JCTypeParameter typeParameter) { + final TypeParameter ret = new TypeParameter(this.ast); + commonSettings(ret, typeParameter); + final SimpleName simpleName = new SimpleName(this.ast); + simpleName.internalSetIdentifier(typeParameter.getName().toString()); + int start = typeParameter.pos; + int end = typeParameter.pos + typeParameter.getName().length(); + simpleName.setSourceRange(start, end - start); + ret.setName(simpleName); + List bounds = typeParameter.getBounds(); + Iterator i = bounds.iterator(); + while(i.hasNext()) { + JCTree t = i.next(); + Type type = convertToType(t); + ret.typeBounds().add(type); + end = typeParameter.getEndPosition(this.javacCompilationUnit.endPositions); + } + if (typeParameter.getAnnotations() != null && this.ast.apiLevel() >= AST.JLS8_INTERNAL) { + typeParameter.getAnnotations().stream() + .map(this::convert) + .forEach(ret.modifiers()::add); + } +// org.eclipse.jdt.internal.compiler.ast.Annotation[] annotations = typeParameter.annotations; +// if (annotations != null) { +// if (annotations[0] != null) +// annotationsStart = annotations[0].sourceStart; +// annotateTypeParameter(typeParameter2, typeParameter.annotations); +// } +// final TypeReference superType = typeParameter.type; +// end = typeParameter.declarationSourceEnd; +// if (superType != null) { +// Type type = convertType(superType); +// typeParameter2.typeBounds().add(type); +// end = type.getStartPosition() + type.getLength() - 1; +// } +// TypeReference[] bounds = typeParameter.bounds; +// if (bounds != null) { +// Type type = null; +// for (int index = 0, length = bounds.length; index < length; index++) { +// type = convertType(bounds[index]); +// typeParameter2.typeBounds().add(type); +// end = type.getStartPosition() + type.getLength() - 1; +// } +// } +// start = annotationsStart < typeParameter.declarationSourceStart ? annotationsStart : typeParameter.declarationSourceStart; +// end = retrieveClosingAngleBracketPosition(end); +// if (this.resolveBindings) { +// recordName(simpleName, typeParameter); +// recordNodes(typeParameter2, typeParameter); +// typeParameter2.resolveBinding(); +// } + ret.setSourceRange(start, end - start); + return ret; + } + + private ASTNode convertBodyDeclaration(JCTree tree, ASTNode parent) { + if( parent instanceof AnnotationTypeDeclaration && tree instanceof JCMethodDecl methodDecl) { + return convertMethodInAnnotationTypeDecl(methodDecl, parent); + } + if (tree instanceof JCMethodDecl methodDecl) { + return convertMethodDecl(methodDecl, parent); + } + if (tree instanceof JCClassDecl jcClassDecl) { + return convertClassDecl(jcClassDecl, parent); + } + if (tree instanceof JCVariableDecl jcVariableDecl) { + return convertFieldDeclaration(jcVariableDecl, parent); + } + if (tree instanceof JCBlock block) { + Initializer res = this.ast.newInitializer(); + commonSettings(res, tree); + if( this.ast.apiLevel != AST.JLS2_INTERNAL) { + // Now we have the tough task of going from a flags number to actual modifiers with source ranges + res.modifiers().addAll(convertModifiersFromFlags(block.getStartPosition(), block.endpos, block.flags)); + } else { + res.internalSetModifiers(getJLS2ModifiersFlags(block.flags)); + } + boolean fillBlock = shouldFillBlock(block, this.focalPoint); + if( fillBlock ) { + res.setBody(convertBlock(block)); + } else { + Block b = this.ast.newBlock(); + commonSettings(res, block); + res.setBody(b); + } + return res; + } + if (tree instanceof JCErroneous || tree instanceof JCSkip) { + return null; + } + ILog.get().error("Unsupported " + tree + " of type" + tree.getClass()); + Block substitute = this.ast.newBlock(); + commonSettings(substitute, tree); + return substitute; + } + + private ASTNode convertMethodInAnnotationTypeDecl(JCMethodDecl javac, ASTNode parent) { + AnnotationTypeMemberDeclaration res = new AnnotationTypeMemberDeclaration(this.ast); + commonSettings(res, javac); + res.modifiers().addAll(convert(javac.getModifiers(), res)); + res.setType(convertToType(javac.getReturnType())); + if( javac.defaultValue != null) { + res.setDefault(convertExpression(javac.defaultValue)); + } + if (convertName(javac.getName()) instanceof SimpleName simpleName) { + res.setName(simpleName); + int start = javac.getPreferredPosition(); + if (start > -1) { + simpleName.setSourceRange(start, javac.getName().toString().length()); + } + } + return res; + } + + private String getNodeName(ASTNode node) { + if( node instanceof AbstractTypeDeclaration atd) { + return atd.getName().toString(); + } + if( node instanceof EnumDeclaration ed) { + return ed.getName().toString(); + } + return null; + } + + private String getMethodDeclName(JCMethodDecl javac, ASTNode parent, boolean records) { + String name = javac.getName().toString(); + boolean javacIsConstructor = Objects.equals(javac.getName(), Names.instance(this.context).init); + if( javacIsConstructor) { + // sometimes javac mistakes a method with no return type as a constructor + String parentName = getNodeName(parent); + String tmpString1 = this.rawText.substring(javac.pos); + int openParen = tmpString1.indexOf("("); + int openBrack = tmpString1.indexOf("{"); + int endPos = -1; + if( openParen != -1 ) { + endPos = openParen; + } + if( records && openBrack != -1 ) { + endPos = endPos == -1 ? openBrack : Math.min(openBrack, endPos); + } + if( endPos != -1 ) { + String methodName = tmpString1.substring(0, endPos).trim(); + if (!methodName.isEmpty() && + Character.isJavaIdentifierStart(methodName.charAt(0)) && + methodName.substring(1).chars().allMatch(Character::isJavaIdentifierPart) && + !methodName.equals(parentName)) { + return methodName; + } + } + return parentName; + } + return name; + } + + private MethodDeclaration convertMethodDecl(JCMethodDecl javac, ASTNode parent) { + if (TreeInfo.getEndPos(javac, this.javacCompilationUnit.endPositions) <= javac.getStartPosition()) { + // not really existing, analysis sugar; let's skip + return null; + } + MethodDeclaration res = this.ast.newMethodDeclaration(); + commonSettings(res, javac); + if( this.ast.apiLevel != AST.JLS2_INTERNAL) { + res.modifiers().addAll(convert(javac.getModifiers(), res)); + } else { + res.internalSetModifiers(getJLS2ModifiersFlags(javac.mods)); + } + String javacName = javac.getName().toString(); + String methodDeclName = getMethodDeclName(javac, parent, parent instanceof RecordDeclaration); + boolean methodDeclNameMatchesInit = Objects.equals(methodDeclName, Names.instance(this.context).init.toString()); + boolean javacNameMatchesInit = javacName.equals(""); + boolean javacNameMatchesError = javacName.equals(ERROR); + boolean javacNameMatchesInitAndMethodNameMatchesTypeName = javacNameMatchesInit && methodDeclName.equals(getNodeName(parent)); + boolean isConstructor = methodDeclNameMatchesInit || javacNameMatchesInitAndMethodNameMatchesTypeName; + res.setConstructor(isConstructor); + if (isConstructor && javac.getParameters().isEmpty() + && javac.getBody() != null && javac.getBody().endpos == Position.NOPOS) { // probably generated + return null; + } + boolean isCompactConstructor = false; + if(isConstructor && parent instanceof RecordDeclaration) { + String postName = this.rawText.substring(javac.pos + methodDeclName.length()).trim(); + String firstChar = postName != null && postName.length() > 0 ? postName.substring(0,1) : null; + isCompactConstructor = ("{".equals(firstChar)); + if( this.ast.apiLevel >= AST.JLS16_INTERNAL) { + res.setCompactConstructor(isCompactConstructor); + } + } + boolean malformed = false; + if(isConstructor && !javacNameMatchesInitAndMethodNameMatchesTypeName) { + malformed = true; + } + if( javacNameMatchesError || (javacNameMatchesInit && !isConstructor )) { + malformed = true; + } + + JCTree retTypeTree = javac.getReturnType(); + Type retType = null; + if( !javacNameMatchesError) { + var name = this.ast.newSimpleName(methodDeclName); + nameSettings(name, javac, methodDeclName, isConstructor); + res.setName(name); + } else { + // javac name is an error, so let's treat the return type as the name + if (retTypeTree instanceof JCIdent jcid) { + var name = this.ast.newSimpleName(jcid.getName().toString()); + nameSettings(name, javac, javacName, isConstructor); + res.setName(name); + retTypeTree = null; + if (jcid.toString().equals(getNodeName(parent))) { + res.setConstructor(true); + isConstructor = true; + } + } + } + + if( retTypeTree == null ) { + if( isConstructor && this.ast.apiLevel == AST.JLS2_INTERNAL ) { + retType = this.ast.newPrimitiveType(convert(TypeKind.VOID)); + // // TODO need to find the right range + retType.setSourceRange(javac.mods.pos + getJLS2ModifiersFlagsAsStringLength(javac.mods.flags), 0); + } + } else { + retType = convertToType(retTypeTree); + } + var dims = convertDimensionsAfterPosition(retTypeTree, javac.pos); + if (!dims.isEmpty() && retTypeTree.pos > javac.pos ) { + // The array dimensions are part of the variable name + if( this.ast.apiLevel < AST.JLS8_INTERNAL) { + res.setExtraDimensions(dims.size()); + } else { + res.extraDimensions().addAll(dims); + } + retType = convertToType(unwrapDimensions(retTypeTree, dims.size())); + } + + if( retType != null || isConstructor) { + if( this.ast.apiLevel != AST.JLS2_INTERNAL) { + res.setReturnType2(retType); + } else { + res.internalSetReturnType(retType); + } + } else { + if( this.ast.apiLevel != AST.JLS2_INTERNAL) { + res.setReturnType2(null); + } + } + + if( !isCompactConstructor) { + // Compact constructor does not show the parameters even though javac finds them + javac.getParameters().stream().map(this::convertVariableDeclaration).forEach(res.parameters()::add); + } + if (javac.getReceiverParameter() != null) { + Type receiverType = convertToType(javac.getReceiverParameter().getType()); + if (receiverType instanceof AnnotatableType annotable) { + javac.getReceiverParameter().getModifiers().getAnnotations().stream() // + .map(this::convert) + .forEach(annotable.annotations()::add); + } + if (receiverType != null) { + res.setReceiverType(receiverType); + } + if (javac.getReceiverParameter().getNameExpression() instanceof JCFieldAccess qualifiedName) { + res.setReceiverQualifier((SimpleName)toName(qualifiedName.getExpression())); + } + } + + if( javac.getTypeParameters() != null ) { + Iterator i = javac.getTypeParameters().iterator(); + while(i.hasNext()) { + JCTypeParameter next = i.next(); + if( this.ast.apiLevel != AST.JLS2_INTERNAL) { + res.typeParameters().add(convert(next)); + } else { + // TODO + } + } + } + + if (javac.getBody() != null + && javac.getBody().endpos > javac.getBody().getStartPosition()) { // otherwise, it's probably generated by lombok + boolean fillBlock = shouldFillBlock(javac, this.focalPoint); + if( fillBlock ) { + Block b = convertBlock(javac.getBody()); + if (b != null) { + AbstractTypeDeclaration td = findSurroundingTypeDeclaration(parent); + boolean isInterface = td instanceof TypeDeclaration td1 && td1.isInterface(); + long modFlags = javac.getModifiers() == null ? 0 : javac.getModifiers().flags; + boolean isAbstractOrNative = (modFlags & (Flags.ABSTRACT | Flags.NATIVE)) != 0; + boolean isJlsBelow8 = this.ast.apiLevel < AST.JLS8_INTERNAL; + boolean isJlsAbove8 = this.ast.apiLevel > AST.JLS8_INTERNAL; + long flagsToCheckForAboveJLS8 = Flags.STATIC | Flags.DEFAULT | (isJlsAbove8 ? Flags.PRIVATE : 0); + boolean notAllowed = (isAbstractOrNative || (isInterface && (isJlsBelow8 || (modFlags & flagsToCheckForAboveJLS8) == 0))); + if (notAllowed) { + res.setFlags(res.getFlags() | ASTNode.MALFORMED); + } + res.setBody(b); + if( (b.getFlags() & ASTNode.MALFORMED) > 0 ) { + malformed = true; + } + } + } else { + Block b = this.ast.newBlock(); + commonSettings(res, javac); + res.setBody(b); + } + } + + for (JCExpression thrown : javac.getThrows()) { + if (this.ast.apiLevel < AST.JLS8_INTERNAL) { + res.thrownExceptions().add(toName(thrown)); + } else { + Type type = convertToType(thrown); + if (type != null) { + res.thrownExceptionTypes().add(type); + } + } + } + if( malformed ) { + res.setFlags(res.getFlags() | ASTNode.MALFORMED); + } + return res; + } + + private boolean shouldFillBlock(JCTree tree, int focalPoint2) { + int start = tree.getStartPosition(); + int endPos = tree.getEndPosition(this.javacCompilationUnit.endPositions); + if( focalPoint == -1 || (focalPoint >= start && focalPoint <= endPos)) { + return true; + } + return false; + } + private AbstractTypeDeclaration findSurroundingTypeDeclaration(ASTNode parent) { + if( parent == null ) + return null; + if( parent instanceof AbstractTypeDeclaration t) { + return t; + } + return findSurroundingTypeDeclaration(parent.getParent()); + } + + private VariableDeclaration convertVariableDeclarationForLambda(JCVariableDecl javac) { + if(javac.getType() == null && javac.getStartPosition() == javac.getPreferredPosition() /* check no "var" */) { + return createVariableDeclarationFragment(javac); + } else if (javac.getType() != null && javac.getType().getPreferredPosition() == Position.NOPOS) { // "virtual" node added for analysis, not part of AST + return createVariableDeclarationFragment(javac); + } else { + return convertVariableDeclaration(javac); + } + } + private VariableDeclaration convertVariableDeclaration(JCVariableDecl javac) { + // if (singleDecl) { + SingleVariableDeclaration res = this.ast.newSingleVariableDeclaration(); + commonSettings(res, javac); + if (convertName(javac.getName()) instanceof SimpleName simpleName) { + nameSettings(simpleName, javac, simpleName.toString()); + res.setName(simpleName); + } + if( this.ast.apiLevel != AST.JLS2_INTERNAL) { + res.modifiers().addAll(convert(javac.getModifiers(), res)); + } else { + res.internalSetModifiers(getJLS2ModifiersFlags(javac.mods)); + } + + JCTree type = javac.getType(); + if (type instanceof JCAnnotatedType annotatedType) { + annotatedType.getAnnotations().stream() + .map(this::convert) + .forEach(res.varargsAnnotations()::add); + type = annotatedType.getUnderlyingType(); + } + + if ( (javac.mods.flags & VARARGS) != 0) { + // We have varity + if(type instanceof JCArrayTypeTree arr) { + type = unwrapDimensions(arr, 1); + } + if( this.ast.apiLevel > AST.JLS2_INTERNAL) { + res.setVarargs(true); + } + } + + List dims = convertDimensionsAfterPosition(javac.getType(), javac.getPreferredPosition()); // +1 to exclude part of the type declared before name + if(!dims.isEmpty() ) { + // Some of the array dimensions are part of the variable name + if( this.ast.apiLevel < AST.JLS8_INTERNAL) { + res.setExtraDimensions(dims.size()); // the type is 1-dim array + } else { + res.extraDimensions().addAll(dims); + } + type = unwrapDimensions(type, dims.size()); + } + + // the array dimensions are part of the type + if (type != null) { + if( !(type instanceof JCErroneous)) { + Type converted = convertToType(type); + if (converted != null) { + res.setType(converted); + } + } + } else if (javac.getStartPosition() != javac.getPreferredPosition() + && this.rawText.substring(javac.getStartPosition(), javac.getPreferredPosition()).matches("var(\\s)+")) { + SimpleName varName = this.ast.newSimpleName("var"); + varName.setSourceRange(javac.getStartPosition(), varName.getIdentifier().length()); + Type varType = this.ast.newSimpleType(varName); + varType.setSourceRange(varName.getStartPosition(), varName.getLength()); + res.setType(varType); + } + if (javac.getInitializer() != null) { + res.setInitializer(convertExpression(javac.getInitializer())); + } + return res; + } + + private int getJLS2ModifiersFlags(JCModifiers mods) { + return getJLS2ModifiersFlags(mods.flags); + } + + private VariableDeclarationFragment createVariableDeclarationFragment(JCVariableDecl javac) { + VariableDeclarationFragment fragment = this.ast.newVariableDeclarationFragment(); + commonSettings(fragment, javac); + int fragmentEnd = javac.getEndPosition(this.javacCompilationUnit.endPositions); + int fragmentStart = javac.pos; + int fragmentLength = fragmentEnd - fragmentStart; // ???? - 1; + fragment.setSourceRange(fragmentStart, Math.max(0, fragmentLength)); + removeSurroundingWhitespaceFromRange(fragment); + removeTrailingCharFromRange(fragment, new char[] {';', ','}); + removeSurroundingWhitespaceFromRange(fragment); + if (convertName(javac.getName()) instanceof SimpleName simpleName) { + fragment.setName(simpleName); + } + var dims = convertDimensionsAfterPosition(javac.getType(), fragmentStart); + if( this.ast.apiLevel < AST.JLS8_INTERNAL) { + fragment.setExtraDimensions(dims.size()); + } else { + fragment.extraDimensions().addAll(dims); + } + if (javac.getInitializer() != null) { + Expression initializer = convertExpression(javac.getInitializer()); + if( initializer != null ) { + fragment.setInitializer(initializer); + // we may receive range for `int i = 0;` (with semicolon and newline). If we + // have an initializer, use it's endPos instead for the fragment + int length = initializer.getStartPosition() + initializer.getLength() - fragment.getStartPosition(); + if (length >= 0) { + fragment.setSourceRange(fragment.getStartPosition(), length); + } + } + } + return fragment; + } + + private FieldDeclaration convertFieldDeclaration(JCVariableDecl javac, ASTNode parent) { + VariableDeclarationFragment fragment = createVariableDeclarationFragment(javac); + List sameStartPosition = new ArrayList<>(); + if( parent instanceof AbstractTypeDeclaration decl) { + decl.bodyDeclarations().stream().filter(x -> x instanceof FieldDeclaration) + .filter(x -> ((FieldDeclaration)x).getType().getStartPosition() == javac.vartype.getStartPosition()) + .forEach(x -> sameStartPosition.add((ASTNode)x)); + } + if( parent instanceof AnonymousClassDeclaration decl) { + decl.bodyDeclarations().stream().filter(x -> x instanceof FieldDeclaration) + .filter(x -> ((FieldDeclaration)x).getType().getStartPosition() == javac.vartype.getStartPosition()) + .forEach(x -> sameStartPosition.add((ASTNode)x)); + } + if( sameStartPosition.size() >= 1 ) { + FieldDeclaration fd = (FieldDeclaration)sameStartPosition.get(0); + if( fd != null ) { + fd.fragments().add(fragment); + int newParentEnd = fragment.getStartPosition() + fragment.getLength(); + fd.setSourceRange(fd.getStartPosition(), newParentEnd - fd.getStartPosition() + 1); + } + return null; + } else { + FieldDeclaration res = this.ast.newFieldDeclaration(fragment); + commonSettings(res, javac); + if( this.ast.apiLevel != AST.JLS2_INTERNAL) { + res.modifiers().addAll(convert(javac.getModifiers(), res)); + } else { + res.internalSetModifiers(getJLS2ModifiersFlags(javac.mods)); + } + + Type resType = null; + int count = fragment.getExtraDimensions(); + if( count > 0 ) { + // must do simple type here + JCTree t = javac.getType(); + if( t instanceof JCArrayTypeTree jcatt) { + // unwrap the jcatt count times? + JCTree working = jcatt; + for( int i = 0; i < count; i++ ) { + if( working instanceof JCArrayTypeTree work2) { + working = work2.getType(); + } + } + resType = convertToType(working); + } else { + resType = convertToType(javac.getType()); + } + } else { + resType = convertToType(javac.getType()); + } + if (resType != null) { + res.setType(resType); + } + if( javac.getType() instanceof JCErroneous && resType instanceof SimpleType st && st.getName() instanceof SimpleName sn && sn.toString().equals(FAKE_IDENTIFIER)) { + if( fragment.getName() instanceof SimpleName fragName && fragName.toString().equals(FAKE_IDENTIFIER)) { + return null; + } + } + + return res; + } + } + + + private void setJavadocForNode(JCTree javac, ASTNode node) { + Comment c = this.javacCompilationUnit.docComments.getComment(javac); + if(c != null && (c.getStyle() == Comment.CommentStyle.JAVADOC_BLOCK || c.getStyle() == CommentStyle.JAVADOC_LINE)) { + org.eclipse.jdt.core.dom.Comment comment = convert(c, javac); + if( !(comment instanceof Javadoc)) { + return; + } + Javadoc javadoc = (Javadoc)comment; + if (node instanceof BodyDeclaration bodyDeclaration) { + bodyDeclaration.setJavadoc(javadoc); + bodyDeclaration.setSourceRange(javadoc.getStartPosition(), bodyDeclaration.getStartPosition() + bodyDeclaration.getLength() - javadoc.getStartPosition()); + } else if (node instanceof ModuleDeclaration moduleDeclaration) { + moduleDeclaration.setJavadoc(javadoc); + moduleDeclaration.setSourceRange(javadoc.getStartPosition(), moduleDeclaration.getStartPosition() + moduleDeclaration.getLength() - javadoc.getStartPosition()); + } else if (node instanceof PackageDeclaration packageDeclaration) { + if( this.ast.apiLevel != AST.JLS2_INTERNAL) { + packageDeclaration.setJavadoc(javadoc); + } else { + this.notAttachedComments.add(javadoc); + } + packageDeclaration.setSourceRange(javadoc.getStartPosition(), packageDeclaration.getStartPosition() + packageDeclaration.getLength() - javadoc.getStartPosition()); + } else { + this.notAttachedComments.add(javadoc); + } + } + } + + private Expression convertExpressionImpl(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 (Objects.equals(Names.instance(this.context)._this, fieldAccess.getIdentifier())) { + ThisExpression res = this.ast.newThisExpression(); + commonSettings(res, javac); + res.setQualifier(toName(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)convertName(fieldAccess.getIdentifier())); + return res; + } + if (fieldAccess.getExpression() instanceof JCIdent parentFieldAccess && Objects.equals(Names.instance(this.context)._super, parentFieldAccess.getName())) { + SuperFieldAccess res = this.ast.newSuperFieldAccess(); + commonSettings(res, javac); + res.setName((SimpleName)convertName(fieldAccess.getIdentifier())); + return res; + } + if (fieldAccess.getExpression() instanceof JCIdent parentFieldAccess && Objects.equals(Names.instance(this.context)._this, parentFieldAccess.getName())) { + FieldAccess res = this.ast.newFieldAccess(); + commonSettings(res, javac); + res.setExpression(convertExpression(parentFieldAccess)); + if (convertName(fieldAccess.getIdentifier()) instanceof SimpleName name) { + res.setName(name); + } + return res; + } + if (fieldAccess.getExpression() instanceof JCIdent qualifier) { + Name qualifierName = convertName(qualifier.getName()); + commonSettings(qualifierName, qualifier); + SimpleName qualifiedName = (SimpleName)convertName(fieldAccess.getIdentifier()); + if (qualifiedName == null) { + // when there are syntax errors where the statement is not completed. + qualifiedName = this.ast.newSimpleName(FAKE_IDENTIFIER); + qualifiedName.setFlags(ASTNode.RECOVERED); + } + QualifiedName res = this.ast.newQualifiedName(qualifierName, qualifiedName); + commonSettings(res, javac); + return res; + } + useQualifiedName: if (fieldAccess.getExpression() instanceof JCFieldAccess parentFieldAccess) { + JCFieldAccess cursor = parentFieldAccess; + if (Objects.equals(Names.instance(this.context)._class, cursor.getIdentifier()) + || Objects.equals(Names.instance(this.context)._this, cursor.getIdentifier()) + || Objects.equals(Names.instance(this.context)._super, cursor.getIdentifier())) { + break useQualifiedName; + } + while (cursor.getExpression() instanceof JCFieldAccess newParent) { + cursor = newParent; + if (Objects.equals(Names.instance(this.context)._class, cursor.getIdentifier()) + || Objects.equals(Names.instance(this.context)._this, cursor.getIdentifier()) + || Objects.equals(Names.instance(this.context)._super, cursor.getIdentifier())) { + break useQualifiedName; + } + } + + if (cursor.getExpression() instanceof JCIdent oldestIdentifier + && !Objects.equals(Names.instance(this.context)._class, oldestIdentifier.getName()) + && !Objects.equals(Names.instance(this.context)._this, oldestIdentifier.getName()) + && !Objects.equals(Names.instance(this.context)._super, oldestIdentifier.getName())) { + // all segments are simple names + return convertQualifiedName(fieldAccess); + } + } + FieldAccess res = this.ast.newFieldAccess(); + commonSettings(res, javac); + res.setExpression(convertExpression(fieldAccess.getExpression())); + if (convertName(fieldAccess.getIdentifier()) instanceof SimpleName name) { + res.setName(name); + } + return res; + } + if (javac instanceof JCMethodInvocation methodInvocation) { + JCExpression nameExpr = methodInvocation.getMethodSelect(); + if (nameExpr instanceof JCFieldAccess access) { + // Handle super method calls first + boolean superCall1 = access.getExpression() instanceof JCFieldAccess && Objects.equals(Names.instance(this.context)._super, ((JCFieldAccess)access.getExpression()).getIdentifier()); + boolean superCall2 = Objects.equals(Names.instance(this.context)._super.toString(), access.getExpression().toString()); + if (superCall1 || superCall2) { + JCFieldAccess fa = superCall1 ? ((JCFieldAccess)access.getExpression()) : access; + SuperMethodInvocation res2 = this.ast.newSuperMethodInvocation(); + commonSettings(res2, javac); + methodInvocation.getArguments().stream().map(this::convertExpression).forEach(res2.arguments()::add); + if( this.ast.apiLevel != AST.JLS2_INTERNAL) { + methodInvocation.getTypeArguments().stream() + .map(this::convertToType) + .filter(Objects::nonNull) + .forEach(res2.typeArguments()::add); + } + if( superCall1 ) { + res2.setQualifier(toName(fa.getExpression())); + } + res2.setName((SimpleName)convertName(access.getIdentifier())); + return res2; + } + } + + MethodInvocation res = this.ast.newMethodInvocation(); + commonSettings(res, methodInvocation); + if (nameExpr instanceof JCIdent ident) { + if (Objects.equals(ident.getName(), Names.instance(this.context)._super)) { + return convertSuperMethodInvocation(methodInvocation); + } + SimpleName name = (SimpleName)convertName(ident.getName()); + commonSettings(name, ident); + res.setName(name); + } else if (nameExpr instanceof JCFieldAccess access) { + boolean superCall1 = access.getExpression() instanceof JCFieldAccess && Objects.equals(Names.instance(this.context)._super, ((JCFieldAccess)access.getExpression()).getIdentifier()); + boolean superCall2 = Objects.equals(Names.instance(this.context)._super.toString(), access.getExpression().toString()); + if (superCall1 || superCall2) { + JCFieldAccess fa = superCall1 ? ((JCFieldAccess)access.getExpression()) : access; + SuperMethodInvocation res2 = this.ast.newSuperMethodInvocation(); + commonSettings(res2, javac); + methodInvocation.getArguments().stream().map(this::convertExpression).forEach(res.arguments()::add); + if( this.ast.apiLevel != AST.JLS2_INTERNAL) { + methodInvocation.getTypeArguments().stream() + .map(this::convertToType) + .filter(Objects::nonNull) + .forEach(res.typeArguments()::add); + } + if( superCall1 ) { + res2.setQualifier(toName(fa.getExpression())); + } + res2.setName((SimpleName)convertName(access.getIdentifier())); + return res2; + } + if (convertName(access.getIdentifier()) instanceof SimpleName simpleName) { + res.setName(simpleName); + String asString = access.getIdentifier().toString(); + commonSettings(simpleName, access); + int foundOffset = this.rawText.indexOf(asString, access.getPreferredPosition()); + if (foundOffset > 0) { + simpleName.setSourceRange(foundOffset, asString.length()); + } + } + res.setExpression(convertExpression(access.getExpression())); + } + if (methodInvocation.getArguments() != null) { + methodInvocation.getArguments().stream() + .map(this::convertExpression) + .forEach(res.arguments()::add); + } + if (methodInvocation.getTypeArguments() != null) { + if( this.ast.apiLevel != AST.JLS2_INTERNAL) { + methodInvocation.getTypeArguments().stream() + .map(this::convertToType) + .filter(Objects::nonNull) + .forEach(res.typeArguments()::add); + } + } + return res; + } + if (javac instanceof JCNewClass newClass) { + ClassInstanceCreation res = this.ast.newClassInstanceCreation(); + commonSettings(res, javac); + if( this.ast.apiLevel != AST.JLS2_INTERNAL) { + res.setType(convertToType(newClass.getIdentifier())); + } else { + Name n = toName(newClass.getIdentifier()); + if( n != null ) + res.setName(n); + } + if (newClass.getClassBody() != null && newClass.getClassBody() instanceof JCClassDecl javacAnon) { + AnonymousClassDeclaration anon = createAnonymousClassDeclaration(javacAnon, res); + res.setAnonymousClassDeclaration(anon); + } + if (newClass.getArguments() != null) { + newClass.getArguments().stream() + .map(this::convertExpression) + .forEach(res.arguments()::add); + } + if (newClass.encl != null) { + res.setExpression(convertExpression(newClass.encl)); + } + if( newClass.getTypeArguments() != null && this.ast.apiLevel != AST.JLS2_INTERNAL) { + Iterator it = newClass.getTypeArguments().iterator(); + while(it.hasNext()) { + Type e = convertToType(it.next()); + if( e != null ) { + res.typeArguments().add(e); + } + } + } + return res; + } + if (javac instanceof JCBinary binary) { + return handleInfixExpression(binary, javac); + + } + 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 JCAssignOp assignOp) { + Assignment res = this.ast.newAssignment(); + commonSettings(res, javac); + res.setLeftHandSide(convertExpression(assignOp.getVariable())); + res.setRightHandSide(convertExpression(assignOp.getExpression())); + res.setOperator(switch (assignOp.getTag()) { + case PLUS_ASG -> Assignment.Operator.PLUS_ASSIGN; + case BITOR_ASG -> Assignment.Operator.BIT_OR_ASSIGN; + case BITXOR_ASG-> Assignment.Operator.BIT_XOR_ASSIGN; + case BITAND_ASG-> Assignment.Operator.BIT_AND_ASSIGN; + case SL_ASG-> Assignment.Operator.LEFT_SHIFT_ASSIGN; + case SR_ASG-> Assignment.Operator.RIGHT_SHIFT_SIGNED_ASSIGN; + case USR_ASG-> Assignment.Operator.RIGHT_SHIFT_UNSIGNED_ASSIGN; + case MINUS_ASG-> Assignment.Operator.MINUS_ASSIGN; + case MUL_ASG-> Assignment.Operator.TIMES_ASSIGN; + case DIV_ASG-> Assignment.Operator.DIVIDE_ASSIGN; + case MOD_ASG-> Assignment.Operator.REMAINDER_ASSIGN; + default -> null; + }); + return res; + } + if (javac instanceof JCInstanceOf jcInstanceOf) { + JCPattern jcPattern = jcInstanceOf.getPattern(); + if (jcInstanceOf.getType() != null && jcPattern == null) { + InstanceofExpression res = this.ast.newInstanceofExpression(); + commonSettings(res, javac); + res.setLeftOperand(convertExpression(jcInstanceOf.getExpression())); + res.setRightOperand(convertToType(jcInstanceOf.getType())); + return res; + } + 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())); + Pattern p = convert(jcPattern); + if( p != null && this.ast.apiLevel >= AST.JLS20_INTERNAL) + res.setPattern(p); + else { + res.setRightOperand(convertToSingleVarDecl(jcPattern)); + } + 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) { + JCExpression qualifierExpression = jcMemberReference.getQualifierExpression(); + if (Objects.equals(Names.instance(this.context).init, jcMemberReference.getName())) { + CreationReference res = this.ast.newCreationReference(); + commonSettings(res, javac); + res.setType(convertToType(qualifierExpression)); + if (jcMemberReference.getTypeArguments() != null) { + jcMemberReference.getTypeArguments().stream() + .map(this::convertToType) + .filter(Objects::nonNull) + .forEach(res.typeArguments()::add); + } + return res; + } else if (qualifierExpression.getKind() == Kind.PARAMETERIZED_TYPE || qualifierExpression.getKind() == Kind.ARRAY_TYPE) { + TypeMethodReference res = this.ast.newTypeMethodReference(); + commonSettings(res, javac); + res.setType(convertToType(qualifierExpression)); + res.setName((SimpleName)convertName(jcMemberReference.getName())); + if (jcMemberReference.getTypeArguments() != null) { + jcMemberReference.getTypeArguments().stream() + .map(this::convertToType) + .filter(Objects::nonNull) + .forEach(res.typeArguments()::add); + } + return res; + } else if (qualifierExpression instanceof JCIdent ident + && Names.instance(this.context)._super.equals(ident.getName())) { + SuperMethodReference res = this.ast.newSuperMethodReference(); + commonSettings(res, javac); + res.setName((SimpleName)convertName(jcMemberReference.getName())); + if (jcMemberReference.getTypeArguments() != null) { + jcMemberReference.getTypeArguments().stream() + .map(this::convertToType) + .filter(Objects::nonNull) + .forEach(res.typeArguments()::add); + } + return res; + } else if (qualifierExpression instanceof JCFieldAccess fieldAccess + && Names.instance(this.context)._super.equals(fieldAccess.getIdentifier())) { + SuperMethodReference res = this.ast.newSuperMethodReference(); + commonSettings(res, javac); + res.setName((SimpleName)convertName(jcMemberReference.getName())); + res.setQualifier(toName(fieldAccess.getExpression())); + if (jcMemberReference.getTypeArguments() != null) { + jcMemberReference.getTypeArguments().stream() + .map(this::convertToType) + .filter(Objects::nonNull) + .forEach(res.typeArguments()::add); + } + return res; + } else { + ExpressionMethodReference res = this.ast.newExpressionMethodReference(); + commonSettings(res, javac); + res.setExpression(convertExpression(jcMemberReference.getQualifierExpression())); + res.setName((SimpleName)convertName(jcMemberReference.getName())); + if (jcMemberReference.getTypeArguments() != null) { + jcMemberReference.getTypeArguments().stream() + .map(this::convertToType) + .filter(Objects::nonNull) + .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(); + commonSettings(res, javac); + jcLambda.getParameters().stream() + .filter(JCVariableDecl.class::isInstance) + .map(JCVariableDecl.class::cast) + .map(this::convertVariableDeclarationForLambda) + .forEach(res.parameters()::add); + int arrowIndex = this.rawText.indexOf("->", jcLambda.getStartPosition()); + int parenthesisIndex = this.rawText.indexOf(")", jcLambda.getStartPosition()); + res.setParentheses(parenthesisIndex >= 0 && parenthesisIndex < arrowIndex); + ASTNode body = jcLambda.getBody() instanceof JCExpression expr ? convertExpression(expr) : + jcLambda.getBody() instanceof JCStatement stmt ? convertStatement(stmt, res) : + null; + if( body != null ) + res.setBody(body); + // TODO set parenthesis looking at the next non-whitespace char after the last parameter + int endPos = jcLambda.getEndPosition(this.javacCompilationUnit.endPositions); + res.setSourceRange(jcLambda.pos, endPos - jcLambda.pos); + return res; + } + if (javac instanceof JCNewArray jcNewArray) { + ArrayCreation res = this.ast.newArrayCreation(); + commonSettings(res, javac); + if (jcNewArray.getType() == null) { + // we have no type, we should return an initializer directly + ArrayInitializer ret = createArrayInitializerFromJCNewArray(jcNewArray); + return ret; + } + + if (jcNewArray.getType() != null) { + Type type = convertToType(jcNewArray.getType()); + ArrayType arrayType; + if (type instanceof ArrayType childArrayType) { + arrayType = childArrayType; + if( this.ast.apiLevel >= AST.JLS8_INTERNAL) { + var extraDimensions = jcNewArray.getDimAnnotations().stream() + .map(annotations -> annotations.stream().map(this::convert).toList()) + .map(annotations -> { + Dimension dim = this.ast.newDimension(); + dim.annotations().addAll(annotations); + int startOffset = annotations.stream().mapToInt(Annotation::getStartPosition).min().orElse(-1); + int endOffset = annotations.stream().mapToInt(ann -> ann.getStartPosition() + ann.getLength()).max().orElse(-1); + dim.setSourceRange(startOffset, endOffset - startOffset); + return dim; + }) + .toList(); + if (arrayType.dimensions().isEmpty()) { + arrayType.dimensions().addAll(extraDimensions); + } else { + var lastDimension = arrayType.dimensions().removeFirst(); + arrayType.dimensions().addAll(extraDimensions); + arrayType.dimensions().add(lastDimension); + } + int totalRequiredDims = countDimensions(jcNewArray.getType()) + 1; + int totalCreated = arrayType.dimensions().size(); + if( totalCreated < totalRequiredDims) { + int endPos = jcNewArray.getEndPosition(this.javacCompilationUnit.endPositions); + int startPos = jcNewArray.getStartPosition(); + String raw = this.rawText.substring(startPos, endPos); + for( int i = 0; i < totalRequiredDims; i++ ) { + int absoluteEndChar = startPos + ordinalIndexOf(raw, "]", i+1); + int absoluteEnd = absoluteEndChar + 1; + int absoluteStart = startPos + ordinalIndexOf(raw, "[", i+1); + boolean found = false; + if( absoluteEnd != -1 && absoluteStart != -1 ) { + for( int j = 0; j < totalCreated && !found; j++ ) { + Dimension d = (Dimension)arrayType.dimensions().get(j); + if( d.getStartPosition() == absoluteStart && (d.getStartPosition() + d.getLength()) == absoluteEnd) { + found = true; + } + } + if( !found ) { + // Need to make a new one + Dimension d = this.ast.newDimension(); + d.setSourceRange(absoluteStart, absoluteEnd - absoluteStart); + arrayType.dimensions().add(i, d); + totalCreated++; + } + } + } + } + } else { + // JLS < 8, just wrap underlying type + arrayType = this.ast.newArrayType(childArrayType); + } + } else if(jcNewArray.dims != null && jcNewArray.dims.size() > 0 ){ + // Child is not array type + arrayType = this.ast.newArrayType(type); + int dims = jcNewArray.dims.size(); + for( int i = 0; i < dims - 1; i++ ) { + if( this.ast.apiLevel >= AST.JLS8_INTERNAL) { + // TODO, this dimension needs source range + arrayType.dimensions().addFirst(this.ast.newDimension()); + } else { + // JLS < 8, wrap underlying + arrayType = this.ast.newArrayType(arrayType); + } + } + } else { + // Child is not array type, and 0 dims for underlying + 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) { + res.setInitializer(createArrayInitializerFromJCNewArray(jcNewArray)); + } + return res; + } + if (javac instanceof JCAnnotation jcAnnot) { + return convert(jcAnnot); + } + if (javac instanceof JCPrimitiveTypeTree primitiveTree) { + SimpleName res = this.ast.newSimpleName(primitiveTree.getPrimitiveTypeKind().name()); + commonSettings(res, javac); + return res; + } + if (javac instanceof JCSwitchExpression jcSwitch) { + SwitchExpression res = this.ast.newSwitchExpression(); + commonSettings(res, javac); + JCExpression switchExpr = jcSwitch.getExpression(); + if( switchExpr instanceof JCParens jcp) { + switchExpr = jcp.getExpression(); + } + res.setExpression(convertExpression(switchExpr)); + + List cases = jcSwitch.getCases(); + Iterator it = cases.iterator(); + ArrayList bodyList = new ArrayList<>(); + while(it.hasNext()) { + JCCase switchCase = it.next(); + bodyList.add(switchCase); + if( switchCase.getCaseKind() == CaseKind.STATEMENT ) { + if( switchCase.getStatements() != null && switchCase.getStatements().size() > 0 ) { + bodyList.addAll(switchCase.getStatements()); + } + } else { + bodyList.add(switchCase.getBody()); + } + } + + Iterator stmtIterator = bodyList.iterator(); + while(stmtIterator.hasNext()) { + JCTree next = stmtIterator.next(); + if( next instanceof JCStatement jcs) { + Statement s1 = convertStatement(jcs, res); + if( s1 != null ) { + res.statements().add(s1); + } + } else if( next instanceof JCExpression jce) { + Expression s1 = convertExpression(jce); + if( s1 != null ) { + // make a yield statement out of it?? + YieldStatement r1 = this.ast.newYieldStatement(); + commonSettings(r1, jce); + r1.setExpression(s1); + res.statements().add(r1); + } + } + } + return res; + } + if (javac instanceof JCTree.JCArrayTypeTree arrayTypeTree) { + Type type = convertToType(javac); + TypeLiteral res = this.ast.newTypeLiteral(); + res.setType(type); + commonSettings(res, arrayTypeTree); + return res; + } + if (javac instanceof JCTypeApply parameterizedType) { + // usually mapping from an error + var recoveredType = convertToType(parameterizedType); + // As we cannot directly map a type to a JDT expr, let's capture it anyway + TypeLiteral decl = this.ast.newTypeLiteral(); + decl.setSourceRange(recoveredType.getStartPosition(), recoveredType.getLength()); + decl.setFlags(ASTNode.MALFORMED); + decl.setType(recoveredType); + return decl; + } + return null; + } + + private SingleVariableDeclaration convertToSingleVarDecl(JCPattern jcPattern) { + if( jcPattern instanceof JCBindingPattern jcbp && jcbp.var instanceof JCVariableDecl decl) { + SingleVariableDeclaration vdd = (SingleVariableDeclaration)convertVariableDeclaration(decl); + return vdd; + } + return null; + } + + private List consecutiveInfixExpressionsWithEqualOps(JCBinary binary, Tag opcode) { + return consecutiveInfixExpressionsWithEqualOps(binary, opcode, new ArrayList()); + } + private List consecutiveInfixExpressionsWithEqualOps( + JCBinary binary, Tag opcode, List consecutive) { + + if( opcode.equals(binary.getTag())) { + if( consecutive != null ) { + JCExpression left = binary.getLeftOperand(); + if( left instanceof JCBinary jcb) { + consecutive = consecutiveInfixExpressionsWithEqualOps(jcb, opcode, consecutive); + } else { + consecutive.add(left); + } + } + if( consecutive != null ) { + JCExpression right = binary.getRightOperand(); + if( right instanceof JCBinary jcb) { + consecutive = consecutiveInfixExpressionsWithEqualOps(jcb, opcode, consecutive); + } else { + consecutive.add(right); + } + } + return consecutive; + } + return null; + } + + private Expression handleInfixExpression(JCBinary binary, JCExpression javac) { + List conseq = consecutiveInfixExpressionsWithEqualOps(binary, binary.getTag()); + if( conseq != null && conseq.size() > 2 ) { + return handleConsecutiveInfixExpression(binary, javac, conseq); + } + + 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(binaryTagToInfixOperator(binary.getTag())); + return res; + } + + private Expression handleConsecutiveInfixExpression(JCBinary binary, JCExpression javac, + List conseq) { + + InfixExpression res = this.ast.newInfixExpression(); + commonSettings(res, javac); + + Expression left = convertExpression(conseq.get(0)); + if (left != null) { + res.setLeftOperand(left); + } + Expression right = convertExpression(conseq.get(1)); + if (right != null) { + res.setRightOperand(right); + } + for( int i = 2; i < conseq.size(); i++ ) { + res.extendedOperands().add(convertExpression(conseq.get(i))); + } + + res.setOperator(binaryTagToInfixOperator(binary.getTag())); + return res; + } + + private InfixExpression.Operator binaryTagToInfixOperator(Tag t) { + return switch (t) { + 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; + }; + } + + + /** + * precondition: you've checked all the segments are identifier that can be used in a qualified name + */ + private Name convertQualifiedName(JCFieldAccess fieldAccess) { + JCExpression parent = fieldAccess.getExpression(); + Name parentName; + if (parent instanceof JCFieldAccess parentFieldAccess) { + parentName = convertQualifiedName(parentFieldAccess); + } else if (parent instanceof JCIdent parentIdent) { + parentName = convertName(parentIdent.getName()); + } else { + throw new IllegalArgumentException("Unrecognized javac AST node type: " + parent.getClass().getCanonicalName()); + } + commonSettings(parentName, parent); + SimpleName segmentName = (SimpleName)convertName(fieldAccess.getIdentifier()); + int endPos = fieldAccess.getEndPosition(this.javacCompilationUnit.endPositions); + int startPos = endPos - fieldAccess.getIdentifier().length(); + segmentName.setSourceRange(startPos, fieldAccess.getIdentifier().length()); + QualifiedName res = this.ast.newQualifiedName(parentName, segmentName); + commonSettings(res, fieldAccess); + return res; + } + + private Expression convertExpression(JCExpression javac) { + Expression ret = convertExpressionImpl(javac); + if( ret != null ) + return ret; + + // Handle errors or default situation + if (javac instanceof JCErroneous error) { + if (error.getErrorTrees().size() == 1) { + JCTree tree = error.getErrorTrees().get(0); + if (tree instanceof JCExpression nestedExpr) { + try { + return convertExpression(nestedExpr); + } catch (Exception ex) { + // pass-through: do not break when attempting such reconcile + } + } + } + } + if( shouldRecoverWithSimpleName(javac)) { + var res = this.ast.newSimpleName(FAKE_IDENTIFIER); + res.setFlags(ASTNode.RECOVERED); + commonSettings(res, javac); + return res; + } + return null; + } + + private boolean shouldRecoverWithSimpleName(JCExpression javac) { + if( javac instanceof JCNewClass) + return false; + return true; + } + private Pattern convert(JCPattern jcPattern) { + if (this.ast.apiLevel >= AST.JLS21_INTERNAL) { + if (jcPattern instanceof JCBindingPattern jcBindingPattern) { + TypePattern jdtPattern = this.ast.newTypePattern(); + commonSettings(jdtPattern, jcBindingPattern); + jdtPattern.setPatternVariable((SingleVariableDeclaration)convertVariableDeclaration(jcBindingPattern.var)); + return jdtPattern; + } else if (jcPattern instanceof JCRecordPattern jcRecordPattern) { + RecordPattern jdtPattern = this.ast.newRecordPattern(); + commonSettings(jdtPattern, jcRecordPattern); + jdtPattern.setPatternType(convertToType(jcRecordPattern.deconstructor)); + for (JCPattern nestedJcPattern : jcRecordPattern.nested) { + jdtPattern.patterns().add(convert(nestedJcPattern)); + } + return jdtPattern; + } else if (jcPattern instanceof JCAnyPattern jcAnyPattern) { + TypePattern jdtPattern = this.ast.newTypePattern(); + commonSettings(jdtPattern, jcAnyPattern); + VariableDeclarationFragment variable = this.ast.newVariableDeclarationFragment(); + commonSettings(variable, jcAnyPattern); + variable.setName(this.ast.newSimpleName("_")); + jdtPattern.setPatternVariable(variable); + return jdtPattern; + } + } + return null; + } + + private ArrayInitializer createArrayInitializerFromJCNewArray(JCNewArray jcNewArray) { + ArrayInitializer initializer = this.ast.newArrayInitializer(); + commonSettings(initializer, jcNewArray); + if (!jcNewArray.getInitializers().isEmpty()) { + jcNewArray.getInitializers().stream().map(this::convertExpression).filter(Objects::nonNull).forEach(initializer.expressions()::add); + int start = ((Expression)initializer.expressions().getFirst()).getStartPosition() - 1; + while (start >= 0 && this.rawText.charAt(start) != '{') { + start--; + } + Expression lastExpr = (Expression)initializer.expressions().getLast(); + int end = lastExpr.getStartPosition() + lastExpr.getLength(); + while (end < this.rawText.length() && this.rawText.charAt(end) != '}') { + end++; + } + initializer.setSourceRange(start, end - start + 1); + } + return initializer; + } + + private AnonymousClassDeclaration createAnonymousClassDeclaration(JCClassDecl javacAnon, ASTNode parent) { + AnonymousClassDeclaration anon = this.ast.newAnonymousClassDeclaration(); + commonSettings(anon, javacAnon); + if (javacAnon.getMembers() != null) { + List members = javacAnon.getMembers(); + for( int i = 0; i < members.size(); i++ ) { + ASTNode decl = convertBodyDeclaration(members.get(i), anon); + if( decl != null ) { + anon.bodyDeclarations().add(decl); + } + } + } + return anon; + } + + /** + * + * @param tree + * @param pos + * @return a list of dimensions for the given type. If target < JLS8, then + * it returns a list of null objects, of the right size for the dimensions + */ + private List convertDimensionsAfterPosition(JCTree tree, int pos) { + if (tree == null) { + return List.of(); + } + List res = new ArrayList<>(); + JCTree elem = tree; + do { + if( elem.pos >= pos) { + if (elem instanceof JCArrayTypeTree arrayType) { + if (this.ast.apiLevel < AST.JLS8_INTERNAL) { + res.add(null); + } else { + Dimension dimension = this.ast.newDimension(); + res.add(dimension); + // Would be better to use a Tokenizer here that is capable of skipping comments + int startPosition = this.rawText.indexOf('[', arrayType.pos); + int endPosition = this.rawText.indexOf(']', startPosition); + dimension.setSourceRange(startPosition, endPosition - startPosition + 1); + } + elem = arrayType.getType(); + } else if (elem instanceof JCAnnotatedType annotated && annotated.getUnderlyingType() instanceof JCArrayTypeTree arrayType) { + if (this.ast.apiLevel < AST.JLS8_INTERNAL) { + res.add(null); + } else { + Dimension dimension = this.ast.newDimension(); + annotated.getAnnotations().stream() + .map(this::convert) + .forEach(dimension.annotations()::add); + // Would be better to use a Tokenizer here that is capable of skipping comments + int startPosition = this.rawText.indexOf('[', arrayType.pos); + int endPosition = this.rawText.indexOf(']', startPosition); + dimension.setSourceRange(startPosition, endPosition - startPosition + 1); + res.add(dimension); + } + elem = arrayType.getType(); + } else { + elem = null; + } + } else { + elem = null; + } + } while (elem != null); + return res; + } + + private JCTree unwrapDimensions(JCTree tree, int count) { + JCTree elem = tree; + while (count > 0) { + if (elem instanceof JCArrayTypeTree arrayTree) { + elem = arrayTree.getType(); + count--; + } else if (elem instanceof JCAnnotatedType annotated && annotated.getUnderlyingType() instanceof JCArrayTypeTree arrayType) { + elem = arrayType.getType(); + count--; + } else { + count = 0; + } + + } + return elem; + } + + private int countDimensions(JCTree tree) { + JCTree elem = tree; + int count = 0; + boolean done = false; + while (!done) { + if (elem instanceof JCArrayTypeTree arrayTree) { + elem = arrayTree.getType(); + count++; + } else if (elem instanceof JCAnnotatedType annotated && annotated.getUnderlyingType() instanceof JCArrayTypeTree arrayType) { + elem = arrayType.getType(); + count++; + } else { + done = true; + } + } + return count; + } + + + private SuperMethodInvocation convertSuperMethodInvocation(JCMethodInvocation javac) { + SuperMethodInvocation res = this.ast.newSuperMethodInvocation(); + commonSettings(res, javac); + javac.getArguments().stream().map(this::convertExpression).forEach(res.arguments()::add); + + //res.setFlags(javac.getFlags() | ASTNode.MALFORMED); + if( this.ast.apiLevel > AST.JLS2_INTERNAL) { + javac.getTypeArguments().stream().map(this::convertToType).forEach(res.typeArguments()::add); + } + return res; + } + + private SuperConstructorInvocation convertSuperConstructorInvocation(JCMethodInvocation javac) { + SuperConstructorInvocation res = this.ast.newSuperConstructorInvocation(); + commonSettings(res, javac); + ensureTrailingSemicolonInRange(res); + javac.getArguments().stream().map(this::convertExpression).forEach(res.arguments()::add); + + //res.setFlags(javac.getFlags() | ASTNode.MALFORMED); + if( this.ast.apiLevel > AST.JLS2_INTERNAL) { + javac.getTypeArguments().stream() + .map(this::convertToType) + .filter(Objects::nonNull) + .forEach(res.typeArguments()::add); + } + if( javac.getMethodSelect() instanceof JCFieldAccess jcfa && jcfa.selected != null ) { + res.setExpression(convertExpression(jcfa.selected)); + } + return res; + } + + + private ConstructorInvocation convertThisConstructorInvocation(JCMethodInvocation javac) { + ConstructorInvocation res = this.ast.newConstructorInvocation(); + commonSettings(res, javac); + // add the trailing `;` + // it's always there, since this is always a statement, since this is always `this();` or `super();` + // (or equivalent with type parameters) + res.setSourceRange(res.getStartPosition(), res.getLength() + 1); + javac.getArguments().stream().map(this::convertExpression).forEach(res.arguments()::add); + if( this.ast.apiLevel > AST.JLS2_INTERNAL) { + javac.getTypeArguments().stream() + .map(this::convertToType) + .filter(Objects::nonNull) + .forEach(res.typeArguments()::add); + } + return res; + } + + private Expression convertLiteral(JCLiteral literal) { + Object value = literal.getValue(); + if (value instanceof Number) { + // to check if the literal is actually a prefix expression of it is a hex + // negative value we need to check the source char value. + char firstChar = this.rawText.substring(literal.getStartPosition(), literal.getStartPosition() + 1) + .charAt(0); + + if( firstChar != '-' ) { + NumberLiteral res = this.ast.newNumberLiteral(); + commonSettings(res, literal); + String fromSrc = this.rawText.substring(res.getStartPosition(), res.getStartPosition() + res.getLength()); + try { + res.setToken(fromSrc); + } catch (IllegalArgumentException ex) { + // probably some lombok oddity, let's ignore + } + return res; + } else { + PrefixExpression res = this.ast.newPrefixExpression(); + commonSettings(res, literal); + + String fromSrc = this.rawText.substring(res.getStartPosition()+1, res.getStartPosition() + res.getLength()); + NumberLiteral operand = this.ast.newNumberLiteral(); + commonSettings(operand, literal); + operand.setToken(fromSrc); + + res.setOperand(operand); + res.setOperator(Operator.MINUS); + return res; + } + } + if (value instanceof String string) { + boolean malformed = false; + if (this.rawText.charAt(literal.pos) == '"' + && this.rawText.charAt(literal.pos + 1) == '"' + && this.rawText.charAt(literal.pos + 2) == '"') { + if (this.ast.apiLevel() > AST.JLS14) { + TextBlock res = this.ast.newTextBlock(); + commonSettings(res, literal); + String rawValue = this.rawText.substring(literal.pos, literal.getEndPosition(this.javacCompilationUnit.endPositions)); + res.internalSetEscapedValue(rawValue, string); + return res; + } + malformed = true; + } + StringLiteral res = this.ast.newStringLiteral(); + commonSettings(res, literal); + int startPos = res.getStartPosition(); + int len = res.getLength(); + if( string.length() != len && len > 2) { + try { + string = this.rawText.substring(startPos, startPos + len); + if (!string.startsWith("\"")) { + string = '"' + string; + } + if (!string.endsWith("\"")) { + string = string + '"'; + } + res.internalSetEscapedValue(string); + } catch(IndexOutOfBoundsException ignore) { + res.setLiteralValue(string); // TODO: we want the token here + } + } else { + res.setLiteralValue(string); // TODO: we want the token here + } + if (malformed) { + res.setFlags(res.getFlags() | ASTNode.MALFORMED); + } + 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 v) { + CharacterLiteral res = this.ast.newCharacterLiteral(); + commonSettings(res, literal); + res.setCharValue(v.charValue()); + return res; + } + throw new UnsupportedOperationException("Not supported yet " + literal + "\n of type" + literal.getClass().getName()); + } + + private Statement convertStatement(JCStatement javac, ASTNode parent) { + int endPos = TreeInfo.getEndPos(javac, this.javacCompilationUnit.endPositions); + int preferredPos = javac.getPreferredPosition(); + if (endPos < preferredPos) { + return null; + } + 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) { + try { + Statement stmt = convertStatement(nestedStmt, parent); + if( stmt != null ) + stmt.setFlags(stmt.getFlags() | ASTNode.RECOVERED); + return stmt; + } catch (Exception ex) { + // pass-through: do not break when attempting such reconcile + } + } + if (tree instanceof JCExpression expr) { + Expression expression = convertExpression(expr); + ExpressionStatement res = this.ast.newExpressionStatement(expression); + commonSettings(res, javac); + return res; + } + } + parent.setFlags(parent.getFlags() | ASTNode.MALFORMED); + return null; + } + boolean uniqueCaseFound = false; + if (jcExpressionStatement.getExpression() instanceof JCMethodInvocation methodInvocation) { + JCExpression nameExpr = methodInvocation.getMethodSelect(); + if (nameExpr instanceof JCIdent ident) { + if (Objects.equals(ident.getName(), Names.instance(this.context)._super)) { + uniqueCaseFound = true; + } + } + if (nameExpr instanceof JCFieldAccess jcfa) { + if (Objects.equals(jcfa.getIdentifier(), Names.instance(this.context)._super)) { + uniqueCaseFound = true; + } + } + } + if( uniqueCaseFound ) { + return convertSuperConstructorInvocation((JCMethodInvocation)jcExpressionStatement.getExpression()); + } + ExpressionStatement res = this.ast.newExpressionStatement(convertExpression(jcExpressionStatement.getExpression())); + commonSettings(res, javac); + return res; + } + if (javac instanceof JCVariableDecl jcVariableDecl) { + VariableDeclarationFragment fragment = createVariableDeclarationFragment(jcVariableDecl); + List sameStartPosition = new ArrayList<>(); + if (parent instanceof Block decl && jcVariableDecl.vartype != null) { + decl.statements().stream().filter(x -> x instanceof VariableDeclarationStatement) + .filter(x -> ((VariableDeclarationStatement)x).getType().getStartPosition() == jcVariableDecl.vartype.getStartPosition()) + .forEach(x -> sameStartPosition.add((ASTNode)x)); + } else if( parent instanceof ForStatement decl && jcVariableDecl.vartype != null) { + // TODO somehow doubt this will work as expected + decl.initializers().stream().filter(x -> x instanceof VariableDeclarationExpression) + .filter(x -> ((VariableDeclarationExpression)x).getType().getStartPosition() == jcVariableDecl.vartype.getStartPosition()) + .forEach(x -> sameStartPosition.add((ASTNode)x)); + } + if( sameStartPosition.size() >= 1 ) { + Object obj0 = sameStartPosition.get(0); + if( obj0 instanceof VariableDeclarationStatement fd ) { + fd.fragments().add(fragment); + int newParentEnd = fragment.getStartPosition() + fragment.getLength(); + fd.setSourceRange(fd.getStartPosition(), newParentEnd - fd.getStartPosition() + 1); + removeSurroundingWhitespaceFromRange(fd); + } else if( obj0 instanceof VariableDeclarationExpression fd ) { + fd.fragments().add(fragment); + int newParentEnd = fragment.getStartPosition() + fragment.getLength(); + fd.setSourceRange(fd.getStartPosition(), newParentEnd - fd.getStartPosition() + 1); + removeTrailingSemicolonFromRange(fd); + removeSurroundingWhitespaceFromRange(fd); + } + return null; + } + VariableDeclarationStatement res = this.ast.newVariableDeclarationStatement(fragment); + commonSettings(res, javac); + + if (jcVariableDecl.vartype != null) { + if( jcVariableDecl.vartype instanceof JCArrayTypeTree jcatt) { + int extraDims = 0; + if( fragment.extraArrayDimensions > 0 ) { + extraDims = fragment.extraArrayDimensions; + } else if( this.ast.apiLevel > AST.JLS4_INTERNAL && fragment.extraDimensions() != null && fragment.extraDimensions().size() > 0 ) { + extraDims = fragment.extraDimensions().size(); + } + res.setType(convertToType(unwrapDimensions(jcatt, extraDims))); + } else { + res.setType(convertToType(findBaseType(jcVariableDecl.vartype))); + } + } else if( jcVariableDecl.declaredUsingVar() ) { + SimpleType st = this.ast.newSimpleType(this.ast.newSimpleName("var")); + st.setSourceRange(javac.getStartPosition(), 3); + res.setType(st); + } + if( this.ast.apiLevel > AST.JLS2_INTERNAL) { + res.modifiers().addAll(convert(jcVariableDecl.getModifiers(), res)); + } else { + JCModifiers mods = jcVariableDecl.getModifiers(); + int[] total = new int[] {0}; + mods.getFlags().forEach(x -> {total[0] += modifierToFlagVal(x);}); + res.internalSetModifiers(total[0]); + } + 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, parent); + } + if (javac instanceof JCSynchronized jcSynchronized) { + SynchronizedStatement res = this.ast.newSynchronizedStatement(); + commonSettings(res, javac); + JCExpression syncExpr = jcSynchronized.getExpression(); + if( syncExpr instanceof JCParens jcp) { + syncExpr = jcp.getExpression(); + } + res.setExpression(convertExpression(syncExpr)); + res.setBody(convertBlock(jcSynchronized.getBlock())); + return res; + } + if (javac instanceof JCForLoop jcForLoop) { + ForStatement res = this.ast.newForStatement(); + commonSettings(res, javac); + Statement stmt = convertStatement(jcForLoop.getStatement(), res); + if( stmt != null ) + res.setBody(stmt); + var initializerIt = jcForLoop.getInitializer().iterator(); + while(initializerIt.hasNext()) { + Expression expr = convertStatementToExpression(initializerIt.next(), res); + if( expr != null ) + res.initializers().add(expr); + } + if (jcForLoop.getCondition() != null) { + Expression expr = convertExpression(jcForLoop.getCondition()); + if( expr != null ) + res.setExpression(expr); + } + + Iterator updateIt = jcForLoop.getUpdate().iterator(); + while(updateIt.hasNext()) { + Expression expr = convertStatementToExpression(updateIt.next(), res); + if( expr != null ) + res.updaters().add(expr); + } + return res; + } + if (javac instanceof JCEnhancedForLoop jcEnhancedForLoop) { + if( this.ast.apiLevel != AST.JLS2_INTERNAL) { + EnhancedForStatement res = this.ast.newEnhancedForStatement(); + commonSettings(res, javac); + res.setParameter((SingleVariableDeclaration)convertVariableDeclaration(jcEnhancedForLoop.getVariable())); + Expression expr = convertExpression(jcEnhancedForLoop.getExpression()); + if( expr != null ) + res.setExpression(expr); + Statement stmt = convertStatement(jcEnhancedForLoop.getStatement(), res); + if( stmt != null ) + res.setBody(stmt); + return res; + } else { + EmptyStatement res = this.ast.newEmptyStatement(); + commonSettings(res, javac); + return res; + } + } + if (javac instanceof JCBreak jcBreak) { + BreakStatement res = this.ast.newBreakStatement(); + commonSettings(res, javac); + if (jcBreak.getLabel() != null) { + res.setLabel((SimpleName)convertName(jcBreak.getLabel())); + } + return res; + } + if (javac instanceof JCSwitch jcSwitch) { + SwitchStatement res = this.ast.newSwitchStatement(); + commonSettings(res, javac); + JCExpression switchExpr = jcSwitch.getExpression(); + if( switchExpr instanceof JCParens jcp) { + switchExpr = jcp.getExpression(); + } + res.setExpression(convertExpression(switchExpr)); + jcSwitch.getCases().stream() + .flatMap(switchCase -> { + List stmts = new ArrayList<>(); + switch(switchCase.getCaseKind()) { + case CaseKind.STATEMENT: { + int numStatements = switchCase.getStatements() != null ? switchCase.getStatements().size() + : 0; + stmts.add(switchCase); + if (numStatements > 0) { + stmts.addAll(switchCase.getStatements()); + } + return stmts.stream(); + } + case CaseKind.RULE: { + stmts.add(switchCase); + JCTree body = switchCase.getBody(); + if (body instanceof JCExpressionStatement stmt) { + stmts.add(stmt); + } + } + } + return stmts.stream(); + }).map(x -> convertStatement(x, res)) + .filter(x -> x != null) + .forEach(res.statements()::add); + return res; + } + if (javac instanceof JCCase jcCase) { + return convertSwitchCase(jcCase); + } + if (javac instanceof JCWhileLoop jcWhile) { + WhileStatement res = this.ast.newWhileStatement(); + commonSettings(res, javac); + JCExpression expr = jcWhile.getCondition(); + if( expr instanceof JCParens jcp) { + expr = jcp.getExpression(); + } + res.setExpression(convertExpression(expr)); + Statement body = convertStatement(jcWhile.getStatement(), res); + if( body != null ) + res.setBody(body); + return res; + } + if (javac instanceof JCDoWhileLoop jcDoWhile) { + DoStatement res = this.ast.newDoStatement(); + commonSettings(res, javac); + JCExpression expr = jcDoWhile.getCondition(); + if( expr instanceof JCParens jcp) { + expr = jcp.getExpression(); + } + Expression expr1 = convertExpression(expr); + if( expr != null ) + res.setExpression(expr1); + + Statement body = convertStatement(jcDoWhile.getStatement(), res); + if( body != null ) + res.setBody(body); + 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)convertName(jcContinue.getLabel())); + } + return res; + } + if (javac instanceof JCLabeledStatement jcLabel) { + LabeledStatement res = this.ast.newLabeledStatement(); + commonSettings(res, javac); + res.setLabel((SimpleName)convertName(jcLabel.getLabel())); + Statement stmt = convertStatement(jcLabel.getStatement(), res); + if( stmt != null ) + res.setBody(stmt); + return res; + } + if (javac instanceof JCAssert jcAssert) { + AssertStatement res =this.ast.newAssertStatement(); + commonSettings(res, javac); + Expression expr = convertExpression(jcAssert.getCondition()); + if( expr != null ) + res.setExpression(expr); + if( jcAssert.getDetail() != null ) { + Expression det = convertExpression(jcAssert.getDetail()); + if( det != null ) + res.setMessage(det); + } + return res; + } + if (javac instanceof JCClassDecl jcclass) { + TypeDeclarationStatement res = this.ast.newTypeDeclarationStatement(convertClassDecl(jcclass, parent)); + commonSettings(res, javac); + return res; + } + if (javac instanceof JCSkip) { + EmptyStatement res = this.ast.newEmptyStatement(); + commonSettings(res, javac); + return res; + } + throw new UnsupportedOperationException("Missing support to convert " + javac + "of type " + javac.getClass().getName()); + } + + private Statement convertSwitchCase(JCCase jcCase) { + SwitchCase res = this.ast.newSwitchCase(); + commonSettings(res, jcCase); + if( this.ast.apiLevel >= AST.JLS14_INTERNAL) { + if (jcCase.getGuard() != null && (jcCase.getLabels().size() > 1 || jcCase.getLabels().get(0) instanceof JCPatternCaseLabel)) { + GuardedPattern guardedPattern = this.ast.newGuardedPattern(); + guardedPattern.setExpression(convertExpression(jcCase.getGuard())); + guardedPattern.setRestrictedIdentifierStartPosition(jcCase.guard.getStartPosition() - 5); // javac gives start position without "when " while jdt expects it with + if (jcCase.getLabels().length() > 1) { + int start = Integer.MAX_VALUE; + int end = Integer.MIN_VALUE; + EitherOrMultiPattern eitherOrMultiPattern = this.ast.newEitherOrMultiPattern(); + for (JCCaseLabel label : jcCase.getLabels()) { + if (label.pos < start) { + start = label.pos; + } + if (end < label.getEndPosition(this.javacCompilationUnit.endPositions)) { + end = label.getEndPosition(this.javacCompilationUnit.endPositions); + } + if (label instanceof JCPatternCaseLabel jcPattern) { + eitherOrMultiPattern.patterns().add(convert(jcPattern.getPattern())); + } + // skip over any constants, they are not valid anyways + } + eitherOrMultiPattern.setSourceRange(start, end - start); + guardedPattern.setPattern(eitherOrMultiPattern); + } else if (jcCase.getLabels().length() == 1) { + if (jcCase.getLabels().get(0) instanceof JCPatternCaseLabel jcPattern) { + guardedPattern.setPattern(convert(jcPattern.getPattern())); + } else { + // see same above note regarding guarded case labels using constants + throw new UnsupportedOperationException("cannot convert case label: " + jcCase.getLabels().get(0)); + } + } + int start = guardedPattern.getPattern().getStartPosition(); + int end = guardedPattern.getExpression().getStartPosition() + guardedPattern.getExpression().getLength(); + guardedPattern.setSourceRange(start, end - start); + res.expressions().add(guardedPattern); + } else { + if (jcCase.getLabels().length() == 1 && jcCase.getLabels().get(0) instanceof JCPatternCaseLabel jcPattern) { + Pattern p = convert(jcPattern.getPattern()); + if( p != null ) { + int start = jcPattern.getStartPosition(); + p.setSourceRange(start, jcPattern.getEndPosition(this.javacCompilationUnit.endPositions)-start); + res.expressions().add(p); + } + } else { + // Override length to just be `case blah:` + for (JCCaseLabel jcLabel : jcCase.getLabels()) { + switch (jcLabel) { + case JCConstantCaseLabel constantLabel: { + if (constantLabel.expr.toString().equals("null")) { + res.expressions().add(this.ast.newNullLiteral()); + } + break; + } + case JCDefaultCaseLabel defaultCase: { + if (jcCase.getLabels().size() != 1) { + res.expressions().add(this.ast.newCaseDefaultExpression()); + } + break; + } + default: { + break; + } + } + } + int start1 = res.getStartPosition(); + int colon = this.rawText.indexOf(":", start1); + if( colon != -1 ) { + res.setSourceRange(start1, colon - start1 + 1); + } + } + jcCase.getExpressions().stream().map(this::convertExpression).forEach(res.expressions()::add); + } + res.setSwitchLabeledRule(jcCase.getCaseKind() == CaseKind.RULE); + } else { + // Override length to just be `case blah:` + int start1 = res.getStartPosition(); + int colon = this.rawText.indexOf(":", start1); + if( colon != -1 ) { + res.setSourceRange(start1, colon - start1 + 1); + } + List l = jcCase.getExpressions(); + if( l.size() == 1 ) { + res.setExpression(convertExpression(l.get(0))); + } else if( l.size() == 0 ) { + res.setExpression(null); + } + } + // jcCase.getStatements is processed as part of JCSwitch conversion + return res; + } + + private Expression convertStatementToExpression(JCStatement javac, ASTNode parent) { + if (javac instanceof JCExpressionStatement jcExpressionStatement) { + return convertExpression(jcExpressionStatement.getExpression()); + } + Statement javacStatement = convertStatement(javac, parent); + 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); + if (javac instanceof JCVariableDecl jcvd && jcvd.vartype != null) { + if( jcvd.vartype instanceof JCArrayTypeTree jcatt) { + int extraDims = 0; + if( fragment.extraArrayDimensions > 0 ) { + extraDims = fragment.extraArrayDimensions; + } else if( this.ast.apiLevel > AST.JLS4_INTERNAL && fragment.extraDimensions() != null && fragment.extraDimensions().size() > 0 ) { + extraDims = fragment.extraDimensions().size(); + } + jdtVariableDeclarationExpression.setType(convertToType(unwrapDimensions(jcatt, extraDims))); + } else { + jdtVariableDeclarationExpression.setType(convertToType(findBaseType(jcvd.vartype))); + } + } + return jdtVariableDeclarationExpression; + } + return null; + } + + private JCTree findBaseType(JCExpression vartype) { + if( vartype instanceof JCArrayTypeTree jcatt) { + return findBaseType(jcatt.elemtype); + } + return vartype; + } + + private Block convertBlock(JCBlock javac) { + Block res = this.ast.newBlock(); + commonSettings(res, javac); + if (javac.getStatements() != null) { + for (JCStatement next : javac.getStatements()) { + Statement s = convertStatement(next, res); + if( s != null ) { + res.statements().add(s); + } + } + } + return res; + } + + private TryStatement convertTryStatement(JCTry javac, ASTNode parent) { + TryStatement res = this.ast.newTryStatement(); + commonSettings(res, javac); + res.setBody(convertBlock(javac.getBlock())); + if (javac.finalizer != null) { + res.setFinally(convertBlock(javac.getFinallyBlock())); + } + + if( this.ast.apiLevel >= AST.JLS8_INTERNAL) { + if( javac.getResources().size() > 0) { + Iterator it = javac.getResources().iterator(); + while(it.hasNext()) { + ASTNode working = convertTryResource(it.next(), parent); + if( working instanceof VariableDeclarationExpression) { + res.resources().add(working); + } else if( this.ast.apiLevel >= AST.JLS9_INTERNAL && working instanceof Name){ + res.resources().add(working); + } else { + res.setFlags(res.getFlags() | ASTNode.MALFORMED); + } + } + } else { + res.setFlags(res.getFlags() | ASTNode.MALFORMED); + } + } + javac.getCatches().stream().map(this::convertCatcher).forEach(res.catchClauses()::add); + return res; + } + + private Expression convertTryResource(JCTree javac, ASTNode parent) { + if (javac instanceof JCVariableDecl decl) { + var converted = convertVariableDeclaration(decl); + final VariableDeclarationFragment fragment; + if (converted instanceof VariableDeclarationFragment f) { + fragment = f; + } else if (converted instanceof SingleVariableDeclaration single) { + single.delete(); + this.domToJavac.remove(single); + fragment = this.ast.newVariableDeclarationFragment(); + commonSettings(fragment, javac); + fragment.setFlags(single.getFlags()); + SimpleName name = (SimpleName)single.getName().clone(this.ast); + fragment.setName(name); + Expression initializer = single.getInitializer(); + if (initializer != null) { + initializer.delete(); + fragment.setInitializer(initializer); + } + if (parent.getAST().apiLevel() > AST.JLS4) { + for (Dimension extraDimension : (List)single.extraDimensions()) { + extraDimension.delete(); + fragment.extraDimensions().add(extraDimension); + } + } + } else { + fragment = this.ast.newVariableDeclarationFragment(); + } + VariableDeclarationExpression res = this.ast.newVariableDeclarationExpression(fragment); + commonSettings(res, javac); + removeTrailingSemicolonFromRange(res); + res.setType(convertToType(decl.getType())); + if( this.ast.apiLevel > AST.JLS2_INTERNAL) { + res.modifiers().addAll(convert(decl.getModifiers(), res)); + } else { + JCModifiers mods = decl.getModifiers(); + int[] total = new int[] {0}; + mods.getFlags().forEach(x -> {total[0] += modifierToFlagVal(x);}); + res.internalSetModifiers(total[0]); + } + return res; + } + if (javac instanceof JCExpression jcExpression) { + return convertExpression(jcExpression); + } + return null; + } + + private void removeTrailingSemicolonFromRange(ASTNode res) { + removeTrailingCharFromRange(res, new char[] {';'}); + } + private void ensureTrailingSemicolonInRange(ASTNode res) { + int end = res.getStartPosition() + res.getLength(); + if( end < this.rawText.length() && this.rawText.charAt(end-1) != ';' && this.rawText.charAt(end) == ';') { + // jdt expects semicolon to be part of the range + res.setSourceRange(res.getStartPosition(), res.getLength() + 1); + } + } + + private void removeSurroundingWhitespaceFromRange(ASTNode res) { + int start = res.getStartPosition(); + if (start >= 0 && start < this.rawText.length()) { + String rawSource = this.rawText.substring(start, start + res.getLength()); + int trimLeading = rawSource.length() - rawSource.stripLeading().length(); + int trimTrailing = rawSource.length() - rawSource.stripTrailing().length(); + if( (trimLeading != 0 || trimTrailing != 0) && res.getLength() > trimLeading + trimTrailing ) { + //String newContent = this.rawText.substring(start+trimLeading, start+trimLeading+res.getLength()-trimLeading-trimTrailing); + res.setSourceRange(start+trimLeading, res.getLength() - trimLeading - trimTrailing); + } + } + } + + private void removeTrailingCharFromRange(ASTNode res, char[] possible) { + int endPos = res.getStartPosition() + res.getLength(); + char lastChar = this.rawText.charAt(endPos-1); + boolean found = false; + for( int i = 0; i < possible.length; i++ ) { + if( lastChar == possible[i]) { + found = true; + } + } + if( found ) { + res.setSourceRange(res.getStartPosition(), res.getLength() - 1); + } + } + + 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) { + JCExpression expr = javac.getCondition(); + if( expr instanceof JCParens jpc) { + res.setExpression(convertExpression(jpc.getExpression())); + } else { + res.setExpression(convertExpression(expr)); + } + } + if (javac.getThenStatement() != null) { + Statement stmt = convertStatement(javac.getThenStatement(), res); + if( stmt != null ) + res.setThenStatement(stmt); + } + if (javac.getElseStatement() != null) { + Statement stmt = convertStatement(javac.getElseStatement(), res); + if( stmt != null ) + res.setElseStatement(stmt); + } + return res; + } + + /** + * ⚠️ node position in JCTree must be absolute + * @param javac + * @return + */ + Type convertToType(JCTree javac) { + if (javac instanceof JCIdent ident) { + Name name = convertName(ident.name); + int len = FAKE_IDENTIFIER.equals(name.toString()) ? 0 : ident.name.length(); + name.setSourceRange(ident.getStartPosition(), len); + SimpleType res = this.ast.newSimpleType(name); + commonSettings(res, ident); + commonSettings(name, ident); + return res; + } + if (javac instanceof JCFieldAccess qualified) { + try { + if( qualified.getExpression() == null ) { + Name qn = toName(qualified); + commonSettings(qn, javac); + SimpleType res = this.ast.newSimpleType(qn); + commonSettings(res, qualified); + return res; + } + } catch (Exception ex) { + } + // case of not translatable name, eg because of generics + // TODO find a better check instead of relying on exception + Type qualifierType = convertToType(qualified.getExpression()); + SimpleName simpleName = (SimpleName)convertName(qualified.getIdentifier()); + int simpleNameStart = this.rawText.indexOf(simpleName.getIdentifier(), qualifierType.getStartPosition() + qualifierType.getLength()); + if (simpleNameStart > 0) { + simpleName.setSourceRange(simpleNameStart, simpleName.getIdentifier().length()); + } else if (simpleName.getIdentifier().isEmpty()){ + // the name second segment is invalid + simpleName.delete(); + return qualifierType; + } else { + // lombok case + // or empty (eg `test.`) + simpleName.setSourceRange(qualifierType.getStartPosition(), 0); + } + if(qualifierType instanceof SimpleType simpleType && (ast.apiLevel() < AST.JLS8 || simpleType.annotations().isEmpty())) { + simpleType.delete(); + Name parentName = simpleType.getName(); + parentName.setParent(null, null); + QualifiedName name = this.ast.newQualifiedName(simpleType.getName(), simpleName); + commonSettings(name, javac); + int length = simpleType.getName().getLength() + 1 + simpleName.getLength(); + if (name.getStartPosition() >= 0) { + name.setSourceRange(name.getStartPosition(), Math.max(0, length)); + } + SimpleType res = this.ast.newSimpleType(name); + commonSettings(res, javac); + if (name.getStartPosition() >= 0) { + res.setSourceRange(name.getStartPosition(), Math.max(0, length)); + } + return res; + } else { + QualifiedType res = this.ast.newQualifiedType(qualifierType, simpleName); + 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) { + if (this.ast.apiLevel() > AST.JLS3) { + UnionType res = this.ast.newUnionType(); + commonSettings(res, javac); + union.getTypeAlternatives().stream() + .map(this::convertToType) + .filter(Objects::nonNull) + .forEach(res.types()::add); + return res; + } else { + Optional lastType = union.getTypeAlternatives().reverse().stream().map(this::convertToType).filter(Objects::nonNull).findFirst(); + lastType.ifPresent(a -> a.setFlags(a.getFlags() | ASTNode.MALFORMED)); + return lastType.get(); + } + } + if (javac instanceof JCArrayTypeTree jcArrayType) { + Type t = convertToType(jcArrayType.getType()); + if (t == null) { + return null; + } + ArrayType res; + if (t instanceof ArrayType childArrayType && this.ast.apiLevel > AST.JLS4_INTERNAL) { + res = childArrayType; + res.dimensions().addFirst(this.ast.newDimension()); + commonSettings(res, jcArrayType.getType()); + } else { + int dims = countDimensions(jcArrayType); + res = this.ast.newArrayType(t); + if( dims == 0 ) { + commonSettings(res, jcArrayType); + } else { + int endPos = jcArrayType.getEndPosition(this.javacCompilationUnit.endPositions); + if (endPos == -1) { + endPos = jcArrayType.pos; + } + int startPos = jcArrayType.getStartPosition(); + try { + String raw = this.rawText.substring(startPos, endPos); + int ordinalEnd = ordinalIndexOf(raw, "]", dims); + int ordinalStart = ordinalIndexOf(raw, "[", dims); + if( ordinalEnd != -1 ) { + commonSettings(res, jcArrayType, ordinalEnd + 1, true); + if( this.ast.apiLevel >= AST.JLS8_INTERNAL ) { + if( res.dimensions().size() > 0 ) { + ((Dimension)res.dimensions().get(0)).setSourceRange(startPos + ordinalStart, ordinalEnd - ordinalStart + 1); + } + } + return res; + } + } catch( Throwable tErr) { + } + commonSettings(res, jcArrayType); + } + } + return res; + } + if (javac instanceof JCTypeApply jcTypeApply) { + if( this.ast.apiLevel != AST.JLS2_INTERNAL) { + ParameterizedType res = this.ast.newParameterizedType(convertToType(jcTypeApply.getType())); + commonSettings(res, javac); + jcTypeApply.getTypeArguments().stream() + .map(this::convertToType) + .filter(Objects::nonNull) + .forEach(res.typeArguments()::add); + return res; + } else { + return convertToType(jcTypeApply.clazz); + } + } + if (javac instanceof JCWildcard wc) { + WildcardType res = this.ast.newWildcardType(); + if( wc.kind.kind == BoundKind.SUPER) { + final Type bound = convertToType(wc.inner); + res.setBound(bound, false); + } else if( wc.kind.kind == BoundKind.EXTENDS) { + final Type bound = convertToType(wc.inner); + res.setBound(bound, true); + } + commonSettings(res, javac); + return res; + } + if (javac instanceof JCTypeIntersection jcTypeIntersection) { + IntersectionType res = this.ast.newIntersectionType(); + commonSettings(res, javac); + jcTypeIntersection.getBounds().stream() + .map(this::convertToType) + .filter(Objects::nonNull) + .forEach(res.types()::add); + return res; + } + if (javac instanceof JCAnnotatedType jcAnnotatedType) { + Type res = null; + JCExpression jcpe = jcAnnotatedType.getUnderlyingType(); + if( jcAnnotatedType.getAnnotations() != null // + && !jcAnnotatedType.getAnnotations().isEmpty() // + && this.ast.apiLevel >= AST.JLS8_INTERNAL + && !(jcpe instanceof JCWildcard)) { + if( jcpe instanceof JCFieldAccess jcfa2) { + if( jcfa2.selected instanceof JCAnnotatedType || jcfa2.selected instanceof JCTypeApply) { + QualifiedType nameQualifiedType = new QualifiedType(this.ast); + commonSettings(nameQualifiedType, javac); + nameQualifiedType.setQualifier(convertToType(jcfa2.selected)); + nameQualifiedType.setName(this.ast.newSimpleName(jcfa2.name.toString())); + res = nameQualifiedType; + } else { + NameQualifiedType nameQualifiedType = new NameQualifiedType(this.ast); + commonSettings(nameQualifiedType, javac); + nameQualifiedType.setQualifier(toName(jcfa2.selected)); + nameQualifiedType.setName(this.ast.newSimpleName(jcfa2.name.toString())); + res = nameQualifiedType; + } + } else if (jcpe instanceof JCIdent simpleType) { + res = this.ast.newSimpleType(convertName(simpleType.getName())); + commonSettings(res, javac); + } + } + if (res == null) { // nothing specific + res = convertToType(jcAnnotatedType.getUnderlyingType()); + } + if (res instanceof AnnotatableType annotatableType && this.ast.apiLevel() >= AST.JLS8_INTERNAL) { + for (JCAnnotation annotation : jcAnnotatedType.getAnnotations()) { + annotatableType.annotations().add(convert(annotation)); + } + } else if (res instanceof ArrayType arrayType) { + if (this.ast.apiLevel() >= AST.JLS8 && !arrayType.dimensions().isEmpty()) { + for (JCAnnotation annotation : jcAnnotatedType.getAnnotations()) { + ((Dimension)arrayType.dimensions().get(0)).annotations().add(convert(annotation)); + } + } + } + return res; + } + if (javac instanceof JCErroneous || javac == null /* when there are syntax errors */) { + // returning null could result in upstream errors, so return a fake type + var res = this.ast.newSimpleType(this.ast.newSimpleName(FAKE_IDENTIFIER)); + if (javac instanceof JCErroneous err) { + res.setSourceRange(err.getStartPosition(), 0); + } + res.setFlags(ASTNode.RECOVERED); + return res; + } + ILog.get().warn("Not supported yet, converting to type type " + javac + " of class" + javac.getClass()); + return null; + } + public static int ordinalIndexOf(String str, String substr, int n) { + int pos = str.indexOf(substr); + while (--n > 0 && pos != -1) { + pos = str.indexOf(substr, pos + 1); + } + return pos; + } + private Code convert(TypeKind javac) { + return switch(javac) { + case BOOLEAN -> PrimitiveType.BOOLEAN; + 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) { + int startPos = javac.getStartPosition(); + int length = javac.getEndPosition(this.javacCompilationUnit.endPositions) - startPos; + String content = this.rawText.substring(startPos, startPos+length); + boolean mustUseNormalAnnot = content != null && content.contains("("); + if( javac.getArguments().size() == 0 && !mustUseNormalAnnot) { + MarkerAnnotation res = this.ast.newMarkerAnnotation(); + commonSettings(res, javac); + res.setTypeName(toName(javac.getAnnotationType())); + return res; + } else if( javac.getArguments().size() == 1 && !(javac.getArguments().get(0) instanceof JCAssign)) { + SingleMemberAnnotation result= ast.newSingleMemberAnnotation(); + commonSettings(result, javac); + result.setTypeName(toName(javac.annotationType)); + JCTree value = javac.getArguments().get(0); + if (value != null) { + if( value instanceof JCExpression jce) { + result.setValue(convertExpression(jce)); + } else { + result.setValue(toName(value)); + } + } + return result; + } else if (javac.getArguments().size() == 1 + && javac.getArguments().get(0) instanceof JCAssign namedArg + && (namedArg.getVariable().getPreferredPosition() == Position.NOPOS + || namedArg.getVariable().getPreferredPosition() == namedArg.getExpression().getPreferredPosition())) { + // actually a @Annotation(value), but returned as a @Annotation(field = value) + SingleMemberAnnotation result= ast.newSingleMemberAnnotation(); + commonSettings(result, javac); + result.setTypeName(toName(javac.annotationType)); + JCTree value = namedArg.getExpression(); + if (value != null) { + if( value instanceof JCExpression jce) { + result.setValue(convertExpression(jce)); + } else { + result.setValue(toName(value)); + } + } + return result; + } else { + NormalAnnotation res = this.ast.newNormalAnnotation(); + commonSettings(res, javac); + res.setTypeName(toName(javac.getAnnotationType())); + Iterator it = javac.getArguments().iterator(); + while(it.hasNext()) { + JCExpression expr = it.next(); + if( expr instanceof JCAssign jcass) { + if( jcass.lhs instanceof JCIdent jcid ) { + final MemberValuePair pair = new MemberValuePair(this.ast); + commonSettings(pair, jcass); + final SimpleName simpleName = new SimpleName(this.ast); + commonSettings(simpleName, jcid); + simpleName.internalSetIdentifier(new String(jcid.getName().toString())); + int start = jcid.pos; + int end = start + jcid.getName().toString().length(); + simpleName.setSourceRange(start, end - start ); + pair.setName(simpleName); + Expression value = null; + if (jcass.rhs instanceof JCNewArray jcNewArray) { + ArrayInitializer initializer = this.ast.newArrayInitializer(); + commonSettings(initializer, javac); + jcNewArray.getInitializers().stream().map(this::convertExpression).forEach(initializer.expressions()::add); + value = initializer; + } else { + value = convertExpression(jcass.rhs); + } + commonSettings(value, jcass.rhs); + pair.setValue(value); + start = value.getStartPosition(); + end = value.getStartPosition() + value.getLength() - 1; + pair.setSourceRange(start, end - start + 1); + res.values().add(pair); + } + } + } + return res; + } + } +// +// public Annotation addAnnotation(IAnnotationBinding annotation, AST ast, ImportRewriteContext context) { +// Type type = addImport(annotation.getAnnotationType(), ast, context, TypeLocation.OTHER); +// Name name; +// if (type instanceof SimpleType) { +// SimpleType simpleType = (SimpleType) type; +// name = simpleType.getName(); +// // cut 'name' loose from its parent, so that it can be reused +// simpleType.setName(ast.newName("a")); //$NON-NLS-1$ +// } else { +// name = ast.newName("invalid"); //$NON-NLS-1$ +// } +// +// IMemberValuePairBinding[] mvps= annotation.getDeclaredMemberValuePairs(); +// if (mvps.length == 0) { +// MarkerAnnotation result = ast.newMarkerAnnotation(); +// result.setTypeName(name); +// return result; +// } else if (mvps.length == 1 && "value".equals(mvps[0].getName())) { //$NON-NLS-1$ +// SingleMemberAnnotation result= ast.newSingleMemberAnnotation(); +// result.setTypeName(name); +// Object value = mvps[0].getValue(); +// if (value != null) +// result.setValue(addAnnotation(ast, value, context)); +// return result; +// } else { +// NormalAnnotation result = ast.newNormalAnnotation(); +// result.setTypeName(name); +// for (int i= 0; i < mvps.length; i++) { +// IMemberValuePairBinding mvp = mvps[i]; +// MemberValuePair mvpNode = ast.newMemberValuePair(); +// mvpNode.setName(ast.newSimpleName(mvp.getName())); +// Object value = mvp.getValue(); +// if (value != null) +// mvpNode.setValue(addAnnotation(ast, value, context)); +// result.values().add(mvpNode); +// } +// return result; +// } +// } + + private List convert(JCModifiers modifiers, ASTNode parent) { + List res = new ArrayList<>(); + convertModifiers(modifiers, parent, res); + convertModifierAnnotations(modifiers, parent, res); + sortModifierNodesByPosition(res); + return res; + } + + private void sortModifierNodesByPosition(List l) { + l.sort((o1, o2) -> { + ASTNode a1 = (ASTNode)o1; + ASTNode a2 = (ASTNode)o2; + return a1.getStartPosition() - a2.getStartPosition(); + }); + } + + private void convertModifiers(JCModifiers modifiers, ASTNode parent, List res) { + Iterator mods = modifiers.getFlags().iterator(); + while(mods.hasNext()) { + Modifier converted = convert(mods.next(), modifiers.pos, modifiers.getEndPosition(this.javacCompilationUnit.endPositions) + 1); + if (converted.getStartPosition() >= 0) { + // some modifiers are added to the list without being really part of + // the text/DOM. JDT doesn't like it, so we filter out the "implicit" + // modifiers + res.add(converted); + } + } + } + + + private List convertModifierAnnotations(JCModifiers modifiers, ASTNode parent ) { + List res = new ArrayList<>(); + convertModifierAnnotations(modifiers, parent, res); + sortModifierNodesByPosition(res); + return res; + } + + private void convertModifierAnnotations(JCModifiers modifiers, ASTNode parent, List res) { + modifiers.getAnnotations().stream().map(this::convert).forEach(res::add); + } + + private List convertModifiersFromFlags(int startPos, int endPos, long oflags) { + String rawTextSub = this.rawText.substring(startPos, endPos); + List res = new ArrayList<>(); + ModifierKeyword[] ops = { + org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.PUBLIC_KEYWORD, + org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.PROTECTED_KEYWORD, + org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.PRIVATE_KEYWORD, + org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.STATIC_KEYWORD, + org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.ABSTRACT_KEYWORD, + org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.FINAL_KEYWORD, + org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.NATIVE_KEYWORD, + org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.SYNCHRONIZED_KEYWORD, + org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.TRANSIENT_KEYWORD, + org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.VOLATILE_KEYWORD, + org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.STRICTFP_KEYWORD, + org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.DEFAULT_KEYWORD, + org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.SEALED_KEYWORD, + org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.NON_SEALED_KEYWORD + }; + for( int i = 0; i < ops.length; i++ ) { + ModifierKeyword k = ops[i]; + int flagVal = k.toFlagValue(); + if( (oflags & flagVal) > 0 ) { + Modifier m = this.ast.newModifier(k); + String asStr = k.toString(); + int foundLoc = rawTextSub.indexOf(asStr); + if( foundLoc != -1 ) { + m.setSourceRange(startPos + foundLoc, asStr.length()); + } + res.add(m); + } + } + return res; + } + + private int getJLS2ModifiersFlags(long oflags) { + int flags = 0; + if( (oflags & Flags.PUBLIC) > 0) flags += Flags.PUBLIC; + if( (oflags & Flags.PRIVATE) > 0) flags += Flags.PRIVATE; + if( (oflags & Flags.PROTECTED) > 0) flags += Flags.PROTECTED; + if( (oflags & Flags.STATIC) > 0) flags += Flags.STATIC; + if( (oflags & Flags.FINAL) > 0) flags += Flags.FINAL; + if( (oflags & Flags.SYNCHRONIZED) > 0) flags += Flags.SYNCHRONIZED; + if( (oflags & Flags.VOLATILE) > 0) flags += Flags.VOLATILE; + if( (oflags & Flags.TRANSIENT) > 0) flags += Flags.TRANSIENT; + if( (oflags & Flags.NATIVE) > 0) flags += Flags.NATIVE; + if( (oflags & Flags.INTERFACE) > 0) flags += Flags.INTERFACE; + if( (oflags & Flags.ABSTRACT) > 0) flags += Flags.ABSTRACT; + if( (oflags & Flags.STRICTFP) > 0) flags += Flags.STRICTFP; + return flags; + } + + private int getJLS2ModifiersFlagsAsStringLength(long flags) { + int len = 0; + if( (flags & Flags.PUBLIC) > 0) len += 6 + 1; + if( (flags & Flags.PRIVATE) > 0) len += 7 + 1; + if( (flags & Flags.PROTECTED) > 0) len += 9 + 1; + if( (flags & Flags.STATIC) > 0) len += 5 + 1; + if( (flags & Flags.FINAL) > 0) len += 5 + 1; + if( (flags & Flags.SYNCHRONIZED) > 0) len += 12 + 1; + if( (flags & Flags.VOLATILE) > 0) len += 8 + 1; + if( (flags & Flags.TRANSIENT) > 0) len += 9 + 1; + if( (flags & Flags.NATIVE) > 0) len += 6 + 1; + if( (flags & Flags.INTERFACE) > 0) len += 9 + 1; + if( (flags & Flags.ABSTRACT) > 0) len += 8 + 1; + if( (flags & Flags.STRICTFP) > 0) len += 8 + 1; + return len; + } + + + private ModifierKeyword modifierToKeyword(javax.lang.model.element.Modifier javac) { + return 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; + }; + } + private Modifier modifierToDom(javax.lang.model.element.Modifier javac) { + return this.ast.newModifier(modifierToKeyword(javac)); + } + private int modifierToFlagVal(javax.lang.model.element.Modifier javac) { + ModifierKeyword m = modifierToKeyword(javac); + if( m != null ) { + return m.toFlagValue(); + } + return 0; + } + + + private Modifier convert(javax.lang.model.element.Modifier javac, int startPos, int endPos) { + Modifier res = modifierToDom(javac); + if (startPos >= 0 && endPos >= startPos && endPos <= this.rawText.length()) { + int indOf = this.rawText.indexOf(res.getKeyword().toString(), startPos, endPos); + if( indOf != -1 ) { + res.setSourceRange(indOf, res.getKeyword().toString().length()); + } + } + return res; + } + + + private Name convertName(com.sun.tools.javac.util.Name javac) { + if (javac == null || Objects.equals(javac, Names.instance(this.context).error)) { + var res = this.ast.newSimpleName(FAKE_IDENTIFIER); + res.setFlags(ASTNode.RECOVERED); + return res; + } + if (Objects.equals(javac, Names.instance(this.context).empty)) { + return this.ast.newSimpleName("_"); + } + String nameString = javac.toString(); + int lastDot = nameString.lastIndexOf("."); + if (lastDot < 0) { + try { + return this.ast.newSimpleName(nameString); + } catch (IllegalArgumentException ex) { // invalid name: super, this... + var res = this.ast.newSimpleName(FAKE_IDENTIFIER); + res.setFlags(ASTNode.RECOVERED); + return res; + } + } else { + return this.ast.newQualifiedName(convertName(javac.subName(0, lastDot)), (SimpleName)convertName(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, JCTree context) { + CommentStyle style = javac.getStyle(); + if ((style == CommentStyle.JAVADOC_BLOCK || style == CommentStyle.JAVADOC_LINE) && context != null) { + var docCommentTree = this.javacCompilationUnit.docComments.getCommentTree(context); + if (docCommentTree instanceof DCDocComment dcDocComment) { + JavadocConverter javadocConverter = new JavadocConverter(this, dcDocComment, TreePath.getPath(this.javacCompilationUnit, context), this.buildJavadoc); + String raw = javadocConverter.getRawContent(); + if( !"/**/".equals(raw)) { + this.javadocConverters.add(javadocConverter); + Javadoc javadoc = javadocConverter.convertJavadoc(); + if (this.ast.apiLevel() >= AST.JLS23) { + javadoc.setMarkdown(javac.getStyle() == CommentStyle.JAVADOC_LINE); + } + this.javadocDiagnostics.addAll(javadocConverter.getDiagnostics()); + return javadoc; + } else { + style = CommentStyle.BLOCK; + } + } + } + org.eclipse.jdt.core.dom.Comment jdt = switch (style) { + case LINE -> this.ast.newLineComment(); + case BLOCK -> this.ast.newBlockComment(); + case JAVADOC_BLOCK -> this.ast.newJavadoc(); + case JAVADOC_LINE -> this.ast.newJavadoc(); + }; + javac.isDeprecated(); javac.getText(); // initialize docComment + int startPos = javac.getPos().getStartPosition(); + int endPos = javac.getPos().getEndPosition(this.javacCompilationUnit.endPositions); + jdt.setSourceRange(startPos, endPos-startPos); + return jdt; + } + + public org.eclipse.jdt.core.dom.Comment convert(Comment javac, int pos, int endPos) { + // testBug113108b expects /// comments to be Line comments, not Javadoc comments + if (javac.getStyle() == CommentStyle.JAVADOC_BLOCK || javac.getStyle() == CommentStyle.JAVADOC_LINE) { + var parser = new com.sun.tools.javac.parser.DocCommentParser(ParserFactory.instance(this.context), Log.instance(this.context).currentSource(), javac); + JavadocConverter javadocConverter = new JavadocConverter(this, parser.parse(), pos, endPos, this.buildJavadoc); + this.javadocConverters.add(javadocConverter); + Javadoc javadoc = javadocConverter.convertJavadoc(); + if (this.ast.apiLevel() >= AST.JLS23) { + javadoc.setMarkdown(javac.getStyle() == CommentStyle.JAVADOC_LINE); + } + this.javadocDiagnostics.addAll(javadocConverter.getDiagnostics()); + return javadoc; + } + org.eclipse.jdt.core.dom.Comment jdt = switch (javac.getStyle()) { + case LINE -> this.ast.newLineComment(); + case BLOCK -> this.ast.newBlockComment(); + case JAVADOC_BLOCK -> this.ast.newJavadoc(); + case JAVADOC_LINE -> this.ast.newLineComment(); + }; + javac.isDeprecated(); javac.getText(); // initialize docComment + jdt.setSourceRange(pos, endPos - pos); + return jdt; + } + + class FixPositions extends ASTVisitor { + private final String contents; + + FixPositions() { + super(true); + 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(), siblingsOf(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 && ! FAKE_IDENTIFIER.equals(name.getIdentifier())) { + int foundOffset = findPositionOfText(name.getIdentifier(), name.getParent(), siblingsOf(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 && relativeStart < modifier.getParent().getLength()) { + modifier.setSourceRange(parentStart + relativeStart, modifier.getKeyword().toString().length()); + } + return true; + } + + @Override + public void endVisit(TagElement tagElement) { + if (tagElement.getStartPosition() < 0) { + OptionalInt start = ((List)tagElement.fragments()).stream() + .filter(node -> node.getStartPosition() >= 0 && node.getLength() >= 0) + .mapToInt(ASTNode::getStartPosition) + .min(); + OptionalInt end = ((List)tagElement.fragments()).stream() + .filter(node -> node.getStartPosition() >= 0 && node.getLength() >= 0) + .mapToInt(node -> node.getStartPosition() + node.getLength()) + .max(); + if (start.isPresent() && end.isPresent()) { + if (JavadocConverter.isInline(tagElement)) { + // include some extra wrapping chars ( `{...}` or `[...]`) + // current heuristic is very approximative as it will fail with whitespace + tagElement.setSourceRange(start.getAsInt() - 1, end.getAsInt() - start.getAsInt() + 2); + } else { + tagElement.setSourceRange(start.getAsInt(), end.getAsInt() - start.getAsInt()); + } + } + } + if (TagElement.TAG_DEPRECATED.equals(tagElement.getTagName()) + && tagElement.getParent() instanceof Javadoc javadoc + && javadoc.getParent() != null) { + javadoc.getParent().setFlags(javadoc.getParent().getFlags() | ClassFileConstants.AccDeprecated); + } + } + + private int findPositionOfText(String text, ASTNode in, List excluding) { + int current = in.getStartPosition(); + PriorityQueue excluded = new PriorityQueue<>(Comparator.comparing(ASTNode::getStartPosition)); + if( current == -1 ) { + return -1; + } + if (excluded.isEmpty()) { + int position = this.contents.indexOf(text, current, current + in.getLength()); + if (position >= 0) { + return position; + } + } else { + ASTNode currentExclusion = null; + while ((currentExclusion = excluded.poll()) != null) { + if (currentExclusion.getStartPosition() >= current) { + int rangeEnd = currentExclusion.getStartPosition(); + int position = this.contents.indexOf(text, current, rangeEnd); + if (position >= 0) { + return position; + } + current = rangeEnd + currentExclusion.getLength(); + } + } + } + return -1; + } + } + + private EnumConstantDeclaration convertEnumConstantDeclaration(JCTree var, ASTNode parent, EnumDeclaration enumDecl) { + EnumConstantDeclaration enumConstantDeclaration = null; + String enumName = null; + if( var instanceof JCVariableDecl enumConstant && (enumConstant.getModifiers().flags & Flags.ENUM) != 0 ) { + if( enumConstant.getType() instanceof JCIdent jcid) { + String o = jcid.getName().toString(); + String o2 = enumDecl.getName().toString(); + if( o.equals(o2)) { + enumConstantDeclaration = new EnumConstantDeclaration(this.ast); + commonSettings(enumConstantDeclaration, enumConstant); + final SimpleName typeName = new SimpleName(this.ast); + enumName = enumConstant.getName().toString(); + typeName.internalSetIdentifier(enumName); + typeName.setSourceRange(enumConstant.getStartPosition(), Math.max(0, enumName.length())); + enumConstantDeclaration.setName(typeName); + if (enumConstant.getModifiers() != null && enumConstant.getPreferredPosition() != Position.NOPOS) { + enumConstantDeclaration.modifiers() + .addAll(convert(enumConstant.getModifiers(), enumConstantDeclaration)); + } + } + if( enumConstant.init instanceof JCNewClass jcnc ) { + if( jcnc.def instanceof JCClassDecl jccd) { + int blockStarts = jcnc.getStartPosition() + (enumName == null ? 0 : enumName.length()); + if(jcnc.getArguments() != null && !jcnc.getArguments().isEmpty() && jcnc.getArguments().get(jcnc.getArguments().length()-1) instanceof JCTree lastArg) { + blockStarts = lastArg.getEndPosition(this.javacCompilationUnit.endPositions); + } + int endPos = jcnc.getEndPosition(this.javacCompilationUnit.endPositions); + AnonymousClassDeclaration e = createAnonymousClassDeclaration(jccd, enumConstantDeclaration); + if( e != null ) { + String tmp = this.rawText.substring(blockStarts); + int bracket = tmp.indexOf("{"); + if( bracket != -1 ) { + blockStarts += bracket; + } + e.setSourceRange(blockStarts, endPos - blockStarts); + enumConstantDeclaration.setAnonymousClassDeclaration(e); + } + } + if( jcnc.getArguments() != null ) { + Iterator it = jcnc.getArguments().iterator(); + while(it.hasNext()) { + Expression e = convertExpression(it.next()); + if( e != null ) { + enumConstantDeclaration.arguments().add(e); + } + } + } + } + } + } + return enumConstantDeclaration; + } + + private static List siblingsOf(ASTNode node) { + return childrenOf(node.getParent()); + } + + public static Name toName(String val, int startPosition, AST ast) { + try { + String stripped = val.stripLeading(); + int strippedAmt = val.length() - stripped.length(); + int lastDot = stripped.lastIndexOf("."); + if( lastDot == -1 ) { + SimpleName sn = ast.newSimpleName(stripped); // TODO error here, testBug51600 + sn.setSourceRange(startPosition + strippedAmt, stripped.length()); + return sn; + } else { + SimpleName sn = ast.newSimpleName(stripped.substring(lastDot+1)); + sn.setSourceRange(startPosition + strippedAmt + lastDot+1, sn.getIdentifier().length()); + + QualifiedName qn = ast.newQualifiedName(toName(stripped.substring(0,lastDot), startPosition + strippedAmt, ast), sn); + qn.setSourceRange(startPosition + strippedAmt, stripped.length()); + return qn; + } + } catch(IllegalArgumentException iae) { + return null; + } + //return null; + } + private static List childrenOf(ASTNode node) { + return ((Collection)node.properties().values()).stream() + .filter(ASTNode.class::isInstance) + .map(ASTNode.class::cast) + .filter(Predicate.not(node::equals)) + .toList(); + } + + public DocTreePath findDocTreePath(ASTNode node) { + return this.javadocConverters.stream() + .map(javadocConverter -> javadocConverter.converted.get(node)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } + + public DocTreePath[] searchRelatedDocTreePath(MethodRef ref) { + ArrayList possibleNodes = new ArrayList<>(); + this.javadocConverters.forEach(x -> possibleNodes.addAll(x.converted.keySet())); + DocTreePath[] r = possibleNodes.stream().filter(x -> x != ref && x instanceof MethodRef mr + && mr.getName().toString().equals(ref.getName().toString()) + && Objects.equals(mr.getQualifier() == null ? null : mr.getQualifier().toString(), + ref.getQualifier() == null ? null : ref.getQualifier().toString())) + .map(x -> findDocTreePath(x)) + .toArray(size -> new DocTreePath[size]); + return r; + } + + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavadocConverter.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavadocConverter.java new file mode 100644 index 00000000000..93a3411342a --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavadocConverter.java @@ -0,0 +1,916 @@ +/******************************************************************************* + * 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.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import org.eclipse.core.runtime.ILog; + +import com.sun.source.doctree.DocTree.Kind; +import com.sun.source.util.DocTreePath; +import com.sun.source.util.TreePath; +import com.sun.tools.javac.tree.DCTree; +import com.sun.tools.javac.tree.DCTree.DCAuthor; +import com.sun.tools.javac.tree.DCTree.DCBlockTag; +import com.sun.tools.javac.tree.DCTree.DCComment; +import com.sun.tools.javac.tree.DCTree.DCDeprecated; +import com.sun.tools.javac.tree.DCTree.DCDocComment; +import com.sun.tools.javac.tree.DCTree.DCEndElement; +import com.sun.tools.javac.tree.DCTree.DCEntity; +import com.sun.tools.javac.tree.DCTree.DCErroneous; +import com.sun.tools.javac.tree.DCTree.DCIdentifier; +import com.sun.tools.javac.tree.DCTree.DCInheritDoc; +import com.sun.tools.javac.tree.DCTree.DCLink; +import com.sun.tools.javac.tree.DCTree.DCLiteral; +import com.sun.tools.javac.tree.DCTree.DCParam; +import com.sun.tools.javac.tree.DCTree.DCRawText; +import com.sun.tools.javac.tree.DCTree.DCReference; +import com.sun.tools.javac.tree.DCTree.DCReturn; +import com.sun.tools.javac.tree.DCTree.DCSee; +import com.sun.tools.javac.tree.DCTree.DCSince; +import com.sun.tools.javac.tree.DCTree.DCSnippet; +import com.sun.tools.javac.tree.DCTree.DCStartElement; +import com.sun.tools.javac.tree.DCTree.DCText; +import com.sun.tools.javac.tree.DCTree.DCThrows; +import com.sun.tools.javac.tree.DCTree.DCUnknownBlockTag; +import com.sun.tools.javac.tree.DCTree.DCUnknownInlineTag; +import com.sun.tools.javac.tree.DCTree.DCUses; +import com.sun.tools.javac.tree.DCTree.DCValue; +import com.sun.tools.javac.tree.DCTree.DCVersion; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree; +import com.sun.tools.javac.tree.TreeScanner; +import com.sun.tools.javac.util.Convert; +import com.sun.tools.javac.util.JCDiagnostic; + +import jdk.javadoc.internal.doclets.formats.html.taglets.snippet.Attribute; +import jdk.javadoc.internal.doclets.formats.html.taglets.snippet.MarkupParser; + +class JavadocConverter { + + // Both copied from jdk.javadoc.internal.doclets.formats.html.taglets.snippet.Parser + private static final Pattern JAVA_COMMENT = Pattern.compile( + "^(?.*)//(?\\s*@\\s*\\w+.+?)$"); + + private final AST ast; + private final JavacConverter javacConverter; + private final DCDocComment docComment; + private final int initialOffset; + private final int endOffset; + private boolean buildJavadoc; + private final TreePath contextTreePath; + private String rawContent; + + public final Map converted = new HashMap<>(); + + final private Set diagnostics = new HashSet<>(); + + private static final Pattern BEGIN_CHOPPER = Pattern.compile("(?:\\s+\\*)(.*)"); + + private JavadocConverter(JavacConverter javacConverter, DCDocComment docComment, TreePath contextTreePath, int initialOffset, int endPos, boolean buildJavadoc) { + this.javacConverter = javacConverter; + this.ast = javacConverter.ast; + this.docComment = docComment; + this.contextTreePath = contextTreePath; + this.buildJavadoc = buildJavadoc; + this.initialOffset = initialOffset; + this.endOffset = endPos; + } + + JavadocConverter(JavacConverter javacConverter, DCDocComment docComment, int initialOffset, int endPos, boolean buildJavadoc) { + this(javacConverter, docComment, null, initialOffset, endPos, buildJavadoc); + } + + JavadocConverter(JavacConverter javacConverter, DCDocComment docComment, TreePath contextTreePath, boolean buildJavadoc) { + this(javacConverter, docComment, contextTreePath, docComment.comment.getPos().getStartPosition(), docComment.comment.getPos().getEndPosition(javacConverter.javacCompilationUnit.endPositions), buildJavadoc); + } + + private void commonSettings(ASTNode res, DCTree javac) { + if (javac != null) { + int startPosition = this.docComment.getSourcePosition(javac.getStartPosition()); + int endPosition = this.docComment.getSourcePosition(javac.getEndPosition()); + int length = endPosition - startPosition; +// if (res instanceof TextElement) { +// length++; +// } + if (startPosition >= 0 && length >= 0) { + res.setSourceRange(startPosition, length); + } + if (this.contextTreePath != null) { + this.converted.put(res, DocTreePath.getPath(this.contextTreePath, this.docComment, javac)); + } + } + } + + public String getRawContent() { + if( this.rawContent == null ) + this.rawContent = this.javacConverter.rawText.substring(this.initialOffset, this.endOffset); + return rawContent; + } + + Javadoc convertJavadoc() { + Javadoc res = this.ast.newJavadoc(); + res.setSourceRange(this.initialOffset, this.endOffset - this.initialOffset); + try { + if( this.javacConverter.ast.apiLevel == AST.JLS2_INTERNAL) { + String rawContent = getRawContent(); + try { + res.setComment(rawContent); + } catch( IllegalArgumentException iae) { + // Ignore + } + } + if (this.buildJavadoc) { + List treeElements = Stream.of(docComment.preamble, docComment.fullBody, docComment.postamble, docComment.tags) + .flatMap(List::stream).toList(); + List elements2 = convertElementCombiningNodes(treeElements); + List elements = convertNestedTagElements(elements2); + + TagElement host = null; + for (IDocElement docElement : elements) { + if (docElement instanceof TagElement tag && !isInline(tag)) { + if (host != null) { + res.tags().add(host); + host = null; + } + res.tags().add(tag); + } else { + if (host == null) { + host = this.ast.newTagElement(); + if(docElement instanceof ASTNode astn) { + host.setSourceRange(astn.getStartPosition(), astn.getLength()); + } + } else if (docElement instanceof ASTNode extraNode && extraNode.getStartPosition() >= 0 && extraNode.getLength() >= 0){ + host.setSourceRange(host.getStartPosition(), extraNode.getStartPosition() + extraNode.getLength() - host.getStartPosition()); + } + + host.fragments().add(docElement); + } + } + if (host != null) { + res.tags().add(host); + } + } + } catch (Exception ex) { + ILog.get().error("Failed to convert Javadoc", ex); + } + return res; + } + + private List convertNestedTagElements(List elements2) { + return elements2.stream().map(x -> { + if( x instanceof TextElement te) { + String s = te.getText(); + if( s != null && s.startsWith("{@") && s.trim().endsWith("}")) { + String txt = this.javacConverter.rawText.substring(te.getStartPosition(), te.getStartPosition() + te.getLength()); + TextElement innerMost = this.ast.newTextElement(); + innerMost.setSourceRange(te.getStartPosition()+2, te.getLength()-3); + innerMost.setText(txt.substring(2, txt.length() - 1)); + + TagElement nested = this.ast.newTagElement(); + int atLoc = txt.indexOf("@"); + String name = atLoc == -1 ? txt : ("@" + txt.substring(atLoc + 1)).split("\\s+")[0]; + nested.setTagName(name); + nested.setSourceRange(te.getStartPosition(), te.getLength()); + nested.fragments().add(innerMost); + + TagElement wrapper = this.ast.newTagElement(); + wrapper.setSourceRange(te.getStartPosition(), te.getLength()); + wrapper.fragments().add(nested); + return wrapper; + } + } + return x; + }).toList(); + } + + Set getDiagnostics() { + return diagnostics; + } + + static boolean isInline(TagElement tag) { + return tag.getTagName() == null || switch (tag.getTagName()) { + case TagElement.TAG_CODE, + TagElement.TAG_DOCROOT, + TagElement.TAG_INHERITDOC, + TagElement.TAG_LINK, + TagElement.TAG_LINKPLAIN, + TagElement.TAG_LITERAL, + TagElement.TAG_SNIPPET, + TagElement.TAG_VALUE -> true; + default -> false; + }; + } + + private Optional convertBlockTag(DCTree javac) { + TagElement res = this.ast.newTagElement(); + commonSettings(res, javac); + if (javac instanceof DCAuthor author) { + res.setTagName(TagElement.TAG_AUTHOR); + convertElementCombiningNodes(author.name.stream().filter(x -> x != null).toList()).forEach(res.fragments::add); + } else if (javac instanceof DCSince since) { + res.setTagName(TagElement.TAG_SINCE); + convertElementCombiningNodes(since.body.stream().filter(x -> x != null).toList()).forEach(res.fragments::add); + } else if (javac instanceof DCVersion version) { + res.setTagName(TagElement.TAG_VERSION); + convertElementCombiningNodes(version.body.stream().filter(x -> x != null).toList()).forEach(res.fragments::add); + } else if (javac instanceof DCSee see) { + res.setTagName(TagElement.TAG_SEE); + convertElementCombiningNodes(see.reference.stream().filter(x -> x != null).toList()).forEach(res.fragments::add); + } else if (javac instanceof DCDeprecated deprecated) { + res.setTagName(TagElement.TAG_DEPRECATED); + convertElementCombiningNodes(deprecated.body.stream().filter(x -> x != null).toList()).forEach(res.fragments::add); + } else if (javac instanceof DCParam param) { + res.setTagName(TagElement.TAG_PARAM); + int tagNameEnds = javac.getStartPosition() + res.getTagName().length(); + if( param.isTypeParameter()) { + int stopSearchRelative = param.getEndPosition(); + if( param.description != null && param.description.size() > 0 ) { + stopSearchRelative = param.description.get(0).getEndPosition(); + } + int stopSearchAbsolute = this.docComment.getSourcePosition(stopSearchRelative); + int start = this.docComment.getSourcePosition(param.getStartPosition()); + int ltRaw = this.javacConverter.rawText.indexOf("<", start, stopSearchAbsolute); + int gtRaw = this.javacConverter.rawText.indexOf(">", start, stopSearchAbsolute); + if( ltRaw != -1 ) { + int ltStart = this.docComment.getSourcePosition(tagNameEnds+1); + // must include spaces + Region r = new Region(ltStart, 1 + (ltRaw - ltStart)); + res.fragments().add(toTextElement(r)); + res.fragments().addAll(convertElement(param.name).toList()); + } else { + res.fragments().addAll(convertElement(param.name).toList()); + } + if( gtRaw != -1 ) { + Region r = new Region(gtRaw, 1); + res.fragments().add(toTextElement(r)); + } + } else { + res.fragments().addAll(convertElement(param.name).toList()); + } + convertElementCombiningNodes(param.description.stream().filter(x -> x != null).toList()).forEach(res.fragments::add); + } else if (javac instanceof DCReturn ret) { + res.setTagName(TagElement.TAG_RETURN); + convertElementCombiningNodes(ret.description.stream().filter(x -> x != null).toList()).forEach(res.fragments::add); + } else if (javac instanceof DCThrows thrown) { + String tagName = thrown.kind == Kind.THROWS ? TagElement.TAG_THROWS : TagElement.TAG_EXCEPTION; + res.setTagName(tagName); + res.fragments().addAll(convertElement(thrown.name).toList()); + convertElementCombiningNodes(thrown.description.stream().filter(x -> x != null).toList()).forEach(res.fragments::add); + } else if (javac instanceof DCUses uses) { + res.setTagName(TagElement.TAG_USES); + // According to SemanticTokensHandlerTest.testSemanticTokens_Modules, + // we want directly a TextElement rather than a name here + //res.fragments().addAll(convertElement(uses.serviceType).toList()); + TextElement serviceName = this.ast.newTextElement(); + serviceName.setText(uses.serviceType.getSignature()); + commonSettings(serviceName, uses.serviceType); + res.fragments().add(serviceName); + convertElementCombiningNodes(uses.description.stream().filter(x -> x != null).toList()).forEach(res.fragments::add); + } else if (javac instanceof DCUnknownBlockTag unknown) { + res.setTagName("@" + unknown.getTagName()); + convertElementCombiningNodes(unknown.content.stream().filter(x -> x != null).toList()).forEach(res.fragments::add); + } else { + return Optional.empty(); + } + if( res != null ) { + if( res.fragments().size() != 0 ) { + // Make sure the tag wrapper has a proper source range + ASTNode lastFrag = ((ASTNode)res.fragments().get(res.fragments().size() - 1)); + int trueEnd = lastFrag.getStartPosition() + lastFrag.getLength(); + if( trueEnd > (res.getStartPosition() + res.getLength())) { + res.setSourceRange(res.getStartPosition(), trueEnd - res.getStartPosition()); + } + } + } + return Optional.of(res); + } + + + + private Stream convertInlineTag(DCTree javac) { + ArrayList collector = new ArrayList<>(); + TagElement res = this.ast.newTagElement(); + commonSettings(res, javac); + collector.add(res); + if (javac instanceof DCLiteral literal) { + res.setTagName(switch (literal.getKind()) { + case CODE -> TagElement.TAG_CODE; + case LITERAL -> TagElement.TAG_LITERAL; + default -> TagElement.TAG_LITERAL; + }); + res.fragments().addAll(convertElement(literal.body).toList()); + } else if (javac instanceof DCLink link) { + res.setTagName(switch (link.getKind()) { + case LINK -> TagElement.TAG_LINK; + case LINK_PLAIN -> TagElement.TAG_LINKPLAIN; + default -> TagElement.TAG_LINK; + }); + res.fragments().addAll(convertElement(link.ref).toList()); + link.label.stream().flatMap(this::convertElement).forEach(res.fragments()::add); + } else if (javac instanceof DCValue dcv) { + res.setTagName(TagElement.TAG_VALUE); + res.fragments().addAll(convertElement(dcv.ref).toList()); + } else if (javac instanceof DCInheritDoc inheritDoc) { + res.setTagName(TagElement.TAG_INHERITDOC); + } else if (javac instanceof DCSnippet snippet) { + res.setTagName(TagElement.TAG_SNIPPET); + res.setProperty(TagProperty.TAG_PROPERTY_SNIPPET_IS_VALID, true); + res.fragments().addAll(splitLines(snippet.body, true) + .map(this::toSnippetFragment) + .toList()); + } else if (javac instanceof DCUnknownInlineTag unknown) { + res.fragments().add(toDefaultTextElement(unknown)); + } else { + return Stream.empty(); + } + return collector.stream(); + } + + private Name toName(JCTree expression, int parentOffset) { + Name n = this.javacConverter.toName(expression, (dom, javac) -> { + int start = parentOffset + javac.getStartPosition(); + int length = javac.toString().length(); + dom.setSourceRange(start, Math.max(0,length)); + this.javacConverter.domToJavac.put(dom, javac); + }); + // We need to clean all the sub-names + if( n instanceof QualifiedName qn ) { + SimpleName sn = qn.getName(); + if( sn.getStartPosition() == 0 || sn.getStartPosition() == -1) { + int qnEnd = qn.getStartPosition() + qn.getLength(); + int start = qnEnd - sn.toString().length(); + sn.setSourceRange(start, qnEnd-start); + } + cleanNameQualifierLocations(qn); + } + return n; + } + + private void cleanNameQualifierLocations(QualifiedName qn) { + Name qualifier = qn.getQualifier(); + if( qualifier != null ) { + qualifier.setSourceRange(qn.getStartPosition(), qualifier.toString().length()); + if( qualifier instanceof QualifiedName qn2) { + cleanNameQualifierLocations(qn2); + } + } + } + + private class Region { + final int startOffset; + final int length; + + Region(int startOffset, int length) { + this.startOffset = startOffset; + this.length = length; + } + + String getContents() { + return JavadocConverter.this.javacConverter.rawText.substring(this.startOffset, this.startOffset + this.length); + } + + public int endPosition() { + return this.startOffset + this.length; + } + } + + private TextElement toTextElement(Region line) { + TextElement res = this.ast.newTextElement(); + String suggestedText = this.javacConverter.rawText.substring(line.startOffset, line.startOffset + line.length); + String strippedLeading = suggestedText.stripLeading(); + int leadingWhitespace = suggestedText.length() - strippedLeading.length(); + res.setSourceRange(line.startOffset + leadingWhitespace, line.length - leadingWhitespace); + res.setText(strippedLeading); + return res; + } + + private TextElement toTextElementPreserveWhitespace(Region line) { + TextElement res = this.ast.newTextElement(); + res.setSourceRange(line.startOffset, line.length); + res.setText(line.getContents()); + return res; + } + + private Stream splitLines(DCText text, boolean keepWhitespaces) { + return splitLines(text.getBody(), text.getStartPosition(), text.getEndPosition(), keepWhitespaces); + } + + private Stream splitLines(String body, int startPos, int endPos, boolean keepWhitespaces) { + String[] bodySplit = body.split("\n"); + ArrayList regions = new ArrayList<>(); + int workingIndexWithinComment = startPos; + for( int i = 0; i < bodySplit.length; i++ ) { + int lineStart = this.docComment.getSourcePosition(workingIndexWithinComment); + int lineEnd = this.docComment.getSourcePosition(workingIndexWithinComment + bodySplit[i].length()); + if (!keepWhitespaces) { + String tmp = this.javacConverter.rawText.substring(lineStart, lineEnd); + int leadingWhite = tmp.length() - tmp.stripLeading().length(); + Region r = new Region(lineStart + leadingWhite, lineEnd - lineStart - leadingWhite); + regions.add(r); + } else { +// if (lineEnd < this.javacConverter.rawText.length() && this.javacConverter.rawText.charAt(lineEnd) == '\n') { +// lineEnd++; +// } + regions.add(new Region(lineStart, lineEnd - lineStart)); + } + workingIndexWithinComment += bodySplit[i].length() + 1; + } + return regions.stream(); + } + + private Stream splitLines(DCTree[] allPositions) { + if( allPositions.length > 0 ) { + int[] startPosition = { this.docComment.getSourcePosition(allPositions[0].getStartPosition()) }; + int lastNodeStart = this.docComment.getSourcePosition(allPositions[allPositions.length - 1].getStartPosition()); + int endPosition = this.docComment.getSourcePosition(allPositions[allPositions.length - 1].getEndPosition()); + if( allPositions[allPositions.length-1] instanceof DCText dct) { + String lastText = dct.text; + String lastTextFromSrc = this.javacConverter.rawText.substring(lastNodeStart, endPosition); + if( !lastTextFromSrc.equals(lastText)) { + // We need to fix this. There might be unicode in here + String convertedText = Convert.escapeUnicode(lastText); + if( convertedText.startsWith(lastTextFromSrc)) { + endPosition = lastNodeStart + convertedText.length(); + } + } + } + String sub = this.javacConverter.rawText.substring(startPosition[0], endPosition); + String[] split = sub.split("(\r)?\n\\s*[*][ \t]*"); + List regions = new ArrayList<>(); + for( int i = 0; i < split.length; i++ ) { + int index = this.javacConverter.rawText.indexOf(split[i], startPosition[0]); + if (index >= 0) { + regions.add(new Region(index, split[i].length())); + startPosition[0] = index + split[i].length(); + } + } + return regions.stream(); + } + return Stream.empty(); + } + + private IDocElement /* TextElement or TagElement for highlight/link... */ toSnippetFragment(Region region) { + TextElement defaultElement = toTextElementPreserveWhitespace(region); + if (!defaultElement.getText().endsWith("\n")) { + defaultElement.setText(defaultElement.getText() + '\n'); + } + String line = region.getContents(); + Matcher markedUpLine = JAVA_COMMENT.matcher(line); + if (!markedUpLine.matches()) { + return defaultElement; + } + int markupStart = markedUpLine.start("markup"); + String markup = line.substring(markupStart); + MarkupParser markupParser = new MarkupParser(null); + try { + List tags = markupParser.parse(markup); + if (tags.isEmpty()) { + return defaultElement; + } + TextElement initialTextElement = this.ast.newTextElement(); + initialTextElement.setSourceRange(region.startOffset, markupStart - 2 /* 2 is length of `//` */); + initialTextElement.setText(line.substring(0, markupStart - 2) + '\n'); + IDocElement currentElement = initialTextElement; + Class tagClass = tags.getFirst().getClass(); + Field nameField = tagClass.getDeclaredField("name"); //$NON-NLS-1$ + nameField.setAccessible(true); + Field attributesFields = tagClass.getDeclaredField("attributes"); //$NON-NLS-1$ + attributesFields.setAccessible(true); + for (Object tag : tags) { + String name = (String)nameField.get(tag); + List attributes = (List)attributesFields.get(tag); + TagElement newElement = this.ast.newTagElement(); + newElement.setSourceRange(region.startOffset, region.length); + newElement.setTagName('@' + name); + newElement.setProperty(TagProperty.TAG_PROPERTY_SNIPPET_INLINE_TAG_COUNT, 1); // TODO what? + attributes.stream().map(this::toTagProperty).forEach(newElement.tagProperties()::add); + newElement.fragments().add(currentElement); + currentElement = newElement; + } + return currentElement; + } catch (Exception ex) { + ILog.get().error("While trying to build snippet line " + line + ": " + ex.getMessage(), ex); + } + return defaultElement; + } + private TagProperty toTagProperty(Attribute snippetMarkupAttribute) { + TagProperty res = this.ast.newTagProperty(); + try { + Field name = Attribute.class.getDeclaredField("name"); //$NON-NLS-1$ + name.setAccessible(true); + res.setName((String)name.get(snippetMarkupAttribute)); + Field value = snippetMarkupAttribute.getClass().getDeclaredField("value"); //$NON-NLS-1$ + value.setAccessible(true); + res.setStringValue((String)value.get(snippetMarkupAttribute)); + } catch (Exception ex) { + ILog.get().error(ex.getMessage(), ex); + } + return res; + } + + private Stream convertElementGroup(DCTree[] javac) { + return splitLines(javac).filter(x -> x.length != 0).flatMap(this::toTextOrTag); + } + private Stream toTextOrTag(Region line) { + String suggestedText = this.javacConverter.rawText.substring(line.startOffset, line.startOffset + line.length); + TextElement postElement = null; + if( suggestedText.startsWith("{@")) { + int closeBracket = suggestedText.indexOf("}"); + int firstWhite = findFirstWhitespace(suggestedText); + if( closeBracket > firstWhite && firstWhite != -1 ) { + Region postRegion = new Region(line.startOffset + closeBracket + 1, line.length - closeBracket - 1); + if( postRegion.length > 0 ) + postElement = toTextElement(postRegion); + String tagName = suggestedText.substring(1, firstWhite).trim(); + TagElement res = this.ast.newTagElement(); + res.setTagName(tagName); + res.fragments.add(toTextElementPreserveWhitespace(new Region(line.startOffset + firstWhite, closeBracket - firstWhite))); + res.setSourceRange(line.startOffset, closeBracket + 1); + if( postElement == null ) + return Stream.of(res); + else + return Stream.of(res, postElement); + } + } + + return Stream.of(toTextElement(line)); + } + + private int findFirstWhitespace(String s) { + int len = s.length(); + for (int index = 0; index < len; index++) { + if (Character.isWhitespace(s.charAt(index))) { + return index; + } + } + return -1; + } + + private List convertElementCombiningNodes(List treeElements) { + List elements = new ArrayList<>(); + List combinable = new ArrayList<>(); + int size = treeElements.size(); + for( int i = 0; i < size; i++ ) { + boolean shouldCombine = false; + boolean lineBreakBefore = false; + DCTree oneTree = treeElements.get(i); + if(oneTree instanceof DCText || oneTree instanceof DCStartElement || oneTree instanceof DCEndElement || oneTree instanceof DCEntity) { + shouldCombine = true; + if((oneTree instanceof DCText dct && dct.text.startsWith("\n")) + || (oneTree instanceof DCRawText raw && raw.getContent().endsWith("\n"))) { + lineBreakBefore = true; + } + } else if( oneTree instanceof DCErroneous derror) { + Stream de = convertDCErroneousElement(derror); + if( de == null ) { + shouldCombine = true; + if( derror.body.startsWith("{@")) { + lineBreakBefore = true; + } + } + } + + if( lineBreakBefore || !shouldCombine) { + if( combinable.size() > 0 ) { + elements.addAll(convertElementGroup(combinable.toArray(new DCTree[0])).toList()); + combinable.clear(); + } + } + + if( shouldCombine ) { + combinable.add(oneTree); + } else { + elements.addAll(convertElement(oneTree).toList()); + } + } + if( combinable.size() > 0 ) + elements.addAll(convertElementGroup(combinable.toArray(new DCTree[0])).toList()); + return elements; + } + private Stream convertElement(DCTree javac) { + if (javac instanceof DCText text) { + return splitLines(text, false).map(this::toTextElement); + } else if (javac instanceof DCRawText rawText) { + TextElement element = this.ast.newTextElement(); + commonSettings(element, javac); + element.setText(rawText.getContent()); + return Stream.of(element); + } else if (javac instanceof DCIdentifier identifier) { + Name res = this.ast.newName(identifier.getName().toString()); + commonSettings(res, javac); + return Stream.of(res); + } else if (javac instanceof DCReference reference) { + return convertReference(javac, reference); + } else if (javac instanceof DCStartElement || javac instanceof DCEndElement || javac instanceof DCEntity) { + return Stream.of(toDefaultTextElement(javac)); + } else if (javac instanceof DCBlockTag || javac instanceof DCReturn) { + Optional> blockTag = convertBlockTag(javac).map(Stream::of); + if (blockTag.isPresent()) { + return blockTag.get(); + } + } else if (javac instanceof DCErroneous erroneous) { + Stream docE = convertDCErroneousElement(erroneous); + if( docE != null ) { + return docE; + } + TextElement res = this.ast.newTextElement(); + commonSettings(res, erroneous); + res.setText(erroneous.body); + diagnostics.add(erroneous.diag); + return Stream.of(res); + } else if (javac instanceof DCComment comment) { + TextElement res = this.ast.newTextElement(); + commonSettings(res, comment); + res.setText(res.text); + return Stream.of(res); + } else { + Stream inlineTag = convertInlineTag(javac); + return inlineTag; + } + var message = "💥🐛 Not supported yet conversion of " + javac.getClass().getSimpleName() + " to element"; + ILog.get().error(message); + JavaDocTextElement res = this.ast.newJavaDocTextElement(); + commonSettings(res, javac); + res.setText(this.docComment.comment.getText().substring(javac.getStartPosition(), javac.getEndPosition()) + System.lineSeparator() + message); + return Stream.of(res); + } + + private Stream convertReference(DCTree javac, DCReference reference) { + String signature = reference.getSignature(); + if (reference.memberName == null) { + if( reference.qualifierExpression != null ) { + Name res = convertReferenceToNameOnly(reference); + return Stream.of(res); + } + } else if (reference.memberName != null) { + if (signature.charAt(signature.length() - 1) == ')') { + return Stream.of(convertMemberReferenceWithParens(reference)); + } else { + return Stream.of(convertReferenceToMemberRef(reference)); + } + } + // just return it as text + int startPosition = this.docComment.getSourcePosition(reference.getPreferredPosition()); + TextElement res = this.ast.newTextElement(); + res.setText(signature); + res.setSourceRange(startPosition, reference.getEndPos() - reference.pos); + return Stream.of(res); + } + + private IDocElement convertMemberReferenceWithParens(DCReference reference) { + MethodRef res = this.ast.newMethodRef(); + commonSettings(res, reference); + int currentOffset = this.docComment.getSourcePosition(reference.getStartPosition()); + + // TODO missing module reference + if (reference.qualifierExpression != null) { + Name qualifierExpressionName = toName(reference.qualifierExpression, res.getStartPosition()); + qualifierExpressionName.setSourceRange(currentOffset, Math.max(0, reference.qualifierExpression.toString().length())); + res.setQualifier(qualifierExpressionName); + currentOffset += qualifierExpressionName.getLength(); + } + currentOffset++; // # + SimpleName name = this.ast.newSimpleName(reference.memberName.toString()); + name.setSourceRange(currentOffset, Math.max(0, reference.memberName.toString().length())); + currentOffset += name.getLength(); + res.setName(name); + if (this.contextTreePath != null) { + this.converted.put(name, DocTreePath.getPath(this.contextTreePath, this.docComment, reference)); + } + currentOffset++; // ( + final int paramListOffset = currentOffset; + List params = new ArrayList<>(); + int separatorOffset = currentOffset; + while (separatorOffset < res.getStartPosition() + res.getLength() + && this.javacConverter.rawText.charAt(separatorOffset) != ')') { + while (separatorOffset < res.getStartPosition() + res.getLength() + && this.javacConverter.rawText.charAt(separatorOffset) != ')' + && this.javacConverter.rawText.charAt(separatorOffset) != ',') { + separatorOffset++; + } + params.add(new Region(currentOffset, separatorOffset - currentOffset)); + separatorOffset++; // consume separator + currentOffset = separatorOffset; + } + for (int i = 0; i < reference.paramTypes.size(); i++) { + JCTree type = reference.paramTypes.get(i); + Region range = i < params.size() ? params.get(i) : null; + res.parameters().add(toMethodRefParam(type, range, paramListOffset)); + } + return res; + } + + private IDocElement convertReferenceToMemberRef(DCReference reference) { + MemberRef res = this.ast.newMemberRef(); + commonSettings(res, reference); + if (this.contextTreePath != null) { + this.converted.put(res, DocTreePath.getPath(this.contextTreePath, this.docComment, reference)); + } + + int startPos = this.docComment.getSourcePosition(reference.pos); + int memberNameStart = startPos; + + Name qualifier = convertReferenceToNameOnly(reference); + if( qualifier != null ) { + res.setQualifier(qualifier); + memberNameStart = qualifier.getStartPosition() + qualifier.getLength() + 1; + } + if( reference.memberName != null ) { + SimpleName name = this.ast.newSimpleName(reference.memberName.toString()); + name.setSourceRange(memberNameStart, Math.max(0, reference.memberName.toString().length())); + res.setName(name); + } + return res; + } + + private Name convertReferenceToNameOnly(DCReference reference) { + int startPos = this.docComment.getSourcePosition(reference.getStartPosition()); + if (reference.qualifierExpression != null) { + int moduleQualifierLen = 0; + if( reference.moduleName != null) { + moduleQualifierLen = this.javacConverter.commonSettingsGetLength(null, reference.moduleName) + 1; + } + Name qualifierExpressionName = toName(reference.qualifierExpression, startPos + moduleQualifierLen); + int len = Math.max(0, reference.qualifierExpression.toString().length()); + qualifierExpressionName.setSourceRange(startPos + moduleQualifierLen, len); + if( reference.moduleName == null ) { + return qualifierExpressionName; + } else { + ModuleQualifiedName mqn = new ModuleQualifiedName(this.ast); + Name moduleName = toName(reference.moduleName, startPos); + moduleName.setSourceRange(startPos, moduleQualifierLen); + mqn.setModuleQualifier(moduleName); + mqn.setName(qualifierExpressionName); + mqn.setSourceRange(startPos, qualifierExpressionName.getStartPosition() + qualifierExpressionName.getLength() - startPos); + return mqn; + } + } + return null; + } + + // Return a stream, or null if empty + private Stream convertDCErroneousElement(DCErroneous erroneous) { + String body = erroneous.body; + MethodRef match = null; + try { + match = matchesMethodReference(erroneous, body); + } catch(Exception e) { + // ignore + } + int start = this.docComment.getSourcePosition(erroneous.getStartPosition()); + int endInd = erroneous.getEndPosition(); + int endPosition = this.docComment.getSourcePosition(endInd); + if( match != null) { + TagElement res = this.ast.newTagElement(); + res.setTagName(TagElement.TAG_SEE); + res.fragments.add(match); + res.setSourceRange(start, endPosition - start); + return Stream.of(res); + } else if( body.startsWith("@")) { + TagElement res = this.ast.newTagElement(); + String tagName = body.split("\\s+")[0]; + res.setTagName(tagName); + int newStart = erroneous.getStartPosition() + tagName.length(); + List l = splitLines(body.substring(tagName.length()), newStart, endInd, false).map(x -> toTextElement(x)).toList(); + res.fragments.addAll(l); + res.setSourceRange(start, endPosition - start); + return Stream.of(res); +// } else if( body.startsWith("{@")) { +// return convertElementGroup(new DCTree[] {erroneous}); + } + return null; + } + + private MethodRef matchesMethodReference(DCErroneous tree, String body) { + if( body.startsWith("@see")) { + String value = body.substring(4); + int hash = value.indexOf("#"); + if( hash != -1 ) { + int startPosition = this.docComment.getSourcePosition(tree.getStartPosition()) + 4; + String prefix = value.substring(0, hash); + int link = prefix.indexOf("@link"); + if (link != -1) { + prefix = prefix.substring(link + 5); + startPosition = startPosition + link + 5; + } + MethodRef ref = this.ast.newMethodRef(); + if( prefix != null && !prefix.isBlank()) { + Name n = toName(prefix, startPosition); + ref.setQualifier(n); + } + String suffix = hash+1 > value.length() ? "" : value.substring(hash+1); + if( suffix.indexOf("(") != -1 ) { + String qualifiedMethod = suffix.substring(0, suffix.indexOf("(")); + int methodNameStart = qualifiedMethod.lastIndexOf(".") + 1; + String methodName = qualifiedMethod.substring(methodNameStart); + SimpleName sn = (SimpleName)toName(methodName, startPosition + prefix.length() + 1 + methodNameStart); + ref.setName(sn); + commonSettings(ref, tree); + diagnostics.add(tree.diag); + return ref; + } + } + } + return null; + } + private Name toName(String val, int startPosition) { + return JavacConverter.toName(val, startPosition, this.ast); + } + + + private TextElement toDefaultTextElement(DCTree javac) { + TextElement res = this.ast.newTextElement(); + commonSettings(res, javac); + String r = this.docComment.comment.getText(); + String s1 = r.substring(javac.getStartPosition(), javac.getEndPosition()); + res.setText(s1); + return res; + } + + private MethodRefParameter toMethodRefParam(JCTree type, Region range, int paramListOffset) { + MethodRefParameter res = this.ast.newMethodRefParameter(); + res.setSourceRange( + range != null ? range.startOffset : paramListOffset + type.getStartPosition(), + range != null ? range.length : type.toString().length()); + // Temporary replace relative positons for types in javadoc by absolute of convertToType is bogus + new TreeScanner() { + @Override + public void scan(JCTree tree) { + tree.setPos(tree.pos + paramListOffset); + super.scan(tree); + } + }.scan(type); + + String[] segments = Stream.of(range.getContents().split("\n")) + .map(t -> { + Matcher m = BEGIN_CHOPPER.matcher(t); + if (m.find()) { + return m.group(1); + } + return t; + }) + .map(String::trim) + .flatMap(t -> Stream.of(t.split("\s"))) + .filter(t -> !t.isEmpty()) + .toArray(String[]::new); + + Type jdtType = null; + if( segments.length > 0 && segments[segments.length-1].endsWith("...")) { + res.setVarargs(true); + if( type instanceof JCArrayTypeTree att) { + jdtType = this.javacConverter.convertToType(att.getType()); + } + } + if( jdtType == null ) { + jdtType = this.javacConverter.convertToType(type); + } + res.setType(jdtType); + // revert position types to origin as some nodes might be shared and reused + new TreeScanner() { + @Override + public void scan(JCTree tree) { + tree.setPos(tree.pos - paramListOffset); + super.scan(tree); + } + }.scan(type); + + // some lengths may be missing + jdtType.accept(new ASTVisitor() { + @Override + public void preVisit(ASTNode node) { + if (node.getLength() == 0 && node.getStartPosition() >= 0) { + node.setSourceRange(node.getStartPosition(), node.toString().length()); + } + super.preVisit(node); + } + }); + if (jdtType.getStartPosition() + jdtType.getLength() < res.getStartPosition() + res.getLength()) { + if (segments.length > 1) { + String nameSegment = segments[segments.length - 1]; + SimpleName name = this.ast.newSimpleName(nameSegment); + name.setSourceRange(this.javacConverter.rawText.lastIndexOf(nameSegment, range.endPosition()), nameSegment.length()); + res.setName(name); + } + } + return res; + } +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionContext.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionContext.java new file mode 100644 index 00000000000..9c456fbd60d --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionContext.java @@ -0,0 +1,436 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Gayan Perera - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.codeassist; + +import java.util.Arrays; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.eclipse.core.runtime.ILog; +import org.eclipse.jdt.core.CompletionContext; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.ITypeRoot; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.Signature; +import org.eclipse.jdt.core.compiler.CharOperation; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; +import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; +import org.eclipse.jdt.core.dom.Block; +import org.eclipse.jdt.core.dom.BodyDeclaration; +import org.eclipse.jdt.core.dom.CharacterLiteral; +import org.eclipse.jdt.core.dom.ClassInstanceCreation; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.IVariableBinding; +import org.eclipse.jdt.core.dom.ImportDeclaration; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.NodeFinder; +import org.eclipse.jdt.core.dom.NumberLiteral; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.Statement; +import org.eclipse.jdt.core.dom.StringLiteral; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.core.dom.VariableDeclaration; +import org.eclipse.jdt.internal.codeassist.DOMCompletionEngine.Bindings; +import org.eclipse.jdt.internal.codeassist.impl.AssistOptions; +import org.eclipse.jdt.internal.compiler.parser.RecoveryScanner; + +class DOMCompletionContext extends CompletionContext { + private final int offset; + private final char[] token; + private final IJavaElement enclosingElement; + private final Supplier> bindingsAcquirer; + final ExpectedTypes expectedTypes; + private boolean inJavadoc = false; + final ASTNode node; + private String textContent; + private boolean isJustAfterStringLiteral; + + DOMCompletionContext(CompilationUnit domUnit, ITypeRoot modelUnit, String textContent, int offset, AssistOptions assistOptions, Bindings bindings) { + this.textContent = textContent; + this.offset = offset; + // Use the raw text to walk back the offset to the first non-whitespace spot + int adjustedOffset = this.offset; + if (adjustedOffset >= textContent.length()) { + adjustedOffset = textContent.length() - 1; + } + if (adjustedOffset + 1 >= textContent.length() + || !Character.isJavaIdentifierStart(textContent.charAt(adjustedOffset))) { + while (adjustedOffset > 0 && Character.isWhitespace(textContent.charAt(adjustedOffset - 1)) ) { + adjustedOffset--; + } + } + ASTNode previousNodeBeforeWhitespaces = NodeFinder.perform(domUnit, adjustedOffset, 0); +// if (cuBuffer.getChar(adjustedOffset - 1) == ',' && Character.isWhitespace(cuBuffer.getChar(adjustedOffset))) { +// // probably an empty parameter +// adjustedOffset = this.offset; +// while (adjustedOffset < cuBuffer.getLength() && Character.isWhitespace(cuBuffer.getChar(adjustedOffset))) { +// adjustedOffset++; +// } +// } + this.node = previousNodeBeforeWhitespaces instanceof SimpleName || previousNodeBeforeWhitespaces instanceof StringLiteral || previousNodeBeforeWhitespaces instanceof CharacterLiteral || previousNodeBeforeWhitespaces instanceof NumberLiteral + ? NodeFinder.perform(domUnit, this.offset, 0) // keep default node from initial offset + : previousNodeBeforeWhitespaces; // use previous node + this.expectedTypes = new ExpectedTypes(assistOptions, this.node, offset); + this.token = tokenBefore(this.textContent).toCharArray(); + this.enclosingElement = computeEnclosingElement(domUnit, modelUnit); + this.bindingsAcquirer = bindings::all; + this.isJustAfterStringLiteral = this.node instanceof StringLiteral && this.node.getLength() > 1 && this.offset >= this.node.getStartPosition() + this.node.getLength() && textContent.charAt(this.offset - 1) == '"'; + } + + private String tokenBefore(String str) { + int position = Math.min(this.offset, str.length()) - 1; + StringBuilder builder = new StringBuilder(); + while (position >= 0 && Character.isJavaIdentifierPart(str.charAt(position))) { + builder.append(str.charAt(position)); + position--; + } + builder.reverse(); + return builder.toString(); + } + + private IJavaElement computeEnclosingElement(CompilationUnit domUnit, ITypeRoot modelUnit) { + if (modelUnit == null) { + return null; + } + IJavaElement enclosingElement1 = modelUnit; + try { + enclosingElement1 = modelUnit.getElementAt(this.offset); + } catch (JavaModelException e) { + ILog.get().error(e.getMessage(), e); + } + if (enclosingElement1 == null) { + return modelUnit; + } + // then refine to get "resolved" element from the matching binding + // pitfall: currently resolve O(depth(node)) bindings while we can + // most likely find a O(1) solution + ASTNode node2 = NodeFinder.perform(domUnit, this.offset, 0); + while (node2 != null) { + IBinding binding = resolveBindingForContext(node2); + if (binding != null) { + IJavaElement bindingBasedJavaElement = binding.getJavaElement(); + if (enclosingElement1.equals(bindingBasedJavaElement)) { + return bindingBasedJavaElement; + } + } + node2 = node2.getParent(); + } + return enclosingElement1; + } + + private IBinding resolveBindingForContext(ASTNode nodep) { + var res = DOMCodeSelector.resolveBinding(nodep); + if (res != null) { + return res; + } + // Some declaration types are intentionally skipped by + // DOMCodeSelector.resolveBinding() as they're not + // expected by `codeSelect` add them here + if (nodep instanceof TypeDeclaration typeDecl) { + return typeDecl.resolveBinding(); + } + if (nodep instanceof MethodDeclaration methodDecl) { + return methodDecl.resolveBinding(); + } + if (nodep instanceof VariableDeclaration varDecl) { + return varDecl.resolveBinding(); + } + return null; + } + + @Override + public int getOffset() { + return this.offset; + } + + @Override + public char[] getToken() { + return this.isJustAfterStringLiteral ? null : this.token; + } + + @Override + public boolean isInJavadoc() { + return this.inJavadoc; + } + + public void setInJavadoc(boolean inJavadoc) { + this.inJavadoc = inJavadoc; + } + + @Override + public IJavaElement getEnclosingElement() { + return this.enclosingElement; + } + + @Override + public IJavaElement[] getVisibleElements(String typeSignature) { + return this.bindingsAcquirer.get() // + .filter(binding -> matchesSignature(binding, typeSignature)) // + .map(binding -> binding.getJavaElement()) // + .filter(obj -> obj != null) // eg. ArrayList.getFirst() when working with a Java 8 project + .toArray(IJavaElement[]::new); + } + + /// Checks if the binding matches the given type signature + /// TODO: this should probably live in a helper method/utils class, + /// along with `castCompatable` + public static boolean matchesSignature(IBinding binding, String typeSignature) { + if (typeSignature == null) { + return binding instanceof IVariableBinding || binding instanceof IMethodBinding; + } + if (binding instanceof IVariableBinding variableBinding) { + return castCompatable(variableBinding.getType(), + typeSignature); + } else if (binding instanceof IMethodBinding methodBinding) { + return castCompatable(methodBinding.getReturnType(), + typeSignature); + } + // notably, ITypeBinding is not used to complete values, + // even, for instance, in the case that a `java.lang.Class` is desired. + return false; + } + + @Override + public char[][] getExpectedTypesKeys() { + if (this.isJustAfterStringLiteral) { + return null; + } + var res = this.expectedTypes.getExpectedTypes().stream() // + .map(ITypeBinding::getKey) // + .map(String::toCharArray) // + .toArray(char[][]::new); + return res.length == 0 ? null : res; + } + @Override + public char[][] getExpectedTypesSignatures() { + if (this.isJustAfterStringLiteral) { + return null; + } + var res = this.expectedTypes.getExpectedTypes().stream() // + .map(ITypeBinding::getQualifiedName) // + .map(name -> Signature.createTypeSignature(name, true)) + .map(String::toCharArray) // + .toArray(char[][]::new); + return res.length == 0 ? null : res; + } + + @Override + public boolean isExtended() { + return true; + } + + @Override + public int getTokenLocation() { + ASTNode wrappingNode = this.node; + while (wrappingNode != null) { + if (wrappingNode instanceof ImportDeclaration) { + return TL_IN_IMPORT; + } + if (wrappingNode instanceof ClassInstanceCreation newObj) { + return getTokenStart() <= newObj.getType().getStartPosition() ? TL_CONSTRUCTOR_START : 0; + } + if (wrappingNode instanceof Statement stmt && getTokenStart() == stmt.getStartPosition()) { + return getTokenStart() == stmt.getStartPosition() ? TL_STATEMENT_START : 0; + } + if (wrappingNode instanceof BodyDeclaration member) { + boolean wrapperParentIsTypeDecl = (member.getParent() instanceof AbstractTypeDeclaration || member.getParent() instanceof AnonymousClassDeclaration); + if( wrapperParentIsTypeDecl && getTokenStart() == member.getStartPosition() ) { + return TL_MEMBER_START; + } + boolean wrapperNodeIsTypeDecl = (wrappingNode instanceof AbstractTypeDeclaration || wrappingNode instanceof AnonymousClassDeclaration); + if(wrapperNodeIsTypeDecl && isWithinTypeDeclarationBody(wrappingNode, this.textContent, this.offset)) { + return TL_MEMBER_START; + } + return 0; + } + if (wrappingNode instanceof Block block) { + return block.statements().isEmpty() ? TL_STATEMENT_START : 0; + } + if( wrappingNode instanceof AnonymousClassDeclaration anon) { + if(isWithinTypeDeclarationBody(wrappingNode, this.textContent, this.offset)) { + return TL_MEMBER_START; + } + } + wrappingNode = wrappingNode.getParent(); + } + return 0; + } + + private boolean isWithinTypeDeclarationBody(ASTNode n, String str, int offset2) { + if( str != null ) { + if( n instanceof AbstractTypeDeclaration atd) { + int nameEndOffset = atd.getName().getStartPosition() + atd.getName().getLength(); + int bodyStart = findFirstOpenBracketFromIndex(str, nameEndOffset); + int bodyEnd = atd.getStartPosition() + atd.getLength() - 1; + return bodyEnd > bodyStart && offset2 > bodyStart && offset2 < bodyEnd; + } + if( n instanceof AnonymousClassDeclaration acd ) { + int bodyStart = findFirstOpenBracketFromIndex(str, acd.getStartPosition()); + int bodyEnd = acd.getStartPosition() + acd.getLength() - 1; + return bodyEnd > bodyStart && offset2 > bodyStart && offset2 < bodyEnd; + } + } + return false; + } + + private int findFirstOpenBracketFromIndex(String str, int start) { + int bodyStart = start; + while (bodyStart < str.length() && str.charAt(bodyStart) != '{') { + bodyStart++; + } + return bodyStart; + } + + @Override + public int getTokenStart() { + if (this.isJustAfterStringLiteral) { + return -1; + } + if (this.node instanceof StringLiteral) { + return this.node.getStartPosition(); + } + if (this.node instanceof SimpleName name && !Arrays.equals(name.getIdentifier().toCharArray(), RecoveryScanner.FAKE_IDENTIFIER)) { + return this.node.getStartPosition(); + } + return this.offset - getToken().length; + } + @Override + public int getTokenEnd() { + if (this.isJustAfterStringLiteral) { + return -1; + } + if (this.node instanceof SimpleName || this.node instanceof StringLiteral) { + return this.node.getStartPosition() + this.node.getLength() - 1; + } + int position = this.offset; + while (position < this.textContent.length() && Character.isJavaIdentifierPart(this.textContent.charAt(position))) { + position++; + } + return position - 1; + } + + @Override + public int getTokenKind() { + if (this.isJustAfterStringLiteral) { + return TOKEN_KIND_UNKNOWN; + } + return this.node instanceof StringLiteral ? TOKEN_KIND_STRING_LITERAL : TOKEN_KIND_NAME; + } + + /// adapted from org.eclipse.jdt.internal.codeassist.InternalExtendedCompletionContext + public boolean canUseDiamond(String[] parameterTypes, char[][] typeVariables) { + // If no LHS or return type expected, then we can safely use diamond + char[][] expectedTypekeys = this.getExpectedTypesKeys(); + if (expectedTypekeys == null || expectedTypekeys.length == 0) + return true; + // Next, find out whether any of the constructor parameters are the same as one of the + // class type variables. If yes, diamond cannot be used. + if (typeVariables != null) { + for (String parameterType : parameterTypes) { + for (char[] typeVariable : typeVariables) { + if (CharOperation.equals(parameterType.toCharArray(), typeVariable)) + return false; + } + } + } + + return true; + } + + /// adapted from org.eclipse.jdt.internal.codeassist.InternalExtendedCompletionContext + public boolean canUseDiamond(String[] parameterTypes, char[] fullyQualifiedTypeName) { + ITypeBinding guessedType = null; + // If no LHS or return type expected, then we can safely use diamond + char[][] expectedTypekeys= this.getExpectedTypesKeys(); + if (expectedTypekeys == null || expectedTypekeys.length == 0) + return true; + + // Next, find out whether any of the constructor parameters are the same as one of the + // class type variables. If yes, diamond cannot be used. + Optional potentialMatch = this.bindingsAcquirer.get() // + .filter(binding -> { + for (char[] expectedTypekey : expectedTypekeys) { + if (CharOperation.equals(expectedTypekey, binding.getKey().toCharArray())) { + return true; + } + } + return false; + }) // + .findFirst(); + if (potentialMatch.isPresent() && potentialMatch.get() instanceof ITypeBinding match) { + guessedType = match; + } + if (guessedType != null && !guessedType.isRecovered()) { + // the erasure must be used because guessedType can be a RawTypeBinding + guessedType = guessedType.getErasure(); + ITypeBinding[] typeVars = guessedType.getTypeParameters(); + for (String parameterType : parameterTypes) { + for (ITypeBinding typeVar : typeVars) { + if (CharOperation.equals(parameterType.toCharArray(), typeVar.getName().toCharArray())) { + return false; + } + } + } + return true; + } + return false; + } + + + + private static boolean castCompatable(ITypeBinding typeBinding, String sig2) { + String sig1 = typeBinding.getKey().replace('/', '.'); + // NOTE: this is actually the "raw" version (no type arguments, no type params) + String sig1Raw = new String(Signature.getTypeErasure(sig1.toCharArray())); + // TODO: consider autoboxing numbers; upstream JDT doesn't handle this yet but it would be nice + switch (sig1) { + case Signature.SIG_LONG: + return sig2.equals(Signature.SIG_LONG) + || sig2.equals(Signature.SIG_DOUBLE) + || sig2.equals(Signature.SIG_FLOAT); + case Signature.SIG_INT: + return sig2.equals(Signature.SIG_LONG) + || sig2.equals(Signature.SIG_INT) + || sig2.equals(Signature.SIG_DOUBLE) + || sig2.equals(Signature.SIG_FLOAT); + case Signature.SIG_BYTE: + return sig2.equals(Signature.SIG_LONG) + || sig2.equals(Signature.SIG_INT) + || sig2.equals(Signature.SIG_BYTE) + || sig2.equals(Signature.SIG_DOUBLE) + || sig2.equals(Signature.SIG_FLOAT); + case Signature.SIG_DOUBLE: + case Signature.SIG_FLOAT: + return sig2.equals(Signature.SIG_DOUBLE) + || sig2.equals(Signature.SIG_FLOAT); + } + if (sig1.equals(sig2) || sig1Raw.equals(sig2)) { + return true; + } + if (typeBinding.getSuperclass() != null && castCompatable(typeBinding.getSuperclass(), sig2)) { + return true; + } + for (ITypeBinding superInterface : typeBinding.getInterfaces()) { + if (castCompatable(superInterface, sig2)) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionEngine.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionEngine.java new file mode 100644 index 00000000000..039aa46fd70 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionEngine.java @@ -0,0 +1,3484 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jdt.internal.codeassist; + +import java.lang.annotation.Target; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Queue; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.core.runtime.ILog; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.jdt.core.CompletionFlags; +import org.eclipse.jdt.core.CompletionProposal; +import org.eclipse.jdt.core.CompletionRequestor; +import org.eclipse.jdt.core.Flags; +import org.eclipse.jdt.core.IAnnotation; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IField; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IMemberValuePair; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IModuleDescription; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.ITypeHierarchy; +import org.eclipse.jdt.core.ITypeRoot; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.Signature; +import org.eclipse.jdt.core.WorkingCopyOwner; +import org.eclipse.jdt.core.compiler.CharOperation; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; +import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; +import org.eclipse.jdt.core.dom.Block; +import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor; +import org.eclipse.jdt.core.dom.ChildPropertyDescriptor; +import org.eclipse.jdt.core.dom.ClassInstanceCreation; +import org.eclipse.jdt.core.dom.Comment; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.EnumDeclaration; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.ExpressionMethodReference; +import org.eclipse.jdt.core.dom.ExpressionStatement; +import org.eclipse.jdt.core.dom.FieldAccess; +import org.eclipse.jdt.core.dom.FieldDeclaration; +import org.eclipse.jdt.core.dom.ForStatement; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.IPackageBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.IVariableBinding; +import org.eclipse.jdt.core.dom.IfStatement; +import org.eclipse.jdt.core.dom.ImportDeclaration; +import org.eclipse.jdt.core.dom.InfixExpression; +import org.eclipse.jdt.core.dom.Javadoc; +import org.eclipse.jdt.core.dom.LambdaExpression; +import org.eclipse.jdt.core.dom.MarkerAnnotation; +import org.eclipse.jdt.core.dom.MemberRef; +import org.eclipse.jdt.core.dom.MemberValuePair; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.MethodInvocation; +import org.eclipse.jdt.core.dom.MethodRef; +import org.eclipse.jdt.core.dom.Modifier; +import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword; +import org.eclipse.jdt.core.dom.ModuleDeclaration; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.NameQualifiedType; +import org.eclipse.jdt.core.dom.NormalAnnotation; +import org.eclipse.jdt.core.dom.NumberLiteral; +import org.eclipse.jdt.core.dom.PackageDeclaration; +import org.eclipse.jdt.core.dom.PrefixExpression; +import org.eclipse.jdt.core.dom.PrimitiveType; +import org.eclipse.jdt.core.dom.QualifiedName; +import org.eclipse.jdt.core.dom.QualifiedType; +import org.eclipse.jdt.core.dom.RecordDeclaration; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.SimpleType; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; +import org.eclipse.jdt.core.dom.Statement; +import org.eclipse.jdt.core.dom.StringLiteral; +import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; +import org.eclipse.jdt.core.dom.SuperFieldAccess; +import org.eclipse.jdt.core.dom.SwitchCase; +import org.eclipse.jdt.core.dom.SwitchExpression; +import org.eclipse.jdt.core.dom.SwitchStatement; +import org.eclipse.jdt.core.dom.TagElement; +import org.eclipse.jdt.core.dom.TextBlock; +import org.eclipse.jdt.core.dom.TextElement; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.core.dom.TypePattern; +import org.eclipse.jdt.core.dom.VariableDeclaration; +import org.eclipse.jdt.core.dom.VariableDeclarationExpression; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jdt.core.dom.VariableDeclarationStatement; +import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants; +import org.eclipse.jdt.core.search.IJavaSearchConstants; +import org.eclipse.jdt.core.search.SearchEngine; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.jdt.core.search.TypeNameMatchRequestor; +import org.eclipse.jdt.internal.codeassist.impl.AssistOptions; +import org.eclipse.jdt.internal.codeassist.impl.Keywords; +import org.eclipse.jdt.internal.compiler.env.AccessRestriction; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.eclipse.jdt.internal.compiler.parser.RecoveryScanner; +import org.eclipse.jdt.internal.compiler.util.HashtableOfObject; +import org.eclipse.jdt.internal.core.JarPackageFragmentRoot; +import org.eclipse.jdt.internal.core.JavaElementRequestor; +import org.eclipse.jdt.internal.core.JavaModelManager; +import org.eclipse.jdt.internal.core.ModuleSourcePathManager; +import org.eclipse.jdt.internal.core.SearchableEnvironment; +import org.eclipse.jdt.internal.core.SourceType; +import org.eclipse.jdt.internal.core.util.Messages; +import org.eclipse.jdt.internal.core.util.Util; + +/** + * A completion engine using a DOM as input (as opposed to {@link CompletionEngine} which + * relies on lower-level parsing with ECJ) + */ +public class DOMCompletionEngine implements ICompletionEngine { + + private static final String FAKE_IDENTIFIER = new String(RecoveryScanner.FAKE_IDENTIFIER); + private static final char[] VOID = PrimitiveType.VOID.toString().toCharArray(); + private static final List TYPE_KEYWORDS_EXCEPT_VOID = List.of( + PrimitiveType.BOOLEAN.toString().toCharArray(), + PrimitiveType.BYTE.toString().toCharArray(), + PrimitiveType.SHORT.toString().toCharArray(), + PrimitiveType.INT.toString().toCharArray(), + PrimitiveType.LONG.toString().toCharArray(), + PrimitiveType.DOUBLE.toString().toCharArray(), + PrimitiveType.FLOAT.toString().toCharArray(), + PrimitiveType.CHAR.toString().toCharArray()); + + private final CompletionRequestor requestor; + private final SearchableEnvironment nameEnvironment; + private final AssistOptions assistOptions; + private final SearchPattern pattern; + private final WorkingCopyOwner workingCopyOwner; + private final IProgressMonitor monitor; + private final Map settings; + private final IJavaProject javaProject; + + private final CompletionEngine nestedEngine; // to reuse some utilities + + private CompilationUnit unit; + private int offset; + private ITypeRoot modelUnit; + + private ExpectedTypes expectedTypes; + private String prefix; + private String qualifiedPrefix; + private ASTNode toComplete; + private String textContent; + + public HashtableOfObject typeCache; + public int openedBinaryTypes; // used during InternalCompletionProposal#findConstructorParameterNames() + + static class Bindings { + // those need to be list since the order matters + // fields must be before methods + private List others = new ArrayList<>(); + + public void add(IBinding binding) { + if (binding instanceof IMethodBinding methodBinding) { + if (methodBinding.isConstructor()) { + return; + } + if (this.methods().anyMatch(method -> method.overrides(methodBinding))) { + return; + } + this.others.removeIf(existing -> existing instanceof IMethodBinding existingMethod && methodBinding.overrides(existingMethod)); + } + if (binding != null) { + this.others.add(binding); + } + } + public void addAll(Collection bindings) { + bindings.forEach(this::add); + } + public Stream all() { + return this.others.stream().distinct(); + } + public Stream methods() { + return all().filter(IMethodBinding.class::isInstance).map(IMethodBinding.class::cast); + } + } + + public DOMCompletionEngine(SearchableEnvironment nameEnvironment, CompletionRequestor requestor, Map settings, IJavaProject javaProject, WorkingCopyOwner workingCopyOwner, IProgressMonitor monitor) { + this.nameEnvironment = nameEnvironment; + this.requestor = requestor; + this.settings = settings; + this.workingCopyOwner = workingCopyOwner; + this.monitor = monitor; + this.javaProject = javaProject; + + this.nestedEngine = new CompletionEngine(this.nameEnvironment, this.requestor, settings, javaProject, workingCopyOwner, monitor); + + // TODO also honor assistOptions.checkVisibility! + // TODO also honor requestor.ignore* + // TODO sorting/relevance: closest/prefix match should go first + // ... + this.assistOptions = new AssistOptions(this.settings); + this.pattern = new SearchPattern(SearchPattern.R_PREFIX_MATCH | + (this.assistOptions.camelCaseMatch ? SearchPattern.R_CAMELCASE_MATCH : 0) | + (this.assistOptions.substringMatch ? SearchPattern.R_SUBSTRING_MATCH : 0) | + (this.assistOptions.subwordMatch ? SearchPattern.R_SUBWORD_MATCH : 0)) { + @Override + public SearchPattern getBlankPattern() { return null; } + }; + } + + private Collection visibleBindings(ASTNode node) { + List visibleBindings = new ArrayList<>(); + + if (node instanceof MethodDeclaration m) { + visibleBindings.addAll(((List) m.parameters()).stream() + .filter(decl -> !FAKE_IDENTIFIER.equals(decl.getName().toString())) + .map(VariableDeclaration::resolveBinding).toList()); + } + + if (node instanceof LambdaExpression le) { + visibleBindings.addAll(((List) le.parameters()).stream() + .filter(decl -> !FAKE_IDENTIFIER.equals(decl.getName().toString())) + .map(VariableDeclaration::resolveBinding).toList()); + } + + if (node instanceof AbstractTypeDeclaration typeDecl) { + // a different mechanism is used to collect class members, which takes into account accessibility & such + // so only the type declaration itself is needed here + visibleBindings.add(typeDecl.resolveBinding()); + } + + if (node instanceof Block block && node.getStartPosition() + node.getLength() > this.offset) { + // TODO: handle negative if statements eg. + // if (!(node instanceof IfStatement ifStatement)) + // return; + // ifState| + var bindings = ((List) block.statements()).stream() + .filter(statement -> statement.getStartPosition() < this.offset) + .filter(VariableDeclarationStatement.class::isInstance) + .map(VariableDeclarationStatement.class::cast) + .flatMap(decl -> ((List)decl.fragments()).stream()) + .filter(frag -> !FAKE_IDENTIFIER.equals(frag.getName().toString())) + .map(VariableDeclarationFragment::resolveBinding) + .toList(); + visibleBindings.addAll(bindings); + for (Statement statement : ((List)block.statements())) { + if (statement.getStartPosition() >= this.offset || statement.getStartPosition() + statement.getLength() >= this.offset) { + break; + } + if (statement instanceof IfStatement ifStatement && ifStatement.getElseStatement() == null) { + visibleBindings.addAll(collectTrueFalseBindings(ifStatement.getExpression()).falseBindings()); + } else if (statement instanceof ForStatement forStatement && forStatement.getExpression() != null) { + visibleBindings.addAll(collectTrueFalseBindings(forStatement.getExpression()).falseBindings()); + } + + } + } + + if (node.getParent() instanceof IfStatement ifStatement && node.getStartPosition() + node.getLength() > this.offset) { + TrueFalseBindings trueFalseBindings = collectTrueFalseBindings(ifStatement.getExpression()); + if (ifStatement.getThenStatement() == node) { + visibleBindings.addAll(trueFalseBindings.trueBindings()); + } else { + visibleBindings.addAll(trueFalseBindings.falseBindings()); + } + } + + if (node.getParent() instanceof ForStatement forStatement && node.getStartPosition() + node.getLength() > this.offset) { + if (forStatement.getExpression() != null) { + TrueFalseBindings trueFalseBindings = collectTrueFalseBindings(forStatement.getExpression()); + visibleBindings.addAll(trueFalseBindings.trueBindings()); + } + if (forStatement.initializers().size() == 1 && forStatement.initializers().get(0) instanceof VariableDeclarationExpression vde) { + var bindings = ((List)vde.fragments()).stream() + .filter(frag -> !FAKE_IDENTIFIER.equals(frag.getName().toString())) + .map(VariableDeclarationFragment::resolveBinding) + .toList(); + visibleBindings.addAll(bindings); + } + } + + if (node instanceof SwitchStatement switchStatement) { + int i; + for (i = 0; i < switchStatement.statements().size(); i++) { + if (((List)switchStatement.statements()).get(i).getStartPosition() >= this.offset) { + break; + } + if (((List)switchStatement.statements()).get(i) instanceof SwitchCase switchCase) { + DOMCompletionUtil.visitChildren(switchCase, ASTNode.TYPE_PATTERN, (TypePattern e) -> { + visibleBindings.add(e.getPatternVariable().resolveBinding()); + }); + } + } + } + + if (node instanceof SwitchExpression switchExpression) { + int i; + for (i = 0; i < switchExpression.statements().size(); i++) { + if (((List)switchExpression.statements()).get(i).getStartPosition() >= this.offset) { + break; + } + if (((List)switchExpression.statements()).get(i) instanceof SwitchCase switchCase) { + DOMCompletionUtil.visitChildren(switchCase, ASTNode.TYPE_PATTERN, (TypePattern e) -> { + visibleBindings.add(e.getPatternVariable().resolveBinding()); + }); + } + } + } + + return visibleBindings; + } + + /** + * Represents collections of bindings that might be accessible depending on whether a boolean expression is true or false. + * + * @param trueBindings the bindings that are accessible when the expression is true + * @param falseBindings the bindings that are accessible when the expression is false + */ + record TrueFalseBindings(List trueBindings, List falseBindings) {} + + private TrueFalseBindings collectTrueFalseBindings(Expression e) { + if (e instanceof PrefixExpression prefixExpression && prefixExpression.getOperator() == PrefixExpression.Operator.NOT) { + TrueFalseBindings notBindings = collectTrueFalseBindings(prefixExpression.getOperand()); + return new TrueFalseBindings(notBindings.falseBindings(), notBindings.trueBindings()); + } else if (e instanceof InfixExpression infixExpression && (infixExpression.getOperator() == InfixExpression.Operator.CONDITIONAL_AND || infixExpression.getOperator() == InfixExpression.Operator.AND )) { + TrueFalseBindings left = collectTrueFalseBindings(infixExpression.getLeftOperand()); + TrueFalseBindings right = collectTrueFalseBindings(infixExpression.getRightOperand()); + List combined = new ArrayList<>(); + combined.addAll(left.trueBindings()); + combined.addAll(right.trueBindings()); + return new TrueFalseBindings(combined, Collections.emptyList()); + } else if (e instanceof InfixExpression infixExpression && (infixExpression.getOperator() == InfixExpression.Operator.CONDITIONAL_OR || infixExpression.getOperator() == InfixExpression.Operator.OR)) { + TrueFalseBindings left = collectTrueFalseBindings(infixExpression.getLeftOperand()); + TrueFalseBindings right = collectTrueFalseBindings(infixExpression.getRightOperand()); + List combined = new ArrayList<>(); + combined.addAll(left.falseBindings()); + combined.addAll(right.falseBindings()); + return new TrueFalseBindings(Collections.emptyList(), combined); + } else { + List typePatternBindings = new ArrayList<>(); + DOMCompletionUtil.visitChildren(e, ASTNode.TYPE_PATTERN, (TypePattern patt) -> { + typePatternBindings.add(patt.getPatternVariable().resolveBinding()); + }); + return new TrueFalseBindings(typePatternBindings, Collections.emptyList()); + } + } + + private Collection visibleTypeBindings(ASTNode node) { + List visibleBindings = new ArrayList<>(); + if (node instanceof AbstractTypeDeclaration typeDeclaration) { + visibleBindings.add(typeDeclaration.resolveBinding()); + for (ASTNode bodyDeclaration : (List)typeDeclaration.bodyDeclarations()) { + visibleBindings.addAll(visibleTypeBindings(bodyDeclaration)); + } + } + if (node instanceof Block block) { + var bindings = ((List) block.statements()).stream() + .filter(statement -> statement.getStartPosition() < this.offset) + .filter(TypeDeclaration.class::isInstance) + .map(TypeDeclaration.class::cast) + .map(TypeDeclaration::resolveBinding).toList(); + visibleBindings.addAll(bindings); + } + return visibleBindings; + } + + private static CompilationUnit getCompletionAST(ITypeRoot typeRoot, IJavaProject javaProject, + WorkingCopyOwner workingCopyOwner, int focalPosition) { + Map options = javaProject.getOptions(true); + // go through AST constructor to convert options to apiLevel + // but we should probably instead just use the latest Java version + // supported by the compiler + ASTParser parser = ASTParser.newParser(new AST(options).apiLevel()); + parser.setWorkingCopyOwner(workingCopyOwner); + parser.setSource(typeRoot); + // greedily enable everything assuming the AST will be used extensively for edition + parser.setResolveBindings(true); + parser.setStatementsRecovery(true); + parser.setBindingsRecovery(true); + parser.setCompilerOptions(options); + parser.setFocalPosition(focalPosition); + if (parser.createAST(null) instanceof org.eclipse.jdt.core.dom.CompilationUnit newAST) { + return newAST; + } + return null; + } + + private static CompilationUnit getCompletionAST(char[] content, char[] unitName, IJavaProject javaProject, + WorkingCopyOwner workingCopyOwner, int focalPosition) { + Map options = javaProject.getOptions(true); + // go through AST constructor to convert options to apiLevel + // but we should probably instead just use the latest Java version + // supported by the compiler + ASTParser parser = ASTParser.newParser(new AST(options).apiLevel()); + parser.setWorkingCopyOwner(workingCopyOwner); + parser.setSource(content); + parser.setProject(javaProject); + parser.setUnitName(new String(unitName)); + // greedily enable everything assuming the AST will be used extensively for edition + parser.setResolveBindings(true); + parser.setStatementsRecovery(true); + parser.setBindingsRecovery(true); + parser.setCompilerOptions(options); + parser.setFocalPosition(focalPosition); + if (parser.createAST(null) instanceof org.eclipse.jdt.core.dom.CompilationUnit newAST) { + return newAST; + } + return null; + } + + @Override + public void complete(org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit, int completionPosition, ITypeRoot root) { + if (this.monitor != null) { + this.monitor.beginTask(Messages.engine_completing, IProgressMonitor.UNKNOWN); + } + this.requestor.beginReporting(); + + this.modelUnit = root; + if (modelUnit != null) { + try { + this.textContent = new String(this.modelUnit.getBuffer().getCharacters()); + } catch (JavaModelException e) { + this.textContent = new String(sourceUnit.getContents()); + ILog.get().error("unable to access buffer for completion", e); //$NON-NLS-1$ + } + this.offset = completionPosition; + if (modelUnit instanceof org.eclipse.jdt.internal.core.CompilationUnit modelCU) { + try { + this.unit = modelCU.getOrBuildAST(this.workingCopyOwner, completionPosition); + } catch (JavaModelException e) { + // do nothing + } + } + if (this.unit == null) { + this.unit = getCompletionAST(this.modelUnit, root.getJavaProject(), this.workingCopyOwner, completionPosition); + } + if (this.unit == null) { + return; + } + } else { + this.textContent = new String(sourceUnit.getContents()); + this.offset = completionPosition; + if (this.unit == null) { + this.unit = getCompletionAST(sourceUnit.getContents(), sourceUnit.getFileName(), this.javaProject, this.workingCopyOwner, completionPosition); + } + } + + + try { + Bindings defaultCompletionBindings = new Bindings(); + Bindings specificCompletionBindings = new Bindings(); +// var completionContext = new DOMCompletionContext(this.offset, completeAfter.toCharArray(), +// computeEnclosingElement(), defaultCompletionBindings::stream, expectedTypes, this.toComplete); + var completionContext = new DOMCompletionContext(this.unit, this.modelUnit, this.textContent, this.offset, this.assistOptions, defaultCompletionBindings); + this.requestor.acceptContext(completionContext); + + this.expectedTypes = completionContext.expectedTypes; + char[] token = completionContext.getToken(); + String completeAfter = token == null ? new String() : new String(token); + ASTNode context = completionContext.node; + this.toComplete = completionContext.node; + completionContext.setInJavadoc(DOMCompletionUtil.findParent(this.toComplete, new int[] {ASTNode.JAVADOC}) != null); + ASTNode potentialTagElement = DOMCompletionUtil.findParent(this.toComplete, new int[] { ASTNode.TAG_ELEMENT, ASTNode.MEMBER_REF, ASTNode.METHOD_REF }); + if (potentialTagElement != null) { + context = potentialTagElement; + } else if (completionContext.node instanceof SimpleName simpleName) { + if (simpleName.getParent() instanceof FieldAccess || simpleName.getParent() instanceof MethodInvocation + || simpleName.getParent() instanceof VariableDeclaration || simpleName.getParent() instanceof QualifiedName + || simpleName.getParent() instanceof SuperFieldAccess || simpleName.getParent() instanceof SingleMemberAnnotation + || simpleName.getParent() instanceof ExpressionMethodReference || simpleName.getParent() instanceof TagElement) { + if (!this.toComplete.getLocationInParent().getId().equals(QualifiedName.QUALIFIER_PROPERTY.getId())) { + context = this.toComplete.getParent(); + } + } + if (simpleName.getParent() instanceof SimpleType simpleType && (simpleType.getParent() instanceof ClassInstanceCreation)) { + context = simpleName.getParent().getParent(); + } + } else if (this.toComplete instanceof SimpleType simpleType) { + if (FAKE_IDENTIFIER.equals(simpleType.getName().toString())) { + context = this.toComplete.getParent(); + } else if (simpleType.getName() instanceof QualifiedName qualifiedName) { + context = qualifiedName; + } + } else if (this.toComplete instanceof Block block && this.offset == block.getStartPosition()) { + context = this.toComplete.getParent(); + } else if (this.toComplete instanceof StringLiteral stringLiteral && (this.offset <= stringLiteral.getStartPosition() || stringLiteral.getStartPosition() + stringLiteral.getLength() <= this.offset)) { + context = stringLiteral.getParent(); + } else if (this.toComplete instanceof VariableDeclaration vd) { + context = vd.getInitializer(); + } else if (this.toComplete instanceof QualifiedName && this.toComplete.getParent() instanceof TagElement tagElement) { + context = tagElement; + } + this.prefix = token == null ? new String() : new String(token); + this.qualifiedPrefix = this.prefix; + if (this.toComplete instanceof QualifiedName qualifiedName) { + this.qualifiedPrefix = qualifiedName.getQualifier().toString(); + } else if (this.toComplete != null && this.toComplete.getParent() instanceof QualifiedName qualifiedName) { + this.qualifiedPrefix = qualifiedName.getQualifier().toString(); + } else if (this.toComplete instanceof SimpleType simpleType && simpleType.getName() instanceof QualifiedName qualifiedName) { + this.qualifiedPrefix = qualifiedName.getQualifier().toString(); + } else if (this.toComplete instanceof TextElement textElement && context instanceof TagElement) { + String packageName = textElement.getText().trim(); + if (!packageName.isEmpty()) { + if (packageName.charAt(packageName.length() - 1) == '.') { + packageName = packageName.substring(0, packageName.length() - 1); + } + this.qualifiedPrefix = packageName; + } + } + // some flags to controls different applicable completion search strategies + boolean suggestDefaultCompletions = true; + + checkCancelled(); + + if (context instanceof StringLiteral || context instanceof TextBlock || context instanceof Comment || context instanceof Javadoc || context instanceof NumberLiteral) { + return; + } + if (context instanceof FieldAccess fieldAccess) { + Expression fieldAccessExpr = fieldAccess.getExpression(); + ITypeBinding fieldAccessType = fieldAccessExpr.resolveTypeBinding(); + if (fieldAccessType != null) { + processMembers(fieldAccess, fieldAccessExpr.resolveTypeBinding(), specificCompletionBindings, false); + publishFromScope(specificCompletionBindings); + } else if (DOMCompletionUtil.findParent(fieldAccessExpr, new int[]{ ASTNode.METHOD_INVOCATION }) == null) { + String packageName = ""; //$NON-NLS-1$ + if (fieldAccess.getExpression() instanceof FieldAccess parentFieldAccess + && parentFieldAccess.getName().resolveBinding() instanceof IPackageBinding packageBinding) { + packageName = packageBinding.getName(); + } else if (fieldAccess.getExpression() instanceof SimpleName name + && name.resolveBinding() instanceof IPackageBinding packageBinding) { + packageName = packageBinding.getName(); + } + suggestPackages(fieldAccess); + suggestTypesInPackage(packageName); + } + suggestDefaultCompletions = false; + } + if (context instanceof MethodInvocation invocation) { + if (this.offset <= invocation.getName().getStartPosition() + invocation.getName().getLength()) { + Expression expression = invocation.getExpression(); + if (expression == null) { + return; + } + // complete name + ITypeBinding type = expression.resolveTypeBinding(); + if (type != null) { + processMembers(expression, type, specificCompletionBindings, false); + specificCompletionBindings.all() + .filter(binding -> this.pattern.matchesName(this.prefix.toCharArray(), binding.getName().toCharArray())) + .filter(IMethodBinding.class::isInstance) + .map(binding -> toProposal(binding)) + .forEach(this.requestor::accept); + } + suggestDefaultCompletions = false; + } else if (invocation.getStartPosition() + invocation.getLength() <= this.offset && this.prefix.isEmpty()) { + // handle `myMethod().|` + IMethodBinding methodBinding = invocation.resolveMethodBinding(); + if (methodBinding != null) { + ITypeBinding returnType = methodBinding.getReturnType(); + processMembers(invocation, returnType, specificCompletionBindings, false); + specificCompletionBindings.all() + .map(binding -> toProposal(binding)) + .forEach(this.requestor::accept); + } + suggestDefaultCompletions = false; + } else { + // args + IMethodBinding methodBinding = invocation.resolveMethodBinding(); + if (methodBinding == null && this.toComplete == invocation) { + // myMethod(|), where myMethod does not exist + suggestDefaultCompletions = false; + } else { + for (ITypeBinding param : this.expectedTypes.getExpectedTypes()) { + IMethodBinding potentialLambda = param.getFunctionalInterfaceMethod(); + if (potentialLambda != null) { + this.requestor.accept(createLambdaExpressionProposal(potentialLambda)); + } + } + } + } + } + if (context instanceof VariableDeclaration declaration) { + if (declaration.getName() == this.toComplete) { + suggestDefaultCompletions = false; + } + } + if (context instanceof ModuleDeclaration mod) { + findModules(this.prefix.toCharArray(), this.javaProject, this.assistOptions, Set.of(mod.getName().toString())); + } + if (context instanceof SimpleName) { + if (context.getParent() instanceof SimpleType simpleType + && simpleType.getParent() instanceof FieldDeclaration fieldDeclaration + && fieldDeclaration.getParent() instanceof AbstractTypeDeclaration typeDecl) { + // eg. + // public class Foo { + // ba| + // } + ITypeBinding typeDeclBinding = typeDecl.resolveBinding(); + findOverridableMethods(typeDeclBinding, this.javaProject, context); + ExtendsOrImplementsInfo extendsOrImplementsInfo = isInExtendsOrImplements(this.toComplete); + suggestTypeKeywords(true); + if (!this.requestor.isIgnored(CompletionProposal.TYPE_REF)) { + findTypes(this.prefix, IJavaSearchConstants.TYPE, null) + // don't care about annotations + .filter(type -> { + try { + return !type.isAnnotation(); + } catch (JavaModelException e) { + return true; + } + }) + .filter(type -> { + return defaultCompletionBindings.all().map(typeBinding -> typeBinding.getJavaElement()).noneMatch(elt -> type.equals(elt)); + }) + .filter(type -> this.pattern.matchesName(this.prefix.toCharArray(), + type.getElementName().toCharArray())) + .filter(type -> { + return filterBasedOnExtendsOrImplementsInfo(type, extendsOrImplementsInfo); + }) + .map(this::toProposal).forEach(this.requestor::accept); + } + if (!this.requestor.isIgnored(CompletionProposal.POTENTIAL_METHOD_DECLARATION)) { + int cursorStart = this.offset - this.prefix.length() - 1; + while (cursorStart > 0 && Character.isWhitespace(this.textContent.charAt(cursorStart))) { + cursorStart--; + } + int cursorEnd = cursorStart; + while (cursorEnd > 0 && Character.isJavaIdentifierPart(this.textContent.charAt(cursorEnd - 1))) { + cursorEnd--; + } + boolean suggest = true; + if (cursorStart != cursorEnd) { + String potentialModifier = this.textContent.substring(cursorEnd, cursorStart + 1); + if (DOMCompletionUtil.isJavaFieldOrMethodModifier(potentialModifier)) { + suggest = false; + } + } + if (suggest) { + this.requestor.accept(toNewMethodProposal(typeDeclBinding, this.prefix)); + } + } + suggestDefaultCompletions = false; + } + if (context.getParent() instanceof MarkerAnnotation) { + completeMarkerAnnotation(completeAfter); + suggestDefaultCompletions = false; + } + if (context.getLocationInParent() == MemberValuePair.NAME_PROPERTY && context.getParent() instanceof MemberValuePair memberValuePair) { + Set names = new HashSet<>(); + if (memberValuePair.getParent() instanceof NormalAnnotation normalAnnotation) { + for (Object o : normalAnnotation.values()) { + if (o instanceof MemberValuePair other && other != memberValuePair) { + names.add(other.getName().getIdentifier()); + } + } + } + Arrays.stream(((Annotation)memberValuePair.getParent()).resolveTypeBinding().getDeclaredMethods()) // + .filter(this::isVisible) // + .filter(method -> !names.contains(method.getName())) + .map(this::toAnnotationAttributeRefProposal) // + .forEach(this.requestor::accept); + suggestDefaultCompletions = false; + } + if (context.getParent() instanceof MemberValuePair) { + // TODO: most of the time a constant value is expected, + // however if an enum is expected, we can build out the completion for that + suggestDefaultCompletions = false; + } + if ((context.getLocationInParent() == SwitchCase.EXPRESSIONS2_PROPERTY || context.getLocationInParent() == SwitchCase.EXPRESSION_PROPERTY) && context.getParent() instanceof SwitchCase switchCase) { + completionContext.expectedTypes.getExpectedTypes().stream() + .map(ITypeBinding::getDeclaredFields) + .flatMap(Arrays::stream) + .filter(IVariableBinding::isEnumConstant) + .filter(constant -> this.pattern.matchesName(this.prefix.toCharArray(), constant.getName().toCharArray())) + .map(this::toProposal) + .forEach(this.requestor::accept); + suggestDefaultCompletions = false; + } + if (context.getParent() instanceof MethodDeclaration) { + suggestDefaultCompletions = false; + } + if (context.getParent() instanceof SimpleType simpleType && simpleType.getParent() instanceof MethodDeclaration + && simpleType.getLocationInParent().getId().equals(MethodDeclaration.THROWN_EXCEPTION_TYPES_PROPERTY.getId())) { + findTypes(completeAfter, null) + .filter(type -> this.pattern.matchesName(this.prefix.toCharArray(), + type.getElementName().toCharArray())) + // ideally we should filter out all classes that don't descend from Throwable + // however JDT doesn't do this yet from what I can tell + .filter(type -> { + try { + return !type.isAnnotation() && !type.isInterface(); + } catch (JavaModelException e) { + return true; + } + }) + .map(this::toProposal).forEach(this.requestor::accept); + suggestDefaultCompletions = false; + } + if (context.getParent() instanceof AnnotationTypeMemberDeclaration) { + suggestDefaultCompletions = false; + } + if (context.getLocationInParent() == QualifiedName.QUALIFIER_PROPERTY && context.getParent() instanceof QualifiedName) { + IBinding incorrectBinding = ((SimpleName) context).resolveBinding(); + if (incorrectBinding != null) { + // eg. + // void myMethod() { + // String myVariable = "hello, mom"; + // myVariable.| + // Object myObj = null; + // } + // It thinks that our variable is a package or some other type. We know that it's a variable. + // Search the scope for the right binding + Bindings localBindings = new Bindings(); + scrapeAccessibleBindings(localBindings); + Optional realBinding = localBindings.all() // + .filter(IVariableBinding.class::isInstance) + .map(IVariableBinding.class::cast) + .filter(varBind -> varBind.getName().equals(incorrectBinding.getName())) + .findFirst(); + if (realBinding.isPresent()) { + processMembers(context, realBinding.get().getType(), specificCompletionBindings, false); + this.prefix = ""; //$NON-NLS-1$ + publishFromScope(specificCompletionBindings); + suggestDefaultCompletions = false; + } + } + } + if (context.getParent() instanceof ImportDeclaration importDeclaration + && context.getAST().apiLevel() >= AST.JLS23 + && this.javaProject.getOption(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, true).equals(JavaCore.ENABLED)) { + for (Modifier modifier : (List)importDeclaration.modifiers()) { + if (modifier.getKeyword() == ModifierKeyword.MODULE_KEYWORD) { + findModules(this.qualifiedPrefix.toCharArray(), this.javaProject, this.assistOptions, Collections.emptySet()); + suggestDefaultCompletions = false; + break; + } + } + } + } + if (context instanceof AbstractTypeDeclaration typeDecl) { + if (this.textContent != null) { + int nameEndOffset = typeDecl.getName().getStartPosition() + typeDecl.getName().getLength(); + int bodyStart = nameEndOffset; + while (bodyStart < this.textContent.length() && this.textContent.charAt(bodyStart) != '{') { + bodyStart++; + } + int prefixCursor = this.offset; + while (prefixCursor > 0 && !Character.isWhitespace(this.textContent.charAt(prefixCursor - 1))) { + prefixCursor--; + } + this.prefix = this.textContent.substring(prefixCursor, this.offset); + if (nameEndOffset < this.offset && this.offset <= bodyStart) { + String extendsOrImplementsContent = this.textContent.substring(nameEndOffset, this.offset); + if (extendsOrImplementsContent.indexOf("implements") < 0 && extendsOrImplementsContent.indexOf("extends") < 0) { //$NON-NLS-1$ //$NON-NLS-2$ + // public class Foo | { + // + // } + boolean isInterface = typeDecl instanceof TypeDeclaration realTypeDecl && realTypeDecl.isInterface(); + if (CharOperation.prefixEquals(this.prefix.toCharArray(), Keywords.EXTENDS)) { + this.requestor.accept(createKeywordProposal(Keywords.EXTENDS, this.offset, this.offset)); + } + if (!isInterface && CharOperation.prefixEquals(this.prefix.toCharArray(), Keywords.IMPLEMENTS)) { + this.requestor.accept(createKeywordProposal(Keywords.IMPLEMENTS, this.offset, this.offset)); + } + } else if (extendsOrImplementsContent.indexOf("implements") < 0 //$NON-NLS-1$ + && (Character.isWhitespace(this.textContent.charAt(this.offset - 1)) || this.textContent.charAt(this.offset - 1) == ',')) { + // public class Foo extends Bar, Baz, | { + // + // } + this.requestor.accept(createKeywordProposal(Keywords.IMPLEMENTS, this.offset, this.offset)); + } + } else if (bodyStart < this.offset) { + // public class Foo { + // | + // } + ITypeBinding typeDeclBinding = typeDecl.resolveBinding(); + findOverridableMethods(typeDeclBinding, this.javaProject, null); + } + suggestDefaultCompletions = false; + } + } + if (context instanceof QualifiedName qualifiedName) { + ImportDeclaration importDecl = (ImportDeclaration)DOMCompletionUtil.findParent(context, new int[] { ASTNode.IMPORT_DECLARATION }); + if (importDecl != null) { + if(importDecl.getAST().apiLevel() >= AST.JLS23 + && this.javaProject.getOption(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, true).equals(JavaCore.ENABLED) + && importDecl.modifiers().stream().anyMatch(node -> node instanceof Modifier modifier && modifier.getKeyword() == ModifierKeyword.MODULE_KEYWORD)) { + findModules((this.qualifiedPrefix + "." + this.prefix).toCharArray(), this.javaProject, this.assistOptions, Collections.emptySet()); //$NON-NLS-1$ + suggestDefaultCompletions = false; + } else { + suggestPackages(context); + suggestTypesInPackage(qualifiedName.toString()); + suggestTypesInPackage(qualifiedName.getQualifier().toString()); + if (importDecl.isStatic() && + qualifiedName.getQualifier().resolveBinding() instanceof ITypeBinding type) { + Stream.of(type.getDeclaredFields(), type.getDeclaredMethods(), type.getDeclaredTypes()) + .flatMap(Arrays::stream) // + .filter(binding -> Modifier.isStatic(binding.getModifiers())) // + .map(this::toProposal) // + .forEach(this.requestor::accept); + } + suggestDefaultCompletions = false; + } + } else { + IBinding qualifiedNameBinding = qualifiedName.getQualifier().resolveBinding(); + if (qualifiedNameBinding instanceof ITypeBinding qualifierTypeBinding && !qualifierTypeBinding.isRecovered()) { + processMembers(qualifiedName, qualifierTypeBinding, specificCompletionBindings, true); + publishFromScope(specificCompletionBindings); + int startPos = this.offset; + int endPos = this.offset; + if ((qualifiedName.getName().getFlags() & ASTNode.MALFORMED) != 0) { + startPos = qualifiedName.getName().getStartPosition(); + endPos = startPos + qualifiedName.getName().getLength(); + } + if (!(this.toComplete instanceof Type)) { + AbstractTypeDeclaration parentTypeDeclaration = DOMCompletionUtil.findParentTypeDeclaration(context); + if (parentTypeDeclaration != null) { + ITypeBinding currentTypeBinding = parentTypeDeclaration.resolveBinding(); + if (currentTypeBinding.isSubTypeCompatible(qualifierTypeBinding)) { + if (!isFailedMatch(this.prefix.toCharArray(), Keywords.THIS)) { + this.requestor.accept(createKeywordProposal(Keywords.THIS, startPos, endPos)); + } + if (!isFailedMatch(this.prefix.toCharArray(), Keywords.SUPER)) { + this.requestor.accept(createKeywordProposal(Keywords.SUPER, startPos, endPos)); + } + } + if (!isFailedMatch(this.prefix.toCharArray(), Keywords.CLASS)) { + this.requestor.accept(createClassKeywordProposal(qualifierTypeBinding, startPos, endPos)); + } + } + } + + suggestDefaultCompletions = false; + } else if (qualifiedNameBinding instanceof IPackageBinding qualifierPackageBinding) { + if (!qualifierPackageBinding.isRecovered()) { + // start of a known package + suggestPackages(null); + // suggests types in the package + suggestTypesInPackage(qualifierPackageBinding.getName()); + suggestDefaultCompletions = false; + } else { + // likely the start of an incomplete field/method access + Bindings tempScope = new Bindings(); + scrapeAccessibleBindings(tempScope); + Optional potentialBinding = tempScope.all() // + .filter(binding -> { + IJavaElement elt = binding.getJavaElement(); + if (elt == null) { + return false; + } + return elt.getElementName().equals(qualifiedName.getQualifier().toString()); + }) // + .map(binding -> { + if (binding instanceof IVariableBinding variableBinding) { + return variableBinding.getType(); + } else if (binding instanceof ITypeBinding typeBinding) { + return typeBinding; + } + throw new IllegalStateException( + "method, type var, etc. are likely not interpreted as a package"); //$NON-NLS-1$ + }) // + .map(ITypeBinding.class::cast) // + .findFirst(); + if (potentialBinding.isPresent()) { + processMembers(qualifiedName, potentialBinding.get(), specificCompletionBindings, + false); + publishFromScope(specificCompletionBindings); + suggestDefaultCompletions = false; + } else { + // maybe it is actually a package? + if (shouldSuggestPackages(context)) { + suggestPackages(context); + } + // suggests types in the package + suggestTypesInPackage(qualifierPackageBinding.getName()); + suggestDefaultCompletions = false; + } + } + } else if (qualifiedNameBinding instanceof IVariableBinding variableBinding) { + ITypeBinding typeBinding = variableBinding.getType(); + processMembers(qualifiedName, typeBinding, specificCompletionBindings, false); + publishFromScope(specificCompletionBindings); + suggestDefaultCompletions = false; + } else { + // UnimportedType.| + List foundTypes = findTypes(qualifiedName.getQualifier().toString(), null).toList(); + // HACK: We requested exact matches from the search engine but some results aren't exact + foundTypes = foundTypes.stream().filter(type -> type.getElementName().equals(qualifiedName.getQualifier().toString())).toList(); + if (!foundTypes.isEmpty()) { + IType firstType = foundTypes.get(0); + ASTParser parser = ASTParser.newParser(AST.getJLSLatest()); + parser.setProject(this.javaProject); + IBinding[] descendantBindings = parser.createBindings(new IType[] { firstType }, new NullProgressMonitor()); + if (descendantBindings.length == 1) { + ITypeBinding qualifierTypeBinding = (ITypeBinding)descendantBindings[0]; + processMembers(qualifiedName, qualifierTypeBinding, specificCompletionBindings, true); + publishFromScope(specificCompletionBindings); + int startPos = this.offset; + int endPos = this.offset; + if ((qualifiedName.getName().getFlags() & ASTNode.MALFORMED) != 0) { + startPos = qualifiedName.getName().getStartPosition(); + endPos = startPos + qualifiedName.getName().getLength(); + } + if (!(this.toComplete instanceof Type) && !isFailedMatch(this.prefix.toCharArray(), Keywords.CLASS)) { + this.requestor.accept(createClassKeywordProposal(qualifierTypeBinding, startPos, endPos)); + } + } + } + suggestDefaultCompletions = false; + } + } + } + if (context instanceof SuperFieldAccess superFieldAccess) { + ITypeBinding superTypeBinding = superFieldAccess.resolveTypeBinding(); + processMembers(superFieldAccess, superTypeBinding, specificCompletionBindings, false); + publishFromScope(specificCompletionBindings); + suggestDefaultCompletions = false; + } + if (context instanceof MarkerAnnotation) { + completeMarkerAnnotation(completeAfter); + suggestDefaultCompletions = false; + } + if (context instanceof SingleMemberAnnotation singleMemberAnnotation) { + if (singleMemberAnnotation.getTypeName().getStartPosition() + singleMemberAnnotation.getTypeName().getLength() > this.offset) { + completeMarkerAnnotation(completeAfter); + } else if (!this.requestor.isIgnored(CompletionProposal.ANNOTATION_ATTRIBUTE_REF)) { + completeAnnotationParams(singleMemberAnnotation, Collections.emptySet(), specificCompletionBindings); + } + suggestDefaultCompletions = false; + } + if (context instanceof NormalAnnotation normalAnnotation) { + if (normalAnnotation.getTypeName().getStartPosition() + normalAnnotation.getTypeName().getLength() > this.offset) { + completeMarkerAnnotation(completeAfter); + } else if (!this.requestor.isIgnored(CompletionProposal.ANNOTATION_ATTRIBUTE_REF)) { + completeNormalAnnotationParams(normalAnnotation, specificCompletionBindings); + } + suggestDefaultCompletions = false; + } + if (context instanceof MethodDeclaration methodDeclaration) { + if (this.offset < methodDeclaration.getName().getStartPosition()) { + completeMethodModifiers(methodDeclaration); + // return type: suggest types from current CU + if (methodDeclaration.getReturnType2() == null) { + ASTNode current = this.toComplete; + while (current != null) { + specificCompletionBindings.addAll(visibleTypeBindings(current)); + current = current.getParent(); + } + publishFromScope(specificCompletionBindings); + } + suggestDefaultCompletions = false; + } else if (methodDeclaration.getBody() == null || (methodDeclaration.getBody() != null && this.offset <= methodDeclaration.getBody().getStartPosition())) { + completeThrowsClause(methodDeclaration, specificCompletionBindings); + suggestDefaultCompletions = false; + } + } + if (context instanceof ClassInstanceCreation) { + if (this.expectedTypes.getExpectedTypes() != null && !this.expectedTypes.getExpectedTypes().isEmpty() && !this.expectedTypes.getExpectedTypes().get(0).isRecovered()) { + completeConstructor(this.expectedTypes.getExpectedTypes().get(0), context, this.javaProject); + } else { + if (!this.requestor.isIgnored(CompletionProposal.TYPE_REF) && !this.requestor.isIgnored(CompletionProposal.CONSTRUCTOR_INVOCATION)) { + String packageName = "";//$NON-NLS-1$ + PackageDeclaration packageDecl = this.unit.getPackage(); + if (packageDecl != null) { + packageName = packageDecl.getName().toString(); + } + this.findTypes(this.prefix, IJavaSearchConstants.TYPE, packageName) + .filter(type -> { + try { + return !type.isAnnotation(); + } catch (JavaModelException e) { + return true; + } + }) // + .flatMap(type -> { + if (this.prefix.isEmpty()) { + return Stream.of(toProposal(type)); + } else { + return toConstructorProposals(type, this.toComplete, false).stream(); + } + }) // + .forEach(this.requestor::accept); + } + } + suggestDefaultCompletions = false; + } + if (context instanceof Javadoc) { + suggestDefaultCompletions = false; + } + if (context instanceof ExpressionMethodReference emr) { + ITypeBinding typeBinding = emr.getExpression().resolveTypeBinding(); + if (typeBinding != null && !this.requestor.isIgnored(CompletionProposal.METHOD_NAME_REFERENCE)) { + processMembers(emr, typeBinding, specificCompletionBindings, false); + specificCompletionBindings.methods() // + .filter(binding -> this.pattern.matchesName(this.prefix.toCharArray(), binding.getName().toCharArray())) + .map(this::toProposal) // + .forEach(this.requestor::accept); + } + suggestDefaultCompletions = false; + } + if (context instanceof TagElement tagElement) { + boolean isTagName = true; + ASTNode cursor = this.toComplete; + while (cursor != null && cursor != tagElement) { + int index = tagElement.fragments().indexOf(cursor); + if (index >= 0) { + isTagName = false; + break; + } + cursor = cursor.getParent(); + } + + if (isTagName || (tagElement.getTagName() != null && tagElement.getTagName().length() == 1) || (tagElement.getTagName() != null && this.offset == (tagElement.getStartPosition() + tagElement.getTagName().length()))) { + completeJavadocBlockTags(tagElement); + completeJavadocInlineTags(tagElement); + suggestDefaultCompletions = false; + } else { + if (tagElement.getTagName() != null) { + Javadoc javadoc = (Javadoc)DOMCompletionUtil.findParent(tagElement, new int[] { ASTNode.JAVADOC }); + switch (tagElement.getTagName()) { + case TagElement.TAG_PARAM: { + + int start = tagElement.getStartPosition() + TagElement.TAG_PARAM.length() + 1; + int endPos = start; + if (this.textContent != null) { + while (endPos < this.textContent.length() && !Character.isWhitespace(this.textContent.charAt(endPos))) { + endPos++; + } + } + String paramPrefix = this.textContent.substring(start, endPos); + + if (javadoc.getParent() instanceof MethodDeclaration methodDecl) { + Set alreadyDocumentedParameters = findAlreadyDocumentedParameters(javadoc); + IMethodBinding methodBinding = methodDecl.resolveBinding(); + Stream.of(methodBinding.getParameterNames()) // + .filter(name -> !alreadyDocumentedParameters.contains(name)) // + .filter(name -> this.pattern.matchesName(paramPrefix.toCharArray(), name.toCharArray())) // + .map(paramName -> toAtParamProposal(paramName, tagElement)) // + .forEach(this.requestor::accept); + Stream.of(methodBinding.getTypeParameters()) // + .map(typeParam -> "<" + typeParam.getName() + ">") //$NON-NLS-1$ //$NON-NLS-2$ + .filter(name -> !alreadyDocumentedParameters.contains(name)) // + .filter(name -> this.pattern.matchesName(paramPrefix.toCharArray(), name.toCharArray())) // + .map(paramName -> toAtParamProposal(paramName, tagElement)) // + .forEach(this.requestor::accept); + } else { + if (javadoc.getParent() instanceof AbstractTypeDeclaration typeDecl) { + Set alreadyDocumentedParameters = findAlreadyDocumentedParameters(javadoc); + ITypeBinding typeBinding = typeDecl.resolveBinding().getTypeDeclaration(); + Stream.of(typeBinding.getTypeParameters()) + .map(typeParam -> "<" + typeParam.getName() + ">") //$NON-NLS-1$ //$NON-NLS-2$ + .filter(name -> !alreadyDocumentedParameters.contains(name)) // + .filter(name -> this.pattern.matchesName(paramPrefix.toCharArray(), name.toCharArray())) // + .map(name -> toAtParamProposal(name, tagElement)) + .forEach(this.requestor::accept); + } + } + suggestDefaultCompletions = false; + break; + } + case TagElement.TAG_LINK: + case TagElement.TAG_SEE: { + + if (this.qualifiedPrefix.indexOf('#') >= 0) { + // eg. MyClass#| + // eg. #| + // Eclipse expects these to be TextElement instead of MemberRef + String classToComplete = this.qualifiedPrefix.substring(0, this.qualifiedPrefix.indexOf('#')); + if (classToComplete.isEmpty()) { + // use parent type + ITypeBinding typeBinding = DOMCompletionUtil.findParentTypeDeclaration(this.toComplete).resolveBinding(); + Bindings javadocScope = new Bindings(); + processMembers(this.toComplete, typeBinding, javadocScope, false); + publishFromScope(javadocScope); + suggestAccessibleConstructorsForType(typeBinding); + } else { + String packageName = classToComplete.lastIndexOf('.') < 0 ? null : classToComplete.substring(0, classToComplete.lastIndexOf('.')); + if (packageName != null) { + classToComplete = classToComplete.substring(classToComplete.lastIndexOf('.') + 1); + } else { + CompilationUnit cu = (CompilationUnit)DOMCompletionUtil.findParent(this.toComplete, new int[] { ASTNode.COMPILATION_UNIT }); + if (cu.getPackage() != null) { + packageName = cu.getPackage().getName().toString(); + } else { + packageName = ""; //$NON-NLS-1$ + } + } + List potentialTypes = findTypes(classToComplete, IJavaSearchConstants.TYPE, packageName).toList(); + List sourceTypes = potentialTypes.stream().filter(type -> type instanceof SourceType).toList(); + if (!potentialTypes.isEmpty()) { + IType typeToComplete; + if (potentialTypes.size() > 1 && !sourceTypes.isEmpty()) { + typeToComplete = sourceTypes.get(0); + } else { + typeToComplete = potentialTypes.get(0); + } + ASTParser parser = ASTParser.newParser(AST.getJLSLatest()); + parser.setProject(this.javaProject); + IBinding[] createdBindings = parser.createBindings(new IType[] { typeToComplete }, new NullProgressMonitor()); + if (createdBindings.length > 0 && createdBindings[0] instanceof ITypeBinding typeBinding) { + Bindings javadocScope = new Bindings(); + processMembers(this.toComplete, typeBinding, javadocScope, false); + publishFromScope(javadocScope); + suggestAccessibleConstructorsForType(typeBinding); + } + } + } + suggestDefaultCompletions = false; + } else { + int endPos = this.offset, startPos = endPos; + if (this.textContent != null) { + while (startPos > 0 && !Character.isWhitespace(this.textContent.charAt(startPos - 1))) { + startPos--; + } + } + + String paramPrefix = this.textContent.substring(startPos, endPos); + if (paramPrefix.indexOf('/') >= 0) { + // TODO: only complete types that are in the specified module + suggestDefaultCompletions = false; + } else { + // local types are suggested first + String currentPackage = ""; //$NON-NLS-1$ + CompilationUnit cuNode = (CompilationUnit) DOMCompletionUtil.findParent(tagElement, new int[] { ASTNode.COMPILATION_UNIT }); + if (cuNode.getPackage() != null) { + currentPackage = cuNode.getPackage().getName().toString(); + } + final String finalizedCurrentPackage = currentPackage; + + Bindings localTypeBindings = new Bindings(); + scrapeAccessibleBindings(localTypeBindings); + localTypeBindings.all() // + .filter(binding -> binding instanceof ITypeBinding) // + .filter(type -> (this.qualifiedPrefix.equals(this.prefix) || this.qualifiedPrefix.equals(finalizedCurrentPackage)) && this.pattern.matchesName(this.prefix.toCharArray(), type.getName().toCharArray())) + .map(this::toProposal).forEach(this.requestor::accept); + + findTypes(completeAfter, IJavaSearchConstants.TYPE, completeAfter.equals(this.qualifiedPrefix) ? null : this.qualifiedPrefix) + .filter(type -> { + return localTypeBindings.all().map(typeBinding -> typeBinding.getJavaElement()).noneMatch(elt -> type.equals(elt)); + }) + .filter(type -> this.pattern.matchesName(this.prefix.toCharArray(), type.getElementName().toCharArray())) + .map(this::toProposal).forEach(this.requestor::accept); + + suggestDefaultCompletions = false; + } + } + break; + } + } + } else { + // the tag name is null, so this is probably a broken conversion + suggestDefaultCompletions = false; + } + } + } + if (context instanceof ImportDeclaration) { + if (context.getAST().apiLevel() >= AST.JLS23 + && this.javaProject.getOption(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, true).equals(JavaCore.ENABLED)) { + findModules(this.qualifiedPrefix.toCharArray(), this.javaProject, this.assistOptions, Collections.emptySet()); + suggestDefaultCompletions = false; + } + } + if (context instanceof CompilationUnit unit && + CharOperation.prefixEquals(completionContext.getToken(), Keywords.PACKAGE) && + unit.getPackage() == null && + this.offset <= ((Collection)unit.imports()).stream().mapToInt(ASTNode::getStartPosition).filter(n -> n >= 0).min().orElse(Integer.MAX_VALUE) && + this.offset <= ((Collection)unit.types()).stream().mapToInt(ASTNode::getStartPosition).filter(n -> n >= 0).min().orElse(Integer.MAX_VALUE)) { + this.requestor.accept(createKeywordProposal(Keywords.PACKAGE, completionContext.getTokenStart(), completionContext.getTokenEnd())); + } + if (context instanceof MethodRef methodRef) { + JavadocMethodReferenceParseState state = JavadocMethodReferenceParseState.BEFORE_IDENTIFIER; + int minNumParams = 0; + int cursor = methodRef.getName().getStartPosition() + methodRef.getName().toString().length() + 1; + while (cursor < this.offset) { + if (this.textContent.charAt(cursor) == ',') { + minNumParams++; + state = JavadocMethodReferenceParseState.BEFORE_IDENTIFIER; + } else { + switch (state) { + case BEFORE_IDENTIFIER: { + char cursorCharacter = this.textContent.charAt(cursor); + if (!Character.isWhitespace(cursorCharacter) && cursorCharacter != ')') { + if (minNumParams == 0) { + minNumParams++; + } + state = JavadocMethodReferenceParseState.IN_IDENTIFIER; + } + break; + } + case IN_IDENTIFIER: { + if (Character.isWhitespace(this.textContent.charAt(cursor))) { + state = JavadocMethodReferenceParseState.AFTER_IDENTIFIER; + } + break; + } + case AFTER_IDENTIFIER: { + // do nothing + break; + } + } + } + cursor++; + } + if (state == JavadocMethodReferenceParseState.BEFORE_IDENTIFIER) { + String expectedMethodName = methodRef.getName().toString(); + final int finalizedMinNumParams = minNumParams; + List potentialMethodCompletions = null; + // FIXME: this should use resolve binding for MethodRef, but the current implementation is unusably broken + if (potentialMethodCompletions == null) { + Name qualifier = methodRef.getQualifier(); + if (qualifier == null) { + ITypeBinding parentTypeBinding = DOMCompletionUtil.findParentTypeDeclaration(this.toComplete).resolveBinding(); + potentialMethodCompletions = Stream.of(parentTypeBinding.getDeclaredMethods()) // + .filter(methodCandidate -> { + if (!expectedMethodName.equals(methodCandidate.getName())) { + return false; + } + return methodCandidate.getParameterTypes().length >= finalizedMinNumParams; + }) // + .toList(); + } else if (qualifier.resolveBinding() instanceof ITypeBinding javadocResolvedTypeBinding) { + potentialMethodCompletions = Stream.of(javadocResolvedTypeBinding.getDeclaredMethods()) // + .filter(methodCandidate -> { + if (!expectedMethodName.equals(methodCandidate.getName())) { + return false; + } + return methodCandidate.getParameterTypes().length >= finalizedMinNumParams; + }) // + .toList(); + } else { + // Use the search engine to get the type binding + String classToComplete = qualifier.toString(); + String packageName = classToComplete.lastIndexOf('.') < 0 ? null : classToComplete.substring(0, classToComplete.lastIndexOf('.')); + if (packageName != null) { + classToComplete = classToComplete.substring(classToComplete.lastIndexOf('.') + 1); + } else { + CompilationUnit cu = (CompilationUnit)DOMCompletionUtil.findParent(this.toComplete, new int[] { ASTNode.COMPILATION_UNIT }); + if (cu.getPackage() != null) { + packageName = cu.getPackage().getName().toString(); + } else { + packageName = ""; //$NON-NLS-1$ + } + } + List potentialTypes = findTypes(classToComplete, IJavaSearchConstants.TYPE, packageName).toList(); + List sourceTypes = potentialTypes.stream().filter(type -> type instanceof SourceType).toList(); + if (!potentialTypes.isEmpty()) { + IType typeToComplete; + if (potentialTypes.size() > 1 && !sourceTypes.isEmpty()) { + typeToComplete = sourceTypes.get(0); + } else { + typeToComplete = potentialTypes.get(0); + } + ASTParser parser = ASTParser.newParser(AST.getJLSLatest()); + parser.setProject(this.javaProject); + IBinding[] createdBindings = parser.createBindings(new IType[] { typeToComplete }, new NullProgressMonitor()); + if (createdBindings.length > 0 && createdBindings[0] instanceof ITypeBinding typeBinding) { + potentialMethodCompletions = Stream.of(typeBinding.getDeclaredMethods()) // + .filter(methodCandidate -> { + if (!expectedMethodName.equals(methodCandidate.getName())) { + return false; + } + return methodCandidate.getParameterTypes().length >= finalizedMinNumParams; + }) // + .toList(); + } + } + } + } + if (potentialMethodCompletions != null) { + for (IMethodBinding potentialMethodCompletion : potentialMethodCompletions) { + CompletionProposal proposal = toProposal(potentialMethodCompletion); + proposal.setReplaceRange(methodRef.getName().getStartPosition(), methodRef.getStartPosition() + methodRef.getLength()); + proposal.setTokenRange(methodRef.getName().getStartPosition(), methodRef.getStartPosition() + methodRef.getLength()); + proposal.setRelevance(RelevanceConstants.R_DEFAULT + RelevanceConstants.R_RESOLVED + + RelevanceConstants.R_INTERESTING + RelevanceConstants.R_CASE + + RelevanceConstants.R_EXACT_NAME + RelevanceConstants.R_UNQUALIFIED + + RelevanceConstants.R_NON_RESTRICTED); + this.requestor.accept(proposal); + } + } + suggestDefaultCompletions = false; + } else if (state == JavadocMethodReferenceParseState.AFTER_IDENTIFIER) { + suggestDefaultCompletions = false; + } + } + if (context instanceof MemberRef memberRef) { + IBinding bindingToComplete; + if (memberRef.getQualifier() != null) { + bindingToComplete = memberRef.getQualifier().resolveBinding(); + } else { + bindingToComplete = DOMCompletionUtil.findParentTypeDeclaration(this.toComplete).resolveBinding(); + } + if (bindingToComplete instanceof ITypeBinding typeBinding) { + Bindings javadocScope = new Bindings(); + processMembers(this.toComplete, typeBinding, javadocScope, false); + publishFromScope(javadocScope); + suggestAccessibleConstructorsForType(typeBinding); + } + suggestDefaultCompletions = false; + } + if (context != null && context.getLocationInParent() == QualifiedType.NAME_PROPERTY && context.getParent() instanceof QualifiedType qType) { + Type qualifier = qType.getQualifier(); + if (qualifier != null) { + ITypeBinding qualifierBinding = qualifier.resolveBinding(); + if (qualifierBinding != null) { + for (ITypeBinding nestedType : qualifierBinding.getDeclaredTypes()) { + this.requestor.accept(toProposal(nestedType)); + } + } + } + } + + // check for accessible bindings to potentially turn into completions. + // currently, this is always run, even when not using the default completion, + // because method argument guessing uses it. + scrapeAccessibleBindings(defaultCompletionBindings); + if (context instanceof SimpleName simple && !(simple.getParent() instanceof Name)) { + for (ImportDeclaration importDecl : (List)this.unit.imports()) { + if (importDecl.isStatic()) { + if (!importDecl.isOnDemand()) { + defaultCompletionBindings.add(importDecl.resolveBinding()); + } else if (importDecl.resolveBinding() instanceof ITypeBinding staticallyImportedAll) { + // only add direct declarations, not inherited ones + Stream.of(staticallyImportedAll.getDeclaredFields(), staticallyImportedAll.getDeclaredMethods()) // + .flatMap(Arrays::stream) // + .filter(binding -> Modifier.isStatic(binding.getModifiers())) + .filter(this::isVisible) + .forEach(defaultCompletionBindings::add); + } + } + } + } + + if (suggestDefaultCompletions) { + ExtendsOrImplementsInfo extendsOrImplementsInfo = isInExtendsOrImplements(this.toComplete); + statementLikeKeywords(); + if (!this.prefix.isEmpty() && extendsOrImplementsInfo == null) { + suggestTypeKeywords(DOMCompletionUtil.findParent(this.toComplete, new int[] { ASTNode.BLOCK }) == null); + } + publishFromScope(defaultCompletionBindings); + if (!completeAfter.isBlank()) { + final int typeMatchRule = this.toComplete.getParent() instanceof Annotation + ? IJavaSearchConstants.ANNOTATION_TYPE + : IJavaSearchConstants.TYPE; + if (!this.requestor.isIgnored(CompletionProposal.TYPE_REF) && completionContext.getExpectedTypesSignatures() != null) { + findTypes(completeAfter, typeMatchRule, null) + .filter(type -> { + return defaultCompletionBindings.all().map(typeBinding -> typeBinding.getJavaElement()).noneMatch(elt -> type.equals(elt)); + }) + .filter(type -> this.pattern.matchesName(this.prefix.toCharArray(), + type.getElementName().toCharArray())) + .filter(type -> { + return filterBasedOnExtendsOrImplementsInfo(type, extendsOrImplementsInfo); + }) + .map(this::toProposal).forEach(this.requestor::accept); + } + } + checkCancelled(); + if (shouldSuggestPackages(toComplete)) { + suggestPackages(toComplete); + } + } + + checkCancelled(); + } finally { + this.requestor.endReporting(); + if (this.monitor != null) { + this.monitor.done(); + } + } + } + + private void suggestAccessibleConstructorsForType(ITypeBinding typeBinding) { + ITypeBinding parentTypeBinding = DOMCompletionUtil.findParentTypeDeclaration(this.toComplete).resolveBinding(); + Stream.of(typeBinding.getDeclaredMethods()) // + .filter(IMethodBinding::isConstructor) // + .filter(method -> { + // note that this isn't the usual logic. + // this is case sensitive, whereas filtering is usually case-insensitive. + // See CompletionEngine:2702 + // FIXME: file this as a bug upstream and see what folks say about it + return CharOperation.prefixEquals(this.prefix.toCharArray(), method.getName().toCharArray()) + || (this.assistOptions.camelCaseMatch && CharOperation.camelCaseMatch(this.prefix.toCharArray(), method.getName().toCharArray())); + }) // + .filter(method -> { + boolean includeProtected = findInSupers(parentTypeBinding, typeBinding); + if ((method.getModifiers() & Flags.AccPrivate) != 0 + && (parentTypeBinding == null || !method.getDeclaringClass().getKey().equals(parentTypeBinding.getKey()))) { + return false; + } + if ((method.getModifiers() & (Flags.AccPrivate | Flags.AccPublic | Flags.AccProtected)) == 0) { + if (parentTypeBinding == null) { + return false; + } + return method.getDeclaringClass().getPackage().getName().equals(parentTypeBinding.getPackage().getName()); + } + if ((method.getModifiers() & Flags.AccProtected) != 0) { + return includeProtected; + } + return true; + }) + .sorted(Comparator.comparing(this::getSignature).reversed()) + .map(this::toProposal) // + .forEach(this.requestor::accept); + } + + private enum JavadocMethodReferenceParseState { + BEFORE_IDENTIFIER, + IN_IDENTIFIER, + AFTER_IDENTIFIER, + ; + } + + private Set findAlreadyDocumentedParameters(Javadoc javadoc) { + Set alreadyDocumentedParameters = new HashSet<>(); + for (TagElement tagElement : (List)javadoc.tags()) { + if (TagElement.TAG_PARAM.equals(tagElement.getTagName())) { + List fragments = tagElement.fragments(); + if (!fragments.isEmpty()) { + if (fragments.get(0) instanceof TextElement textElement) { + if (fragments.size() >= 3 && fragments.get(1) instanceof Name) { + // ["<", "MyTypeVarName", ">"] + StringBuilder builder = new StringBuilder(); + builder.append(fragments.get(0)); + builder.append(fragments.get(1)); + builder.append(fragments.get(2)); + alreadyDocumentedParameters.add(builder.toString()); + } else if (!textElement.getText().isEmpty()) { + alreadyDocumentedParameters.add(textElement.getText()); + } + } else if (fragments.get(0) instanceof String str && !str.isBlank()) { + alreadyDocumentedParameters.add(str); + } else if (fragments.get(0) instanceof Name name && !name.toString().isBlank()) { + alreadyDocumentedParameters.add(name.toString()); + } + } + } + } + return alreadyDocumentedParameters; + } + + private CompletionProposal toAtParamProposal(String paramName, TagElement tagElement) { + InternalCompletionProposal res = createProposal(CompletionProposal.JAVADOC_PARAM_REF); + boolean isTypeParam = paramName.startsWith("<"); //$NON-NLS-1$ + res.setCompletion(paramName.toCharArray()); + if (isTypeParam) { + res.setName(paramName.substring(1, paramName.length() - 1).toCharArray()); + } else { + res.setName(paramName.toCharArray()); + } + int relevance = RelevanceConstants.R_DEFAULT + // FIXME: "interesting" is added twice upstream for normal parameters, + // (but 0 times for type parameters). + // This doesn't make sense to me. + + (isTypeParam ? 0 : 2 * RelevanceConstants.R_INTERESTING) + + RelevanceConstants.R_NON_RESTRICTED; + res.setRelevance(relevance); + + int tagStart = tagElement.getStartPosition(); + int realStart = tagStart + TagElement.TAG_PARAM.length() + 1; + + int endPos = realStart; + if (this.textContent != null) { + while (endPos < this.textContent.length() && !Character.isWhitespace(this.textContent.charAt(endPos))) { + endPos++; + } + } + + res.setReplaceRange(realStart, endPos); + res.setTokenRange(realStart, endPos); + return res; + } + + private boolean shouldSuggestPackages(ASTNode context) { + if (context instanceof CompilationUnit) { + return false; + } + if (context instanceof PackageDeclaration decl && decl.getName() != null && !Arrays.equals(decl.getName().toString().toCharArray(), RecoveryScanner.FAKE_IDENTIFIER)) { + // on `package` token + return false; + } + boolean inExtendsOrImplements = false; + while (context != null) { + if (context instanceof SimpleName) { + if (context.getLocationInParent() == QualifiedName.NAME_PROPERTY + || (context.getLocationInParent() instanceof ChildPropertyDescriptor child && child.getChildType() == Expression.class) + || (context.getLocationInParent() instanceof ChildListPropertyDescriptor childList && childList.getElementType() == Expression.class)) { + return true; + } + } + if (context instanceof QualifiedName) { + return true; + } + if (context instanceof Type || context instanceof Name) { + context = context.getParent(); + } else if ((context.getLocationInParent() instanceof ChildPropertyDescriptor child && child.getChildType() == Type.class) + || (context.getLocationInParent() instanceof ChildListPropertyDescriptor childList && childList.getElementType() == Type.class)) { + return true; + } else { + return false; + } + } + return !inExtendsOrImplements; + } + + private void suggestTypeKeywords(boolean includeVoid) { + for (char[] keyword : TYPE_KEYWORDS_EXCEPT_VOID) { + if (!this.isFailedMatch(this.prefix.toCharArray(), keyword)) { + this.requestor.accept(createKeywordProposal(keyword, -1, -1)); + } + } + if (includeVoid && !this.isFailedMatch(this.prefix.toCharArray(), VOID)) { + this.requestor.accept(createKeywordProposal(VOID, -1, -1)); + } + } + + private void scrapeAccessibleBindings(Bindings scope) { + ASTNode current = this.toComplete; + while (current != null) { + Collection gottenVisibleBindings = visibleBindings(current); + scope.addAll(gottenVisibleBindings); + if (current instanceof AbstractTypeDeclaration typeDecl) { + processMembers(this.toComplete, typeDecl.resolveBinding(), scope, false); + } + current = current.getParent(); + } + + // handle favourite members + if (this.requestor.getFavoriteReferences() == null) { + return; + } + + Set scopedMethods = scope.methods().map(IBinding::getName).collect(Collectors.toSet()); + Set scopedVariables = scope.all().filter(IVariableBinding.class::isInstance).map(IBinding::getName).collect(Collectors.toSet()); + Set scopedTypes = scope.all().filter(ITypeBinding.class::isInstance).map(IBinding::getName).collect(Collectors.toSet()); + + Set keysToResolve = new HashSet<>(); + IJavaProject project = this.javaProject; + for (String favouriteReference: this.requestor.getFavoriteReferences()) { + if (favouriteReference.endsWith(".*")) { //$NON-NLS-1$ + favouriteReference = favouriteReference.substring(0, favouriteReference.length() - 2); + String packageName = favouriteReference.indexOf('.') < 0 ? "" : favouriteReference.substring(0, favouriteReference.lastIndexOf('.')); //$NON-NLS-1$ + String typeName = favouriteReference.indexOf('.') < 0 ? favouriteReference : favouriteReference.substring(favouriteReference.lastIndexOf('.') + 1); + findTypes(typeName, SearchPattern.R_EXACT_MATCH, packageName).filter(type -> type.getElementName().equals(typeName)).forEach(keysToResolve::add); + } else if (favouriteReference.lastIndexOf('.') >= 0) { + String memberName = favouriteReference.substring(favouriteReference.lastIndexOf('.') + 1); + String typeFqn = favouriteReference.substring(0, favouriteReference.lastIndexOf('.')); + String packageName = typeFqn.indexOf('.') < 0 ? "" : typeFqn.substring(0, typeFqn.lastIndexOf('.')); //$NON-NLS-1$ + String typeName = typeFqn.indexOf('.') < 0 ? typeFqn : typeFqn.substring(typeFqn.lastIndexOf('.') + 1); + findTypes(typeName, SearchPattern.R_EXACT_MATCH, packageName).filter(type -> type.getElementName().equals(typeName)).findFirst().ifPresent(type -> { + try { + for (IMethod method : type.getMethods()) { + if (method.exists() && (method.getFlags() & Flags.AccStatic) != 0 && memberName.equals(method.getElementName())) { + keysToResolve.add(method); + } + } + IField field = type.getField(memberName); + if (field.exists() && (field.getFlags() & Flags.AccStatic) != 0) { + keysToResolve.add(type.getField(memberName)); + } + } catch (JavaModelException e) { + // do nothing + } + }); + } + } + Bindings favoriteBindings = new Bindings(); + ASTParser parser = ASTParser.newParser(AST.getJLSLatest()); + parser.setProject(project); + if (!keysToResolve.isEmpty()) { + IBinding[] bindings = parser.createBindings(keysToResolve.toArray(IJavaElement[]::new), this.monitor); + for (IBinding binding : bindings) { + if (binding instanceof ITypeBinding typeBinding) { + processMembers(this.toComplete, typeBinding, favoriteBindings, true); + } else if (binding instanceof IMethodBinding methodBinding) { + favoriteBindings.add(methodBinding); + } else { + favoriteBindings.add(binding); + } + } + } + favoriteBindings.all() + .filter(binding -> { + if (binding instanceof IMethodBinding) { + return !scopedMethods.contains(binding.getName()); + } else if (binding instanceof IVariableBinding) { + return !scopedVariables.contains(binding.getName()); + } + return !scopedTypes.contains(binding.getName()); + }) + .forEach(scope::add); + } + + private void completeMethodModifiers(MethodDeclaration methodDeclaration) { + List keywords = new ArrayList<>(); + + if ((methodDeclaration.getModifiers() & Flags.AccAbstract) == 0) { + keywords.add(Keywords.ABSTRACT); + } + if ((methodDeclaration.getModifiers() & (Flags.AccPublic | Flags.AccPrivate | Flags.AccProtected)) == 0) { + keywords.add(Keywords.PUBLIC); + keywords.add(Keywords.PRIVATE); + keywords.add(Keywords.PROTECTED); + } + if ((methodDeclaration.getModifiers() & Flags.AccDefaultMethod) == 0) { + keywords.add(Keywords.DEFAULT); + } + if ((methodDeclaration.getModifiers() & Flags.AccFinal) == 0) { + keywords.add(Keywords.FINAL); + } + if ((methodDeclaration.getModifiers() & Flags.AccNative) == 0) { + keywords.add(Keywords.NATIVE); + } + if ((methodDeclaration.getModifiers() & Flags.AccStrictfp) == 0) { + keywords.add(Keywords.STRICTFP); + } + if ((methodDeclaration.getModifiers() & Flags.AccSynchronized) == 0) { + keywords.add(Keywords.SYNCHRONIZED); + } + + for (char[] keyword : keywords) { + if (!isFailedMatch(this.prefix.toCharArray(), keyword)) { + this.requestor.accept(createKeywordProposal(keyword, this.offset, this.offset)); + } + } + } + + private void checkCancelled() { + if (this.monitor != null && this.monitor.isCanceled()) { + throw new OperationCanceledException(); + } + } + + private void statementLikeKeywords() { + List keywords = new ArrayList<>(); + keywords.add(Keywords.ASSERT); + keywords.add(Keywords.RETURN); + keywords.add(Keywords.SUPER); + if (DOMCompletionUtil.findParent(this.toComplete, + new int[] { ASTNode.WHILE_STATEMENT, ASTNode.DO_STATEMENT, ASTNode.FOR_STATEMENT }) != null) { + keywords.add(Keywords.BREAK); + keywords.add(Keywords.CONTINUE); + } + ExpressionStatement exprStatement = (ExpressionStatement) DOMCompletionUtil.findParent(this.toComplete, new int[] {ASTNode.EXPRESSION_STATEMENT}); + if (exprStatement != null) { + + ASTNode statementParent = exprStatement.getParent(); + if (statementParent instanceof Block block) { + int exprIndex = block.statements().indexOf(exprStatement); + if (exprIndex > 0) { + ASTNode prevStatement = (ASTNode)block.statements().get(exprIndex - 1); + if (prevStatement.getNodeType() == ASTNode.IF_STATEMENT) { + keywords.add(Keywords.ELSE); + } + } + } + } + for (char[] keyword : keywords) { + if (!isFailedMatch(this.toComplete.toString().toCharArray(), keyword)) { + this.requestor.accept(createKeywordProposal(keyword, this.toComplete.getStartPosition(), this.offset)); + } + } + } + + private void completeJavadocBlockTags(TagElement tagNode) { + if (this.requestor.isIgnored(CompletionProposal.JAVADOC_BLOCK_TAG)) { + return; + } + for (char[] blockTag : DOMCompletionEngineJavadocUtil.getJavadocBlockTags(this.javaProject, tagNode)) { + if (!isFailedMatch(this.prefix.toCharArray(), blockTag)) { + this.requestor.accept(toJavadocBlockTagProposal(blockTag)); + } + } + } + + private void completeJavadocInlineTags(TagElement tagNode) { + if (this.requestor.isIgnored(CompletionProposal.JAVADOC_INLINE_TAG)) { + return; + } + for (char[] blockTag : DOMCompletionEngineJavadocUtil.getJavadocInlineTags(this.javaProject, tagNode)) { + if (!isFailedMatch(this.prefix.toCharArray(), blockTag)) { + this.requestor.accept(toJavadocInlineTagProposal(blockTag)); + } + } + } + + private void completeThrowsClause(MethodDeclaration methodDeclaration, Bindings scope) { + if (methodDeclaration.thrownExceptionTypes().size() == 0) { + int startScanIndex = Math.max( + methodDeclaration.getName().getStartPosition() + methodDeclaration.getName().getLength(), + methodDeclaration.getReturnType2().getStartPosition() + methodDeclaration.getReturnType2().getLength()); + if (!methodDeclaration.parameters().isEmpty()) { + SingleVariableDeclaration lastParam = (SingleVariableDeclaration)methodDeclaration.parameters().get(methodDeclaration.parameters().size() - 1); + startScanIndex = lastParam.getName().getStartPosition() + lastParam.getName().getLength(); + } + if (this.textContent != null) { + String text = this.textContent.substring(startScanIndex, this.offset); + // JDT checks for "throw" instead of "throws", probably assuming that it's a common misspelling + int firstThrow = text.indexOf("throw"); //$NON-NLS-1$ + if (firstThrow == -1 || firstThrow >= this.offset - this.prefix.length()) { + if (!isFailedMatch(this.prefix.toCharArray(), Keywords.THROWS)) { + this.requestor.accept(createKeywordProposal(Keywords.THROWS, -1, -1)); + } + } + } else { + if (!isFailedMatch(this.prefix.toCharArray(), Keywords.THROWS)) { + this.requestor.accept(createKeywordProposal(Keywords.THROWS, -1, -1)); + } + } + } + } + + private void suggestPackages(ASTNode context) { + checkCancelled(); + while (context != null && context.getParent() instanceof Name) { + context = context.getParent(); + } + String prefix = context instanceof Name name ? name.toString() : this.prefix; + if (prefix != null && !prefix.isBlank()) { + Set packageNames = new HashSet<>(); + this.nameEnvironment.findPackages(prefix.toCharArray(), new ISearchRequestor() { + + @Override + public void acceptConstructor(int modifiers, char[] simpleTypeName, int parameterCount, + char[] signature, char[][] parameterTypes, char[][] parameterNames, int typeModifiers, + char[] packageName, int extraFlags, String path, AccessRestriction access) { + // do nothing + } + + @Override + public void acceptType(char[] packageName, char[] typeName, char[][] enclosingTypeNames, int modifiers, + AccessRestriction accessRestriction) { + // do nothing + } + + @Override + public void acceptPackage(char[] packageName) { + String packageNameString = new String(packageName); + // TODO: CompletionEngine has a caching mechanism for this + if (isValidPackageName(packageNameString)) { + packageNames.add(new String(packageName)); + } + } + + @Override + public void acceptModule(char[] moduleName) { + // do nothing + } + + }); + for (String packageName : packageNames) { + this.requestor.accept(toPackageProposal(packageName, context)); + } + } + } + + private boolean isValidPackageName(String packageName) { + String[] names = packageName.split("\\."); //$NON-NLS-1$ + for (String name : names) { + if (!Util.isValidFolderNameForPackage( + name, + this.modelUnit.getJavaProject().getOption(JavaCore.COMPILER_SOURCE, true), + this.modelUnit.getJavaProject().getOption(JavaCore.COMPILER_COMPLIANCE, true))) { + return false; + } + } + return true; + } + + private CompletionProposal toPackageProposal(String packageName, ASTNode completing) { + InternalCompletionProposal res = createProposal(CompletionProposal.PACKAGE_REF); + res.setCompletion(packageName.toCharArray()); + res.setDeclarationSignature(packageName.toCharArray()); + res.setPackageName(packageName.toCharArray()); + QualifiedName qualifiedName = (QualifiedName)DOMCompletionUtil.findParent(completing, new int[] {ASTNode.QUALIFIED_NAME}); + int relevance = RelevanceConstants.R_DEFAULT + + RelevanceConstants.R_RESOLVED + + RelevanceConstants.R_INTERESTING + + RelevanceUtils.computeRelevanceForCaseMatching(this.qualifiedPrefix.toCharArray(), packageName.toCharArray(), this.assistOptions) + + RelevanceUtils.computeRelevanceForQualification(true, this.prefix, this.qualifiedPrefix) + + RelevanceConstants.R_NON_RESTRICTED; + res.setRelevance(relevance); + if (qualifiedName != null) { + res.setReplaceRange(qualifiedName.getStartPosition(), this.offset); + } + return res; + } + + private void suggestTypesInPackage(String packageName) { + if (!this.requestor.isIgnored(CompletionProposal.TYPE_REF)) { + List foundTypes = findTypes(this.prefix, packageName).toList(); + ExtendsOrImplementsInfo extendsOrImplementsInfo = isInExtendsOrImplements(this.toComplete); + for (IType foundType : foundTypes) { + if (this.pattern.matchesName(this.prefix.toCharArray(), foundType.getElementName().toCharArray())) { + if (filterBasedOnExtendsOrImplementsInfo(foundType, extendsOrImplementsInfo)) { + this.requestor.accept(this.toProposal(foundType)); + } + } + } + } + } + + private boolean filterBasedOnExtendsOrImplementsInfo(IType toFilter, ExtendsOrImplementsInfo info) { + if (info == null) { + return true; + } + try { + if (!(info.typeDecl instanceof TypeDeclaration typeDeclaration) + || (toFilter.getFlags() & Flags.AccFinal) != 0 + || typeDeclaration.resolveBinding().getKey().equals(toFilter.getKey())) { + return false; + } + if (typeDeclaration.isInterface() + // in an interface extends clause, we should rule out non-interfaces + && toFilter.isInterface() + // prevent double extending + && !extendsOrImplementsGivenType(typeDeclaration, toFilter)) { + return true; + } else if (!typeDeclaration.isInterface() + // in an extends clause, only accept non-interfaces + // in an implements clause, only accept interfaces + && (info.isImplements == toFilter.isInterface()) + // prevent double extending + && !extendsOrImplementsGivenType(typeDeclaration, toFilter)) { + return true; + } + return false; + } catch (JavaModelException e) { + // we can't really tell if it's appropriate + return true; + } + } + + /** + * Returns info if the given node is in an extends or implements clause, or null if not in either clause + * + * @see ExtendsOrImplementsInfo + * @param completion the node to check + * @return info if the given node is in an extends or implements clause, or null if not in either clause + */ + private static ExtendsOrImplementsInfo isInExtendsOrImplements(ASTNode completion) { + ASTNode cursor = completion; + while (cursor != null + && cursor.getNodeType() != ASTNode.TYPE_DECLARATION + && cursor.getNodeType() != ASTNode.ENUM_DECLARATION + && cursor.getNodeType() != ASTNode.RECORD_DECLARATION + && cursor.getNodeType() != ASTNode.ANNOTATION_TYPE_DECLARATION) { + StructuralPropertyDescriptor locationInParent = cursor.getLocationInParent(); + if (locationInParent == null) { + return null; + } + if (locationInParent.isChildListProperty()) { + String locationId = locationInParent.getId(); + if (TypeDeclaration.SUPER_INTERFACE_TYPES_PROPERTY.getId().equals(locationId) + || EnumDeclaration.SUPER_INTERFACE_TYPES_PROPERTY.getId().equals(locationId) + || RecordDeclaration.SUPER_INTERFACE_TYPES_PROPERTY.getId().equals(locationId)) { + return new ExtendsOrImplementsInfo((AbstractTypeDeclaration)cursor.getParent(), true); + } + } else if (locationInParent.isChildProperty()) { + String locationId = locationInParent.getId(); + if (TypeDeclaration.SUPERCLASS_TYPE_PROPERTY.getId().equals(locationId)) { + return new ExtendsOrImplementsInfo((AbstractTypeDeclaration)cursor.getParent(), false); + } + } + cursor = cursor.getParent(); + } + return null; + } + + /** + * @param typeDecl the type declaration that holds the completion node + * @param isImplements true if the node to complete is in an implements clause, or false if the node + */ + private static record ExtendsOrImplementsInfo(AbstractTypeDeclaration typeDecl, boolean isImplements) { + } + + /** + * Returns true if the given declaration already extends or implements the given reference + * + * @param typeDecl the declaration to check the extends and implements of + * @param typeRef the reference to check for in the extends and implements + * @return true if the given declaration already extends or implements the given reference + */ + private static boolean extendsOrImplementsGivenType(TypeDeclaration typeDecl, IType typeRef) { + String refKey = typeRef.getKey(); + if (typeDecl.getSuperclassType() != null + && typeDecl.getSuperclassType().resolveBinding() != null + && refKey.equals(typeDecl.getSuperclassType().resolveBinding().getKey())) { + return true; + } + for (var superInterface : typeDecl.superInterfaceTypes()) { + ITypeBinding superInterfaceBinding = ((Type)superInterface).resolveBinding(); + if (superInterfaceBinding != null && refKey.equals(superInterfaceBinding.getKey())) { + return true; + } + } + return false; + } + + private void completeMarkerAnnotation(String completeAfter) { + findTypes(completeAfter, IJavaSearchConstants.ANNOTATION_TYPE, null) + .filter(type -> this.pattern.matchesName(this.prefix.toCharArray(), + type.getElementName().toCharArray())) + .map(this::toProposal).forEach(this.requestor::accept); + } + + private void completeNormalAnnotationParams(NormalAnnotation normalAnnotation, Bindings scope) { + Set definedKeys = ((List)normalAnnotation.values()).stream() // + .map(mvp -> mvp.getName().toString()) // + .collect(Collectors.toSet()); + completeAnnotationParams(normalAnnotation, definedKeys, scope); + } + + private void completeAnnotationParams(Annotation annotation, Set definedKeys, Bindings scope) { + Arrays.stream(annotation.resolveTypeBinding().getDeclaredMethods()) // + .filter(declaredMethod -> { + return (declaredMethod.getModifiers() & Flags.AccStatic) == 0 + && !definedKeys.contains(declaredMethod.getName().toString()); + }) // + .forEach(scope::add); + scope.methods() // + .filter(binding -> this.pattern.matchesName(this.prefix.toCharArray(), binding.getName().toCharArray())) // + .map(binding -> toAnnotationAttributeRefProposal(binding)) + .forEach(this.requestor::accept); + } + + private void publishFromScope(Bindings scope) { + scope.all() // + .filter(binding -> this.pattern.matchesName(this.prefix.toCharArray(), binding.getName().toCharArray())) // + .map(binding -> toProposal(binding)) + .forEach(this.requestor::accept); + } + + private void completeConstructor(ITypeBinding typeBinding, ASTNode referencedFrom, IJavaProject javaProject) { + // compute type hierarchy + boolean isArray = typeBinding.isArray(); + IType typeHandle = ((IType)typeBinding.getJavaElement()); + AbstractTypeDeclaration enclosingType = (AbstractTypeDeclaration) DOMCompletionUtil.findParent(referencedFrom, new int[] { ASTNode.TYPE_DECLARATION, ASTNode.ENUM_DECLARATION, ASTNode.RECORD_DECLARATION, ASTNode.ANNOTATION_TYPE_DECLARATION }); + ITypeBinding enclosingTypeBinding = enclosingType.resolveBinding(); + IType enclosingTypeElement = (IType) enclosingTypeBinding.getJavaElement(); + if (typeHandle != null) { + try { + ITypeHierarchy newTypeHierarchy = typeHandle.newTypeHierarchy(javaProject, null); + ASTParser parser = ASTParser.newParser(AST.getJLSLatest()); + parser.setProject(javaProject); + List subtypes = new ArrayList<>(); + subtypes.add(typeHandle); + for (IType subtype : newTypeHierarchy.getSubtypes(typeHandle)) { + subtypes.add(subtype); + } + // Always include the current type as a possible constructor suggestion, + // regardless if it's appropriate + // FIXME: I feel like this is a misfeature of JDT + // I want to fix it upstream as well as here + if (enclosingTypeElement != null) { + boolean alreadyThere = false; + for (IType subtype : subtypes) { + if (subtype.getKey().equals(enclosingTypeElement.getKey())) { + alreadyThere = true; + } + } + if (!alreadyThere) { + subtypes.add(enclosingTypeElement); + } + } + + if (isArray) { + for (IType subtype : subtypes) { + if (!this.isFailedMatch(this.prefix.toCharArray(), subtype.getElementName().toCharArray())) { + InternalCompletionProposal typeProposal = (InternalCompletionProposal) toProposal(subtype); + typeProposal.setArrayDimensions(typeBinding.getDimensions()); + typeProposal.setRelevance(typeProposal.getRelevance() + RelevanceConstants.R_EXACT_EXPECTED_TYPE); + this.requestor.accept(typeProposal); + } + } + } else { + for (IType subtype : subtypes) { + if (!this.isFailedMatch(this.prefix.toCharArray(), subtype.getElementName().toCharArray())) { + List proposals = toConstructorProposals(subtype, referencedFrom, true); + for (CompletionProposal proposal : proposals) { + this.requestor.accept(proposal); + } + } + } + } + + } catch (JavaModelException e) { + ILog.get().error("Unable to compute type hierarchy while performing completion", e); //$NON-NLS-1$ + } + } else if (enclosingTypeElement != null) { + if (isArray) { + suggestTypeKeywords(false); + } + // for some reason the enclosing type is almost always suggested + if (!this.isFailedMatch(this.prefix.toCharArray(), enclosingTypeElement.getElementName().toCharArray())) { + List proposals = toConstructorProposals(enclosingTypeElement, referencedFrom, true); + for (CompletionProposal proposal : proposals) { + this.requestor.accept(proposal); + } + } + } + } + + private void findOverridableMethods(ITypeBinding typeBinding, IJavaProject javaProject, ASTNode toReplace) { + String originalPackageKey = typeBinding.getPackage().getKey(); + Set alreadySuggestedMethodKeys = new HashSet<>(); + if (typeBinding.getSuperclass() != null) { + findOverridableMethods0(typeBinding.getSuperclass(), alreadySuggestedMethodKeys, javaProject, originalPackageKey, toReplace); + } + for (ITypeBinding superInterface : typeBinding.getInterfaces()) { + findOverridableMethods0(superInterface, alreadySuggestedMethodKeys, javaProject, originalPackageKey, toReplace); + } + } + + private void findOverridableMethods0(ITypeBinding typeBinding, Set alreadySuggestedKeys, IJavaProject javaProject, String originalPackageKey, ASTNode toReplace) { + next : for (IMethodBinding method : typeBinding.getDeclaredMethods()) { + if (alreadySuggestedKeys.contains(method.getKey())) { + continue next; + } + if (method.isSynthetic() || method.isConstructor() + || (this.assistOptions.checkDeprecation && method.isDeprecated()) + || Modifier.isStatic(method.getModifiers()) + || Modifier.isPrivate(method.getModifiers()) + || ((method.getModifiers() & (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED)) == 0) && !typeBinding.getPackage().getKey().equals(originalPackageKey)) { + continue next; + } + alreadySuggestedKeys.add(method.getKey()); + if (Modifier.isFinal(method.getModifiers())) { + continue next; + } + if (isFailedMatch(this.prefix.toCharArray(), method.getName().toCharArray())) { + continue next; + } + DOMInternalCompletionProposal proposal = createProposal(CompletionProposal.METHOD_DECLARATION); + proposal.setReplaceRange(this.offset, this.offset); + if (toReplace != null) { + proposal.setReplaceRange(toReplace.getStartPosition(), toReplace.getStartPosition() + toReplace.getLength()); + } + proposal.setName(method.getName().toCharArray()); + proposal.setFlags(method.getModifiers()); + proposal.setTypeName(method.getReturnType().getName().toCharArray()); + proposal.setDeclarationPackageName(typeBinding.getPackage().getName().toCharArray()); + proposal.setDeclarationTypeName(typeBinding.getQualifiedName().toCharArray()); + proposal.setDeclarationSignature(DOMCompletionEngineBuilder.getSignature(method.getDeclaringClass())); + proposal.setKey(method.getKey().toCharArray()); + proposal.setSignature(DOMCompletionEngineBuilder.getSignature(method)); + proposal.setParameterNames(Stream.of(method.getParameterNames()).map(name -> name.toCharArray()).toArray(char[][]::new)); + + int relevance = RelevanceConstants.R_DEFAULT + + RelevanceConstants.R_RESOLVED + + RelevanceConstants.R_INTERESTING + + RelevanceConstants.R_METHOD_OVERIDE + + (Modifier.isAbstract(method.getModifiers()) ? RelevanceConstants.R_ABSTRACT_METHOD : 0) + + RelevanceConstants.R_NON_RESTRICTED; + proposal.setRelevance(relevance); + + StringBuilder completion = new StringBuilder(); + DOMCompletionEngineBuilder.createMethod(method, completion); + proposal.setCompletion(completion.toString().toCharArray()); + this.requestor.accept(proposal); + } + if (typeBinding.getSuperclass() != null) { + findOverridableMethods0(typeBinding.getSuperclass(), alreadySuggestedKeys, javaProject, originalPackageKey, toReplace); + } + for (ITypeBinding superInterface : typeBinding.getInterfaces()) { + findOverridableMethods0(superInterface, alreadySuggestedKeys, javaProject, originalPackageKey, toReplace); + } + } + + private Stream findTypes(String namePrefix, String packageName) { + return findTypes(namePrefix, IJavaSearchConstants.TYPE, packageName); + } + + private Stream findTypes(String namePrefix, int typeMatchRule, String packageName) { + if (namePrefix == null) { + namePrefix = ""; //$NON-NLS-1$ + } + List types = new ArrayList<>(); + var searchScope = SearchEngine.createJavaSearchScope(new IJavaElement[] { this.javaProject }); + TypeNameMatchRequestor typeRequestor = new TypeNameMatchRequestor() { + @Override + public void acceptTypeNameMatch(org.eclipse.jdt.core.search.TypeNameMatch match) { + types.add(match.getType()); + } + }; + try { + new SearchEngine(this.modelUnit instanceof ICompilationUnit modelCU ? modelCU.getOwner() : this.workingCopyOwner).searchAllTypeNames( + packageName == null ? null : packageName.toCharArray(), SearchPattern.R_EXACT_MATCH, + namePrefix.toCharArray(), + SearchPattern.R_PREFIX_MATCH + | (this.assistOptions.substringMatch ? SearchPattern.R_SUBSTRING_MATCH : 0) + | (this.assistOptions.subwordMatch ? SearchPattern.R_SUBWORD_MATCH : 0) + | (this.assistOptions.camelCaseMatch ? SearchPattern.R_CAMELCASE_MATCH : 0), + typeMatchRule, searchScope, typeRequestor, IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, null); + // TODO also resolve potential sub-packages + } catch (JavaModelException ex) { + ILog.get().error(ex.getMessage(), ex); + } + return types.stream(); + } + + private void processMembers(ASTNode referencedFrom, ITypeBinding typeBinding, Bindings scope, boolean isStaticContext) { + AbstractTypeDeclaration parentType = (AbstractTypeDeclaration)DOMCompletionUtil.findParent(referencedFrom, new int[] {ASTNode.ANNOTATION_TYPE_DECLARATION, ASTNode.TYPE_DECLARATION, ASTNode.ENUM_DECLARATION, ASTNode.RECORD_DECLARATION}); + if (parentType == null) { + return; + } + ITypeBinding referencedFromBinding = parentType.resolveBinding(); + boolean includePrivate = referencedFromBinding.getKey().equals(typeBinding.getKey()); + MethodDeclaration methodDeclaration = (MethodDeclaration)DOMCompletionUtil.findParent(referencedFrom, new int[] {ASTNode.METHOD_DECLARATION}); + // you can reference protected fields/methods from a static method, + // as long as those protected fields/methods are declared in the current class. + // otherwise, the (inherited) fields/methods can only be accessed in non-static methods. + boolean includeProtected; + if (referencedFromBinding.getKey().equals(typeBinding.getKey())) { + includeProtected = true; + } else if (methodDeclaration != null + && (methodDeclaration.getModifiers() & Flags.AccStatic) != 0) { + includeProtected = false; + } else { + includeProtected = findInSupers(referencedFromBinding, typeBinding); + } + processMembers(typeBinding, scope, includePrivate, includeProtected, referencedFromBinding.getPackage().getKey(), isStaticContext, typeBinding.isInterface(), + new HashSet<>(), new HashSet<>()); + } + + private void processMembers(ITypeBinding typeBinding, Bindings scope, + boolean includePrivate, + boolean includeProtected, + String originalPackageKey, + boolean isStaticContext, + boolean canUseAbstract, + Set impossibleMethods, + Set impossibleFields) { + if (typeBinding == null) { + return; + } + + Predicate accessFilter = binding -> { + if (binding == null) { + return false; + } + boolean field = binding instanceof IVariableBinding; + if (field) { + if (impossibleFields.contains(binding.getName())) { + return false; + } + } else { + if (impossibleMethods.contains(binding.getName())) { + return false; + } + if (((IMethodBinding)binding).isConstructor()) { + return false; + } + } + if ( + // check private + (!includePrivate && (binding.getModifiers() & Flags.AccPrivate) != 0) + // check protected + || (!includeProtected && (binding.getModifiers() & Flags.AccProtected) != 0) + // check package private + || ((binding.getModifiers() & (Flags.AccPublic | Flags.AccProtected | Flags.AccPrivate)) == 0 && !originalPackageKey.equals(typeBinding.getPackage().getKey())) + // check static + || (isStaticContext && (binding.getModifiers() & Flags.AccStatic) == 0) + // check abstract + || (!canUseAbstract && (binding.getModifiers() & Flags.AccAbstract) != 0) + ) { + if (field) { + impossibleFields.add(binding.getName()); + } else { + impossibleMethods.add(binding.getName()); + } + return false; + } + return true; + }; + + ASTNode foundDecl = DOMCompletionUtil.findParent(this.toComplete, new int[] {ASTNode.FIELD_DECLARATION, ASTNode.METHOD_DECLARATION, ASTNode.LAMBDA_EXPRESSION, ASTNode.BLOCK}); + // includePrivate means we are in the declaring class + if (includePrivate && foundDecl instanceof FieldDeclaration fieldDeclaration) { + // we need to take into account the order of field declarations and their fragments, + // because any declared after this node are not viable. + VariableDeclarationFragment fragment = (VariableDeclarationFragment)DOMCompletionUtil.findParent(this.toComplete, new int[] {ASTNode.VARIABLE_DECLARATION_FRAGMENT}); + AbstractTypeDeclaration typeDecl = (AbstractTypeDeclaration)((CompilationUnit)this.toComplete.getRoot()).findDeclaringNode(typeBinding); + int indexOfField = typeDecl.bodyDeclarations().indexOf(fieldDeclaration); + if (indexOfField < 0) { + // oops we messed up, probably this fieldDecl is in a nested class + // proceed as normal + Arrays.stream(typeBinding.getDeclaredFields()) // + .filter(accessFilter) // + .forEach(scope::add); + } else { + for (int i = 0; i < indexOfField + 1; i++) { + if (typeDecl.bodyDeclarations().get(i) instanceof FieldDeclaration fieldDecl) { + List frags = fieldDecl.fragments(); + int fragIterEndpoint = frags.indexOf(fragment); + if (fragIterEndpoint == -1) { + fragIterEndpoint = frags.size(); + } + for (int j = 0; j < fragIterEndpoint; j++) { + IVariableBinding varBinding = frags.get(j).resolveBinding(); + if (accessFilter.test(varBinding)) { + scope.add(varBinding); + } + } + } + } + } + } else { + Arrays.stream(typeBinding.getDeclaredFields()) // + .filter(accessFilter) // + .forEach(scope::add); + } + Arrays.stream(typeBinding.getDeclaredMethods()) // + .filter(accessFilter) // + .sorted(Comparator.comparing(this::getSignature).reversed()) // as expected by tests + .forEach(scope::add); + if (typeBinding.getInterfaces() != null) { + for (ITypeBinding superinterfaceBinding : typeBinding.getInterfaces()) { + processMembers(superinterfaceBinding, scope, false, includeProtected, originalPackageKey, isStaticContext, true, impossibleMethods, impossibleFields); + } + } + ITypeBinding superclassBinding = typeBinding.getSuperclass(); + if (superclassBinding != null) { + processMembers(superclassBinding, scope, false, includeProtected, originalPackageKey, isStaticContext, true, impossibleMethods, impossibleFields); + } + } + + private String getSignature(IMethodBinding method) { + return method.getName() + '(' + + Arrays.stream(method.getParameterTypes()).map(ITypeBinding::getName).collect(Collectors.joining(",")) + + ')'; + } + + private static boolean findInSupers(ITypeBinding root, ITypeBinding toFind) { + ITypeBinding superFind = toFind.getErasure(); + if( superFind != null ) { + String keyToFind = superFind.getKey(); + return findInSupers(root, keyToFind); + } + return false; + } + + private static boolean findInSupers(ITypeBinding root, String keyOfTypeToFind) { + String keyToFind = keyOfTypeToFind; + Queue toCheck = new LinkedList<>(); + Set alreadyChecked = new HashSet<>(); + toCheck.add(root.getErasure()); + while (!toCheck.isEmpty()) { + ITypeBinding current = toCheck.poll(); + String currentKey = current.getErasure().getKey(); + if (alreadyChecked.contains(currentKey)) { + continue; + } + alreadyChecked.add(currentKey); + if (currentKey.equals(keyToFind)) { + return true; + } + for (ITypeBinding superInterface : current.getInterfaces()) { + toCheck.add(superInterface); + } + if (current.getSuperclass() != null) { + toCheck.add(current.getSuperclass()); + } + } + return false; + } + + private CompletionProposal toProposal(IBinding binding) { + return toProposal(binding, binding.getName()); + } + + private CompletionProposal toProposal(IBinding binding, String completion) { + if (binding instanceof ITypeBinding && binding.getJavaElement() instanceof IType type) { + return toProposal(type); + } + + int kind = -1; + boolean inJavadoc = DOMCompletionUtil.findParent(this.toComplete, new int [] { ASTNode.JAVADOC }) != null; + if (binding instanceof ITypeBinding) { + kind = CompletionProposal.TYPE_REF; + } else if (binding instanceof IMethodBinding m) { + if (DOMCompletionUtil.findParent(this.toComplete, new int[] { ASTNode.EXPRESSION_METHOD_REFERENCE }) != null) { + kind = CompletionProposal.METHOD_NAME_REFERENCE; + } else if (m.getDeclaringClass() != null && m.getDeclaringClass().isAnnotation()) { + kind = CompletionProposal.ANNOTATION_ATTRIBUTE_REF; + } else { + kind = CompletionProposal.METHOD_REF; + } + } else if (binding instanceof IVariableBinding variableBinding) { + if (variableBinding.isField()) { + kind = CompletionProposal.FIELD_REF; + } else { + kind = CompletionProposal.LOCAL_VARIABLE_REF; + } + } + + DOMInternalCompletionProposal res = createProposal(kind); + res.setName(binding.getName().toCharArray()); + if (kind == CompletionProposal.METHOD_REF) { + completion += "()"; //$NON-NLS-1$ + } + res.setCompletion(completion.toCharArray()); + res.setFlags(binding.getModifiers()); + + boolean inheritedValue = false; + if (kind == CompletionProposal.METHOD_REF || kind == CompletionProposal.METHOD_NAME_REFERENCE) { + var methodBinding = (IMethodBinding) binding; + + if (inJavadoc) { + // the completion text is completely different from method invocations, + // since we add the type names instead of guessing the parameters + StringBuilder javadocCompletion = new StringBuilder(); + javadocCompletion.append(binding.getName().toCharArray()); + javadocCompletion.append('('); + boolean isVarargs = methodBinding.isVarargs(); + for (int p=0, ln=methodBinding.getParameterTypes().length; p < ln; p++) { + if (p>0) javadocCompletion.append(", "); //$NON-NLS-1$ + ITypeBinding argTypeBinding = methodBinding.getParameterTypes()[p]; + if (isVarargs && p == ln - 1) { + javadocCompletion.append(argTypeBinding.getElementType().getName()); + javadocCompletion.append("..."); //$NON-NLS-1$ + } else { + javadocCompletion.append(argTypeBinding.getName()); + } + } + javadocCompletion.append(')'); + res.setCompletion(javadocCompletion.toString().toCharArray()); + } + + if (methodBinding.isConstructor()) { + res.setIsContructor(true); + } + + var paramNames = DOMCompletionEngineMethodDeclHandler.findVariableNames(methodBinding); + if (paramNames.isEmpty()) { + res.setParameterNames(null); + } else { + res.setParameterNames(paramNames.stream().map(String::toCharArray).toArray(i -> new char[i][])); + } + res.setParameterTypeNames(Stream.of(methodBinding.getParameterNames()).map(String::toCharArray).toArray(char[][]::new)); + res.setSignature(DOMCompletionEngineBuilder.getSignature(methodBinding)); + if (!methodBinding.getDeclaringClass().getQualifiedName().isEmpty()) { + res.setDeclarationSignature(Signature + .createTypeSignature(methodBinding.getDeclaringClass().getQualifiedName().toCharArray(), true) + .toCharArray()); + } + + if (Modifier.isStatic(methodBinding.getModifiers()) + && this.toComplete.getLocationInParent() != QualifiedName.NAME_PROPERTY && this.toComplete.getLocationInParent() != FieldAccess.NAME_PROPERTY + && !isStaticallyImported(binding)) { + ITypeBinding topLevelClass = methodBinding.getDeclaringClass(); + while (topLevelClass.getDeclaringClass() != null) { + topLevelClass = topLevelClass.getDeclaringClass(); + } + final ITypeBinding finalizedTopLevelClass = topLevelClass; + ITypeBinding methodTypeBinding = methodBinding.getDeclaringClass(); + AbstractTypeDeclaration parentTypeDecl = DOMCompletionUtil.findParentTypeDeclaration(this.toComplete); + if (parentTypeDecl != null) { + ITypeBinding completionContextTypeBinding = parentTypeDecl.resolveBinding(); + while (completionContextTypeBinding != null) { + if (completionContextTypeBinding.getErasure().getKey().equals(methodTypeBinding.getErasure().getKey())) { + inheritedValue = true; + break; + } + completionContextTypeBinding = completionContextTypeBinding.getSuperclass(); + } + } + + boolean isMethodInCurrentCU = ((List)this.unit.types()).stream().anyMatch(typeDecl -> typeDecl.getName().toString().equals(finalizedTopLevelClass.getName())); + if (!inheritedValue && !isMethodInCurrentCU) { + if (this.qualifiedPrefix.equals(this.prefix) && !this.javaProject.getOption(JavaCore.CODEASSIST_SUGGEST_STATIC_IMPORTS, true).equals(JavaCore.DISABLED)) { + res.setRequiredProposals(new CompletionProposal[] { toStaticImportProposal(methodBinding) }); + } else { + ITypeBinding directParentClass = methodBinding.getDeclaringClass(); + res.setRequiredProposals(new CompletionProposal[] { toStaticImportProposal(directParentClass) }); + StringBuilder builder = new StringBuilder(new String(res.getCompletion())); + builder.insert(0, '.'); + builder.insert(0, directParentClass.getName()); + res.setCompletion(builder.toString().toCharArray()); + } + } + } + } else if (kind == CompletionProposal.LOCAL_VARIABLE_REF) { + var variableBinding = (IVariableBinding) binding; + res.setSignature( + Signature.createTypeSignature(variableBinding.getType().getQualifiedName().toCharArray(), true) + .toCharArray()); + } else if (kind == CompletionProposal.FIELD_REF) { + var variableBinding = (IVariableBinding) binding; + ITypeBinding declaringClass = variableBinding.getDeclaringClass(); + res.setSignature( + Signature.createTypeSignature(variableBinding.getType().getQualifiedName().toCharArray(), true) + .toCharArray()); + if (declaringClass != null && !declaringClass.getQualifiedName().isEmpty()) { + char[] declSignature = Signature + .createTypeSignature( + declaringClass.getQualifiedName().toCharArray(), true) + .toCharArray(); + res.setDeclarationSignature(declSignature); + } else { + res.setDeclarationSignature(new char[0]); + } + + if ((variableBinding.getModifiers() & Flags.AccStatic) != 0) { + ITypeBinding topLevelClass = variableBinding.getDeclaringClass(); + while (topLevelClass.getDeclaringClass() != null) { + topLevelClass = topLevelClass.getDeclaringClass(); + } + final ITypeBinding finalizedTopLevelClass = topLevelClass; + ITypeBinding variableTypeBinding = variableBinding.getDeclaringClass(); + AbstractTypeDeclaration parentTypeDecl = DOMCompletionUtil.findParentTypeDeclaration(this.toComplete); + if (parentTypeDecl != null) { + ITypeBinding completionContextTypeBinding = parentTypeDecl.resolveBinding(); + while (completionContextTypeBinding != null) { + if (completionContextTypeBinding.getErasure().getKey().equals(variableTypeBinding.getErasure().getKey())) { + inheritedValue = true; + break; + } + completionContextTypeBinding = completionContextTypeBinding.getSuperclass(); + } + } + boolean isVariableInCurrentCU = ((List)this.unit.types()).stream().anyMatch(typeDecl -> typeDecl.getName().toString().equals(finalizedTopLevelClass.getName())); + if (!inheritedValue && !isVariableInCurrentCU) { + if (this.qualifiedPrefix.equals(this.prefix) && !this.javaProject.getOption(JavaCore.CODEASSIST_SUGGEST_STATIC_IMPORTS, true).equals(JavaCore.DISABLED)) { + if (!isStaticallyImported(variableBinding) && !(variableBinding.isEnumConstant() && Set.of(SwitchCase.EXPRESSION_PROPERTY, SwitchCase.EXPRESSIONS2_PROPERTY).contains(this.toComplete.getLocationInParent()))) { + res.setRequiredProposals(new CompletionProposal[] { toStaticImportProposal(variableBinding) }); + } + } else { + ITypeBinding directParentClass = variableBinding.getDeclaringClass(); + res.setRequiredProposals(new CompletionProposal[] { toStaticImportProposal(directParentClass) }); + if (this.toComplete.getLocationInParent() != QualifiedName.NAME_PROPERTY && + this.toComplete.getLocationInParent() != FieldAccess.NAME_PROPERTY && + this.toComplete.getLocationInParent() != NameQualifiedType.NAME_PROPERTY) { + StringBuilder builder = new StringBuilder(new String(res.getCompletion())); + builder.insert(0, '.'); + builder.insert(0, directParentClass.getName()); + res.setCompletion(builder.toString().toCharArray()); + } + } + } + } + } else if (kind == CompletionProposal.TYPE_REF) { + var typeBinding = (ITypeBinding) binding; + res.setSignature( + Signature.createTypeSignature(typeBinding.getQualifiedName().toCharArray(), true).toCharArray()); + } else if (kind == CompletionProposal.ANNOTATION_ATTRIBUTE_REF) { + var methodBinding = (IMethodBinding) binding; + StringBuilder annotationCompletion = new StringBuilder(completion); + boolean surroundWithSpaces = JavaCore.INSERT.equals(this.unit.getJavaElement().getJavaProject().getOption(DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_BEFORE_ASSIGNMENT_OPERATOR, true)); + if (surroundWithSpaces) { + annotationCompletion.append(' '); + } + annotationCompletion.append('='); + if (surroundWithSpaces) { + annotationCompletion.append(' '); + } + res.setCompletion(annotationCompletion.toString().toCharArray()); + res.setSignature(Signature.createTypeSignature(qualifiedTypeName(methodBinding.getReturnType()), true) + .toCharArray()); + res.setReceiverSignature(Signature + .createTypeSignature(methodBinding.getDeclaringClass().getQualifiedName().toCharArray(), true) + .toCharArray()); + res.setDeclarationSignature(Signature + .createTypeSignature(methodBinding.getDeclaringClass().getQualifiedName().toCharArray(), true) + .toCharArray()); + } else { + res.setSignature(new char[] {}); + res.setReceiverSignature(new char[] {}); + res.setDeclarationSignature(new char[] {}); + } + + if (this.toComplete instanceof SimpleName && !this.toComplete.getLocationInParent().getId().equals(QualifiedName.QUALIFIER_PROPERTY.getId()) && !this.prefix.isEmpty() && !inJavadoc) { + res.setReplaceRange(this.toComplete.getStartPosition(), this.offset); + } else { + setRange(res); + } + var element = binding.getJavaElement(); + if (element != null) { + res.setDeclarationTypeName(((IType)element.getAncestor(IJavaElement.TYPE)).getFullyQualifiedName().toCharArray()); + res.setDeclarationPackageName(element.getAncestor(IJavaElement.PACKAGE_FRAGMENT).getElementName().toCharArray()); + } + boolean isInQualifiedName = this.toComplete.getLocationInParent() == QualifiedName.NAME_PROPERTY || this.toComplete.getLocationInParent() == FieldAccess.NAME_PROPERTY; + res.setRelevance(RelevanceConstants.R_DEFAULT + + RelevanceConstants.R_RESOLVED + + RelevanceConstants.R_INTERESTING + + (res.isConstructor() ? 0 : RelevanceUtils.computeRelevanceForCaseMatching(this.prefix.toCharArray(), binding.getName().toCharArray(), this.assistOptions)) + + RelevanceUtils.computeRelevanceForExpectingType(binding instanceof ITypeBinding typeBinding ? typeBinding : + binding instanceof IMethodBinding methodBinding ? methodBinding.getReturnType() : + binding instanceof IVariableBinding variableBinding ? variableBinding.getType() : + this.toComplete.getAST().resolveWellKnownType(Object.class.getName()), this.expectedTypes) + + (isInQualifiedName || res.getRequiredProposals() != null || inJavadoc ? 0 : RelevanceUtils.computeRelevanceForQualification(false, this.prefix, this.qualifiedPrefix)) + + RelevanceConstants.R_NON_RESTRICTED + + ((insideQualifiedReference() && !staticOnly() && !Modifier.isStatic(binding.getModifiers())) || (inJavadoc && !res.isConstructor()) ? RelevanceConstants.R_NON_STATIC : 0) + + (!staticOnly() || inheritedValue ? 0 : RelevanceConstants.R_NON_INHERITED) + // TODO: when is this active? + (binding instanceof IVariableBinding field && field.isEnumConstant() ? RelevanceConstants.R_ENUM + RelevanceConstants.R_ENUM_CONSTANT : 0) + ); + if (res.getRequiredProposals() != null) { + for (CompletionProposal req : res.getRequiredProposals()) { + req.setRelevance(res.getRelevance()); + } + } + return res; + } + + private boolean isStaticallyImported(IBinding binding) { + return ((List)unit.imports()).stream() // + .filter(ImportDeclaration::isStatic) // + .map(ImportDeclaration::resolveBinding) // + .anyMatch(importBinding -> binding.isEqualTo(importBinding) || getDeclaringClass(binding).isEqualTo(importBinding)); + } + + private boolean insideQualifiedReference() { + return this.toComplete instanceof QualifiedName || + (this.toComplete instanceof SimpleName simple && (simple.getParent() instanceof QualifiedName || simple.getParent() instanceof FieldAccess)); + } + + private boolean staticOnly() { + if (this.toComplete.getLocationInParent() == QualifiedName.NAME_PROPERTY) { + return DOMCodeSelector.resolveBinding(((QualifiedName)this.toComplete.getParent()).getQualifier()) instanceof ITypeBinding; + } + return false; + } + + private String qualifiedTypeName(ITypeBinding typeBinding) { + if (typeBinding.isTypeVariable()) { + return typeBinding.getName(); + } else { + return typeBinding.getQualifiedName(); + } + } + + private CompletionProposal toProposal(IType type) { + DOMInternalCompletionProposal res = createProposal(CompletionProposal.TYPE_REF); + char[] simpleName = type.getElementName().toCharArray(); + char[] signature = Signature.createTypeSignature(type.getFullyQualifiedName(), true).toCharArray(); + + res.setSignature(signature); + + // set owner package + IJavaElement cursor = type; + while (cursor != null && !(cursor instanceof IPackageFragment)) { + cursor = cursor.getParent(); + } + IPackageFragment packageFrag = (IPackageFragment) cursor; + if (packageFrag != null) { + res.setDeclarationSignature(packageFrag.getElementName().toCharArray()); + } + + // set completion, considering nested types + cursor = type; + StringBuilder completion = new StringBuilder(); + AbstractTypeDeclaration parentTypeDeclaration = DOMCompletionUtil.findParentTypeDeclaration(this.toComplete); + if (parentTypeDeclaration != null && type.getFullyQualifiedName().equals(((IType)parentTypeDeclaration.resolveBinding().getJavaElement()).getFullyQualifiedName())) { + completion.insert(0, cursor.getElementName()); + } else { + ASTNode currentName = this.toComplete instanceof Name ? this.toComplete : null; + while (cursor instanceof IType currentType && (currentName == null || !Objects.equals(currentName.toString(), currentType.getFullyQualifiedName()))) { + if (!completion.isEmpty()) { + completion.insert(0, '.'); + } + completion.insert(0, cursor.getElementName()); + cursor = cursor.getParent(); + if (currentName != null && currentName.getLocationInParent() == QualifiedName.NAME_PROPERTY) { + currentName = ((QualifiedName)currentName.getParent()).getQualifier(); + } else { + currentName = null; + } + } + } + AbstractTypeDeclaration parentType = DOMCompletionUtil.findParentTypeDeclaration(this.toComplete); + Javadoc javadoc = (Javadoc) DOMCompletionUtil.findParent(this.toComplete, new int[] { ASTNode.JAVADOC }); + if (parentType != null || javadoc != null) { + IPackageBinding currentPackageBinding = parentType == null ? null : parentType.resolveBinding().getPackage(); + if (packageFrag != null && (currentPackageBinding == null + || (!packageFrag.getElementName().equals(currentPackageBinding.getName()) + && !packageFrag.getElementName().equals("java.lang")))) { //$NON-NLS-1$ + completion.insert(0, '.'); + completion.insert(0, packageFrag.getElementName()); + } + } else { + // in imports list + int lastOffset = this.toComplete.getStartPosition() + this.toComplete.getLength(); + if (lastOffset >= this.textContent.length() || this.textContent.charAt(lastOffset) != ';') { + completion.append(';'); + } + } + res.setCompletion(completion.toString().toCharArray()); + + if (this.toComplete instanceof FieldAccess || this.prefix.isEmpty()) { + res.setReplaceRange(this.offset, this.offset); + } else if (this.toComplete instanceof MarkerAnnotation) { + res.setReplaceRange(this.toComplete.getStartPosition() + 1, this.toComplete.getStartPosition() + this.toComplete.getLength()); + } else if (this.toComplete instanceof SimpleName currentName && FAKE_IDENTIFIER.equals(currentName.toString())) { + res.setReplaceRange(this.offset, this.offset); + } else if (this.toComplete instanceof SimpleName) { + res.setReplaceRange(this.toComplete.getStartPosition(), this.toComplete.getStartPosition() + this.toComplete.getLength()); + } else { + res.setReplaceRange(this.toComplete.getStartPosition(), this.offset); + } + try { + res.setFlags(type.getFlags()); + } catch (JavaModelException ex) { + ILog.get().error(ex.getMessage(), ex); + } + if (this.toComplete instanceof SimpleName) { + res.setTokenRange(this.toComplete.getStartPosition(), this.toComplete.getStartPosition() + this.toComplete.getLength()); + } else if (this.toComplete instanceof MarkerAnnotation) { + res.setTokenRange(this.offset, this.offset); + } + boolean nodeInImports = DOMCompletionUtil.findParent(this.toComplete, new int[] { ASTNode.IMPORT_DECLARATION }) != null; + + IType topLevelClass = type; + while (topLevelClass.getParent() instanceof IType parentIType) { + topLevelClass = parentIType; + } + final IType finalizedTopLevelClass = topLevelClass; + + boolean fromCurrentCU = ((List)this.unit.types()).stream().anyMatch(typeDecl -> typeDecl.resolveBinding().getQualifiedName().equals(finalizedTopLevelClass.getFullyQualifiedName())); + boolean inSamePackage = false; + boolean typeIsImported = ((List)this.unit.imports()).stream().anyMatch(importDecl -> { + return importDecl.getName().toString().equals(type.getFullyQualifiedName()); + }); + PackageDeclaration packageDeclaration = this.unit.getPackage(); + if (packageDeclaration != null) { + inSamePackage = packageDeclaration.getName().toString().equals(type.getPackageFragment().getElementName()); + } else { + inSamePackage = type.getPackageFragment().getElementName().isEmpty(); + } + int relevance = RelevanceConstants.R_DEFAULT + + RelevanceConstants.R_RESOLVED + + RelevanceConstants.R_INTERESTING + + RelevanceConstants.R_NON_RESTRICTED + + RelevanceUtils.computeRelevanceForQualification(!type.getFullyQualifiedName().startsWith("java.") && !nodeInImports && !fromCurrentCU && !inSamePackage && !typeIsImported, this.prefix, this.qualifiedPrefix) + + (type.getFullyQualifiedName().startsWith("java.") ? RelevanceConstants.R_JAVA_LIBRARY : 0) + + (expectedTypes.getExpectedTypes().stream().map(ITypeBinding::getQualifiedName).anyMatch(type.getFullyQualifiedName()::equals) ? RelevanceConstants.R_EXACT_EXPECTED_TYPE : + expectedTypes.getExpectedTypes().stream().map(ITypeBinding::getQualifiedName).anyMatch(Object.class.getName()::equals) ? RelevanceConstants.R_EXPECTED_TYPE : + 0) + + RelevanceUtils.computeRelevanceForCaseMatching(this.prefix.toCharArray(), simpleName, this.assistOptions); + try { + if (type.isAnnotation()) { + ASTNode current = this.toComplete; + while (current instanceof Name) { + current = current.getParent(); + } + if (current instanceof Annotation annotation) { + relevance += RelevanceConstants.R_ANNOTATION; + IAnnotation targetAnnotation = type.getAnnotation(Target.class.getName()); + if (targetAnnotation == null || !targetAnnotation.exists()) { + // On Javadoc for @Target: "If a Target meta-annotation is not present on an annotation type declaration, + // the declared type may be used on any program element." + relevance += RelevanceConstants.R_TARGET; + } else { + var memberValuePairs = targetAnnotation.getMemberValuePairs(); + if (memberValuePairs != null) { + if (Stream.of(memberValuePairs) + .filter(memberValue -> "value".equals(memberValue.getMemberName())) + .map(IMemberValuePair::getValue) + .anyMatch(target -> matchHostType(annotation.getParent(), target))) { + relevance += RelevanceConstants.R_TARGET; + } + } + } + } + } + } catch (JavaModelException ex) { + ILog.get().warn(ex.getMessage(), ex); + } + if (isInExtendsOrImplements(this.toComplete) != null) { + try { + if (type.isAnnotation()) { + relevance += RelevanceConstants.R_ANNOTATION; + } + if (type.isInterface()) { + relevance += RelevanceConstants.R_INTERFACE; + } + if (type.isClass()) { + relevance += RelevanceConstants.R_CLASS; + } + } catch (JavaModelException e) { + // do nothing + } + } + res.setRelevance(relevance); + if (parentType != null) { + String packageName = ""; //$NON-NLS-1$ + PackageDeclaration packageDecl = this.unit.getPackage(); + if (packageDecl != null) { + packageName = packageDecl.getName().toString(); + } + if (!packageName.equals(type.getPackageFragment().getElementName())) { + // propose importing the type + res.setRequiredProposals(new CompletionProposal[] { toImportProposal(simpleName, signature, type.getPackageFragment().getElementName().toCharArray()) }); + } + } + return res; + } + + private static boolean matchHostType(ASTNode host, Object targetAnnotationElementValue) { + return false; + } + + private CompletionProposal toNewMethodProposal(ITypeBinding parentType, String newMethodName) { + DOMInternalCompletionProposal res = createProposal(CompletionProposal.POTENTIAL_METHOD_DECLARATION); + res.setDeclarationSignature(DOMCompletionEngineBuilder.getSignature(parentType)); + res.setSignature(Signature.createMethodSignature(CharOperation.NO_CHAR_CHAR, Signature.createCharArrayTypeSignature(VOID, true))); + res.setDeclarationPackageName(parentType.getPackage().getName().toCharArray()); + res.setDeclarationTypeName(parentType.getQualifiedName().toCharArray()); + res.setTypeName(VOID); + res.setName(newMethodName.toCharArray()); + res.setCompletion(newMethodName.toCharArray()); + res.setFlags(Flags.AccPublic); + setRange(res); + int relevance = RelevanceConstants.R_DEFAULT; + relevance += RelevanceConstants.R_RESOLVED; + relevance += RelevanceConstants.R_INTERESTING; + relevance += RelevanceConstants.R_NON_RESTRICTED; + res.setRelevance(relevance); + return res; + } + + private List toConstructorProposals(IType type, ASTNode referencedFrom, boolean exactType) { + + List proposals = new ArrayList<>(); + + AbstractTypeDeclaration parentType = (AbstractTypeDeclaration)DOMCompletionUtil.findParent(referencedFrom, new int[] {ASTNode.ANNOTATION_TYPE_DECLARATION, ASTNode.TYPE_DECLARATION, ASTNode.ENUM_DECLARATION, ASTNode.RECORD_DECLARATION}); + if (parentType == null) { + return proposals; + } + + ITypeBinding referencedFromBinding = parentType.resolveBinding(); + boolean includePrivate = referencedFromBinding.getKey().equals(type.getKey()); + MethodDeclaration methodDeclaration = (MethodDeclaration)DOMCompletionUtil.findParent(referencedFrom, new int[] {ASTNode.METHOD_DECLARATION}); + // you can reference protected fields/methods from a static method, + // as long as those protected fields/methods are declared in the current class. + // otherwise, the (inherited) fields/methods can only be accessed in non-static methods. + boolean includeProtected; + if (referencedFromBinding.getKey().equals(type.getKey())) { + includeProtected = true; + } else if (methodDeclaration != null + && (methodDeclaration.getModifiers() & Flags.AccStatic) != 0) { + includeProtected = false; + } else { + includeProtected = findInSupers(referencedFromBinding, type.getKey()); + } + + IPackageFragment packageFragment = (IPackageFragment)type.getAncestor(IJavaElement.PACKAGE_FRAGMENT); + String packageKey = packageFragment == null ? "" : packageFragment.getElementName().replace('.', '/'); //$NON-NLS-1$ + + boolean isInterface = false; + try { + isInterface = type.isInterface(); + } catch (JavaModelException e) { + // do nothing + } + + + if (!this.requestor.isIgnored(CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION) && isInterface) { + // create an anonymous declaration: `new MyInterface() { }`; + proposals.add(toAnonymousConstructorProposal(type)); + // TODO: JDT allows completing the constructors declared on an abstract class, + // without adding a body to these instance creations. + // This doesn't make sense, since the abstract methods need to be implemented. + // We should consider making those completion items here instead + } else { + try { + List constructors = Stream.of(type.getMethods()).filter(method -> { + try { + return method.isConstructor(); + } catch (JavaModelException e) { + return false; + } + }).toList(); + if (!constructors.isEmpty()) { + for (IMethod constructor: constructors) { + if ( + // public + (constructor.getFlags() & Flags.AccPublic) != 0 + // protected + || (includeProtected && (constructor.getFlags() & Flags.AccProtected) != 0) + // private + || (includePrivate && (constructor.getFlags() & Flags.AccPrivate) != 0) + // package private + ||((constructor.getFlags() & (Flags.AccPrivate | Flags.AccProtected | Flags.AccPublic)) == 0 && packageKey.equals(referencedFromBinding.getPackage().getKey()))) { + proposals.add(toConstructorProposal(constructor, exactType)); + } + } + } else { + proposals.add(toDefaultConstructorProposal(type, exactType)); + } + } catch (JavaModelException e) { + ILog.get().error("Model exception while trying to collect constructors for completion", e); //$NON-NLS-1$ + } + } + + return proposals; + } + + private CompletionProposal toConstructorProposal(IMethod method, boolean isExactType) throws JavaModelException { + DOMInternalCompletionProposal res = createProposal(CompletionProposal.CONSTRUCTOR_INVOCATION); + IType declaringClass = method.getDeclaringType(); + char[] simpleName = method.getElementName().toCharArray(); + res.setCompletion(new char[] {'(', ')'}); + res.setName(simpleName); + + char[] signature = method.getKey().substring(method.getKey().indexOf('.') + 1).toCharArray(); + res.setSignature(signature); + res.setOriginalSignature(signature); + + IPackageFragment packageFragment = (IPackageFragment)declaringClass.getAncestor(IJavaElement.PACKAGE_FRAGMENT); + + res.setDeclarationSignature(Signature.createTypeSignature(declaringClass.getFullyQualifiedName(), true).toCharArray()); + res.setDeclarationTypeName(simpleName); + res.setDeclarationPackageName(packageFragment.getElementName().toCharArray()); + res.setParameterPackageNames(CharOperation.NO_CHAR_CHAR); + res.setParameterTypeNames(CharOperation.NO_CHAR_CHAR); + + if (method.getParameterNames().length == 0) { + res.setParameterNames(CharOperation.NO_CHAR_CHAR); + } else { + char[][] paramNamesCharChar = Stream.of(method.getParameterNames()) // + .map(String::toCharArray) + .toArray(char[][]::new); + res.setParameterNames(paramNamesCharChar); + } + + res.setIsContructor(true); + if (declaringClass.getTypeParameters() != null && declaringClass.getTypeParameters().length > 0) { + res.setDeclarationTypeVariables(Stream.of(declaringClass.getTypeParameters()).map(a -> a.getElementName().toCharArray()).toArray(char[][]::new)); + } + res.setCompatibleProposal(true); + + res.setReplaceRange(this.offset, this.offset); + res.setTokenRange(this.toComplete.getStartPosition(), this.offset); + res.setFlags(method.getFlags()); + + int relevance = RelevanceConstants.R_DEFAULT + + RelevanceConstants.R_RESOLVED + + RelevanceConstants.R_INTERESTING + + (isExactType ? RelevanceConstants.R_EXACT_EXPECTED_TYPE : 0) + + RelevanceConstants.R_UNQUALIFIED + + RelevanceConstants.R_NON_RESTRICTED + + RelevanceConstants.R_CONSTRUCTOR + + RelevanceUtils.computeRelevanceForCaseMatching(this.prefix.toCharArray(), simpleName, this.assistOptions); + res.setRelevance(relevance); + + CompletionProposal typeProposal = toProposal(declaringClass); + if (this.toComplete instanceof SimpleName) { + typeProposal.setReplaceRange(this.toComplete.getStartPosition(), this.offset); + typeProposal.setTokenRange(this.toComplete.getStartPosition(), this.offset); + } else { + typeProposal.setReplaceRange(this.offset, this.offset); + typeProposal.setTokenRange(this.offset, this.offset); + } + typeProposal.setRequiredProposals(null); + typeProposal.setRelevance(relevance); + + res.setRequiredProposals(new CompletionProposal[] { typeProposal }); + return res; + } + + private CompletionProposal toDefaultConstructorProposal(IType type, boolean isExactType) throws JavaModelException { + DOMInternalCompletionProposal res = createProposal(CompletionProposal.CONSTRUCTOR_INVOCATION); + char[] simpleName = type.getElementName().toCharArray(); + res.setCompletion(new char[] {'(', ')'}); + res.setName(simpleName); + + char[] signature = Signature.createMethodSignature(CharOperation.NO_CHAR_CHAR, new char[]{ 'V' }); + res.setSignature(signature); + res.setOriginalSignature(signature); + + IPackageFragment packageFragment = (IPackageFragment)type.getAncestor(IJavaElement.PACKAGE_FRAGMENT); + + res.setDeclarationSignature(Signature.createTypeSignature(type.getFullyQualifiedName(), true).toCharArray()); + res.setDeclarationTypeName(simpleName); + res.setDeclarationPackageName(packageFragment.getElementName().toCharArray()); + res.setParameterPackageNames(CharOperation.NO_CHAR_CHAR); + res.setParameterTypeNames(CharOperation.NO_CHAR_CHAR); + + res.setParameterNames(CharOperation.NO_CHAR_CHAR); + + res.setIsContructor(true); + if (type.getTypeParameters() != null && type.getTypeParameters().length > 0) { + res.setDeclarationTypeVariables(Stream.of(type.getTypeParameters()).map(a -> a.getElementName().toCharArray()).toArray(char[][]::new)); + } + res.setCompatibleProposal(true); + + res.setReplaceRange(this.offset, this.offset); + res.setTokenRange(this.toComplete.getStartPosition(), this.offset); + res.setFlags(type.getFlags() & (Flags.AccPublic | Flags.AccPrivate | Flags.AccProtected)); + + int relevance = RelevanceConstants.R_DEFAULT + + RelevanceConstants.R_RESOLVED + + RelevanceConstants.R_INTERESTING + + (isExactType ? RelevanceConstants.R_EXACT_EXPECTED_TYPE : 0) + + RelevanceConstants.R_UNQUALIFIED + + RelevanceConstants.R_NON_RESTRICTED + + RelevanceConstants.R_CONSTRUCTOR; + res.setRelevance(relevance); + + CompletionProposal typeProposal = toProposal(type); + if (this.toComplete instanceof SimpleName) { + typeProposal.setReplaceRange(this.toComplete.getStartPosition(), this.offset); + typeProposal.setTokenRange(this.toComplete.getStartPosition(), this.offset); + } else { + typeProposal.setReplaceRange(this.offset, this.offset); + typeProposal.setTokenRange(this.offset, this.offset); + } + typeProposal.setRequiredProposals(null); + typeProposal.setRelevance(relevance); + + res.setRequiredProposals(new CompletionProposal[] { typeProposal }); + return res; + } + + private CompletionProposal toAnonymousConstructorProposal(IType type) { + DOMInternalCompletionProposal res = createProposal(CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION); + res.setDeclarationSignature(Signature.createTypeSignature(type.getFullyQualifiedName(), true).toCharArray()); + res.setDeclarationKey(type.getKey().toCharArray()); + res.setSignature( + CompletionEngine.createMethodSignature( + CharOperation.NO_CHAR_CHAR, + CharOperation.NO_CHAR_CHAR, + CharOperation.NO_CHAR, + CharOperation.NO_CHAR)); + + IPackageFragment packageFragment = (IPackageFragment)type.getAncestor(IJavaElement.PACKAGE_FRAGMENT); + + res.setDeclarationPackageName(packageFragment.getElementName().toCharArray()); + res.setDeclarationTypeName(type.getElementName().toCharArray()); + res.setName(type.getElementName().toCharArray()); + + int relevance = RelevanceConstants.R_DEFAULT; + relevance += RelevanceConstants.R_RESOLVED; + relevance += RelevanceConstants.R_INTERESTING; + relevance += RelevanceUtils.computeRelevanceForCaseMatching(this.prefix.toCharArray(), type.getElementName().toCharArray(), this.assistOptions); + relevance += RelevanceConstants.R_EXACT_EXPECTED_TYPE; + relevance += RelevanceConstants.R_UNQUALIFIED; + relevance += RelevanceConstants.R_NON_RESTRICTED; + if (packageFragment.getElementName().startsWith("java.")) { //$NON-NLS-1$ + relevance += RelevanceConstants.R_JAVA_LIBRARY; + } + + try { + if(type.isClass()) { + relevance += RelevanceConstants.R_CLASS; +// relevance += computeRelevanceForException(typeName); // TODO: + } else if(type.isEnum()) { + relevance += RelevanceConstants.R_ENUM; + } else if(type.isInterface()) { + relevance += RelevanceConstants.R_INTERFACE; + } + } catch (JavaModelException e) { + // do nothing + } + + DOMInternalCompletionProposal typeProposal = createProposal(CompletionProposal.TYPE_REF); + typeProposal.setDeclarationSignature(packageFragment.getElementName().toCharArray()); + typeProposal.setSignature(Signature.createTypeSignature(type.getFullyQualifiedName(), true).toCharArray()); + typeProposal.setPackageName(packageFragment.getElementName().toCharArray()); + typeProposal.setTypeName(type.getElementName().toCharArray()); + typeProposal.setCompletion(type.getElementName().toCharArray()); + try { + typeProposal.setFlags(type.getFlags()); + } catch (JavaModelException e) { + // do nothing + } + setRange(typeProposal); + typeProposal.setRelevance(relevance); + res.setRequiredProposals( new CompletionProposal[]{typeProposal}); + + res.setCompletion(new char[] {'(', ')'}); + res.setFlags(Flags.AccPublic); + res.setReplaceRange(this.offset, this.offset); + res.setTokenRange(this.toComplete.getStartPosition(), this.offset); + res.setRelevance(relevance); + return res; + } + + private CompletionProposal toImportProposal(char[] simpleName, char[] signature, char[] packageName) { + DOMInternalCompletionProposal res = createProposal(CompletionProposal.TYPE_IMPORT); + res.setName(simpleName); + res.setSignature(signature); + res.setPackageName(packageName); + return res; + } + + private CompletionProposal toStaticImportProposal(IBinding binding) { + DOMInternalCompletionProposal res = null; + if (binding instanceof IMethodBinding methodBinding) { + res = createProposal(CompletionProposal.METHOD_IMPORT); + res.setName(methodBinding.getName().toCharArray()); + res.setSignature(DOMCompletionEngineBuilder.getSignature(methodBinding)); + + res.setDeclarationSignature(DOMCompletionEngineBuilder.getSignature(methodBinding.getDeclaringClass())); + res.setSignature(DOMCompletionEngineBuilder.getSignature(methodBinding)); + if(methodBinding != methodBinding.getMethodDeclaration()) { + res.setOriginalSignature(DOMCompletionEngineBuilder.getSignature(methodBinding.getMethodDeclaration())); + } + res.setDeclarationPackageName(methodBinding.getDeclaringClass().getPackage().getName().toCharArray()); + res.setDeclarationTypeName(methodBinding.getDeclaringClass().getQualifiedName().toCharArray()); + res.setParameterPackageNames(Stream.of(methodBinding.getParameterTypes())// + .map(typeBinding -> { + if (typeBinding.getPackage() != null) { + return typeBinding.getPackage().getName().toCharArray(); + } + return CharOperation.NO_CHAR; + }) // + .toArray(char[][]::new)); + res.setParameterTypeNames(Stream.of(methodBinding.getParameterTypes())// + .map(typeBinding -> { + return typeBinding.getName().toCharArray(); + }) // + .toArray(char[][]::new)); + if (methodBinding.getReturnType().getPackage() != null) { + res.setPackageName(methodBinding.getReturnType().getPackage().getName().toCharArray()); + } + res.setTypeName(methodBinding.getReturnType().getQualifiedName().toCharArray()); + res.setName(methodBinding.getName().toCharArray()); + res.setCompletion(("import static " + methodBinding.getDeclaringClass().getQualifiedName() + "." + methodBinding.getName() + ";\n").toCharArray()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + res.setFlags(methodBinding.getModifiers()); + res.setAdditionalFlags(CompletionFlags.StaticImport); + res.setParameterNames(Stream.of(methodBinding.getParameterNames()) // + .map(String::toCharArray) // + .toArray(char[][]::new)); + } else if (binding instanceof IVariableBinding variableBinding) { + res = createProposal(CompletionProposal.FIELD_IMPORT); + + res.setDeclarationSignature(DOMCompletionEngineBuilder.getSignature(variableBinding.getDeclaringClass())); + res.setSignature(Signature.createTypeSignature(variableBinding.getType().getQualifiedName().toCharArray(), true) + .toCharArray()); + res.setDeclarationPackageName(variableBinding.getDeclaringClass().getPackage().getName().toCharArray()); + res.setDeclarationTypeName(variableBinding.getDeclaringClass().getQualifiedName().toCharArray()); + if (variableBinding.getType().getPackage() != null) { + res.setPackageName(variableBinding.getType().getPackage().getName().toCharArray()); + } + res.setTypeName(variableBinding.getType().getQualifiedName().toCharArray()); + res.setName(variableBinding.getName().toCharArray()); + res.setCompletion(("import static " + variableBinding.getDeclaringClass().getQualifiedName() + "." + variableBinding.getName() + ";\n").toCharArray()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + res.setFlags(variableBinding.getModifiers()); + res.setAdditionalFlags(CompletionFlags.StaticImport); + } else if (binding instanceof ITypeBinding typeBinding) { + // NOTE: slightly different fields are filled out when a type import + qualification is being used in place of a static import + // That's why we do something different here + + res = createProposal(CompletionProposal.TYPE_IMPORT); + res.setDeclarationSignature(typeBinding.getPackage().getName().toCharArray()); + res.setSignature(DOMCompletionEngineBuilder.getSignature(typeBinding)); + res.setPackageName(typeBinding.getPackage().getName().toCharArray()); + res.setTypeName(typeBinding.getQualifiedName().toCharArray()); + res.setAdditionalFlags(CompletionFlags.Default); + + StringBuilder importCompletionBuilder = new StringBuilder("import "); //$NON-NLS-1$ + importCompletionBuilder.append(typeBinding.getQualifiedName().replace('$', '.')); + importCompletionBuilder.append(';'); + importCompletionBuilder.append('\n'); + res.setCompletion(importCompletionBuilder.toString().toCharArray()); + } + if (res != null) { + CompilationUnit cu = ((CompilationUnit)this.toComplete.getRoot()); + List imports = cu.imports(); + int place; + if (!imports.isEmpty()) { + int lastIndex = imports.size() - 1; + place = imports.get(lastIndex).getStartPosition() + imports.get(lastIndex).getLength(); + } else if (cu.getPackage() != null) { + place = cu.getPackage().getStartPosition() + cu.getPackage().getLength(); + } else { + place = 0; + } + if (this.textContent != null && place != 0) { + if (this.textContent.charAt(place) == '\n') { + place++; + } else if (this.textContent.charAt(place) == '\r' && this.textContent.charAt(place + 1) == '\n') { + place += 2; + } + } + res.setReplaceRange(place, place); + res.setTokenRange(place, place); + // relevance is set in invokee, since it's expected to be the same as that of the parent completion proposal + return res; + } + throw new IllegalArgumentException("unexpected binding type: " + binding.getClass()); //$NON-NLS-1$ + } + + private CompletionProposal toAnnotationAttributeRefProposal(IMethodBinding method) { + CompletionProposal proposal = createProposal(CompletionProposal.ANNOTATION_ATTRIBUTE_REF); + proposal.setDeclarationSignature(DOMCompletionEngineBuilder.getSignature(method.getDeclaringClass())); + proposal.setSignature(DOMCompletionEngineBuilder.getSignature(method.getReturnType())); + proposal.setName(method.getName().toCharArray()); + // add "=" to completion since it will always be needed + char[] completion= method.getName().toCharArray(); + if (JavaCore.INSERT.equals(this.javaProject.getOption(DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_BEFORE_ASSIGNMENT_OPERATOR, true))) { + completion= CharOperation.concat(completion, new char[] {' '}); + } + completion= CharOperation.concat(completion, new char[] {'='}); + if (JavaCore.INSERT.equals(this.javaProject.getOption(DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_AFTER_ASSIGNMENT_OPERATOR, true))) { + completion= CharOperation.concat(completion, new char[] {' '}); + } + proposal.setCompletion(completion); + proposal.setFlags(method.getModifiers()); + setRange(proposal); + if (this.toComplete instanceof SimpleName simpleName) { + proposal.setReplaceRange(simpleName.getStartPosition(), simpleName.getStartPosition() + simpleName.getLength()); + proposal.setTokenRange(simpleName.getStartPosition(), simpleName.getStartPosition() + simpleName.getLength()); + } + int relevance = RelevanceConstants.R_DEFAULT + + RelevanceConstants.R_RESOLVED + + RelevanceConstants.R_INTERESTING + + RelevanceUtils.computeRelevanceForCaseMatching(this.prefix.toCharArray(), method.getName().toCharArray(), this.assistOptions) + + RelevanceConstants.R_UNQUALIFIED + + RelevanceConstants.R_NON_RESTRICTED; + proposal.setRelevance(relevance); + return proposal; + } + + private CompletionProposal toJavadocBlockTagProposal(char[] blockTag) { + InternalCompletionProposal res = createProposal(CompletionProposal.JAVADOC_BLOCK_TAG); + res.setName(blockTag); + StringBuilder completion = new StringBuilder(); + completion.append('@'); + completion.append(blockTag); + res.setCompletion(completion.toString().toCharArray()); + setRange(res); + ASTNode replaceNode = this.toComplete; + if (replaceNode instanceof TextElement) { + replaceNode = replaceNode.getParent(); + } + res.setReplaceRange(replaceNode.getStartPosition(), replaceNode.getStartPosition() + replaceNode.getLength()); + res.setRelevance(RelevanceConstants.R_DEFAULT + RelevanceConstants.R_INTERESTING + RelevanceConstants.R_NON_RESTRICTED); + return res; + } + + private CompletionProposal toJavadocInlineTagProposal(char[] inlineTag) { + InternalCompletionProposal res = createProposal(CompletionProposal.JAVADOC_INLINE_TAG); + res.setName(inlineTag); + StringBuilder completion = new StringBuilder(); + completion.append('{'); + completion.append('@'); + completion.append(inlineTag); + completion.append('}'); + res.setCompletion(completion.toString().toCharArray()); + setRange(res); + ASTNode replaceNode = this.toComplete; + if (replaceNode instanceof TextElement) { + replaceNode = replaceNode.getParent(); + } + res.setReplaceRange(replaceNode.getStartPosition(), replaceNode.getStartPosition() + replaceNode.getLength()); + res.setRelevance(RelevanceConstants.R_DEFAULT + RelevanceConstants.R_INTERESTING + RelevanceConstants.R_NON_RESTRICTED); + return res; + } + + private Map getAllJarModuleNames(IJavaProject project) { + Map modules = new HashMap<>(); + try { + for (IPackageFragmentRoot root : project.getAllPackageFragmentRoots()) { + if (root instanceof JarPackageFragmentRoot) { + IModuleDescription desc = root.getModuleDescription(); + desc = desc == null ? ((JarPackageFragmentRoot) root).getAutomaticModuleDescription() : desc; + String name = desc != null ? desc.getElementName() : null; + if (name != null && name.length() > 0) + modules.putIfAbsent(name, desc); + } + } + } catch (JavaModelException e) { + // do nothing + } + return modules; + } + + private void findModules(char[] prefix, IJavaProject project, AssistOptions options, Set skip) { + if(this.requestor.isIgnored(CompletionProposal.MODULE_REF)) { + return; + } + HashMap probableModules = new HashMap<>(); + ModuleSourcePathManager mManager = JavaModelManager.getModulePathManager(); + JavaElementRequestor javaElementRequestor = new JavaElementRequestor(); + try { + mManager.seekModule(prefix, true, javaElementRequestor); + IModuleDescription[] modules = javaElementRequestor.getModules(); + for (IModuleDescription module : modules) { + String name = module.getElementName(); + if (name == null || name.equals("")) //$NON-NLS-1$ + continue; + probableModules.putIfAbsent(name, module); + } + } catch (JavaModelException e) { + // ignore the error + } + probableModules.putAll(getAllJarModuleNames(project)); + Set requiredModules = collectRequiredModules(probableModules); + List removeList = new ArrayList<>(); + if (prefix != CharOperation.ALL_PREFIX && prefix != null && prefix.length > 0) { + for (String key : probableModules.keySet()) { + if (!this.pattern.matchesName(prefix, key.toCharArray())) { + removeList.add(key); + } + } + } + for (String key : removeList) { + probableModules.remove(key); + } + removeList.clear(); + for (String key : skip) { + probableModules.remove(key); + } + probableModules.entrySet().forEach(m -> this.requestor.accept(toModuleCompletion(m.getKey(), prefix, requiredModules))); + } + + /** + * Returns the list of modules required by the current module, including transitive ones. + * + * The current module and java.base included in the set. + * + * @param reachableModules the map of reachable modules + * @return the list of modules required by the current module, including transitive ones + */ + private Set collectRequiredModules(Map reachableModules) { + Set requiredModules = new HashSet<>(); + requiredModules.add("java.base"); //$NON-NLS-1$ + try { + IModuleDescription ownDescription = this.javaProject.getModuleDescription(); + if (ownDescription != null && !ownDescription.getElementName().isEmpty()) { + Deque moduleQueue = new ArrayDeque<>(); + requiredModules.add(ownDescription.getElementName()); + for (String moduleName : ownDescription.getRequiredModuleNames()) { + moduleQueue.add(moduleName); + } + while (!moduleQueue.isEmpty()) { + String top = moduleQueue.pollFirst(); + requiredModules.add(top); + if (reachableModules.containsKey(top)) { + for (String moduleName : reachableModules.get(top).getRequiredModuleNames()) { + if (!requiredModules.contains(moduleName)) { + moduleQueue.add(moduleName); + } + } + } + } + } else { + // working with the default module, so everything is required I think? + return reachableModules.keySet(); + } + } catch (JavaModelException e) { + // do nothing + } + return requiredModules; + } + + private CompletionProposal toModuleCompletion(String moduleName, char[] prefix, Set requiredModules) { + + char[] completion = moduleName.toCharArray(); + int relevance = RelevanceConstants.R_DEFAULT; + relevance += RelevanceConstants.R_RESOLVED; + relevance += RelevanceConstants.R_INTERESTING; + relevance += RelevanceUtils.computeRelevanceForCaseMatching(prefix, completion, this.assistOptions); + relevance += RelevanceUtils.computeRelevanceForQualification(true, this.prefix, this.qualifiedPrefix); + if (requiredModules.contains(moduleName)) { + relevance += RelevanceConstants.R_NON_RESTRICTED; + } + DOMInternalCompletionProposal proposal = createProposal(CompletionProposal.MODULE_REF); + proposal.setModuleName(completion); + proposal.setDeclarationSignature(completion); + proposal.setCompletion(completion); + + // replacement range using import decl range: + ImportDeclaration importDecl = (ImportDeclaration) DOMCompletionUtil.findParent(this.toComplete, new int[] {ASTNode.IMPORT_DECLARATION}); + proposal.setReplaceRange(importDecl.getName().getStartPosition(), importDecl.getName().getStartPosition() + importDecl.getName().getLength()); + + proposal.setRelevance(relevance); + + return proposal; + } + + /** + * Returns an internal completion proposal of the given kind. + * + * Inspired by {@link CompletionEngine#createProposal} + * + * @param kind the kind of completion proposal (see the constants in {@link CompletionProposal}) + * @return an internal completion proposal of the given kind + */ + protected DOMInternalCompletionProposal createProposal(int kind) { + DOMInternalCompletionProposal proposal = new DOMInternalCompletionProposal(kind, this.offset, this); + proposal.setNameLookup(this.nameEnvironment.nameLookup); + proposal.setCompletionEngine(this.nestedEngine); + return proposal; + } + + /** + * Returns true if the orphaned content DOESN'T match the given name (the completion suggestion), + * according to the matching rules the user has configured. + * + * Inspired by {@link CompletionEngine#isFailedMatch}. + * However, this version also checks that the length of the orphaned content is not longer than then suggestion. + * + * @param orphanedContent the orphaned content to be completed + * @param name the completion suggestion + * @return true if the orphaned content DOESN'T match the given name + */ + protected boolean isFailedMatch(char[] orphanedContent, char[] name) { + if (name.length < orphanedContent.length) { + return true; + } + return !( + (this.assistOptions.substringMatch && CharOperation.substringMatch(orphanedContent, name)) + || (this.assistOptions.camelCaseMatch && CharOperation.camelCaseMatch(orphanedContent, name)) + || (CharOperation.prefixEquals(orphanedContent, name, false)) + || (this.assistOptions.subwordMatch && CharOperation.subWordMatch(orphanedContent, name)) + ); + } + + private CompletionProposal createKeywordProposal(char[] keyword, int startPos, int endPos) { + int relevance = RelevanceConstants.R_DEFAULT + + RelevanceConstants.R_RESOLVED + + RelevanceConstants.R_INTERESTING + + RelevanceConstants.R_NON_RESTRICTED + + RelevanceUtils.computeRelevanceForCaseMatching(this.prefix.toCharArray(), keyword, this.assistOptions); + CompletionProposal keywordProposal = createProposal(CompletionProposal.KEYWORD); + keywordProposal.setCompletion(keyword); + keywordProposal.setName(keyword); + if (startPos == -1 && endPos == -1) { + setRange(keywordProposal); + } else { + keywordProposal.setReplaceRange(startPos, endPos); + } + keywordProposal.setRelevance(relevance); + return keywordProposal; + } + + private CompletionProposal createClassKeywordProposal(ITypeBinding typeBinding, int startPos, int endPos) { + int relevance = RelevanceConstants.R_DEFAULT + + RelevanceConstants.R_RESOLVED + + RelevanceConstants.R_INTERESTING + + RelevanceConstants.R_NON_RESTRICTED + + RelevanceConstants.R_EXPECTED_TYPE; + if (!isFailedMatch(this.prefix.toCharArray(), Keywords.CLASS)) { + relevance += RelevanceConstants.R_SUBSTRING; + } + DOMInternalCompletionProposal keywordProposal = createProposal(CompletionProposal.FIELD_REF); + keywordProposal.setCompletion(Keywords.CLASS); + keywordProposal.setReplaceRange(startPos, endPos); + keywordProposal.setRelevance(relevance); + keywordProposal.setPackageName(CharOperation.concatWith(TypeConstants.JAVA_LANG, '.')); + keywordProposal.setTypeName("Class".toCharArray()); //$NON-NLS-1$ + keywordProposal.setName(Keywords.CLASS); + + // create the signature + StringBuilder builder = new StringBuilder(); + builder.append("Ljava.lang.Class<"); //$NON-NLS-1$ + String typeBindingKey = typeBinding.getKey().replace('/', '.'); + builder.append(typeBindingKey); + builder.append(">;"); //$NON-NLS-1$ + keywordProposal.setSignature(builder.toString().toCharArray()); + + return keywordProposal; + } + + private static final char[] LAMBDA = new char[] {'-', '>'}; + private CompletionProposal createLambdaExpressionProposal(IMethodBinding method) { + DOMInternalCompletionProposal res = createProposal(CompletionProposal.LAMBDA_EXPRESSION); + + int relevance = RelevanceConstants.R_DEFAULT; + relevance += RelevanceConstants.R_EXACT_EXPECTED_TYPE; + relevance += RelevanceConstants.R_ABSTRACT_METHOD; + relevance += RelevanceConstants.R_RESOLVED; + relevance += RelevanceConstants.R_INTERESTING; + relevance += RelevanceConstants.R_NON_RESTRICTED; + + int length = method.getParameterTypes().length; + char[][] parameterTypeNames = new char[length][]; + + for (int j = 0; j < length; j++) { + ITypeBinding p = method.getParameterTypes()[j]; + parameterTypeNames[j] = p.getQualifiedName().toCharArray(); + } + char[][] parameterNames = Stream.of(method.getParameterNames())// + .map(s -> s.toCharArray()) // + .toArray(char[][]::new); + + res.setDeclarationSignature(DOMCompletionEngineBuilder.getSignature(method.getDeclaringClass())); + res.setSignature(DOMCompletionEngineBuilder.getSignature(method)); + + IMethodBinding original = method.getMethodDeclaration(); + if (original != method) { + res.setOriginalSignature(DOMCompletionEngineBuilder.getSignature(original)); + } + + setRange(res); + + res.setRelevance(relevance); + res.setCompletion(LAMBDA); + res.setParameterTypeNames(parameterTypeNames); + res.setFlags(method.getModifiers()); + res.setDeclarationPackageName(method.getDeclaringClass().getPackage().getName().toCharArray()); + res.setDeclarationTypeName(method.getDeclaringClass().getQualifiedName().toCharArray()); + res.setName(method.getName().toCharArray()); + res.setTypeName(method.getReturnType().getQualifiedName().toCharArray()); + if (parameterNames != null) { + res.setParameterNames(parameterNames); + } + + return res; + } + + /** + * Sets the replace and token ranges of the completion based on the contents of the buffer. + * + * Useful as a last case resort if there is no SimpleName node to base the range on. + * + * @param completionProposal the proposal whose range to set + */ + private void setRange(CompletionProposal completionProposal) { + int startPos = this.offset - this.prefix.length(); + int cursor = this.offset; + while (cursor < this.textContent.length() + && Character.isJavaIdentifierPart(this.textContent.charAt(cursor))) { + cursor++; + } + completionProposal.setReplaceRange(startPos, cursor); + completionProposal.setTokenRange(startPos, cursor); + } + + private boolean isVisible(IBinding binding) { + if (binding == null) { + return false; + } + if (Modifier.isPublic(binding.getModifiers())) { + return true; + } + if (Modifier.isPrivate(binding.getModifiers())) { + return binding.isEqualTo(DOMCompletionUtil.findParentTypeDeclaration(this.toComplete).resolveBinding()); + } + ITypeBinding declaringClass = getDeclaringClass(binding); + if (declaringClass == null) { + return false; + } + if (Modifier.isProtected(binding.getModifiers())) { + return declaringClass.isSubTypeCompatible(DOMCompletionUtil.findParentTypeDeclaration(this.toComplete).resolveBinding()); + } + return declaringClass.getPackage().isEqualTo(DOMCompletionUtil.findParentTypeDeclaration(this.toComplete).resolveBinding().getPackage()); + } + + private ITypeBinding getDeclaringClass(IBinding binding) { + return binding instanceof ITypeBinding typeBinding ? typeBinding : + binding instanceof IMethodBinding methodBinding ? methodBinding.getDeclaringClass() : + binding instanceof IVariableBinding variableBinding && variableBinding.isField() ? variableBinding.getDeclaringClass() : + null; + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionEngineBuilder.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionEngineBuilder.java new file mode 100644 index 00000000000..b0cd0e4a77e --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionEngineBuilder.java @@ -0,0 +1,195 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.codeassist; + +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; + +/** + * Builders adapted from org.eclipse.jdt.internal.codeassist.CompletionEngine in order to work with IBindings + */ +class DOMCompletionEngineBuilder { + + private static final String EXTENDS = "extends"; //$NON-NLS-1$ + private static final String THROWS = "throws"; //$NON-NLS-1$ + private static final String SUPER = "super"; //$NON-NLS-1$ + + static void createMethod(IMethodBinding methodBinding, StringBuilder completion) { + + // Modifiers + // flush uninteresting modifiers + int insertedModifiers = methodBinding.getModifiers() + & ~(ClassFileConstants.AccNative | ClassFileConstants.AccAbstract); + if (insertedModifiers != ClassFileConstants.AccDefault) { + ASTNode.printModifiers(insertedModifiers, completion); + } + + // Type parameters + + ITypeBinding[] typeVariableBindings = methodBinding.getTypeParameters(); + if (typeVariableBindings != null && typeVariableBindings.length != 0) { + completion.append('<'); + for (int i = 0; i < typeVariableBindings.length; i++) { + if (i != 0) { + completion.append(','); + completion.append(' '); + } + createTypeVariable(typeVariableBindings[i], completion); + } + completion.append('>'); + completion.append(' '); + } + + // Return type + createType(methodBinding.getReturnType(), completion); + completion.append(' '); + + // Selector (name) + completion.append(methodBinding.getName()); + + completion.append('('); + + // Parameters + ITypeBinding[] parameterTypes = methodBinding.getParameterTypes(); + String[] parameterNames = methodBinding.getParameterNames(); + int length = parameterTypes.length; + for (int i = 0; i < length; i++) { + if (i != 0) { + completion.append(','); + completion.append(' '); + } + createType(parameterTypes[i], completion); + completion.append(' '); + if (parameterNames != null) { + completion.append(parameterNames[i]); + } else { + completion.append('%'); + } + } + + completion.append(')'); + + // Exceptions + ITypeBinding[] exceptions = methodBinding.getExceptionTypes(); + + if (exceptions != null && exceptions.length > 0) { + completion.append(' '); + completion.append(THROWS); + completion.append(' '); + for (int i = 0; i < exceptions.length; i++) { + if (i != 0) { + completion.append(' '); + completion.append(','); + } + createType(exceptions[i], completion); + } + } + } + + static void createType(ITypeBinding type, StringBuilder completion) { + if (type.isWildcardType() || type.isIntersectionType()) { + completion.append('?'); + if (type.isUpperbound() && type.getBound() != null) { + completion.append(' '); + completion.append(EXTENDS); + completion.append(' '); + createType(type.getBound(), completion); + if (type.getTypeBounds() != null) { + for (ITypeBinding bound : type.getTypeBounds()) { + completion.append(' '); + completion.append('&'); + completion.append(' '); + createType(bound, completion); + } + } + } else if (type.getBound() != null) { + completion.append(' '); + completion.append(SUPER); + completion.append(' '); + createType(type.getBound(), completion); + } + } else if (type.isArray()) { + createType(type.getElementType(), completion); + int dim = type.getDimensions(); + for (int i = 0; i < dim; i++) { + completion.append("[]"); //$NON-NLS-1$ + } + } else if (type.isParameterizedType()) { + if (type.isMember()) { + createType(type.getDeclaringClass(), completion); + completion.append('.'); + completion.append(type.getName()); + } else { + completion.append(type.getQualifiedName()); + } + ITypeBinding[] typeArguments = type.getTypeArguments(); + if (typeArguments != null) { + completion.append('<'); + for (int i = 0, length = typeArguments.length; i < length; i++) { + if (i != 0) + completion.append(','); + createType(typeArguments[i], completion); + } + completion.append('>'); + } + } else { + completion.append(type.getQualifiedName()); + } + } + + static void createTypeVariable(ITypeBinding typeVariable, StringBuilder completion) { + completion.append(typeVariable.getName()); + + if (typeVariable.getSuperclass() != null + && typeVariable.getTypeBounds()[0].getKey().equals(typeVariable.getSuperclass().getKey())) { + completion.append(' '); + completion.append(EXTENDS); + completion.append(' '); + createType(typeVariable.getSuperclass(), completion); + } + if (typeVariable.getInterfaces() != null) { + if (!typeVariable.getTypeBounds()[0].getKey().equals(typeVariable.getSuperclass().getKey())) { + completion.append(' '); + completion.append(EXTENDS); + completion.append(' '); + } + for (int i = 0, length = typeVariable.getInterfaces().length; i < length; i++) { + if (i > 0 || typeVariable.getTypeBounds()[0].getKey().equals(typeVariable.getSuperclass().getKey())) { + completion.append(' '); + completion.append(EXTENDS); + completion.append(' '); + } + createType(typeVariable.getInterfaces()[i], completion); + } + } + } + + static char[] getSignature(IMethodBinding methodBinding) { + String fullKey = methodBinding.getKey().replace('/', '.'); + String removeName = fullKey.substring(fullKey.indexOf('(')); + int firstException = removeName.indexOf('|'); + if (firstException > 0) { + return removeName.substring(0, firstException).toCharArray(); + } else { + return removeName.toCharArray(); + } + } + + static char[] getSignature(ITypeBinding typeBinding) { + return typeBinding.getKey().replace('/', '.').toCharArray(); + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionEngineJavadocUtil.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionEngineJavadocUtil.java new file mode 100644 index 00000000000..6748f81b4a2 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionEngineJavadocUtil.java @@ -0,0 +1,192 @@ +/******************************************************************************* + * 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.internal.codeassist; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; +import org.eclipse.jdt.core.dom.TagElement; +import org.eclipse.jdt.internal.compiler.parser.JavadocTagConstants; + +class DOMCompletionEngineJavadocUtil { + + // block tags + + private static final List JAVA_8_BLOCK_TAGS; + static { + JAVA_8_BLOCK_TAGS = new ArrayList<>(); + for (int i = 0 ; i < 4 ; i++) { + for (char[] entry : JavadocTagConstants.BLOCK_TAGS_RAW[i].tags()) { + JAVA_8_BLOCK_TAGS.add(entry); + } + } + } + private static final List JAVA_9_BLOCK_TAGS; + static { + JAVA_9_BLOCK_TAGS = new ArrayList<>(); + JAVA_9_BLOCK_TAGS.addAll(JAVA_8_BLOCK_TAGS); + for (char[] entry : JavadocTagConstants.BLOCK_TAGS_RAW[4].tags()) { + JAVA_9_BLOCK_TAGS.add(entry); + } + } + + // inline tags + + private static final List JAVA_8_INLINE_TAGS; + static { + JAVA_8_INLINE_TAGS = new ArrayList<>(); + for (int i = 0 ; i < 4 ; i++) { + for (char[] entry : JavadocTagConstants.INLINE_TAGS_RAW[i].tags()) { + JAVA_8_INLINE_TAGS.add(entry); + } + } + } + + private static final List JAVA_9_INLINE_TAGS; + static { + JAVA_9_INLINE_TAGS = new ArrayList<>(); + JAVA_9_INLINE_TAGS.addAll(JAVA_8_INLINE_TAGS); + for (char[] entry : JavadocTagConstants.INLINE_TAGS_RAW[4].tags()) { + JAVA_9_INLINE_TAGS.add(entry); + } + } + + private static final List JAVA_10_INLINE_TAGS; + static { + JAVA_10_INLINE_TAGS = new ArrayList<>(); + JAVA_10_INLINE_TAGS.addAll(JAVA_9_INLINE_TAGS); + for (char[] entry : JavadocTagConstants.INLINE_TAGS_RAW[5].tags()) { + JAVA_10_INLINE_TAGS.add(entry); + } + } + + private static final List JAVA_12_INLINE_TAGS; + static { + JAVA_12_INLINE_TAGS = new ArrayList<>(); + JAVA_12_INLINE_TAGS.addAll(JAVA_10_INLINE_TAGS); + for (char[] entry : JavadocTagConstants.INLINE_TAGS_RAW[6].tags()) { + JAVA_12_INLINE_TAGS.add(entry); + } + } + + private static final List JAVA_16_INLINE_TAGS; + static { + JAVA_16_INLINE_TAGS = new ArrayList<>(); + JAVA_16_INLINE_TAGS.addAll(JAVA_12_INLINE_TAGS); + for (char[] entry : JavadocTagConstants.INLINE_TAGS_RAW[7].tags()) { + JAVA_16_INLINE_TAGS.add(entry); + } + } + + private static final List JAVA_18_INLINE_TAGS; + static { + JAVA_18_INLINE_TAGS = new ArrayList<>(); + JAVA_18_INLINE_TAGS.addAll(JAVA_16_INLINE_TAGS); + for (char[] entry : JavadocTagConstants.INLINE_TAGS_RAW[8].tags()) { + JAVA_18_INLINE_TAGS.add(entry); + } + } + + private static final Set FIELD_TAGS_SET = Stream.of(JavadocTagConstants.FIELD_TAGS).collect(Collectors.toSet()); + private static final Set CLASS_TAGS_SET = Stream.of(JavadocTagConstants.CLASS_TAGS).collect(Collectors.toSet()); + private static final Set METHOD_TAGS_SET = Stream.of(JavadocTagConstants.METHOD_TAGS).collect(Collectors.toSet()); + private static final Set PACKAGE_TAGS_SET = Stream.of(JavadocTagConstants.PACKAGE_TAGS).collect(Collectors.toSet()); + + public static List getJavadocBlockTags(IJavaProject project, TagElement tagNode) { + List tagsForVersion; + String projectVersion = project.getOption(JavaCore.COMPILER_COMPLIANCE, true); + if (projectVersion.contains(".") || Integer.parseInt(projectVersion) < 9) { //$NON-NLS-1$ + tagsForVersion = JAVA_8_BLOCK_TAGS; + } else { + tagsForVersion = JAVA_9_BLOCK_TAGS; + } + + return tagsForNode(tagsForVersion, tagNode); + } + + public static List getJavadocInlineTags(IJavaProject project, TagElement tagNode) { + List tagsForVersion; + String projectVersion = project.getOption(JavaCore.COMPILER_COMPLIANCE, true); + if (projectVersion.contains(".")) { //$NON-NLS-1$ + tagsForVersion = JAVA_8_INLINE_TAGS; + } else { + int versionNumber = Integer.parseInt(projectVersion); + if (versionNumber < 9) { + tagsForVersion = JAVA_8_INLINE_TAGS; + } else if (versionNumber < 10) { + tagsForVersion = JAVA_9_INLINE_TAGS; + } else if (versionNumber < 12) { + tagsForVersion = JAVA_10_INLINE_TAGS; + } else if (versionNumber < 16) { + tagsForVersion = JAVA_12_INLINE_TAGS; + } else if (versionNumber < 18) { + tagsForVersion = JAVA_16_INLINE_TAGS; + } else { + tagsForVersion = JAVA_18_INLINE_TAGS; + } + } + return tagsForNode(tagsForVersion, tagNode); + } + + private static List tagsForNode(List tagsForVersion, TagElement tagNode) { + boolean isField = DOMCompletionUtil.findParent(tagNode, new int[]{ ASTNode.FIELD_DECLARATION }) != null; + if (isField) { + return tagsForVersion.stream() // + .filter(tag -> FIELD_TAGS_SET.contains(tag)) // + .toList(); + } + + ASTNode astNode = DOMCompletionUtil.findParent(tagNode, new int[] { + ASTNode.METHOD_DECLARATION, + ASTNode.TYPE_DECLARATION, + ASTNode.ENUM_DECLARATION, + ASTNode.RECORD_DECLARATION, + ASTNode.ANNOTATION_TYPE_DECLARATION, + ASTNode.TYPE_DECLARATION_STATEMENT}); + + boolean isMethod = astNode != null && astNode.getNodeType() == ASTNode.METHOD_DECLARATION; + if (isMethod) { + return tagsForVersion.stream() // + .filter(tag -> METHOD_TAGS_SET.contains(tag)) // + .toList(); + } + + boolean isType = astNode != null; + if (isType) { + + return tagsForVersion.stream() // + .filter(tag -> CLASS_TAGS_SET.contains(tag)) // + .filter(tag -> { + if (JavadocTagConstants.TAG_PARAM.equals(tag)) { + return ((AbstractTypeDeclaration)astNode).resolveBinding().isGenericType(); + } + return true; + }) + .toList(); + } + + boolean isPackage = DOMCompletionUtil.findParent(tagNode, new int[] {ASTNode.PACKAGE_DECLARATION}) != null; + if (isPackage) { + return tagsForVersion.stream() // + .filter(tag -> PACKAGE_TAGS_SET.contains(tag)) // + .toList(); + } + + throw new IllegalStateException("I was expecting one of the above nodes to be documented"); //$NON-NLS-1$ + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionEngineMethodDeclHandler.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionEngineMethodDeclHandler.java new file mode 100644 index 00000000000..b5e82564237 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionEngineMethodDeclHandler.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Gayan Perera - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.codeassist; + +import java.util.List; + +import org.eclipse.core.runtime.ILog; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.dom.IMethodBinding; + +/** + * This class define methods which are used for handling dom based completions for method declarations. + */ +final class DOMCompletionEngineMethodDeclHandler { + private DOMCompletionEngineMethodDeclHandler() { + } + + /** + * Find parameter names for given method binding. + */ + public static List findVariableNames(IMethodBinding binding) { + if (binding.getJavaElement() instanceof IMethod m) { + try { + return List.of(m.getParameterNames()); + } catch (JavaModelException ex) { + ILog.get().warn(ex.getMessage(), ex); + } + } + return List.of(binding.getParameterNames()); + } +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionUtil.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionUtil.java new file mode 100644 index 00000000000..ec6ceb18719 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionUtil.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * 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.internal.codeassist; + +import java.util.List; +import java.util.function.Consumer; + +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; +import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword; + +public class DOMCompletionUtil { + + /** + * Returns the first parent node that is one of the given types, or null if there is no matching parent node. + * + * @param nodeToSearch the node whose parents should be searched + * @param kindsToFind array of node types that count as matches. See the constants in {@link ASTNode} + * @return the first parent node that is one of the given types, or null if there is no matching parent node + */ + public static ASTNode findParent(ASTNode nodeToSearch, int[] kindsToFind) { + ASTNode cursor = nodeToSearch; + while (cursor != null) { + for (int kindToFind : kindsToFind) { + if (cursor.getNodeType() == kindToFind) { + return cursor; + } + } + cursor = cursor.getParent(); + } + return null; + } + + public static void visitChildren(ASTNode root, int kind, Consumer consumer) { + ASTVisitor visitor = new ASTVisitor() { + @Override + public void preVisit(ASTNode node) { + if (node.getNodeType() == kind) { + consumer.accept((T)node); + } + } + }; + root.accept(visitor); + } + + /** + * Returns the first parent type declaration (class, enum, record, annotation, etc), or null if there is no parent type declaration. + * + * @param nodeToSearch the node whose parents should be searched + * @return the first parent type declaration (class, enum, record, annotation, etc), or null if there is no parent type declaration + */ + public static AbstractTypeDeclaration findParentTypeDeclaration(ASTNode nodeToSearch) { + return (AbstractTypeDeclaration) DOMCompletionUtil.findParent(nodeToSearch, new int[] { ASTNode.TYPE_DECLARATION, ASTNode.ENUM_DECLARATION, ASTNode.RECORD_DECLARATION, ASTNode.ANNOTATION_TYPE_DECLARATION }); + } + + private static final List JAVA_MODIFIERS = List.of( + ModifierKeyword.PUBLIC_KEYWORD.toString(), + ModifierKeyword.PRIVATE_KEYWORD.toString(), + ModifierKeyword.STATIC_KEYWORD.toString(), + ModifierKeyword.PROTECTED_KEYWORD.toString(), + ModifierKeyword.SYNCHRONIZED_KEYWORD.toString(), + ModifierKeyword.ABSTRACT_KEYWORD.toString(), + ModifierKeyword.FINAL_KEYWORD.toString(), + ModifierKeyword.DEFAULT_KEYWORD.toString(), + ModifierKeyword.NATIVE_KEYWORD.toString(), + ModifierKeyword.STRICTFP_KEYWORD.toString(), + ModifierKeyword.TRANSIENT_KEYWORD.toString(), + ModifierKeyword.VOLATILE_KEYWORD.toString() + ); + + /** + * Returns true if the given String is a java modifier for fields and methods and false otherwise. + * + * In this case, modifiers are that you can include before methods or fields. + * + * @param potentialModifer the String to check if it's a modifier + * @return true if the given String is a java modifier for fields and methods and false otherwise + */ + public static boolean isJavaFieldOrMethodModifier(String potentialModifer) { + return JAVA_MODIFIERS.contains(potentialModifer); + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMInternalCompletionProposal.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMInternalCompletionProposal.java new file mode 100644 index 00000000000..e38281ac7f0 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMInternalCompletionProposal.java @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (c) 2025 Red Hat, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jdt.internal.codeassist; + +import org.eclipse.jdt.core.CompletionContext; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.Signature; +import org.eclipse.jdt.core.compiler.CharOperation; +import org.eclipse.jdt.internal.core.NameLookup; + +class DOMInternalCompletionProposal extends InternalCompletionProposal { + + private DOMCompletionEngine engine; + + public DOMInternalCompletionProposal(int kind, int completionLocation, DOMCompletionEngine engine) { + super(kind, completionLocation); + this.engine = engine; + } + + @Override + public boolean canUseDiamond(CompletionContext coreContext) { + // ECJ-based implementation uses a downcast, + // so re-implement this method with our own downcast + if (!coreContext.isExtended()) return false; + if (coreContext instanceof DOMCompletionContext domCompletionContext) { + char[] name1 = this.declarationPackageName; + char[] name2 = this.declarationTypeName; + char[] declarationType = CharOperation.concat(name1, name2, '.'); // fully qualified name + // even if the type arguments used in the method have been substituted, + // extract the original type arguments only, since thats what we want to compare with the class + // type variables (Substitution might have happened when the constructor is coming from another + // CU and not the current one). + char[] sign = (this.originalSignature != null)? this.originalSignature : getSignature(); + if (!(sign == null || sign.length < 2)) { + sign = Signature.removeCapture(sign); + } + char[][] types= Signature.getParameterTypes(sign); + String[] paramTypeNames= new String[types.length]; + for (int i= 0; i < types.length; i++) { + paramTypeNames[i]= new String(Signature.toCharArray(types[i])); + } + if (this.getDeclarationTypeVariables() != null) { + return domCompletionContext.canUseDiamond(paramTypeNames, this.getDeclarationTypeVariables()); + } + return domCompletionContext.canUseDiamond(paramTypeNames, declarationType); + } + return false; + } + + public void setNameLookup(NameLookup nameLookup) { + this.nameLookup = nameLookup; + } + + public void setCompletionEngine(CompletionEngine completionEngine) { + this.completionEngine = completionEngine; + } + + @Override + protected void setDeclarationPackageName(char[] declarationPackageName) { + super.setDeclarationPackageName(declarationPackageName); + } + + @Override + protected void setTypeName(char[] typeName) { + super.setTypeName(typeName); + } + + @Override + protected void setDeclarationTypeName(char[] declarationTypeName) { + super.setDeclarationTypeName(declarationTypeName); + } + + @Override + protected void setIsContructor(boolean isConstructor) { + super.setIsContructor(isConstructor); + } + + @Override + public void setParameterNames(char[][] parameterNames) { + super.setParameterNames(parameterNames); + } + + @Override + protected void setParameterTypeNames(char[][] parameterTypeNames) { + super.setParameterTypeNames(parameterTypeNames); + } + + @Override + protected void setParameterPackageNames(char[][] parameterPackageNames) { + super.setParameterPackageNames(parameterPackageNames); + } + + @Override + protected void setPackageName(char[] packageName) { + super.setPackageName(packageName); + } + + @Override + protected void setModuleName(char[] moduleName) { + super.setModuleName(moduleName); + } + + @Override + public boolean isConstructor() { + return this.isConstructor; + } + + @Override + protected void incrementOpenedBinaryTypesCount() { + this.engine.openedBinaryTypes++; + } + + @Override + protected int getOpenedBinaryTypesCount() { + return this.engine.openedBinaryTypes; + } + + @Override + protected void addToCompletionEngineTypeCache(char[] tName, IType type) { + this.engine.typeCache.put(tName, type); + } + + @Override + protected Object getFromEngineTypeCache(char[] tName) { + return this.engine.typeCache.get(tName); + } + +} \ No newline at end of file diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/RelevanceUtils.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/RelevanceUtils.java new file mode 100644 index 00000000000..bf4fa96524a --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/RelevanceUtils.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2025 Red Hat, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jdt.internal.codeassist; + +import java.util.Objects; + +import org.eclipse.jdt.core.compiler.CharOperation; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.PrimitiveType; +import org.eclipse.jdt.internal.codeassist.impl.AssistOptions; + +/** + * Helper methods for calculating the relevance numbers of completion proposals. + * + * Most of these are adapted from {@link CompletionEngine} + */ +class RelevanceUtils { + + /** + * Returns the sum of the appropriate relevance constants based on how the + * current name matches the given proposal name according to the enabled rules. + * + * @param token the uncompleted content + * @param proposalName the proposal text to check against the uncompleted + * content + * @param options the assist options, which specifies which matching rules + * are enabled + * @see CompletionEngine#computeRelevanceForCaseMatching(char[], char[], + * AssistOptions) + * @return the sum of the appropriate relevance constants + */ + public static int computeRelevanceForCaseMatching(char[] token, char[] proposalName, AssistOptions options) { + if (CharOperation.equals(token, proposalName, true)) { + return RelevanceConstants.R_EXACT_NAME + RelevanceConstants.R_CASE; + } else if (CharOperation.equals(token, proposalName, false)) { + return RelevanceConstants.R_EXACT_NAME; + } else if (CharOperation.prefixEquals(token, proposalName, false)) { + if (CharOperation.prefixEquals(token, proposalName, true)) + return RelevanceConstants.R_CASE; + } else if (options.camelCaseMatch && CharOperation.camelCaseMatch(token, proposalName)) { + return RelevanceConstants.R_CAMEL_CASE; + } else if (options.substringMatch && CharOperation.substringMatch(token, proposalName)) { + return RelevanceConstants.R_SUBSTRING; + } else if (options.subwordMatch && CharOperation.subWordMatch(token, proposalName)) { + return RelevanceConstants.R_SUBWORD; + } + return 0; + } + + /** + * Returns the appropriate "qualified" relevance constant based on if the current content is qualified and if the content is expected to be qualified. + * + * @param prefixRequired true if the current completion item should be qualified, false otherwise + * @param prefix the completion prefix; the java identifier that completion was triggered on + * @param qualifiedPrefix the completion prefix with any qualifiers (eg. package qualifiers, type qualifiers, nested member access, etc.). May contain `.` + * @see CompletionEngine#computeRelevanceForQualification(boolean) + * @return the appropriate "qualified" relevance constant based on if the current content is qualified and if the content is expected to be qualified + */ + static int computeRelevanceForQualification(boolean prefixRequired, String prefix, String qualifiedPrefix) { + boolean insideQualifiedReference = !prefix.equals(qualifiedPrefix); + if (!prefixRequired && !insideQualifiedReference) { + return RelevanceConstants.R_UNQUALIFIED; + } + if (prefixRequired && insideQualifiedReference) { + return RelevanceConstants.R_QUALIFIED; + } + return 0; + } + + /** + * Returns the appropriate relevance constant based on if type associated with the proposal matches the expected types. + * + * @param proposalType the binding of the type associated with the proposal + * @param expectedTypes the expected types + * @return the appropriate relevance constant based on if type associated with the proposal matches the expected types + */ + static int computeRelevanceForExpectingType(ITypeBinding proposalType, ExpectedTypes expectedTypes) { + if (proposalType != null) { + int relevance = 0; + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=271296 + // If there is at least one expected type, then void proposal types attract a degraded relevance. + if (!expectedTypes.getExpectedTypes().isEmpty() && PrimitiveType.VOID.toString().equals(proposalType.getName())) { + return RelevanceConstants.R_VOID; + } + for (ITypeBinding expectedType : expectedTypes.getExpectedTypes()) { + if(expectedTypes.allowsSubtypes() + && proposalType.getErasure().isSubTypeCompatible(expectedType.getErasure())) { + + if(Objects.equals(expectedType.getQualifiedName(), proposalType.getQualifiedName())) { + return RelevanceConstants.R_EXACT_EXPECTED_TYPE; + } else if (proposalType.getPackage() != null && proposalType.getPackage().isUnnamed()) { + return RelevanceConstants.R_PACKAGE_EXPECTED_TYPE; + } + relevance = RelevanceConstants.R_EXPECTED_TYPE; + + } + if(expectedTypes.allowsSupertypes() && expectedType.isSubTypeCompatible(proposalType)) { + + if(Objects.equals(expectedType.getQualifiedName(), proposalType.getQualifiedName())) { + return RelevanceConstants.R_EXACT_EXPECTED_TYPE; + } + relevance = RelevanceConstants.R_EXPECTED_TYPE; + } + // Bug 84720 - [1.5][assist] proposal ranking by return value should consider auto(un)boxing + // Just ensuring that the unitScope is not null, even though it's an unlikely case. +// if (this.unitScope != null && this.unitScope.isBoxingCompatibleWith(proposalType, this.expectedTypes[i])) { +// relevance = CompletionEngine.R_EXPECTED_TYPE; +// } + } + return relevance; + } + return 0; + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/javac/BuildNotifierCompilationProgress.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/javac/BuildNotifierCompilationProgress.java new file mode 100644 index 00000000000..308958e228f --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/javac/BuildNotifierCompilationProgress.java @@ -0,0 +1,83 @@ +/******************************************************************************* +* Copyright (c) 2024 Red Hat, Inc. and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +*******************************************************************************/ +package org.eclipse.jdt.internal.javac; + +import java.lang.reflect.Field; + +import org.eclipse.core.runtime.ILog; +import org.eclipse.jdt.core.compiler.CompilationProgress; +import org.eclipse.jdt.internal.compiler.ICompilerRequestor; +import org.eclipse.jdt.internal.core.builder.AbstractImageBuilder; +import org.eclipse.jdt.internal.core.builder.BuildNotifier; + +/******************************************************************************* + * 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 + *******************************************************************************/ +public class BuildNotifierCompilationProgress extends CompilationProgress { + + private BuildNotifier buildNotifier; + + public BuildNotifierCompilationProgress(ICompilerRequestor requestor) { + this.buildNotifier = findBuildNotifier(requestor); + } + + private BuildNotifier findBuildNotifier(ICompilerRequestor requestor) { + if (requestor instanceof AbstractImageBuilder) { + try { + Field notifierField = AbstractImageBuilder.class.getDeclaredField("notifier"); + notifierField.setAccessible(true); + return notifierField.get(requestor) instanceof BuildNotifier notifier ? notifier : null; + } catch (Exception ex) { + ILog.get().warn(ex.getMessage(), ex); + } + } + return null; + } + + @Override + public void begin(int remainingWork) { + if (this.buildNotifier != null) { + this.buildNotifier.checkCancelWithinCompiler(); + } + } + + @Override + public void done() { + // do not forward as done() is sent via requestor + } + + @Override + public boolean isCanceled() { + if (this.buildNotifier != null) { + this.buildNotifier.checkCancel(); + } + return false; + } + + @Override + public void setTaskName(String name) { + if (this.buildNotifier != null) { + this.buildNotifier.subTask(name); + } + } + + @Override + public void worked(int workIncrement, int remainingWork) { + // TODO Auto-generated method stub + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/javac/CachingJDKPlatformArguments.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/javac/CachingJDKPlatformArguments.java new file mode 100644 index 00000000000..96d4aad6298 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/javac/CachingJDKPlatformArguments.java @@ -0,0 +1,190 @@ +/******************************************************************************* + * 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.internal.javac; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import javax.annotation.processing.Processor; +import javax.tools.FileObject; +import javax.tools.ForwardingFileObject; +import javax.tools.ForwardingJavaFileManager; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.JavaFileObject.Kind; + +import org.eclipse.core.runtime.ILog; + +import com.sun.source.util.Plugin; +import com.sun.tools.javac.file.PathFileObject; +import com.sun.tools.javac.main.Arguments; +import com.sun.tools.javac.main.DelegatingJavaFileManager; +import com.sun.tools.javac.main.Option; +import com.sun.tools.javac.platform.PlatformDescription; +import com.sun.tools.javac.platform.PlatformUtils; +import com.sun.tools.javac.resources.CompilerProperties.Errors; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Log; +import com.sun.tools.javac.util.Options; +import com.sun.tools.javac.util.Context.Factory; + +public class CachingJDKPlatformArguments extends Arguments { + + private static Map platformFMCache = new ConcurrentHashMap<>(); + + private final Options options; + private final Context context; + + public static void preRegister(Context context) { + context.put(Arguments.argsKey, (Factory) c -> new CachingJDKPlatformArguments(c)); + } + + private CachingJDKPlatformArguments(Context context) { + super(context); + this.options = Options.instance(context); + this.context = context; + } + + @Override + public boolean handleReleaseOptions(Predicate> additionalOptions) { + // mostly copied from super, only wrapping the platformDescription so its + // fileManager is reusable + String platformString = options.get(Option.RELEASE); + + checkOptionAllowed(platformString == null, + option -> Log.instance(this.context).error(Errors.ReleaseBootclasspathConflict(option)), + Option.BOOT_CLASS_PATH, Option.XBOOTCLASSPATH, Option.XBOOTCLASSPATH_APPEND, + Option.XBOOTCLASSPATH_PREPEND, Option.ENDORSEDDIRS, Option.DJAVA_ENDORSED_DIRS, Option.EXTDIRS, + Option.DJAVA_EXT_DIRS, Option.SOURCE, Option.TARGET, Option.SYSTEM, Option.UPGRADE_MODULE_PATH); + + if (platformString != null) { + PlatformDescription platformDescription = toReusable( + PlatformUtils.lookupPlatformDescription(platformString)); + if (platformDescription == null) { + Log.instance(this.context).error(Errors.UnsupportedReleaseVersion(platformString)); + return false; + } + + options.put(Option.SOURCE, platformDescription.getSourceVersion()); + options.put(Option.TARGET, platformDescription.getTargetVersion()); + + context.put(PlatformDescription.class, platformDescription); + + if (!additionalOptions.test(platformDescription.getAdditionalOptions())) + return false; + + JavaFileManager platformFM = platformDescription.getFileManager(); + DelegatingJavaFileManager.installReleaseFileManager(context, platformFM, + context.get(JavaFileManager.class)); + } + return true; + } + + private static PlatformDescription toReusable(PlatformDescription delegate) { + if (delegate == null) { + return null; + } + return new PlatformDescription() { + @Override + public JavaFileManager getFileManager() { + return platformFMCache.computeIfAbsent(getSourceVersion(), _ -> new ForwardingJavaFileManager(delegate.getFileManager()) { + @Override + public void close() { + // do nothing, to keep instance usable in the future + } + @Override + public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException { + var res = super.getFileForInput(location, packageName, relativeName); + makeUnderlyingFileObjectUninterruptible(res); + return res; + } + @Override + public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException { + var res = super.getJavaFileForInput(location, className, kind); + makeUnderlyingFileObjectUninterruptible(res); + return res; + } + @Override + public Iterable list(Location location, String packageName, java.util.Set kinds, boolean recurse) throws IOException { + var res = super.list(location, packageName, kinds, recurse); + res.forEach(this::makeUnderlyingFileObjectUninterruptible); + return res; + } + private void makeUnderlyingFileObjectUninterruptible(FileObject fo) { + PathFileObject toUninterrupted = null; + if (fo instanceof PathFileObject o) { + toUninterrupted = o; + } + if (fo instanceof ForwardingFileObject forwarding) { + try { + Field fileObjectField = ForwardingFileObject.class.getDeclaredField("fileObject"); + fileObjectField.setAccessible(true); + Object o = fileObjectField.get(forwarding); + if (o instanceof PathFileObject pathFileObject) { + toUninterrupted = pathFileObject; + } + } catch (Exception e) { + ILog.get().error(e.getMessage(), e); + } + } + if (toUninterrupted != null) { + ZipFileSystemProviderWithCache.makeFileSystemUninterruptible(toUninterrupted.getPath().getFileSystem()); + } + } + }); + } + + @Override + public String getSourceVersion() { + return delegate.getSourceVersion(); + } + + @Override + public String getTargetVersion() { + return delegate.getTargetVersion(); + } + + @Override + public List> getAnnotationProcessors() { + return delegate.getAnnotationProcessors(); + } + + @Override + public List> getPlugins() { + return delegate.getPlugins(); + } + + @Override + public List getAdditionalOptions() { + return delegate.getAdditionalOptions(); + } + + @Override + public void close() throws IOException { + // DO NOTHING! + } + + }; + } + + void checkOptionAllowed(boolean allowed, Consumer