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..8d39a81e6b4 --- /dev/null +++ b/.github/workflows/ci-dom-javac.yml @@ -0,0 +1,68 @@ +name: Continuous Integration with DOM/Javac +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-dom + cancel-in-progress: true + +on: + push: + branches: [ 'dom-with-javac' ] + pull_request: + branches: [ '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: Set up JDKs ☕ + uses: actions/setup-java@v4 + with: + java-version: | + 8 + 17 + 21 + 23 + 24 + mvn-toolchain-id: | + JavaSE-1.8 + JavaSE-17 + JavaSE-21 + JavaSE-23 + JavaSE-24 + distribution: 'temurin' + - name: Set up Maven + uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5 + with: + maven-version: 3.9.9 + - name: Build with Maven 🏗️ + run: | + mvn install --batch-mode -DskipTests -Pbree-libs -Dtycho.buildqualifier.format="'z'yyyyMMdd-HHmm" -Dproject.build.sourceEncoding=UTF-8 -pl org.eclipse.jdt.core.javac,org.eclipse.jdt.core.javac.feature,org.eclipse.jdt.core.tests.model,org.eclipse.jdt.core.tests.compiler + mvn verify --batch-mode -f org.eclipse.jdt.core.tests.javac --fail-at-end -Ptest-on-javase-24 -Pbree-libs -DfailIfNoTests=false -DexcludedGroups=org.junit.Ignore -DproviderHint=junit47 -Papi-check -Dproject.build.sourceEncoding=UTF-8 -Dmaven.test.failure.ignore=true -Dmaven.test.error.ignore=true + - name: 'Upload Artifact' + if: success() || failure() # run this step even if previous step failed + uses: actions/upload-artifact@v4 + with: + name: Test results + path: '*/target/surefire-reports/TEST-*.xml' + - name: Test Report + if: success() || failure() # run this step even if previous step failed + run: | + message="▶️ TESTS RUN: $(xmllint --xpath 'string(/testsuite/@tests)' */target/surefire-reports/TEST-*.xml | awk '{n += $1}; END{print n}' -) + ❌ FAILURES: $(xmllint --xpath 'string(/testsuite/@failures)' */target/surefire-reports/TEST-*.xml | awk '{n += $1}; END{print n}' -) + 💥 ERRORS: $(xmllint --xpath 'string(/testsuite/@errors)' */target/surefire-reports/TEST-*.xml | awk '{n += $1}; END{print n}' -) + 🛑 SKIPPED: $(xmllint --xpath 'string(/testsuite/@skipped)' */target/surefire-reports/TEST-*.xml | awk '{n += $1}; END{print n}' -)" + echo "$message" + echo "$message" >> $GITHUB_STEP_SUMMARY + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + files: '*/target/surefire-reports/TEST-*.xml' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e6c8d27860..e034f59b5a3 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 7e3bd17d862..dd11c17c2a8 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -13,51 +13,34 @@ pipeline { jdk 'openjdk-jdk24-latest' } stages { - stage('Build') { + stage('javac specific tests') { steps { - sh """#!/bin/bash -x - - java -version - + sh """#!/bin/bash -x mkdir -p $WORKSPACE/tmp unset JAVA_TOOL_OPTIONS unset _JAVA_OPTIONS - - # The max heap should be specified for tycho explicitly - # via configuration/argLine property in pom.xml - # 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 - - mvn -U clean verify --batch-mode --fail-at-end -Dmaven.repo.local=$WORKSPACE/.m2/repository \ - -Ptest-on-javase-24 -Pbree-libs -Papi-check -Pjavadoc -Pp2-repo \ - -Dmaven.test.failure.ignore=true \ - -Dcompare-version-with-baselines.skip=false \ + # 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 -Dmaven.repo.local=$WORKSPACE/.m2/repository \ + -Pbree-libs \ + -Dtycho.buildqualifier.format="'z'yyyyMMdd-HHmm" \ + -Pp2-repo \ -Djava.io.tmpdir=$WORKSPACE/tmp -Dproject.build.sourceEncoding=UTF-8 \ - -Dtycho.surefire.argLine="--add-modules ALL-SYSTEM -Dcompliance=1.8,11,17,21,23,24 -Djdt.performance.asserts=disabled" \ - -DDetectVMInstallationsJob.disabled=true \ - -Dtycho.apitools.debug \ - -Dtycho.debug.artifactcomparator \ - -e \ - -Dcbi-ecj-version=99.99 - """ + -pl 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 -Dmaven.repo.local=$WORKSPACE/.m2/repository \ + --fail-at-end -Ptest-on-javase-24 -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 - // 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 - // To accept unstable builds (test errors or new warnings introduced by third party changes) as reference using "ignoreQualityGate:true" - // To only show warnings related to the PR on a PR using "publishAllIssues:false" - discoverGitReferenceBuild referenceJob: 'eclipse.jdt.core-github/master' - junit allowEmptyResults: true, testResults: '**/target/surefire-reports/*.xml' - recordIssues publishAllIssues: false, ignoreQualityGate: true, enabledForFailure: true, tools: [ - eclipse(name: 'Compiler', pattern: '**/target/compilelogs/*.xml'), - issues(name: 'API Tools', id: 'apitools', pattern: '**/target/apianalysis/*.xml'), - ], qualityGates: [[threshold: 1, type: 'DELTA', unstable: true]] - recordIssues tools: [javaDoc(), mavenConsole()] + 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 d8b29bda305..505947f5529 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 @@ -143,6 +143,7 @@ public interface ClassFileConstants { int MAJOR_VERSION_24 = 68; int MAJOR_VERSION_0 = 44; + // Latest version supported by ECJ (not necessarily latest known Java version) int MAJOR_LATEST_VERSION = MAJOR_VERSION_24; int MINOR_VERSION_0 = 0; @@ -179,6 +180,10 @@ public interface ClassFileConstants { long JDK23 = ((long)ClassFileConstants.MAJOR_VERSION_23 << 16) + ClassFileConstants.MINOR_VERSION_0; long JDK24 = ((long)ClassFileConstants.MAJOR_VERSION_24 << 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.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..d9ae1d43738 --- /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..2619ff190f0 --- /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=24 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=24 +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=24 +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..d87cd32d4ce --- /dev/null +++ b/org.eclipse.jdt.core.javac/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,152 @@ +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=true +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_unnecessary_suppress_warnings=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.switch_for_instanceof_pattern=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..9dd73c62dee --- /dev/null +++ b/org.eclipse.jdt.core.javac/META-INF/MANIFEST.MF @@ -0,0 +1,13 @@ +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=24))" +Import-Package: org.eclipse.core.resources, + 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..780ba9735b3 --- /dev/null +++ b/org.eclipse.jdt.core.javac/META-INF/p2.inf @@ -0,0 +1,93 @@ +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\n\ +-DMatchLocator.DOM_BASED_MATCH=true\n\ +-DIJavaSearchDelegate=org.eclipse.jdt.internal.core.search.DOMJavaSearchDelegate\n\ +-Xss16m\ +); + +# 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);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:-DMatchLocator.DOM_BASED_MATCH=true);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:-DIJavaSearchDelegate=org.eclipse.jdt.internal.core.search.DOMJavaSearchDelegate);\ +org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:-Xss16m);\ 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..30fd3fca6df --- /dev/null +++ b/org.eclipse.jdt.core.javac/fragment.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jdt.core.javac/pom.xml b/org.eclipse.jdt.core.javac/pom.xml new file mode 100644 index 00000000000..c15318f6db3 --- /dev/null +++ b/org.eclipse.jdt.core.javac/pom.xml @@ -0,0 +1,64 @@ + + + + 4.0.0 + + eclipse.jdt.core + org.eclipse.jdt + 4.37.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..99db489fb54 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacBindingResolver.java @@ -0,0 +1,2017 @@ +/******************************************************************************* + * 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.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +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.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.Signature; +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.JavacErrorTypeBinding; +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.ClassFinder; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Symbol.ClassSymbol; +import com.sun.tools.javac.code.Symbol.CompletionFailure; +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.comp.Modules; +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.JCTypeIntersection; +import com.sun.tools.javac.tree.JCTree.JCTypeParameter; +import com.sun.tools.javac.tree.JCTree.JCTypeUnion; +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) { }; + annotationBindings.putIfAbsent(newInstance, newInstance); + return annotationBindings.get(newInstance); + } + // + private Map memberValuePairBindings = new HashMap<>(); + public JavacMemberValuePairBinding getMemberValuePairBinding(MethodSymbol key, Attribute value) { + JavacMemberValuePairBinding newInstance = new JavacMemberValuePairBinding(key, value, JavacBindingResolver.this) { }; + memberValuePairBindings.putIfAbsent(newInstance, newInstance); + return memberValuePairBindings.get(newInstance); + } + public JavacMemberValuePairBinding getDefaultMemberValuePairBinding(IMethodBinding defaultAnnotationMethod) { + if (!defaultAnnotationMethod.isAnnotationMember()) { + return null; + } + JavacMemberValuePairBinding newInstance = new JavacMemberValuePairBinding(defaultAnnotationMethod, JavacBindingResolver.this) { }; + memberValuePairBindings.putIfAbsent(newInstance, newInstance); + return memberValuePairBindings.get(newInstance); + } + // + private Map methodBindings = new HashMap<>(); + public JavacMethodBinding getMethodBinding(MethodType methodType, MethodSymbol sym, + com.sun.tools.javac.code.Type type, + boolean isSynthetic, boolean isDeclaration, + List typeArgs) { + if( isSynthetic ) { + return getSyntheticMethodBinding(methodType, sym, type, typeArgs); + } else { + return getMethodBinding(methodType, sym, type, isDeclaration, typeArgs); + } + } + + public JavacMethodBinding getMethodBinding(MethodType methodType, MethodSymbol methodSymbol, + com.sun.tools.javac.code.Type parentType, boolean isDeclaration, + List resolvedTypeArgs) { + JavacMethodBinding newInstance = new JavacMethodBinding(methodType, methodSymbol, parentType, JavacBindingResolver.this, false, isDeclaration, resolvedTypeArgs) { }; + return insertAndReturn(newInstance); + } + public JavacMethodBinding getSyntheticMethodBinding(MethodType methodType, MethodSymbol methodSymbol, + com.sun.tools.javac.code.Type parentType, List resolvedTypeArgs) { + JavacMethodBinding newInstance = new JavacMethodBinding(methodType, methodSymbol, parentType, JavacBindingResolver.this, true, false, resolvedTypeArgs) { }; + return insertAndReturn(newInstance); + } + public JavacMethodBinding getErrorMethodBinding(MethodType methodType, Symbol originatingSymbol, List typeArgs) { + 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) { }; + moduleBindings.putIfAbsent(newInstance, newInstance); + return moduleBindings.get(newInstance); + } + public JavacModuleBinding getModuleBinding(ModuleSymbol moduleSymbol) { + JavacModuleBinding newInstance = new JavacModuleBinding(moduleSymbol, JavacBindingResolver.this) { }; + moduleBindings.putIfAbsent(newInstance, newInstance); + return moduleBindings.get(newInstance); + } + public JavacModuleBinding getModuleBinding(JCModuleDecl moduleDecl) { + JavacModuleBinding newInstance = new JavacModuleBinding(moduleDecl, JavacBindingResolver.this) { }; + // Overwrite existing + moduleBindings.put(newInstance, newInstance); + return moduleBindings.get(newInstance); + } + + // + private Map packageBindings = new HashMap<>(); + public JavacPackageBinding getPackageBinding(PackageSymbol packageSymbol) { + if (!packageSymbol.exists()) { + return null; + } + 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. + if( newest != null ) { + JavacPackageBinding current = packageBindings.get(newest); + if( current == null ) { + packageBindings.putIfAbsent(newest, newest); + } else if( current.getPackageSymbol() == null && newest.getPackageSymbol() != null) { + current.setPackageSymbol(newest.getPackageSymbol()); + } + return packageBindings.get(newest); + } + 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; + } + + boolean likelyGeneric = false; +// if( type instanceof ClassType ct && ct.isParameterized()) { +// List typeArgs = ct.getTypeArguments(); +// int size = typeArgs.size(); +// for( int i = 0; i < size && !likelyGeneric; i++ ) { +// if( typeArgs.get(i) instanceof TypeVar) { +// likelyGeneric = true; +// } +// } +// } +// + return getTypeBinding(type.baseType() /* remove metadata for constant values */, likelyGeneric); + } + public JavacTypeBinding getTypeBinding(com.sun.tools.javac.code.Type type, boolean isGeneric) { + return getTypeBinding(type, null, isGeneric); + } + public JavacTypeBinding getTypeBinding(com.sun.tools.javac.code.Type type, com.sun.tools.javac.code.Type[] alternatives, boolean isGeneric) { + 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, alternatives, isGeneric, 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, alternatives, isGeneric, 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.tsym instanceof ClassSymbol sym && sym.classfile == null && sym.sourcefile == null) + && !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, alternatives, isGeneric, 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) { }; + variableBindings.putIfAbsent(newInstance, newInstance); + return variableBindings.get(newInstance); + } + // + private Map lambdaBindings = new HashMap<>(); + public JavacLambdaBinding getLambdaBinding(JavacMethodBinding javacMethodBinding, LambdaExpression lambda) { + JavacLambdaBinding newInstance = new JavacLambdaBinding(javacMethodBinding, lambda); + lambdaBindings.putIfAbsent(newInstance, newInstance); + return lambdaBindings.get(newInstance); + } + + 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 != null && (type instanceof ErrorType || owner == null || owner.owner == null || owner.owner.type == com.sun.tools.javac.code.Type.noType)) { + if (type.getOriginalType() instanceof MethodType missingMethodType) { + return getErrorMethodBinding(missingMethodType, owner, null); + } + } + 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, null); + } + } 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 = new ArrayList<>(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)); + } + + public IBinding findBinding(String bindingKey) { + return this.bindings.getBinding(bindingKey); + } + + private void compoundListWithAction(HashSet list, Function f) { + Iterator it = new ArrayList(list).iterator(); + while(it.hasNext()) { + String transformed = f.apply(it.next()); + if( transformed != null && !list.contains(transformed)) { + list.add(transformed); + } + } + } + + public IBinding findUnresolvedBinding(String bindingKey) { + boolean isUnresolved = bindingKey.startsWith("Q") || bindingKey.startsWith("+Q") || bindingKey.startsWith("-Q"); + if( !isUnresolved) { + return findBinding(bindingKey); + } + + final boolean bkExtends = bindingKey.startsWith("+"); + final boolean bkSuper = bindingKey.startsWith("-"); + + String withoutSuperExtends = bindingKey.startsWith("+") || bindingKey.startsWith("-") ? bindingKey.substring(1) : bindingKey; + + HashSet validNames = new HashSet(); + validNames.add(bindingKey); + compoundListWithAction(validNames, x -> x.replaceAll("\\.", "/")); + compoundListWithAction(validNames, x -> x.endsWith(";") ? x.substring(0, x.length() - 1) : null); + compoundListWithAction(validNames, x -> x.startsWith("+") || x.startsWith("-") ? x.substring(1) : null); + compoundListWithAction(validNames, x -> x.lastIndexOf(".", x.length() - 1) != -1 ? x.substring(x.lastIndexOf(".") + 1) : null); + compoundListWithAction(validNames, x -> x.startsWith("Q") ? x.substring(1) : null); + compoundListWithAction(validNames, x -> x.contains(" x.startsWith("+Q") ? x.replaceAll("\\+Q", "? extends ") : null); + compoundListWithAction(validNames, x -> x.startsWith("-Q") ? x.replaceAll("-Q", "? super ") : null); + String bindingKeySimpleName = Signature.getSignatureSimpleName(withoutSuperExtends); + validNames.add(bindingKeySimpleName); + + Collection c = new ArrayList<>(this.bindings.typeBinding.values()); + int matchesKey = 0x80; + int matchesSimpleName = 0x40; + int matchesSuperExtends = 0x10; + record Pair(JavacTypeBinding binding, int weight) {}; + List collector = new ArrayList(); + + c.stream().forEach(x -> { + int total = 0; + String k = x.getKey(); + if( validNames.contains(k)) + total += matchesKey; + String n = x.getName(); + if( validNames.contains(n)) { + total += matchesSimpleName; + } + if( bkExtends || bkSuper ) { + if( x.isWildcardType() && x.getBound() != null ) { + if( bkExtends && x.isUpperbound()) { + total += matchesSuperExtends; + } else if( bkSuper && !x.isUpperbound() ) { + total += matchesSuperExtends; + } + } + } + if( total > 0 ) + collector.add(new Pair(x, total)); + }); + + Collections.sort(collector, (o1, o2) -> o2.weight - o1.weight); + return collector.size() > 0 ? collector.get(0).binding : null; + } + + @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); + } + if (jcTree instanceof JCTypeUnion unionType) { + com.sun.tools.javac.code.Type[] alternativesArray = new com.sun.tools.javac.code.Type[unionType.alternatives.size()]; + for (int i = 0 ; i < alternativesArray.length; i++) { + alternativesArray[i] = unionType.alternatives.get(i).type; + } + return this.bindings.getTypeBinding(unionType.type, alternativesArray, false); + } + if (jcTree instanceof JCTypeIntersection intersectionType) { + com.sun.tools.javac.code.Type[] alternativesArray = new com.sun.tools.javac.code.Type[intersectionType.bounds.size()]; + for (int i = 0 ; i < alternativesArray.length; i++) { + alternativesArray[i] = intersectionType.bounds.get(i).type; + } + return this.bindings.getTypeBinding(intersectionType.type, alternativesArray, false); + } + +// 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(bindings.getPackageBinding(Symtab.instance(context).rootPackage)); + } + return bindings.getPackageBinding(Symtab.instance(context).rootPackage); + } + }; + } + + @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 && (javacNode.type != null && "".equals(javacNode.type.toString()))) { + return new JavacErrorTypeBinding(javacNode.type, javacNode.type.tsym, null, true, JavacBindingResolver.this, jcClassDecl.sym); + } + 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 = null; + if (javacElement instanceof JCMethodInvocation javacMethodInvocation) { + typeArgs = List.of(); + 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, typeArgs); + if (res != null) { + return res; + } + } + } + var sym = javacElement instanceof JCIdent ident ? ident.sym : + javacElement instanceof JCFieldAccess fieldAccess ? fieldAccess.sym : + null; + + // Let's handle error types first + if (type instanceof ErrorType errorType ) { + com.sun.tools.javac.code.Type original = errorType; + while(original instanceof ErrorType et && original != et.getOriginalType()) { + original = et.getOriginalType(); + } + + if( original instanceof ForAll fa) { + original = fa.asMethodType(); + } + if( original 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, typeArgs); + } + } + return this.bindings.getErrorMethodBinding(methodType, sym, typeArgs); + } + } + + + + 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, typeArgs); + } + if (type == null && sym instanceof MethodSymbol methodSym && methodSym.type instanceof ForAll methodTemplateType) { + // build type from template + Map resolutionMapping = new HashMap<>(); + var templateParameters = methodTemplateType.getTypeVariables(); + if( typeArgs != null ) { + 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, typeArgs); + } + 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, null); + } + if (methodDecl.sym instanceof MethodSymbol methodSymbol && methodSymbol.type != null) { + return this.bindings.getMethodBinding(methodSymbol.type.asMethodType(), methodSymbol, null, true, null); + } + } + 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) { + List typeArgs = streamOfTreeType(memberRef.getTypeArguments()); + if (memberRef.referentType != null && memberRef.referentType instanceof MethodType) { + return this.bindings.getMethodBinding(memberRef.referentType.asMethodType(), methodSymbol, null, false, typeArgs); + } + if (methodSymbol.type instanceof MethodType) { + return this.bindings.getMethodBinding(methodSymbol.type.asMethodType(), methodSymbol, null, false, typeArgs); + } + } + return null; + } + + private List streamOfTreeType(List items ) { + return items != null ? items.stream().map(x -> x.type).toList() : new ArrayList(); + } + + @Override + IMethodBinding resolveMember(AnnotationTypeMemberDeclaration member) { + resolve(); + JCTree javacElement = this.converter.domToJavac.get(member); + if (javacElement instanceof JCMethodDecl methodDecl) { + List typeArgs = streamOfTreeType(methodDecl.getTypeParameters()); + return this.bindings.getMethodBinding(methodDecl.type.asMethodType(), methodDecl.sym, null, true, typeArgs); + } + return null; + } + + @Override + IMethodBinding resolveConstructor(EnumConstantDeclaration enumConstantDeclaration) { + resolve(); + JCTree javacElement = this.converter.domToJavac.get(enumConstantDeclaration); + if( javacElement instanceof JCVariableDecl jcvd ) { + javacElement = jcvd.init; + } + if(javacElement instanceof JCNewClass jcExpr && !jcExpr.constructor.type.isErroneous()) { + List typeArgs = streamOfTreeType(jcExpr.typeargs); + return this.bindings.getMethodBinding(jcExpr.constructor.type.asMethodType(), (MethodSymbol)jcExpr.constructor, null, true, typeArgs); + } + return 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, null); + } else if (methodSymbol.asType() instanceof MethodType || methodSymbol.asType() instanceof ForAll) { + return this.bindings.getMethodBinding(methodSymbol.asType().asMethodType(), methodSymbol, null, false, null); + } + } + if (javacElement instanceof JCFieldAccess fieldAccess && fieldAccess.sym instanceof MethodSymbol methodSymbol) { + return this.bindings.getMethodBinding(fieldAccess.type.asMethodType(), methodSymbol, null, false, null); + } + 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, null); + } + 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, null); + } + return null; + } + + IBinding resolveCached(ASTNode node, Function l) { + resolve(); + // 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(); + if (name.getParent() instanceof MemberRef memberRef) { + resolveReference(memberRef); // initialize symbols on Javadoc + } + if (name.getParent() instanceof MethodRef methodRef) { + resolveReference(methodRef); + } + + // 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); + } + if (name.getLocationInParent() == MethodInvocation.NAME_PROPERTY && name.getParent() instanceof MethodInvocation method) { + return resolveMethod(method); + } + + 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 && ps.exists()) { + 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 psym && psym.exists(); + } + 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 + && !isRecoveringBindings()) { + 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) { + if (newClass.encl != null) { + String className = newClass.clazz instanceof JCTypeApply typeApply ? typeApply.clazz.toString() : newClass.clazz.toString(); + ITypeBinding enclosingTypeBinding = resolveExpressionType(((ClassInstanceCreation)expr).getExpression()); + List potentialTypes = Stream.of(enclosingTypeBinding.getDeclaredTypes()).filter(innerType -> { + String cleanedName = innerType.getName(); + if (cleanedName.endsWith(">")) { + cleanedName = cleanedName.substring(0, cleanedName.lastIndexOf("<")); + } + return className.equals(cleanedName); + }).toList(); + if (!potentialTypes.isEmpty()) { + return potentialTypes.get(0); + } + } + 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); + } + if (jcFieldAccess.type != null && !jcFieldAccess.type.isErroneous()) { + return this.bindings.getTypeBinding(jcFieldAccess.type); + } + if (jcFieldAccess.sym != null) { + return this.bindings.getTypeBinding(jcFieldAccess.sym.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 classInstanceCreation.getType().resolveBinding(); +// 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()) { + List javacTypeArgs = + jcExpr.getTypeArguments().stream().map(jc -> jc.type).toList(); + return this.bindings.getMethodBinding(jcExpr.constructor.type.asMethodType(), (MethodSymbol)jcExpr.constructor, jcExpr.type, false, javacTypeArgs); + } + } + 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) { + MethodType mt = ident.type != null && ident.type.getKind() == TypeKind.EXECUTABLE ? ident.type.asMethodType() : methodSymbol.type.asMethodType(); + return this.bindings.getMethodBinding(mt, methodSymbol, null, false, null); + } + if (javacElement instanceof JCFieldAccess fieldAccess && fieldAccess.sym instanceof MethodSymbol methodSymbol) { + return this.bindings.getMethodBinding(fieldAccess.type.asMethodType(), methodSymbol, null, false, null); + } + 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); + } else if( attribute instanceof Attribute.Error) { + return null; + } + 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) { + ITypeBinding typeBinding = this.bindings.getTypeBinding(type); + if (typeBinding != null) { + IBinding binding = Arrays.stream(typeBinding.getDeclaredMethods()) + .filter(method -> Objects.equals(fieldAccess.getIdentifier().toString(), method.getName())) + .findAny() + .orElse(null); + if (binding == null) { + binding = Arrays.stream(typeBinding.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) { + ClassFinder finder = ClassFinder.instance(context); + Modules modules = Modules.instance(context); + Names names = Names.instance(context); + if (finder != null && modules != null && names != null) { + try { + ClassSymbol sym = finder.loadClass(modules.getDefaultModule(), names.fromString(typeName)); + if (sym != null) { + type = sym.type; + } + } catch (CompletionFailure failure) { + // do nothing, class not found + } + } + } + return type != null ? this.bindings.getTypeBinding(type, true) : null; + } + + @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; + IBinding bRet = this.bindings.getBinding(jcfa2.sym, typeToUse); + if(bRet == null && jcfa2.selected instanceof JCTypeApply) { + // ??? no idea if this is a good answer + typeToUse = jcfa2.sym.type; + } + + 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(); + } + if (jcTree == null && expression.getParent() != null) { + jcTree = this.converter.domToJavac.get(expression.getParent()); + if (jcTree == null) { + return null; + } + } + 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..1701dd9a825 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacCompilationUnitResolver.java @@ -0,0 +1,1194 @@ +/******************************************************************************* + * 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.DefaultErrorHandlingPolicies; +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.problem.ProblemReporter; +import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; +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.CachingClassSymbolClassReader; +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.ProcessorConfig; +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.Context.Key; +import com.sun.tools.javac.util.DiagnosticSource; +import com.sun.tools.javac.util.Names; +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 { + + public static final String MOCK_NAME_FOR_CLASSES = "whatever_InvalidNameWE_HOP3_n00ne_will_Ever_use_in_real_file.java"; + public static final Key> FILE_OBJECTS_TO_JAR_KEY = new Key<>(); + + 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++) { + String encoding = encodings == null ? null : i >= encodings.length ? null : encodings[i]; + org.eclipse.jdt.internal.compiler.env.ICompilationUnit obj = createSourceUnit(sourceFilePaths[i], encoding); + 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 && b.ast.getBindingResolver() instanceof JavacBindingResolver javacBindingResolver) { + bindingResolver[0] = javacBindingResolver; + } + resolveBindings(b, bindingMap, apiLevel); + requestor.acceptAST(a,b); + }); + + 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, new ProblemReporter(DefaultErrorHandlingPolicies.ignoreAllProblems() , null, new DefaultProblemFactory()) { + @Override + public int computeSeverity(int problemID) { + return ProblemSeverities.Ignore; + } + }, 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( apiLevel >= AST.JLS9_INTERNAL) { + if (unit.getModule() != null) { + IModuleBinding mb = unit.getModule().resolveBinding(); + if (mb != null) { + bindingMap.put(mb.getKey(), mb); + } + } + } + unit.accept(new ASTVisitor() { + @Override + public void preVisit(ASTNode node) { + if( node instanceof Type t) { + ITypeBinding tb = t.resolveBinding(); + if (tb != null) { + bindingMap.put(tb.getKey(), tb); + } + } + } + }); + + 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); + } + } + } + + } 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 | (resolveBindings ? AST.RESOLVED_BINDINGS : 0), project, typeRootWorkingCopyOwner, focalPoint, monitor).get(sourceUnit); + if (resolveBindings && focalPoint == -1) { + // force analysis and reports + resolveBindings(res, apiLevel); + } + return res; + } + + private static Names names = new Names(new Context()) { + @Override + public void dispose() { + // do nothing, keep content for re-use + } + }; + + 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(); + context.put(Names.namesKey, names); + CachingJarsJavaFileManager.preRegister(context); + CachingJDKPlatformArguments.preRegister(context); + CachingClassSymbolClassReader.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); + Map fileObjectsToJars = new HashMap<>(); + context.put(FILE_OBJECTS_TO_JAR_KEY, fileObjectsToJars); + 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.put(Option.XLINT_CUSTOM, "none"); + javacOptions.remove(Option.XDOCLINT.primaryName); + javacOptions.put(Option.XDOCLINT_CUSTOM, "none"); + } + javacOptions.put(Option.PROC, ProcessorConfig.isAnnotationProcessingEnabled(javaProject) ? "only" : "none"); + 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(System.identityHashCode(sourceUnit) + '/' + MOCK_NAME_FOR_CLASSES).toURI()); + } else if (unitFile.getName().endsWith(".jar")) { + sourceUnitPath = Path.of(unitFile.toURI()).resolve(System.identityHashCode(sourceUnit) + '/' + MOCK_NAME_FOR_CLASSES); + } else { + sourceUnitPath = Path.of(unitFile.toURI()); + } + var fileObject = fileManager.getJavaFileObject(sourceUnitPath); + if (unitFile.getName().endsWith(".jar")) { + fileObjectsToJars.put(fileObject, unitFile); + } + 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); + } + + + 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())) { + try { + task.analyze(); + } catch (Throwable t) { + ILog.get().error("Error while analyzing", t); + // continue anyway + } + } + + Throwable cachedThrown = null; + + while (elements.hasNext() && elements.next() instanceof JCCompilationUnit u) { + javacCompilationUnits.add(u); + if (sourceUnits.length == 1 && focalPoint >= 0) { + JavacUtils.trimUnvisibleContent(u, focalPoint, context); + } + try { + String rawText = null; + try { + rawText = u.getSourceFile().getCharContent(true).toString(); + } catch( IOException ioe) { + ILog.get().error(ioe.getMessage(), ioe); + return null; + } + CompilationUnit res = filesToUnits.get(u.getSourceFile()); + AST ast = res.ast; + JavacConverter converter = new JavacConverter(ast, u, context, rawText, docEnabled, focalPoint); + converter.populateCompilationUnit(res, u); + // 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 " + u.sourcefile); + 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 && !generated(comment) && 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 boolean generated(Comment comment) { + ASTNode parentNode = comment.getParent(); + if (parentNode instanceof MethodDeclaration md) { + for (Object modifier: md.modifiers()) { + if (modifier instanceof MarkerAnnotation ma) { + return "lombok.Generated".equals(ma.getTypeName().getFullyQualifiedName()); + } + } + } + return false; + } + + 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..a29686df24e --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacConverter.java @@ -0,0 +1,3631 @@ +/******************************************************************************* + * 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.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.codeassist.DOMCodeSelector; +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.code.Type.PackageType; +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.JCTree; +import com.sun.tools.javac.tree.TreeInfo; +import com.sun.tools.javac.tree.DCTree.DCDocComment; +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.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 MISSING_IDENTIFIER = "$missing$"; + 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, false)) + .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()) { + 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 (res.getName().toString().contains(JavacConverter.FAKE_IDENTIFIER)) { + res.setFlags(res.getFlags() | ASTNode.MALFORMED); + } + if (javac.isStatic() || javac.isModule()) { + 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; + Modifier preExisting = (Modifier)res.modifiers().stream().filter(x -> x instanceof Modifier && ((Modifier)x).isStatic()).findFirst().orElse(null); + if( preExisting != null ) { + res.modifiers().remove(preExisting); + } + } + 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()) { + 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); + } else { + name.setSourceRange(0, 0); + } + } + + 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()); + } else { + name.setSourceRange(0, 0); + } + } + + 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, boolean parentBodyFilled) { + if( javacClassDecl.getKind() == Kind.ANNOTATION_TYPE && + (this.ast.scanner.complianceLevel < ClassFileConstants.JDK1_5)) { + return null; + } + if( javacClassDecl.getKind() == Kind.ENUM && + (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, parentBodyFilled); + } + + private AbstractTypeDeclaration convertClassDecl(JCClassDecl javacClassDecl, ASTNode parent, AbstractTypeDeclaration res, boolean parentBodyFilled) { + 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()); + } else if (!MISSING_IDENTIFIER.equals(simpName.getFullyQualifiedName())) { + // lombok case + if (DOMCodeSelector.isGenerated(res)) { + simpName.setSourceRange(0, 0); + } + } + } + res.modifiers().addAll(convert(javacClassDecl.mods, res)); + if (res instanceof TypeDeclaration typeDeclaration) { + if (javacClassDecl.getExtendsClause() != null) { + typeDeclaration.setSuperclassType(convertToType(javacClassDecl.getExtendsClause())); + } + if (javacClassDecl.getImplementsClause() != null) { + javacClassDecl.getImplementsClause().stream() + .map(this::convertToType) + .filter(Objects::nonNull) + .forEach(typeDeclaration.superInterfaceTypes()::add); + } + + if( javacClassDecl.getTypeParameters() != null ) { + 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, parentBodyFilled); + 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, parentBodyFilled); + 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, parentBodyFilled); + 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, parentBodyFilled); + if( converted != null ) { + res.bodyDeclarations.add(converted); + } + } + } + } else if (res instanceof ImplicitTypeDeclaration) { + javacClassDecl.getMembers().stream() + .map(member -> convertBodyDeclaration(member, res, parentBodyFilled)) + .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) { + 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, boolean parentBodyFilled) { + if( parent instanceof AnnotationTypeDeclaration && tree instanceof JCMethodDecl methodDecl) { + return convertMethodInAnnotationTypeDecl(methodDecl, parent); + } + if (tree instanceof JCMethodDecl methodDecl) { + return convertMethodDecl(methodDecl, parent, parentBodyFilled); + } + if (tree instanceof JCClassDecl jcClassDecl) { + return convertClassDecl(jcClassDecl, parent, parentBodyFilled); + } + if (tree instanceof JCVariableDecl jcVariableDecl) { + return convertFieldDeclaration(jcVariableDecl, parent); + } + if (tree instanceof JCBlock block) { + Initializer res = this.ast.newInitializer(); + commonSettings(res, tree); + // 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)); + 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, boolean parentBodyFilled) { + 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); + res.modifiers().addAll(convert(javac.getModifiers(), res)); + 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.endsWith(ERROR); // lombok creates set + 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 ) { + 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 + res.extraDimensions().addAll(dims); + retType = convertToType(unwrapDimensions(retTypeTree, dims.size())); + } + + if( retType != null || isConstructor) { + res.setReturnType2(retType); + } else { + 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(); + res.typeParameters().add(convert(next)); + } + } + + boolean generatedByLombok = javac.getModifiers() != null && javac.getModifiers().toString().contains("@lombok.Generated"); + if (!generatedByLombok && javac.getBody() != null + && javac.getBody().endpos > javac.getBody().getStartPosition()) { // otherwise, it's probably generated by lombok + boolean fillBlock = shouldFillBlock(javac, this.focalPoint); + fillBlock = parentBodyFilled || fillBlock; + 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 isJlsAbove8 = this.ast.apiLevel > AST.JLS8_INTERNAL; + long flagsToCheckForAboveJLS8 = Flags.STATIC | Flags.DEFAULT | (isJlsAbove8 ? Flags.PRIVATE : 0); + boolean notAllowed = (isAbstractOrNative || (isInterface && (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()) { + 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); + } + res.modifiers().addAll(convert(javac.getModifiers(), res)); + + 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); + } + 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 + 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 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); + 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); + res.modifiers().addAll(convert(javac.getModifiers(), res)); + + 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) { + packageDeclaration.setJavadoc(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); + 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); + 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) { + 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); + res.setType(convertToType(newClass.getIdentifier())); + 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) { + 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; + 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 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++ ) { + // TODO, this dimension needs source range + arrayType.dimensions().addFirst(this.ast.newDimension()); + } + } 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) { + int pos = javac.getPreferredPosition(); + char c = this.rawText.length() > pos ? this.rawText.charAt(pos) : 0; + if (error.getErrorTrees().isEmpty() && c == '"') { + int newLine = this.rawText.indexOf('\n', pos); + int lineEnd = newLine == -1 ? this.rawText.length() - 1 : newLine; + String litText = this.rawText.substring(pos+1, lineEnd); + Expression res = convertStringToLiteral(litText, pos, lineEnd, null); + res.setSourceRange(pos, lineEnd - pos); + res.setFlags(res.getFlags() | ASTNode.MALFORMED); + return res; + } + 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); + if (this.ast.apiLevel < AST.JLS22) { + jdtPattern.setPatternVariable((SingleVariableDeclaration)convertVariableDeclaration(jcBindingPattern.var)); + } else { + jdtPattern.setPatternVariable(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, true); + 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) { + 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) { + 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); + 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); + 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); + 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. + // fix ASTConverterAST8Test.test0049()/test0052() + boolean isNumberLiteral = (this.rawText.charAt(literal.getStartPosition()) != '-') + || ((value instanceof Integer i) && i == Integer.MIN_VALUE) + || ((value instanceof Long l) && l == Long.MIN_VALUE); + if( isNumberLiteral) { + 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) { + return convertStringToLiteral(string, literal.pos, literal.getEndPosition(this.javacCompilationUnit.endPositions), literal); + } + 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()); + res.setEscapedValue(this.rawText.substring(res.getStartPosition(), res.getStartPosition() + res.getLength())); + return res; + } + throw new UnsupportedOperationException("Not supported yet " + literal + "\n of type" + literal.getClass().getName()); + } + + private Expression convertStringToLiteral(String string, int pos, int endPos, JCLiteral literal) { + boolean malformed = false; + if (this.rawText.charAt(pos) == '"' + && this.rawText.charAt(pos + 1) == '"' + && this.rawText.charAt(pos + 2) == '"') { + if (this.ast.apiLevel() > AST.JLS14) { + TextBlock res = this.ast.newTextBlock(); + commonSettings(res, literal); + String rawValue = this.rawText.substring(pos, endPos); + 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; + } + 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); + if (expression instanceof SimpleName simpleName + && expression.getStartPosition() + expression.getLength() + 1 < this.rawText.length() + && Character.isSpaceChar(this.rawText.charAt(expression.getStartPosition() + expression.getLength()))) { + // assume variable declaration statement + VariableDeclarationFragment fakeVdf = this.ast.newVariableDeclarationFragment(); + SimpleName fakeSimpleName = this.ast.newSimpleName(FAKE_IDENTIFIER); + int fakePos = expression.getStartPosition() + expression.getLength() + 1; + fakeSimpleName.setSourceRange(fakePos, 0); + fakeVdf.setName(fakeSimpleName); + fakeVdf.setSourceRange(fakePos, 0); + fakeVdf.setFlags(fakeVdf.getFlags() | ASTNode.MALFORMED); + SimpleType simpleType = this.ast.newSimpleType(simpleName); + commonSettings(simpleType, expr); + VariableDeclarationStatement res = this.ast.newVariableDeclarationStatement(fakeVdf); + res.setType(simpleType); + commonSettings(res, expr); + return res; + } + 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); + if (jcVariableDecl.vartype != null) { + List sameStartPosition = List.of(); + if (parent instanceof Block decl) { + sameStartPosition = ((List)decl.statements()).stream() + .filter(VariableDeclarationStatement.class::isInstance) + .map(VariableDeclarationStatement.class::cast) + .filter(x -> x.getType().getStartPosition() == jcVariableDecl.vartype.getStartPosition()) + .toList(); + } else if( parent instanceof ForStatement decl) { + // TODO somehow doubt this will work as expected + sameStartPosition = ((List)decl.initializers()).stream() + .filter(VariableDeclarationExpression.class::isInstance) + .map(VariableDeclarationExpression.class::cast) + .filter(x -> x.getType().getStartPosition() == jcVariableDecl.vartype.getStartPosition()) + .toList(); + } else if (parent instanceof SwitchStatement decl) { + sameStartPosition = ((List)decl.statements()).stream() + .filter(VariableDeclarationStatement.class::isInstance) + .map(VariableDeclarationStatement.class::cast) + .filter(x -> x.getType().getStartPosition() == jcVariableDecl.vartype.getStartPosition()) + .toList(); + } + if( sameStartPosition.size() >= 1 ) { + // factorize + 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.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); + } + res.modifiers().addAll(convert(jcVariableDecl.getModifiers(), res)); + 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) { + 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; + } + 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, true)); + 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.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( 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 if( this.ast.apiLevel >= AST.JLS9_INTERNAL && working instanceof SuperFieldAccess){ + res.resources().add(working); + } else if( this.ast.apiLevel >= AST.JLS9_INTERNAL && working instanceof FieldAccess){ + res.resources().add(working); + } 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); + } + 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())); + res.modifiers().addAll(convert(decl.getModifiers(), res)); + 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(); + int startPosition = ident.getStartPosition(); + if (startPosition < 0 && ident.type instanceof PackageType) { + ILog.get().info("negative start position " + startPosition); // generated by lombok + startPosition = 0; + } + name.setSourceRange(startPosition, 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 { + if (MISSING_IDENTIFIER.equals(simpleName.getFullyQualifiedName())) { + // empty (eg `test.`) + simpleName.setSourceRange(qualifierType.getStartPosition(), 0); + } else { + // lombok case + simpleName.setSourceRange(0, 0); + } + } + if(qualifierType instanceof SimpleType simpleType && 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 (simpleNameStart > 0 && name.getStartPosition() >= 0) { + name.setSourceRange(name.getStartPosition(), Math.max(0, length)); + } else if (!MISSING_IDENTIFIER.equals(simpleName.getFullyQualifiedName())) { + // lombok case + name.setSourceRange(0, 0); + } + SimpleType res = this.ast.newSimpleType(name); + commonSettings(res, javac); + if (simpleNameStart > 0 && name.getStartPosition() >= 0) { + res.setSourceRange(name.getStartPosition(), Math.max(0, length)); + } else if (!MISSING_IDENTIFIER.equals(simpleName.getFullyQualifiedName())) { + res.setSourceRange(0, 0); + } + 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) { + UnionType res = this.ast.newUnionType(); + commonSettings(res, javac); + union.getTypeAlternatives().stream() + .map(this::convertToType) + .filter(Objects::nonNull) + .forEach(res.types()::add); + return res; + } + if (javac instanceof JCArrayTypeTree jcArrayType) { + Type t = convertToType(jcArrayType.getType()); + if (t == null) { + return null; + } + ArrayType res; + if (t instanceof ArrayType childArrayType) { + res = childArrayType; + res.dimensions().addFirst(this.ast.newDimension()); + commonSettings(res, jcArrayType); + } 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 (ordinalStart == -1) { + ordinalStart = ordinalIndexOf(raw, "\\u005b", dims); + } + if( ordinalEnd != -1 ) { + commonSettings(res, jcArrayType, ordinalEnd + 1, true); + 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) { + 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; + } + 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() // + && !(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) { + for (JCAnnotation annotation : jcAnnotatedType.getAnnotations()) { + annotatableType.annotations().add(convert(annotation)); + } + } else if (res instanceof ArrayType arrayType) { + if (!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) { + int startPosition = err.getStartPosition(); + if (startPosition < 0) { + ILog.get().info("negative start position " + startPosition); + startPosition = 0; + } + res.setSourceRange(startPosition, 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 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 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.getName().getStartPosition() + node.getName().getLength() - node.getQualifier().getStartPosition()) >= 0) { + node.setSourceRange(node.getQualifier().getStartPosition(), node.getName().getStartPosition() + node.getName().getLength() - node.getQualifier().getStartPosition()); + } else if (!MISSING_IDENTIFIER.equals(node.getName().getIdentifier())) { + node.setSourceRange(0, 0); + } + } + + @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..2ca3dcdcb3e --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavadocConverter.java @@ -0,0 +1,981 @@ +/******************************************************************************* + * 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.parser.Tokens.Comment.CommentStyle; +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.DCDocRoot; +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.DCIndex; +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.DCSerial; +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.DCSummary; +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) + (docComment.comment.getStyle() == CommentStyle.JAVADOC_LINE ? 1 /* include line end */ : 0), 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.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 if (javac instanceof DCSerial serial) { + res.setTagName(TagElement.TAG_SERIAL); + convertElementCombiningNodes(serial.description.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(this.docComment.comment.getStyle() == CommentStyle.JAVADOC_LINE ? TagElement.TAG_LINK : + switch (link.getKind()) { + case LINK -> TagElement.TAG_LINK; + case LINK_PLAIN -> TagElement.TAG_LINKPLAIN; + default -> TagElement.TAG_LINK; + }); + if (link.label != null && !link.label.isEmpty() && link.ref != null && link.label.getFirst().getStartPosition() < link.ref.getStartPosition()) { + // markdown style + link.label.stream().flatMap(this::convertElement).forEach(res.fragments()::add); + res.fragments().addAll(convertElement(link.ref).toList()); + // workaround position not set by javadoc (not reported) + } else { + res.fragments().addAll(convertElement(link.ref).toList()); + link.label.stream().flatMap(this::convertElement).forEach(res.fragments()::add); + } + if (res.getStartPosition() < 0) { + int start = ((ASTNode)res.fragments().getFirst()).getStartPosition() - 1 /* [ */; + ASTNode lastChild = (ASTNode)res.fragments().getLast(); + int end = lastChild.getStartPosition() + lastChild.getLength() + 1 /* ) */; + res.setSourceRange(start, end - start); + } + } 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 DCDocRoot) { + res.setTagName(TagElement.TAG_DOCROOT); + } else if (javac instanceof DCSummary summary) { + res.setTagName(TagElement.TAG_SUMMARY); + res.fragments().addAll(summary.summary.stream().flatMap(this::convertElement).toList()); + } else if (javac instanceof DCIndex index) { + res.setTagName(TagElement.TAG_INDEX); + res.fragments().addAll(index.description.stream().flatMap(this::convertElement).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) { + if (this.docComment.comment.getStyle() == CommentStyle.JAVADOC_LINE && rawText.getContent().contains("\n")) { + // un-combine multiple javadoc lines + String[] segments = rawText.getContent().split("\n"); + List regions = new ArrayList<>(segments.length); + int currentStart = this.docComment.getSourcePosition(rawText.getStartPosition()); + for (String segment : segments) { + int end = this.javacConverter.rawText.indexOf('\n', currentStart); + if (end < currentStart) { + end = this.docComment.getSourcePosition(rawText.getEndPosition()); + } + TextElement element = this.ast.newTextElement(); + element.setSourceRange(currentStart, segment.length()); + element.setText(segment); + regions.add(element); + currentStart = this.javacConverter.rawText.indexOf("///", end); + } + return regions.stream(); + } else { + String content = rawText.getContent(); + char last = content.charAt(content.length()-1); + if (last != 26 /* EOF */) { + // https://github.com/eclipse-jdtls/eclipse-jdt-core-incubator/issues/1185 + // Possible error converting Javadoc + TextElement element = this.ast.newTextElement(); + commonSettings(element, javac); + element.setText(rawText.getContent()); + return Stream.of(element); + } else { + content = rawText.getContent().substring(0, content.length() - 1); + int currentStart = this.docComment.getSourcePosition(rawText.getStartPosition()); + TextElement element = this.ast.newTextElement(); + element.setSourceRange(currentStart, content.length()); + element.setText(content); + 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 = null; + if (reference.qualifierExpression instanceof JCArrayTypeTree arrayType) { + qualifierExpressionName = toName(arrayType.elemtype, res.getStartPosition()); + } else { + 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(); + } + memberNameStart++; + 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 = null; + if (reference.qualifierExpression instanceof JCArrayTypeTree arrayType) { + qualifierExpressionName = toName(arrayType.elemtype, startPos + moduleQualifierLen); + } else { + 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/core/dom/JdtCoreDomPackagePrivateUtility.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JdtCoreDomPackagePrivateUtility.java new file mode 100644 index 00000000000..cf1e82b7f01 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JdtCoreDomPackagePrivateUtility.java @@ -0,0 +1,33 @@ +package org.eclipse.jdt.core.dom; + +public class JdtCoreDomPackagePrivateUtility { + public static BindingResolver getBindingResolver(ASTNode node) { + return node.getAST().getBindingResolver(); + } + + public static JavacBindingResolver getJavacBindingResolverOrNull(ASTNode node) { + BindingResolver br = getBindingResolver(node); + return br instanceof JavacBindingResolver br2 ? br2 : null; + } + + public static IBinding findBindingForType(ASTNode node, String signature) { + JavacBindingResolver jcbr = getJavacBindingResolverOrNull(node); + IBinding ret1 = jcbr instanceof JavacBindingResolver br2 ? br2.findBinding(signature) : null; + if( ret1 == null ) { + String sig2 = signature.replaceAll("\\.", "/"); + ret1 = jcbr instanceof JavacBindingResolver br2 ? br2.findBinding(sig2) : null; + } + return ret1; + } + + public static IBinding findUnresolvedBindingForType(ASTNode node, String signature) { + JavacBindingResolver jcbr = getJavacBindingResolverOrNull(node); + IBinding ret1 = jcbr instanceof JavacBindingResolver br2 ? br2.findBinding(signature) : null; + if( ret1 == null ) { + ret1 = jcbr instanceof JavacBindingResolver br2 ? br2.findUnresolvedBinding(signature) : null; + } + return ret1; + } + + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/SignatureUtils.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/SignatureUtils.java new file mode 100644 index 00000000000..a181ff0822e --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/SignatureUtils.java @@ -0,0 +1,223 @@ +/******************************************************************************* + * 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; + +import java.util.ArrayList; +import java.util.List; +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.jdt.core.IMethod; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.Signature; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.ParameterizedType; +import org.eclipse.jdt.core.dom.SimpleType; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.search.IJavaSearchConstants; +import org.eclipse.jdt.core.search.IJavaSearchScope; +import org.eclipse.jdt.core.search.SearchEngine; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.jdt.core.search.TypeNameMatch; +import org.eclipse.jdt.core.search.TypeNameMatchRequestor; + +public class SignatureUtils { + + public static String createSignature(Type type, SearchEngine searchEngine, IJavaSearchScope scope, IProgressMonitor monitor) { + ITypeBinding binding = type.resolveBinding(); + if (binding != null && !binding.isRecovered()) { + return getSignature(binding); + } + String simpleName = simpleName(type); + IType resolvedType = binding.getJavaElement() instanceof IType element ? element : null; + if (resolvedType == null || resolvedType.exists()) { + List types = new ArrayList<>(); + try { + searchEngine.searchAllTypeNames(null, SearchPattern.R_PREFIX_MATCH, simpleName.toCharArray(), SearchPattern.R_EXACT_MATCH, IJavaSearchConstants.TYPE, scope, new TypeNameMatchRequestor() { + @Override + public void acceptTypeNameMatch(TypeNameMatch match) { + types.add(match.getType()); + } + }, IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, monitor); + } catch (JavaModelException ex) { + ILog.get().error(ex.getMessage(), ex); + } + if (types.size() == 1) { + resolvedType = types.get(0); + } + } + StringBuilder res = new StringBuilder(); + if (resolvedType != null && resolvedType.exists()) { + res.append(Signature.C_RESOLVED); + res.append(resolvedType.getFullyQualifiedName()); + } else { + res.append(Signature.C_UNRESOLVED); + res.append(simpleName); + } + if (type instanceof ParameterizedType parameterized) { + res.append(Signature.C_GENERIC_START); + ((List)parameterized.typeArguments()).stream() + .map(param -> createSignature(param, searchEngine, scope, monitor)) + .forEach(res::append); + res.append(Signature.C_GENERIC_END); + } + res.append(';'); + return res.toString(); + } + + private static String simpleName(Type type) { + if (type instanceof SimpleType simple) { + return simple.getName().toString(); + } + if (type instanceof ParameterizedType parameterized) { + return simpleName(parameterized.getType()); + } + return type.toString(); + } + + /** + * Returns the signature of the given model type. + * + * @param type the model type to get the signature of + * @return the signature of the given model type + */ + public static String createSignature(IType type) { + return getSignatureForTypeKey(type.getKey()); + } + + /** + * Returns the signature of the given type binding as a character array. + * + * @param typeBinding the type binding to get the signature of + * @return the signature of the given type binding as a character array + */ + public static char[] getSignatureChar(ITypeBinding typeBinding) { + return SignatureUtils.getSignature(typeBinding).toCharArray(); + } + + /** + * Returns the signature of the given type binding. + * + * @param typeBinding the type binding to get the signature of + * @return the signature of the given type binding + */ + public static String getSignature(ITypeBinding typeBinding) { + if (typeBinding.isArray()) { + return Signature.createArraySignature(getSignature(typeBinding.getComponentType()), 1); + } + if (typeBinding.isWildcardType()) { + // TODO if typeBinding.getBounds(): C_EXTENDS, C_SUPER + return Character.toString(Signature.C_STAR); + } + ITypeBinding[] typeBounds = typeBinding.getTypeBounds(); + if (typeBinding.isTypeVariable() || typeBinding.isWildcardType()) { + return Signature.C_TYPE_VARIABLE + typeBinding.getName() + Signature.C_NAME_END; + } + if (typeBinding.isIntersectionType()) { + return Signature.createIntersectionTypeSignature(Stream.of(typeBounds).map(SignatureUtils::getSignature).toArray(String[]::new)); + } + if (typeBinding.isParameterizedType()) { + StringBuilder res = new StringBuilder(Signature.createTypeSignature(typeBinding.getErasure().getQualifiedName(), true)); + res.deleteCharAt(res.length() - 1); + return res.toString() + + Signature.C_GENERIC_START + + Stream.of(typeBinding.getTypeArguments()).map(SignatureUtils::getSignature).collect(Collectors.joining()) + + Signature.C_GENERIC_END + + Signature.C_NAME_END; + } + if (typeBinding.isGenericType()) { + StringBuilder res = new StringBuilder(Signature.createTypeSignature(typeBinding.getErasure().getQualifiedName(), true)); + res.deleteCharAt(res.length() - 1); + return res.toString() + + Signature.C_GENERIC_START + + Stream.of(typeBinding.getTypeParameters()).map(SignatureUtils::getSignature).collect(Collectors.joining()) + + Signature.C_GENERIC_END + + Signature.C_NAME_END; + } + return SignatureUtils.getSignatureForTypeKey(typeBinding.getKey()); + } + + /** + * Returns the signature of the given type key. + * + * @param key the type key to get the signature of + * @return the signature of the given type key + */ + public static String getSignatureForTypeKey(String key) { + return key.replace('/', '.').replaceFirst("(?<=\\.|L)[_$A-Za-z][_$A-Za-z0-9]*~", ""); + } + + public static String getSignature(IMethodBinding methodBinding) { + return getSignatureForMethodKey(methodBinding.getKey()); + } + + /** + * Returns the signature of the given method binding as a character array. + * + * @param methodBinding the method binding to get the signature of + * @return the signature of the given method binding as a character array + */ + public static char[] getSignatureChar(IMethodBinding methodBinding) { + return getSignatureForMethodKey(methodBinding.getKey()).toCharArray(); + } + + public static char[] getSignatureChar(IMethod method) { + return getSignatureForMethodKey(method.getKey()).toCharArray(); + } + + /** + * Returns the signature for the given method key. + * + * @param key the method key to get the signature of + * @return the signature for the given method key + */ + public static String getSignatureForMethodKey(String key) { + String fullKey = key + .replace('/', '.') + .replace("<+Ljava.lang.Object;>", "<*>"); + String removeName = fullKey.substring(fullKey.indexOf('(')); + int firstException = removeName.indexOf('|'); + String exceptionRemoved; + if (firstException > 0) { + exceptionRemoved = removeName.substring(0, firstException); + } else { + exceptionRemoved = removeName; + } + // strip out the weird information the keys need to differentiate themselves + return exceptionRemoved.replaceAll("!L[^;]+;\\{[0-9]+\\}(\\+L[^;]+;)[0-9]+;", "$1"); + } + + public static String stripTypeArgumentsFromKey(String key) { + if (key.indexOf(">;") < 0) { + return key; + } + return key.substring(0, key.lastIndexOf("<")) + "<>;"; + } + + /** + * Returns true if the given signature is a primitive number, and false + * otherwise. + * + * @param sig the signature to check + * @return true if the given signature is a primitive number, and false + * otherwise + */ + public static boolean isNumeric(String sig) { + return sig.equals(Signature.SIG_BYTE) || sig.equals(Signature.SIG_CHAR) || sig.equals(Signature.SIG_DOUBLE) + || sig.equals(Signature.SIG_FLOAT) || sig.equals(Signature.SIG_INT) || sig.equals(Signature.SIG_LONG) + || sig.equals(Signature.SIG_SHORT); + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/Timer.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/Timer.java new file mode 100644 index 00000000000..dec07c5cdf8 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/Timer.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * 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; + +import java.time.Duration; +import java.time.Instant; +import java.util.AbstractMap.SimpleEntry; +import java.util.Deque; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.function.Supplier; + +public class Timer { + Map durations = new HashMap<>(); + Deque> started = new LinkedList<>(); + public void reportDuration(String id, Runnable r) { + Instant before = Instant.now(); + r.run(); + Duration d = Duration.between(before, Instant.now()); + System.err.println( + id + " current:" + d.toMillis() + " total:" + durations.compute(id, (_, prev) -> prev == null ? d : prev.plus(d)).toMillis()); + } + public T reportDuration(String id, Supplier r) { + Instant before = Instant.now(); + T res = r.get(); + Duration d = Duration.between(before, Instant.now()); + System.err.println( + id + " current:" + d.toMillis() + " total:" + durations.compute(id, (_, prev) -> prev == null ? d : prev.plus(d)).toMillis()); + return res; + } + public void start(String id) { + started.push(new SimpleEntry<>(id, Instant.now())); + } + public void stopCurrent() { + if (!started.isEmpty()) { + Entry entry = started.pop(); + String current = entry.getKey(); + Duration d = Duration.between(entry.getValue(), Instant.now()); + System.err.println( + current + " current:" + d.toMillis() + " total:" + durations.compute(current, (_, prev) -> prev == null ? d : prev.plus(d)).toMillis()); + } + } + public void stopLast(String id) { + Entry e = this.started.reversed().stream().filter(entry -> Objects.equals(id, entry.getKey())).findFirst().orElse(null); + if (e != null) { + started.remove(e); + String current = e.getKey(); + Duration d = Duration.between(e.getValue(), Instant.now()); + System.err.println( + current + " current:" + d.toMillis() + " total:" + durations.compute(current, (_, prev) -> prev == null ? d : prev.plus(d)).toMillis()); + + } + } +} \ No newline at end of file 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..e125b7f9995 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionContext.java @@ -0,0 +1,552 @@ +/******************************************************************************* + * 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.List; +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.ASTVisitor; +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.Comment; +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.MarkerAnnotation; +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 CompilationUnit domUnit; + private final ITypeRoot modelUnit; + private final int offset; + private final char[] token; + private final String tokenStr; + + private IJavaElement enclosingElement; + private boolean enclosingElementComputed; + private final Supplier> bindingsAcquirer; + final ExpectedTypes expectedTypes; + private boolean inJavadoc = false; + final ASTNode node; + private String textContent; + private boolean isJustAfterStringLiteral; + private transient Optional currentTypeBinding = null; + + DOMCompletionContext(CompilationUnit domUnit, ITypeRoot modelUnit, String textContent, int offset, AssistOptions assistOptions, Bindings bindings) { + this.domUnit = domUnit; + this.modelUnit = modelUnit; + this.textContent = textContent; + this.offset = offset; + int adjustedOffset = this.offset; + boolean isGenerated = DOMCompletionContext.isGenerated(domUnit); + if (!isGenerated) { + if (adjustedOffset > 0 && Character.isJavaIdentifierPart(textContent.charAt(adjustedOffset - 1))) { + // workaround for cases where right node is empty and reported (wrongly) as starting at same offset + adjustedOffset--; + } + ASTNode currentNode = NodeFinder.perform(domUnit, adjustedOffset, 0); + // Use the raw text to walk back the offset to the first non-whitespace spot + adjustedOffset = this.offset; + if (adjustedOffset >= textContent.length()) { + adjustedOffset = textContent.length() - 1; + } + if (adjustedOffset > 0 && Character.isJavaIdentifierPart(textContent.charAt(adjustedOffset - 1))) { + // workaround for cases where right node is empty and reported (wrongly) as starting at same offset + adjustedOffset--; + } + 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); + adjustedOffset = this.offset; + if (adjustedOffset < textContent.length() - 1 && Character.isWhitespace(textContent.charAt(adjustedOffset)) ) { + adjustedOffset++; + } + ASTNode nextNodeAfterWhitespaces = NodeFinder.perform(domUnit, adjustedOffset, 0); + ASTNode commentNode = null; + // there may be unparented comments that we need to search separately + for (Comment comment : (List)domUnit.getCommentList()) { + // if it's parented, don't bother; if the cursor's on a boundary, don't pick the comment + if (comment.getParent() == null && comment.getStartPosition() < this.offset && this.offset < comment.getStartPosition() + comment.getLength()) { + ASTNode candidateNode = NodeFinder.perform(comment, this.offset, 0); + if (candidateNode != null) { + commentNode = candidateNode; + break; + } + } + } + this.node = commentNode != null ? commentNode : (nextNodeAfterWhitespaces.getLength() == 0 && nextNodeAfterWhitespaces.getParent() == currentNode) + ? nextNodeAfterWhitespaces + : (previousNodeBeforeWhitespaces instanceof SimpleName || previousNodeBeforeWhitespaces instanceof StringLiteral || previousNodeBeforeWhitespaces instanceof CharacterLiteral || previousNodeBeforeWhitespaces instanceof NumberLiteral) + ? currentNode + : previousNodeBeforeWhitespaces; + } else { + if (adjustedOffset + 1 <= textContent.length() + || !Character.isJavaIdentifierStart(textContent.charAt(adjustedOffset))) { + while (adjustedOffset > 0 && Character.isJavaIdentifierStart(textContent.charAt(adjustedOffset - 1))) { + adjustedOffset--; + } + } + int length = 0; + while (adjustedOffset + length <= textContent.length() + && Character.isJavaIdentifierStart(textContent.charAt(adjustedOffset + length))) { + length++; + } + this.node = NodeFinder.perform(domUnit, adjustedOffset, length); + } + this.expectedTypes = new ExpectedTypes(assistOptions, this.node, offset); + this.inJavadoc = DOMCompletionUtils.findParent(this.node, new int[] { ASTNode.JAVADOC }) != null; + this.tokenStr = tokenBefore(this.textContent); + this.token = this.tokenStr.toCharArray(); + 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)) || (this.inJavadoc && 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 AnonymousClassDeclaration anonymousClassDeclaration) { + return anonymousClassDeclaration.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; + } + + public String getTokenString() { + return this.isJustAfterStringLiteral ? null : this.tokenStr; + } + + @Override + public boolean isInJavadoc() { + return this.inJavadoc; + } + + @Override + public IJavaElement getEnclosingElement() { + if (!this.enclosingElementComputed) { + this.enclosingElement = computeEnclosingElement(domUnit, modelUnit); + this.enclosingElementComputed = true; + } + 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(binding -> binding.isTypeVariable() ? + 'T' + binding.getQualifiedName() + ';' : binding.isLocal() ? Signature.createTypeSignature(binding.getName(), true) : + Signature.createTypeSignature(binding.getQualifiedName(), 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.getLength() == 0) { // recovered + return this.offset - 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; + } + + /** + * Returns the binding of the current parent type, or null if there is no parent type + * + * Lazy loaded. + * + * @return the binding of the current parent type, or null if there is no parent type + */ + public ITypeBinding getCurrentTypeBinding() { + if (currentTypeBinding == null) { + ASTNode parentType = DOMCompletionUtils.findParent(node, new int[] { + ASTNode.TYPE_DECLARATION, ASTNode.ENUM_DECLARATION, ASTNode.RECORD_DECLARATION, + ASTNode.ANNOTATION_TYPE_DECLARATION, ASTNode.ANONYMOUS_CLASS_DECLARATION + }); + if (parentType instanceof AbstractTypeDeclaration abstractTypeDecl) { + currentTypeBinding = Optional.of(abstractTypeDecl.resolveBinding()); + } else if (parentType instanceof AnonymousClassDeclaration anonTypeDecl) { + currentTypeBinding = Optional.of(anonTypeDecl.resolveBinding()); + } else { + currentTypeBinding = Optional.empty(); + } + } + return currentTypeBinding.orElse(null); + } + + + + 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_SHORT: + return sig2.equals(Signature.SIG_LONG) + || sig2.equals(Signature.SIG_INT) + || sig2.equals(Signature.SIG_SHORT) + || 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; + } + + /// Checks if the node is generated + /// @param node the AST node + /// @return `true` if the node is generated. + private static boolean isGenerated(ASTNode node) { + if (node != null) { + boolean[] isGenerated = {false}; + node.accept(new ASTVisitor() { + + @Override + public void endVisit(MarkerAnnotation markerAnnotation) { + if (!isGenerated[0]) { + // check lombok only for now + isGenerated[0] = "lombok.Generated".equals(markerAnnotation.getTypeName().getFullyQualifiedName()); //$NON-NLS-1$ + super.endVisit(markerAnnotation); + } + } + + }); + return isGenerated[0]; + } + 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..0041732e542 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionEngine.java @@ -0,0 +1,6176 @@ +/******************************************************************************* + * 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.LinkedHashSet; +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.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.lang.model.SourceVersion; + +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.IAccessRule; +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.IMember; +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.ASTVisitor; +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.AnonymousClassDeclaration; +import org.eclipse.jdt.core.dom.ArrayInitializer; +import org.eclipse.jdt.core.dom.Assignment; +import org.eclipse.jdt.core.dom.Block; +import org.eclipse.jdt.core.dom.BodyDeclaration; +import org.eclipse.jdt.core.dom.BreakStatement; +import org.eclipse.jdt.core.dom.CatchClause; +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.ContinueStatement; +import org.eclipse.jdt.core.dom.DoStatement; +import org.eclipse.jdt.core.dom.EnhancedForStatement; +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.InfixExpression.Operator; +import org.eclipse.jdt.core.dom.Initializer; +import org.eclipse.jdt.core.dom.Javadoc; +import org.eclipse.jdt.core.dom.LabeledStatement; +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.MethodRefParameter; +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.NodeFinder; +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.ParameterizedType; +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.ReturnStatement; +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.SuperMethodReference; +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.ThisExpression; +import org.eclipse.jdt.core.dom.TryStatement; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.core.dom.TypeDeclarationStatement; +import org.eclipse.jdt.core.dom.TypeMethodReference; +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.dom.WhileStatement; +import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants; +import org.eclipse.jdt.core.search.IJavaSearchConstants; +import org.eclipse.jdt.core.search.IJavaSearchScope; +import org.eclipse.jdt.core.search.SearchEngine; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.jdt.core.search.TypeNameMatch; +import org.eclipse.jdt.core.search.TypeNameMatchRequestor; +import org.eclipse.jdt.internal.SignatureUtils; +import org.eclipse.jdt.internal.codeassist.DOMCompletionUtils.TrueFalseBindings; +import org.eclipse.jdt.internal.codeassist.DOMCompletionUtils.TrueFalseCasts; +import org.eclipse.jdt.internal.codeassist.impl.AssistOptions; +import org.eclipse.jdt.internal.codeassist.impl.Keywords; +import org.eclipse.jdt.internal.codeassist.impl.RestrictedIdentifiers; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.eclipse.jdt.internal.compiler.parser.RecoveryScanner; +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; + +/** + * 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 static final String STATIC = "static"; + + 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 ITypeBinding qualifyingType; + private ASTNode toComplete; + private String textContent; + private ExtendsOrImplementsInfo extendsOrImplementsInfo; + private DOMCompletionContext completionContext; + + private Map typeHierarchyCache = new HashMap<>(); + private boolean suggestDefaultCompletions = true; + + class Bindings { + // those need to be list since the order matters + // fields must be before methods + private List others = new ArrayList<>(); + + private Set shadowed = new HashSet<>(); + private boolean alreadyScrapedAccessibleBindings = false; + private boolean requestAccessibleBindings = false; + + public void requestAccessibleBindings() { + this.requestAccessibleBindings = true; + } + + 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)); + } else if (binding instanceof IVariableBinding variableBinding) { + String nameToCheckShadowing = variableBinding.getName(); + List overlapping = this.others.stream().filter(IVariableBinding.class::isInstance).map(IVariableBinding.class::cast) + .filter(varBinding -> nameToCheckShadowing.equals(varBinding.getName())).toList(); + if (!overlapping.isEmpty()) { + shadowed.addAll(overlapping); + shadowed.add(variableBinding); + } + } + if (binding != null) { + this.others.add(binding); + } + } + public void addAll(Collection bindings) { + bindings.forEach(this::add); + } + public Stream all() { + if (!alreadyScrapedAccessibleBindings && requestAccessibleBindings) { + scrapeAccessibleBindings(); + } + return this.others.stream().distinct(); + } + public Stream methods() { + return all().filter(IMethodBinding.class::isInstance).map(IMethodBinding.class::cast); + } + public Stream variables() { + return all().filter(IVariableBinding.class::isInstance).map(IVariableBinding.class::cast); + } + public boolean isShadowed(IBinding binding) { + return this.shadowed.contains(binding); + } + public Stream toProposals() { + return all() // + .filter(binding -> pattern.matchesName(prefix.toCharArray(), binding.getName().toCharArray())) // + .filter(binding -> { + if (binding instanceof IVariableBinding varBinding) { + if (DOMCompletionEngine.this.requestor.isIgnored(CompletionProposal.LOCAL_VARIABLE_REF) + && DOMCompletionEngine.this.requestor.isIgnored(CompletionProposal.FIELD_REF)) { + return false; + } + if (isShadowed(binding) && varBinding.isField() && varBinding.getDeclaringClass().isAnonymous()) { + return false; + } + } + return true; + }) + .filter(binding -> { + if (binding instanceof ITypeBinding typeBinding) { + if (DOMCompletionEngine.this.requestor.isIgnored(CompletionProposal.TYPE_REF)) { + return false; + } + return filterBasedOnExtendsOrImplementsInfo((IType)typeBinding.getJavaElement(), extendsOrImplementsInfo); + } else if (binding instanceof IMethodBinding) { + if (DOMCompletionEngine.this.requestor.isIgnored(CompletionProposal.METHOD_REF)) { + return false; + } + } + return true; + }) + .filter(binding -> !assistOptions.checkDeprecation || !isDeprecated(binding.getJavaElement())) + .map(binding -> { + if (binding instanceof IVariableBinding varBinding && isShadowed(binding) && varBinding.isField() && (varBinding.getModifiers() & Flags.AccStatic) == 0) { + StringBuilder completion = new StringBuilder(); + completion.append(varBinding.getDeclaringClass().getName()); + completion.append(".this."); + completion.append(varBinding.getName()); + return toProposal(binding, completion.toString()); + } + return toProposal(binding); + }); + } + + /** + * Like toProposals() except it only generates proposals for bindings that match the expected type(s). + */ + public Stream toExpectedProposals() { + return all() // + .filter(binding -> pattern.matchesName(prefix.toCharArray(), binding.getName().toCharArray())) // + .filter(binding -> { + ITypeBinding valueBinding = null; + if (binding instanceof IMethodBinding methodBinding) { + valueBinding = methodBinding.getReturnType(); + } else if (binding instanceof IVariableBinding variableBinding) { + valueBinding = variableBinding.getType(); + } + if (valueBinding == null) { + return false; + } + return RelevanceUtils.computeRelevanceForExpectingType(valueBinding, DOMCompletionEngine.this.expectedTypes) > 0; + }) // + .filter(binding -> { + if (binding instanceof IVariableBinding varBinding) { + if (isShadowed(binding) && varBinding.isField() && varBinding.getDeclaringClass().isAnonymous()) { + return false; + } + } + return true; + }).filter(binding -> { + if (binding instanceof ITypeBinding typeBinding) { + return filterBasedOnExtendsOrImplementsInfo((IType)typeBinding.getJavaElement(), extendsOrImplementsInfo); + } + return true; + }) + .filter(binding -> !assistOptions.checkDeprecation || !isDeprecated(binding.getJavaElement())) + .map(binding -> { + if (binding instanceof IVariableBinding varBinding && isShadowed(binding) && varBinding.isField() && (varBinding.getModifiers() & Flags.AccStatic) == 0) { + StringBuilder completion = new StringBuilder(); + completion.append(varBinding.getDeclaringClass().getName()); + completion.append(".this."); + completion.append(varBinding.getName()); + return toProposal(binding, completion.toString()); + } + return toProposal(binding); + }); + } + + private void scrapeAccessibleBindings() { + if (alreadyScrapedAccessibleBindings) { + return; + } + alreadyScrapedAccessibleBindings = true; // set it early to prevent from infinite recursion + ASTNode current = toComplete; + while (current != null) { + Collection gottenVisibleBindings = visibleBindings(current); + addAll(gottenVisibleBindings); + if (current instanceof AbstractTypeDeclaration typeDecl) { + processMembers(toComplete, typeDecl.resolveBinding(), this, false); + } else if (current instanceof AnonymousClassDeclaration anonymousClass) { + processMembers(toComplete, anonymousClass.resolveBinding(), this, false); + } else if (current instanceof CompilationUnit cu) { + for (AbstractTypeDeclaration typeDecl : (List)cu.types()) { + // STATIC members only + processMembers(toComplete, typeDecl.resolveBinding(), this, true); + } + } + current = current.getParent(); + } + + // handle favourite members + if (requestor.getFavoriteReferences() == null) { + return; + } + + Set scopedMethods = methods().map(IBinding::getName).collect(Collectors.toSet()); + Set scopedVariables = variables().map(IBinding::getName).collect(Collectors.toSet()); + Set scopedTypes = all().filter(ITypeBinding.class::isInstance).map(IBinding::getName).collect(Collectors.toSet()); + + Set keysToResolve = new HashSet<>(); + IJavaProject project = javaProject; + for (String favouriteReference: 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, IJavaSearchConstants.TYPE, packageName) // + .map(TypeNameMatch::getType) + .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, IJavaSearchConstants.TYPE, packageName) // + .map(TypeNameMatch::getType) + .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), monitor); + for (IBinding binding : bindings) { + if (binding instanceof ITypeBinding typeBinding) { + processMembers(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(this::add); + } + + } + + 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()); + visibleBindings.addAll(((List)m.thrownExceptionTypes()).stream().map(Type::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) { + for (Statement statement : ((List)block.statements())) { + if (statement.getStartPosition() + statement.getLength() >= this.offset) { + break; + } + if (statement instanceof IfStatement ifStatement && ifStatement.getElseStatement() == null) { + visibleBindings.addAll(DOMCompletionUtils.collectTrueFalseBindings(ifStatement.getExpression()).falseBindings()); + } else if (statement instanceof ForStatement forStatement && forStatement.getExpression() != null) { + visibleBindings.addAll(DOMCompletionUtils.collectTrueFalseBindings(forStatement.getExpression()).falseBindings()); + } else if (statement instanceof VariableDeclarationStatement variableDeclarationStatement) { + for (var fragment : (List)variableDeclarationStatement.fragments()) { + if (!FAKE_IDENTIFIER.equals(fragment.getName().toString())) { + visibleBindings.add(fragment.resolveBinding()); + } + } + } else if (statement instanceof TypeDeclarationStatement tds) { + visibleBindings.add(tds.resolveBinding()); + } + } + } + + if (node.getParent() instanceof IfStatement ifStatement + && (node.getStartPosition() + node.getLength() > this.offset + || ifStatement.getThenStatement() == node || ifStatement.getElseStatement() == node)) { + TrueFalseBindings trueFalseBindings = DOMCompletionUtils.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 + || forStatement.getBody() == node)) { + if (forStatement.getExpression() != null) { + TrueFalseBindings trueFalseBindings = DOMCompletionUtils.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.getParent() instanceof EnhancedForStatement foreachStatement + && (node.getStartPosition() + node.getLength() > this.offset + || foreachStatement.getBody() == node)) { + visibleBindings.add(foreachStatement.getParameter().resolveBinding()); + } + + 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) { + DOMCompletionUtils.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) { + DOMCompletionUtils.visitChildren(switchCase, ASTNode.TYPE_PATTERN, (TypePattern e) -> { + visibleBindings.add(e.getPatternVariable().resolveBinding()); + }); + } + } + } + + return visibleBindings; + } + + 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, int adjustment, 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 { + defaultCompletionBindings = new Bindings(); + defaultCompletionBindings.requestAccessibleBindings(); // will be used by DOMCompletionContext.getVisibleElements(), necessary for method parameter value suggestion + this.completionContext = new DOMCompletionContext(this.unit, this.modelUnit, this.textContent, this.offset, this.assistOptions, defaultCompletionBindings); + this.nestedEngine.completionToken = completionContext.getToken(); + 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; + ASTNode potentialTagElement = DOMCompletionUtils.findParent(this.toComplete, new int[] { ASTNode.TAG_ELEMENT, ASTNode.MEMBER_REF, ASTNode.METHOD_REF }); + if (potentialTagElement != null + // if it's a text element that nested under a tag element with no tag, treat it as a text element and (later) perform type completion on the text content + && (!(potentialTagElement instanceof TagElement tagElement) || tagElement.getTagName() != 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 TypeMethodReference + || simpleName.getParent() instanceof SuperMethodReference + || simpleName.getParent() instanceof TagElement + || simpleName.getParent() instanceof BreakStatement + || simpleName.getParent() instanceof ContinueStatement + || simpleName.getParent() instanceof MarkerAnnotation + || simpleName.getParent() instanceof AnnotationTypeMemberDeclaration + || ((simpleName.getLocationInParent() == SwitchCase.EXPRESSIONS2_PROPERTY || simpleName.getLocationInParent() == SwitchCase.EXPRESSION_PROPERTY) && simpleName.getParent() instanceof SwitchCase)) { + if (!this.toComplete.getLocationInParent().getId().equals(QualifiedName.QUALIFIER_PROPERTY.getId())) { + context = this.toComplete.getParent(); + } + } else if (simpleName.getParent() instanceof ExpressionStatement && simpleName.getParent().getParent() instanceof SwitchStatement) { + // eg. + // switch (i) { + // cas| + // } + // but also: + // switch (i) { + // case 0: + // ass| + // } + context = simpleName.getParent().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) { + if (this.offset == block.getStartPosition() + || this.offset >= block.getStartPosition() + block.getLength()) { + 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) { + if (vd.getParent() instanceof CatchClause catchClause && catchClause.getException().getLength() != 0) { + context = context.getParent(); + } else if (vd.getInitializer() != null) { + context = vd.getInitializer(); + } + } else if (this.toComplete instanceof QualifiedName && this.toComplete.getParent() instanceof TagElement tagElement) { + context = tagElement; + } else if (this.toComplete instanceof Modifier && this.toComplete.getParent() instanceof ImportDeclaration) { + context = this.toComplete.getParent(); + } + this.prefix = token == null ? "" : new String(token); + if (this.toComplete instanceof MethodInvocation methodInvocation && this.offset == (methodInvocation.getName().getStartPosition() + methodInvocation.getName().getLength()) + 1) { + // myMethod(|) + this.prefix = methodInvocation.getName().toString(); + } + 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; + } + } else if (this.toComplete instanceof TextElement) { + int cursor = this.offset; + while (cursor > 0 && (Character.isJavaIdentifierPart(this.textContent.charAt(cursor - 1)) + || Character.isJavaIdentifierStart(this.textContent.charAt(cursor - 1)) + || this.textContent.charAt(cursor - 1) == '.')) { + cursor--; + } + this.qualifiedPrefix = this.textContent.substring(cursor, this.offset); + if (this.qualifiedPrefix.contains(".")) { + this.qualifiedPrefix = this.qualifiedPrefix.substring(0, this.qualifiedPrefix.lastIndexOf('.')); + } else { + this.qualifiedPrefix = this.prefix; + } + } else if (this.toComplete instanceof ThisExpression thisExpression) { + if (thisExpression.getQualifier() != null) { + this.qualifiedPrefix = thisExpression.getQualifier().toString(); + } + } else if (DOMCompletionUtils.findParent(this.toComplete, new int[] { ASTNode.CATCH_CLAUSE }) instanceof CatchClause catchClause + && catchClause.getException().getType().getStartPosition() <= this.offset + && this.offset <= catchClause.getException().getType().getStartPosition() + catchClause.getException().getType().getLength()) { + context = catchClause; + } + this.extendsOrImplementsInfo = isInExtendsOrImplements(context); + // some flags to controls different applicable completion search strategies + suggestDefaultCompletions = true; + + checkCancelled(); + + switch (context) { + case StringLiteral _, TextBlock _, Comment _, NumberLiteral _, AnnotationTypeMemberDeclaration _: + return; + case FieldAccess fieldAccess: + completeFieldAccess(fieldAccess); + break; + case MethodInvocation methodInvocation: + completeMethodInvocation(methodInvocation); + break; + case ModuleDeclaration moduleDeclaration: + completeModuleDeclaration(moduleDeclaration); + break; + case SimpleName simpleName: + completeSimpleName(simpleName); + break; + case AbstractTypeDeclaration typeDecl: + completeTypeDeclaration(typeDecl); + break; + case AnonymousClassDeclaration anonymousClassDeclaration: + completeAnonymousClassDeclaration(anonymousClassDeclaration); + break; + case QualifiedName qualifiedName: + completeQualifiedName(qualifiedName); + break; + case SuperFieldAccess superFieldAccess: + completeSuperFieldAccess(superFieldAccess); + break; + case MarkerAnnotation _: + completeMarkerAnnotation(); + break; + case SingleMemberAnnotation singleMemberAnnotation: + completeSingleMemberAnnotation(singleMemberAnnotation); + break; + case NormalAnnotation normalAnnotation: + completeNormalAnnotation(normalAnnotation); + break; + case MethodDeclaration methodDeclaration: + completeMethodDeclaration(methodDeclaration); + break; + case ClassInstanceCreation classInstanceCreation: + completeClassInstanceCreation(classInstanceCreation); + break; + case ExpressionMethodReference expressionMethodReference: + completeExpressionMethodReference(expressionMethodReference); + break; + case TypeMethodReference typeMethodReference: + completeTypeMethodReference(typeMethodReference); + break; + case SuperMethodReference superMethodReference: + completeSuperMethodReference(superMethodReference); + break; + case TextElement textElement: + completeTextElement(textElement); + break; + case TagElement tagElement: + completeTagElement(tagElement); + break; + case ImportDeclaration importDeclaration: + completeImportDeclaration(importDeclaration); + break; + case CompilationUnit _: + // TODO: we can grab it this.unit in completeCompilationUnit. + // should I do this? It will make it inconsistent with the other ones + completeCompilationUnit(this.unit); + break; + case MethodRef methodRef: + completeMethodRef(methodRef); + break; + case MemberRef memberRef: + completeMemberRef(memberRef); + break; + case ThisExpression thisExpression: + completeThisExpression(thisExpression); + break; + case VariableDeclarationFragment variableDeclarationFragment: + completeVariableDeclarationFragment(variableDeclarationFragment); + break; + case SingleVariableDeclaration singleVariableDeclaration: + completeSingleVariableDeclaration(singleVariableDeclaration); + break; + case CatchClause catchClause: + completeCatchClause(catchClause); + break; + case TryStatement tryStatement: + completeTryStatement(tryStatement); + break; + case SwitchStatement switchStatement: + completeSwitchStatement(switchStatement); + break; + case SwitchExpression switchExpression: + completeSwitchExpression(switchExpression); + break; + case ArrayInitializer _: + completeArrayInitializer(); + break; + case QualifiedType qualifiedType: + completeQualifiedType(qualifiedType); + break; + case BreakStatement breakStatement: + completeBreakStatement(breakStatement); + break; + case ContinueStatement continueStatement: + completeContinueStatement(continueStatement); + break; + case SwitchCase _: + completeSwitchCase(); + break; + default: + // Fall back to default completion strategy (accessible bindings + type search) + } + 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)); + } + } + } + } + { + // If the completion is after a try block without a catch or finally, + // we need to disable default completion. + ASTNode cursor = this.toComplete; + ASTNode parentCursor = this.toComplete.getParent(); + while (parentCursor != null && !(parentCursor instanceof Block)) { + cursor = parentCursor; + parentCursor = parentCursor.getParent(); + } + if (parentCursor instanceof Block parentBlock) { + int statementIndex = parentBlock.statements().indexOf(cursor); + if (statementIndex > 0) { + Statement prevStatement = ((List)parentBlock.statements()).get(statementIndex - 1); + if (prevStatement instanceof TryStatement tryStatement) { + if (!isFailedMatch(this.prefix.toCharArray(), Keywords.CATCH)) { + this.requestor.accept(createKeywordProposal(Keywords.CATCH, -1, -1)); + } + if (tryStatement.getFinally() == null) { + if (!isFailedMatch(this.prefix.toCharArray(), Keywords.FINALLY)) { + this.requestor.accept(createKeywordProposal(Keywords.FINALLY, -1, -1)); + } + } + if (tryStatement.catchClauses().isEmpty() && tryStatement.getFinally() == null) { + suggestDefaultCompletions = false; + } + } + } + } + } + if (isParameterInNonParameterizedType(context)) { + suggestDefaultCompletions = false; + } + + if (shouldSuggestPackages(toComplete)) { + suggestPackages(toComplete); + } + if ((context instanceof SimpleName simple || context instanceof MethodInvocation) && !(context.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) { + statementLikeKeywords(); + if (this.expectedTypes.getExpectedTypes().size() == 1 && toComplete instanceof SimpleName) { + ITypeBinding expected = this.expectedTypes.getExpectedTypes().getFirst(); + if (expected.isEnum()) { + Arrays.stream(expected.getDeclaredFields()) + .filter(field -> field.getName().startsWith(this.prefix)) + .map(this::toProposal) + .forEach(this.requestor::accept); + } + } + if (!this.prefix.isEmpty() && this.extendsOrImplementsInfo == null) { + suggestTypeKeywords(DOMCompletionUtils.findParent(this.toComplete, new int[] { ASTNode.BLOCK }) == null); + } + publishFromScope(defaultCompletionBindings); + suggestSuperConstructors(); + if (!completeAfter.isBlank()) { + final int typeMatchRule = this.toComplete.getParent() instanceof Annotation + ? IJavaSearchConstants.ANNOTATION_TYPE + : IJavaSearchConstants.TYPE; + if (!this.requestor.isIgnored(CompletionProposal.TYPE_REF)) { + final Set alreadySuggestedFqn = ConcurrentHashMap.newKeySet(); + findTypes(completeAfter, -1, typeMatchRule, null) + .filter(typeMatch -> this.pattern.matchesName(this.prefix.toCharArray(), typeMatch.getType().getElementName().toCharArray())) + .filter(typeMatch -> { + for (var scrapedBinding : defaultCompletionBindings.all().toList()) { + if (scrapedBinding instanceof ITypeBinding scrapedTypeBinding) { + if (typeMatch.getType().equals(scrapedTypeBinding.getJavaElement()) || typeMatch.getType().getKey().equals(scrapedTypeBinding.getKey())) { + return false; + } + } + } + return true; + }).filter(typeMatch -> filterBasedOnExtendsOrImplementsInfo(typeMatch.getType(), this.extendsOrImplementsInfo)) + .filter(typeMatch -> { + if (alreadySuggestedFqn.contains(typeMatch.getType().getFullyQualifiedName())) { + return false; + } + alreadySuggestedFqn.add(typeMatch.getType().getFullyQualifiedName()); + return true; + }).map(this::toProposal) + .forEach(this.requestor::accept); + } + } + checkCancelled(); + if (shouldSuggestPackages(toComplete)) { + suggestPackages(toComplete); + } + } + + checkCancelled(); + } catch (JavaModelException e) { + ILog.get().error(e.getMessage(), e); + } finally { + this.requestor.endReporting(); + if (this.monitor != null) { + this.monitor.done(); + } + } + } + + private void completeSwitchCase() { + // find the enum if there is one + ITypeBinding firstEnumType = null; + for (ITypeBinding expectedType : completionContext.expectedTypes.getExpectedTypes()) { + if (expectedType.isEnum()) { + firstEnumType = expectedType; + break; + } + } + if (firstEnumType != null) { + Stream.of(firstEnumType.getDeclaredFields()) + .filter(IVariableBinding::isEnumConstant) + .filter(constant -> this.pattern.matchesName(this.prefix.toCharArray(), constant.getName().toCharArray())) + .map(this::toProposal) + .forEach(this.requestor::accept); + } else { + // if there is no expected enum type, use the default completion. + // perhaps there is a suitable int or String constant + Bindings caseBindings = new Bindings(); + caseBindings.scrapeAccessibleBindings(); + caseBindings.all() + .filter(IVariableBinding.class::isInstance) + .map(IVariableBinding.class::cast) + .filter(varBinding -> { + return ((varBinding.getModifiers() & Flags.AccFinal) != 0); + }) + .filter(varBinding -> this.pattern.matchesName(this.prefix.toCharArray(), varBinding.getName().toCharArray())) + .filter(varBinding -> { + for (ITypeBinding expectedType : completionContext.expectedTypes.getExpectedTypes()) { + if (varBinding.getType().getKey().equals(expectedType.getKey())) { + return true; + } + } + return false; + }) + .map(this::toProposal) + .forEach(proposal -> { + // Seems like the `R_FINAL` constant is only added when completing switch statements: + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=195346 + proposal.setRelevance(proposal.getRelevance() + RelevanceConstants.R_FINAL); + this.requestor.accept(proposal); + }); + } + suggestDefaultCompletions = false; + } + + private void completeContinueStatement(ContinueStatement continueStatement) { + completeLabelProposals(continueStatement); + suggestDefaultCompletions = false; + } + + private void completeLabelProposals(ASTNode startNode) { + ASTNode cursor = startNode; + Set labels = new HashSet<>(); + while (cursor != null) { + if (cursor instanceof LabeledStatement labeledStatement) { + labels.add(labeledStatement.getLabel().toString()); + } + cursor = cursor.getParent(); + } + labels.stream() // + .filter(label -> !this.isFailedMatch(completionContext.getToken(), label.toCharArray())) // + .map(this::toLabelProposal) // + .forEach(this.requestor::accept); + } + + private void completeBreakStatement(BreakStatement breakStatement) { + completeLabelProposals(breakStatement); + suggestDefaultCompletions = false; + } + + private void completeQualifiedType(QualifiedType qualifiedType) { + Type qualifier = qualifiedType.getQualifier(); + if (qualifier != null) { + ITypeBinding qualifierBinding = qualifier.resolveBinding(); + if (qualifierBinding != null) { + for (ITypeBinding nestedType : qualifierBinding.getDeclaredTypes()) { + this.requestor.accept(toProposal(nestedType)); + } + suggestDefaultCompletions = false; + } + } + } + + private void completeArrayInitializer() { + publishFromScope(defaultCompletionBindings); + suggestDefaultCompletions = false; + } + + private void completeSwitchExpression(SwitchExpression switchExpression) { + completeSwitchStatementOrExpression(switchExpression.statements()); + } + + private void completeSwitchStatement(SwitchStatement switchStatement) { + completeSwitchStatementOrExpression(switchStatement.statements()); + } + + private void completeSwitchStatementOrExpression(List statements) { + boolean hasDefault = false; + boolean afterLabel = false; + + for (Statement statement : statements) { + if (statement instanceof SwitchCase switchCase) { + if (switchCase.isDefault()) { + hasDefault = true; + } + if (switchCase.getStartPosition() < this.offset) { + afterLabel = true; + } + } + } + if (!this.isFailedMatch(this.prefix.toCharArray(), Keywords.CASE)) { + this.requestor.accept(createKeywordProposal(Keywords.CASE, -1, -1)); + } + if (!hasDefault) { + if (!this.isFailedMatch(this.prefix.toCharArray(), Keywords.DEFAULT)) { + this.requestor.accept(createKeywordProposal(Keywords.DEFAULT, -1, -1)); + } + } + if (!afterLabel) { + // case or default label required before regular body statements + suggestDefaultCompletions = false; + } + } + + private void completeTryStatement(TryStatement tryStatement) { + if (tryStatement.getBody().getStartPosition() + tryStatement.getBody().getLength() < this.offset) { + if (tryStatement.getFinally() == null) { + if (!isFailedMatch(this.prefix.toCharArray(), Keywords.FINALLY)) { + this.requestor.accept(createKeywordProposal(Keywords.FINALLY, -1, -1)); + } + } + if (!isFailedMatch(this.prefix.toCharArray(), Keywords.CATCH)) { + this.requestor.accept(createKeywordProposal(Keywords.CATCH, -1, -1)); + } + if (tryStatement.getFinally() == null && tryStatement.catchClauses().isEmpty()) { + suggestDefaultCompletions = false; + } + } + } + + private void completeCatchClause(CatchClause catchClause) { + if (catchClause.getException().getLength() != 0 && catchClause == this.toComplete && catchClause.getException().getName().toString().equals(FAKE_IDENTIFIER)) { + ITypeBinding exceptionType = catchClause.getException().getType().resolveBinding(); + Set alreadySuggestedNames = new HashSet<>(); + if (!catchClause.getBody().statements().isEmpty()) { + suggestUndeclaredVariableNames(catchClause.getBody(), exceptionType, alreadySuggestedNames); + } + suggestVariableNamesForType(exceptionType, alreadySuggestedNames); + suggestDefaultCompletions = false; + } else { + DOMThrownExceptionFinder thrownExceptionFinder = new DOMThrownExceptionFinder(); + Bindings catchExceptionBindings = new Bindings(); + Bindings contextBindings = new Bindings(); + contextBindings.scrapeAccessibleBindings(); + thrownExceptionFinder.processThrownExceptions((TryStatement) catchClause.getParent()); + for (ITypeBinding thrownUncaughtException : thrownExceptionFinder.getThrownUncaughtExceptions()) { + ITypeBinding cursor = thrownUncaughtException; + // jdt doesn't suggest Throwable itself for some reason + while (cursor != null && !"Ljava/lang/Throwable;".equals(cursor.getKey())) { + catchExceptionBindings.add(cursor); + cursor = cursor.getSuperclass(); + } + } + // consolidate context bindings into catchExceptionBindings + contextBindings: for (IBinding contextBinding : contextBindings.all().toList()) { + if (contextBinding instanceof ITypeBinding contextTypeBinding) { + for (ITypeBinding caughtException : thrownExceptionFinder.getAlreadyCaughtExceptions()) { + if (contextTypeBinding.getKey().equals(caughtException.getKey())) { + continue contextBindings; + } + } + if (DOMCompletionUtils.findInSupers(contextTypeBinding, "Ljava/lang/Throwable;")) { + catchExceptionBindings.add(contextTypeBinding); + } + } + } + publishFromScope(catchExceptionBindings); + if (!completionContext.getTokenString().isBlank()) { + Set alreadySuggestedFqn = ConcurrentHashMap.newKeySet(); + findTypes(completionContext.getTokenString(), null) + .filter(typeMatch -> this.pattern.matchesName(this.prefix.toCharArray(), + typeMatch.getType().getElementName().toCharArray())) + .filter(typeMatch -> { + for (var scrapedBinding : catchExceptionBindings.all().toList()) { + if (scrapedBinding instanceof ITypeBinding scrapedTypeBinding) { + if (typeMatch.getType().equals(scrapedTypeBinding.getJavaElement()) || typeMatch.getType().getKey().equals(scrapedTypeBinding.getKey())) { + return false; + } + } + } + return true; + }) + .filter(typeMatch -> { + for (ITypeBinding caughtException : thrownExceptionFinder.getAlreadyCaughtExceptions()) { + if (typeMatch.getType().getKey().equals(caughtException.getKey())) { + return false; + } + } + return true; + }) + .filter(typeMatch -> { + if (alreadySuggestedFqn.contains(typeMatch.getType().getFullyQualifiedName())) { + return false; + } + alreadySuggestedFqn.add(typeMatch.getType().getFullyQualifiedName()); + return true; + }) + .map(this::toProposal).forEach(this.requestor::accept); + } + suggestDefaultCompletions = false; + } + } + + private void completeSingleVariableDeclaration(SingleVariableDeclaration singleVariableDeclaration) { + ITypeBinding typeBinding = singleVariableDeclaration.getType().resolveBinding(); + Block block = null; + if (singleVariableDeclaration.getParent() instanceof CatchClause catchClause) { + block = catchClause.getBody(); + } else if (singleVariableDeclaration.getParent() instanceof MethodDeclaration methDecl) { + block = methDecl.getBody(); + } + Set alreadySuggestedNames = new HashSet<>(); + if (block != null) { + suggestUndeclaredVariableNames(block, typeBinding, alreadySuggestedNames); + } else { + suggestUndeclaredVariableNames(typeBinding, alreadySuggestedNames); + } + suggestVariableNamesForType(typeBinding, alreadySuggestedNames); + suggestDefaultCompletions = false; + } + + private void completeVariableDeclarationFragment(VariableDeclarationFragment variableDeclarationFragment) { + if (this.toComplete.equals(variableDeclarationFragment.getName()) + || this.toComplete.getLength() == 0 /* recovered */) { + ITypeBinding typeBinding = null; + if (variableDeclarationFragment.getParent() instanceof VariableDeclarationStatement vds) { + typeBinding = vds.getType().resolveBinding(); + } else if (variableDeclarationFragment.getParent() instanceof FieldDeclaration fieldDecl) { + typeBinding = fieldDecl.getType().resolveBinding(); + } + if (typeBinding != null) { + Set alreadySuggestedNames = new HashSet<>(); + suggestUndeclaredVariableNames(typeBinding, alreadySuggestedNames); + suggestVariableNamesForType(typeBinding, alreadySuggestedNames); + } + suggestDefaultCompletions = false; + } + } + + private void completeThisExpression(ThisExpression thisExpression) { + if (thisExpression.getQualifier() != null) { + IBinding binding = thisExpression.getQualifier().resolveBinding(); + if (binding.isRecovered() && this.prefix.isEmpty()) { + // TODO: maybe we should port this over to the binding resolution logic somehow? + // eg. + // this.value = myValue.| + // this.font = "Times New Roman"; + // we need to recover the binding ourselves + String nameToRecover = thisExpression.getQualifier().toString(); + Optional potentialBinding = defaultCompletionBindings.all() + .filter(IVariableBinding.class::isInstance) + .map(IVariableBinding.class::cast) + .filter(accessibleBinding -> accessibleBinding.getName().equals(nameToRecover)) + .findFirst(); + if (potentialBinding.isPresent()) { + Bindings namesMembers = new Bindings(); + processMembers(this.toComplete, potentialBinding.get().getType(), namesMembers, false); + publishFromScope(namesMembers); + } + } else { + if (binding instanceof ITypeBinding typeBinding) { + this.qualifyingType = typeBinding; + Bindings typesMembers = new Bindings(); + processMembers(this.toComplete, typeBinding, typesMembers, true); + publishFromScope(typesMembers); + this.requestor.accept(createClassKeywordProposal(typeBinding, -1,-1)); + } + for (char[] keyword : List.of(Keywords.SUPER, Keywords.THIS)) { + if (!isFailedMatch(this.prefix.toCharArray(), keyword)) { + CompletionProposal res = createKeywordProposal(keyword, -1, -1); + res.setRelevance(res.getRelevance() + RelevanceConstants.R_NON_INHERITED); + this.requestor.accept(res); + } + } + } + suggestDefaultCompletions = false; + } + } + + private void completeMemberRef(MemberRef memberRef) { + IBinding bindingToComplete; + if (memberRef.getQualifier() != null) { + bindingToComplete = memberRef.getQualifier().resolveBinding(); + } else { + bindingToComplete = completionContext.getCurrentTypeBinding(); + } + if (bindingToComplete instanceof ITypeBinding typeBinding) { + Bindings javadocScope = new Bindings(); + processMembers(this.toComplete, typeBinding, javadocScope, false); + publishFromScope(javadocScope); + suggestAccessibleConstructorsForType(typeBinding); + } + suggestDefaultCompletions = false; + } + + private void completeMethodRef(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) { + potentialMethodCompletions = Stream.of(completionContext.getCurrentTypeBinding().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 { + if (this.unit.getPackage() != null) { + packageName = this.unit.getPackage().getName().toString(); + } else { + packageName = ""; //$NON-NLS-1$ + } + } + List potentialTypes = findTypes(classToComplete, packageName).map(TypeNameMatch::getType).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; + } else if (state == JavadocMethodReferenceParseState.IN_IDENTIFIER) { + if (completionContext.getTokenString().isEmpty()) { + defaultCompletionBindings.all() + .filter(ITypeBinding.class::isInstance) + .map(this::toProposal) + .forEach(this.requestor::accept); + } else { + findTypes(completionContext.getTokenString(), null) + .filter(typeMatch -> this.pattern.matchesName(this.prefix.toCharArray(), typeMatch.getType().getElementName().toCharArray())) + .map(this::toProposal) + .forEach(this.requestor::accept); + } + suggestTypeKeywords(false); + suggestDefaultCompletions = false; + } + } + + private void completeCompilationUnit(CompilationUnit unit2) { + if (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 (CharOperation.prefixEquals(completionContext.getToken(), Keywords.IMPORT) + && (unit.getPackage() == null + || this.offset >= unit.getPackage().getStartPosition() + unit.getPackage().getLength()) + && (unit.types().isEmpty() || this.offset < ((ASTNode)unit.types().get(0)).getStartPosition())) { + if (unit.imports().isEmpty()) { + this.requestor.accept(createKeywordProposal(Keywords.IMPORT, -1, -1)); + } else { + for (int i = unit.imports().size() - 1; i >= 0; i--) { + ImportDeclaration importDeclaration = (ImportDeclaration)unit.imports().get(i); + if (this.offset > importDeclaration.getStartPosition() + importDeclaration.getLength()) { + if ((importDeclaration.getFlags() & ASTNode.MALFORMED) == 0) { + this.requestor.accept(createKeywordProposal(Keywords.IMPORT, -1, -1)); + } + break; + } + } + } + } + + // recover existing modifiers (they can't exist in the DOM since there is no TypeDeclaration) + boolean afterBrokenImport = false; + int startOffset = 0; + if (this.unit.getPackage() != null) { + startOffset = this.unit.getPackage().getStartPosition() + this.unit.getPackage().getLength(); + } + if (!this.unit.imports().isEmpty()) { + ImportDeclaration lastImport = (ImportDeclaration)this.unit.imports().get(this.unit.imports().size() - 1); + startOffset = lastImport.getStartPosition() + lastImport.getLength(); + + if (lastImport.getName().toString().endsWith(FAKE_IDENTIFIER)) { + afterBrokenImport = true; + } + } + + if (!afterBrokenImport && startOffset <= this.offset) { + int existingModifiers = 0; + String[] potentialModifiers = this.textContent.substring(startOffset).split("\\s+"); + for (String potentialModifier : potentialModifiers) { + ModifierKeyword potentialModifierKeyword = Modifier.ModifierKeyword.toKeyword(potentialModifier); + if (potentialModifierKeyword != null) { + existingModifiers |= potentialModifierKeyword.toFlagValue(); + } + } + + suggestModifierKeywords(existingModifiers); + suggestClassDeclarationLikeKeywords(); + } + suggestDefaultCompletions = false; + } + + private void completeImportDeclaration(ImportDeclaration importDeclaration) { + if (importDeclaration.getAST().apiLevel() >= AST.JLS23 + && this.javaProject.getOption(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, true).equals(JavaCore.ENABLED) + && Modifier.isModule(importDeclaration.getModifiers())) { + int startPos = importDeclaration.getName().getStartPosition(); + int endPos = importDeclaration.getName().getStartPosition() + importDeclaration.getName().getLength(); + findModules(this.qualifiedPrefix.toCharArray(), this.javaProject, this.assistOptions, Collections.emptySet(), startPos, endPos); + suggestDefaultCompletions = false; + } + } + + private void completeTagElement(TagElement tagElement) throws JavaModelException { + boolean isTagName = tagElement.getTagName() == null || tagElement.getTagName().isEmpty() || !this.prefix.isEmpty(); + 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()))) { + if ("package-info.java".equals(this.modelUnit.getElementName()) + || DOMCompletionUtils.findParent(toComplete, new int[] { ASTNode.PACKAGE_DECLARATION }) == null) { + completeJavadocBlockTags(tagElement); + completeJavadocInlineTags(tagElement); + } + suggestDefaultCompletions = false; + } else { + if (tagElement.getTagName() != null) { + Javadoc javadoc = (Javadoc)DOMCompletionUtils.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 + Bindings javadocScope = new Bindings(); + processMembers(this.toComplete, completionContext.getCurrentTypeBinding(), javadocScope, false); + publishFromScope(javadocScope); + suggestAccessibleConstructorsForType(completionContext.getCurrentTypeBinding()); + } else { + String packageName; + String moduleName = null; + if (classToComplete.contains("/")) { + moduleName = classToComplete.substring(0, classToComplete.indexOf("/")); + String moduleRemoved = classToComplete.substring(classToComplete.indexOf("/") + 1); + packageName = moduleRemoved.lastIndexOf('.') < 0 ? null : moduleRemoved.substring(0, moduleRemoved.lastIndexOf('.')); + } else { + packageName = classToComplete.lastIndexOf('.') < 0 ? null : classToComplete.substring(0, classToComplete.lastIndexOf('.')); + } + if (packageName != null) { + classToComplete = classToComplete.substring(classToComplete.lastIndexOf('.') + 1); + } else { + CompilationUnit cu = (CompilationUnit)DOMCompletionUtils.findParent(this.toComplete, new int[] { ASTNode.COMPILATION_UNIT }); + if (cu.getPackage() != null) { + packageName = cu.getPackage().getName().toString(); + } else { + packageName = ""; //$NON-NLS-1$ + } + } + String finalizedModuleName = moduleName; + List potentialTypes = findTypes(classToComplete, packageName) // + .filter(a -> finalizedModuleName == null || a.getPackageFragmentRoot().getModuleDescription().getElementName().equals(finalizedModuleName)) // + .map(TypeNameMatch::getType).toList(); + List sourceTypes = potentialTypes.stream().filter(type -> type instanceof SourceType).toList(); + String compliance = this.javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true); + if (!potentialTypes.isEmpty() // + && (moduleName == null || (!(compliance.contains(".")) && Integer.parseInt(compliance) > 14))) { + 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) { + String compliance = this.javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true); + if (!compliance.contains(".") && Integer.parseInt(compliance) > 14) { + String[] portions = paramPrefix.split("/"); + String moduleName = portions[0]; + String packagePrefix = portions.length > 1 ? portions[1] : ""; + IModuleDescription modDesc = this.javaProject.findModule(moduleName, workingCopyOwner); + boolean oldIgnorePackages = this.requestor.isIgnored(CompletionProposal.PACKAGE_REF); + try { + this.requestor.setIgnored(CompletionProposal.PACKAGE_REF, false); + suggestPackagesInModule((IPackageFragmentRoot)modDesc.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT), packagePrefix, startPos, endPos); + } finally { + this.requestor.setIgnored(CompletionProposal.PACKAGE_REF, oldIgnorePackages); + } + if (packagePrefix.contains(".")) { + String typeName = packagePrefix.substring(packagePrefix.lastIndexOf('.') + 1); + String packageName = packagePrefix.substring(0, packagePrefix.lastIndexOf('.')); + findTypes(typeName, packageName) + .filter(typeMatch -> this.pattern.matchesName(this.prefix.toCharArray(), typeMatch.getType().getElementName().toCharArray())) + .map(this::toProposal).forEach(this.requestor::accept); + } + } + suggestDefaultCompletions = false; + } else { + // local types are suggested first + String currentPackage = ""; //$NON-NLS-1$ + + if (this.unit.getPackage() != null) { + currentPackage = this.unit.getPackage().getName().toString(); + } + final String finalizedCurrentPackage = currentPackage; + + Bindings localTypeBindings = new Bindings(); + localTypeBindings.scrapeAccessibleBindings(); + 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); + + if (!this.prefix.isEmpty()) { + findTypes(completionContext.getTokenString(), completionContext.getTokenString().equals(this.qualifiedPrefix) ? null : this.qualifiedPrefix) + .filter(typeMatch -> { + return localTypeBindings.all().map(typeBinding -> typeBinding.getJavaElement()).noneMatch(elt -> typeMatch.getType().equals(elt)); + }) + .filter(typeMatch -> this.pattern.matchesName(this.prefix.toCharArray(), typeMatch.getType().getElementName().toCharArray())) + .map(this::toProposal).forEach(this.requestor::accept); + } + + // suggest modules if modules are supported + String compliance = this.javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true); + if (!compliance.contains(".") && Integer.parseInt(compliance) > 14) { + findModules(paramPrefix.toCharArray(), javaProject, assistOptions, Collections.emptySet(), startPos, endPos); + } + + suggestDefaultCompletions = false; + } + } + break; + + } + case TagElement.TAG_THROWS: { + // we only want types here + if (completionContext.getTokenString().isBlank()) { + defaultCompletionBindings.all() + .filter(binding -> binding instanceof ITypeBinding) + .map(this::toProposal) + .forEach(this.requestor::accept); + } else { + findTypes(completionContext.getTokenString(), null) + .filter(typeMatch -> this.pattern.matchesName(this.prefix.toCharArray(), typeMatch.getType().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; + } + } + } + + private void completeTextElement(TextElement textElement) { + int cursor = this.offset - 1; + boolean containsInvalidChars = false; + boolean startsWithAtSymbol = false; + while (cursor >= 0 && !Character.isWhitespace(this.textContent.charAt(cursor))) { + char currentChar = this.textContent.charAt(cursor); + if (!Character.isJavaIdentifierPart(currentChar) + && !Character.isJavaIdentifierStart(currentChar) + && currentChar != '.') { + if (this.textContent.charAt(cursor) == '@' + && (Character.isWhitespace(this.textContent.charAt(cursor - 1)) + || this.textContent.charAt(cursor - 1) == '{')) { + startsWithAtSymbol = true; + } else { + containsInvalidChars = true; + } + break; + } + cursor--; + } + if (startsWithAtSymbol) { + TagElement tagElement = (TagElement)textElement.getParent(); + completeJavadocInlineTags(tagElement); + } else if (!containsInvalidChars) { + // this is a copy of the "suggest types" logic from the `@see` TagElement completion, + // except we need to suggest the types and their "@link" forms + String currentPackage = ""; //$NON-NLS-1$ + CompilationUnit cuNode = (CompilationUnit) DOMCompletionUtils.findParent(textElement, new int[] { ASTNode.COMPILATION_UNIT }); + if (cuNode.getPackage() != null) { + currentPackage = cuNode.getPackage().getName().toString(); + } + final String finalizedCurrentPackage = currentPackage; + Bindings localTypeBindings = new Bindings(); + localTypeBindings.scrapeAccessibleBindings(); + localTypeBindings.all() // + .filter(binding -> binding instanceof ITypeBinding) // + .filter(type -> { + ITypeBinding typeBinding = (ITypeBinding)type; + if (((ITypeBinding)type).isMember()) { + StringBuilder classQualifiedName = new StringBuilder(); + ITypeBinding parentTypeCursor = typeBinding; + do { + parentTypeCursor = parentTypeCursor.getDeclaringClass(); + if (!classQualifiedName.isEmpty()) { + classQualifiedName.insert(0, "."); + } + classQualifiedName.insert(0, parentTypeCursor.getName()); + } while (parentTypeCursor.isMember()); + if (this.qualifiedPrefix.equals(classQualifiedName.toString())) { + return this.pattern.matchesName(this.prefix.toCharArray(), type.getName().toCharArray()); + } + } + return this.pattern.matchesName((this.qualifiedPrefix + "." + this.prefix).toCharArray(), type.getName().toCharArray()) || + (this.qualifiedPrefix.equals(this.prefix) || this.qualifiedPrefix.equals(finalizedCurrentPackage)) && this.pattern.matchesName(this.prefix.toCharArray(), type.getName().toCharArray()); + }) + .forEach(type -> { + DOMInternalCompletionProposal typeProposal = (DOMInternalCompletionProposal)this.toProposal(type); + this.requestor.accept(typeProposal); + this.requestor.accept(this.toLinkProposal(typeProposal)); + }); + + if (!this.prefix.isEmpty()) { + findTypes(completionContext.getTokenString(), completionContext.getTokenString().equals(this.qualifiedPrefix) ? null : this.qualifiedPrefix) + .filter(typeMatch -> { + return localTypeBindings.all().map(typeBinding -> typeBinding.getJavaElement()).noneMatch(elt -> typeMatch.getType().equals(elt)); + }) + .filter(typeMatch -> this.pattern.matchesName(this.prefix.toCharArray(), typeMatch.getType().getElementName().toCharArray())) + .forEach(type -> { + DOMInternalCompletionProposal typeProposal = (DOMInternalCompletionProposal)this.toProposal(type); + this.requestor.accept(typeProposal); + this.requestor.accept(this.toLinkProposal(typeProposal)); + }); + } + } + suggestDefaultCompletions = false; + } + + private void completeSuperMethodReference(SuperMethodReference superMethodReference) { + Bindings specificCompletionBindings = new Bindings(); + ITypeBinding typeBinding = completionContext.getCurrentTypeBinding() == null ? null : completionContext.getCurrentTypeBinding().getSuperclass(); + if (typeBinding != null && !this.requestor.isIgnored(CompletionProposal.METHOD_NAME_REFERENCE)) { + processMembers(superMethodReference, 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; + } + + private void completeTypeMethodReference(TypeMethodReference typeMethodReference) { + Bindings specificCompletionBindings = new Bindings(); + ITypeBinding typeBinding = typeMethodReference.getType().resolveBinding(); + if (typeBinding != null && !this.requestor.isIgnored(CompletionProposal.METHOD_NAME_REFERENCE)) { + processMembers(typeMethodReference, 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; + } + + private void completeExpressionMethodReference(ExpressionMethodReference expressionMethodReference) { + Bindings specificCompletionBindings = new Bindings(); + ITypeBinding typeBinding = expressionMethodReference.getExpression().resolveTypeBinding(); + if (typeBinding != null && !this.requestor.isIgnored(CompletionProposal.METHOD_NAME_REFERENCE)) { + processMembers(expressionMethodReference, 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; + } + + private void completeClassInstanceCreation(ClassInstanceCreation classInstanceCreation) { + ITypeBinding constructorTypeBinding = classInstanceCreation.resolveTypeBinding(); + ITypeBinding expectedConstructorTypeBinding = null; + boolean exactType = false; + // it could be a recovered binding: + // 1. If it's not actually an existing type + // 2. If it is an existing generic type but the type arguments aren't provided + // (diamond operator in a case where it can't be inferred) + // So, I use this mechanism to check if it's a "real" type + if (constructorTypeBinding != null && Stream.of(constructorTypeBinding.getDeclaredMethods()).anyMatch(IMethodBinding::isConstructor)) { + expectedConstructorTypeBinding = constructorTypeBinding; + exactType = true; + } else if (this.expectedTypes.getExpectedTypes() != null && !this.expectedTypes.getExpectedTypes().isEmpty() && !this.expectedTypes.getExpectedTypes().get(0).isRecovered()) { + expectedConstructorTypeBinding = this.expectedTypes.getExpectedTypes().get(0); + } + if (expectedConstructorTypeBinding != null) { + completeConstructor(expectedConstructorTypeBinding, classInstanceCreation, this.javaProject, exactType); + if (classInstanceCreation.getType().getStartPosition() + classInstanceCreation.getType().getLength() < this.offset) { + List expectedProposals = defaultCompletionBindings.toExpectedProposals().toList(); + expectedProposals.forEach(this.requestor::accept); + } + } else if (this.toComplete == classInstanceCreation) { + // completing empty args + } 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, packageName) + .filter(typeMatch -> { + try { + return !typeMatch.getType().isAnnotation(); + } catch (JavaModelException e) { + return true; + } + }) // + .flatMap(typeMatch -> { + if (this.prefix.isEmpty()) { + return Stream.of(toProposal(typeMatch.getType())); + } else { + return toConstructorProposals(typeMatch.getType(), this.toComplete, false).stream(); + } + }) // + .forEach(this.requestor::accept); + } + suggestDefaultCompletions = false; + } + + private void completeMethodDeclaration(MethodDeclaration methodDeclaration) { + Bindings specificCompletionBindings = new Bindings(); + int closingParenLocation = methodDeclaration.getName().getStartPosition() + methodDeclaration.getName().getLength(); + boolean afterComma = true; + while (closingParenLocation < this.textContent.length() && this.textContent.charAt(closingParenLocation) != ')') { + if (afterComma) { + if (!Character.isWhitespace(this.textContent.charAt(closingParenLocation))) { + afterComma = false; + } + } else { + // TODO: handle \u002C + if (this.textContent.charAt(closingParenLocation) == ',') { + afterComma = true; + } + } + closingParenLocation++; + } + 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()) && closingParenLocation < this.offset) { + completeThrowsClause(methodDeclaration, specificCompletionBindings); + suggestDefaultCompletions = false; + } else if (methodDeclaration.getName().getStartPosition() + methodDeclaration.getName().getLength() < this.offset && this.offset <= closingParenLocation && !afterComma) { + // void myMethod(Integer a, Boolean |) { ... + SingleVariableDeclaration svd = (SingleVariableDeclaration)methodDeclaration.parameters().get(methodDeclaration.parameters().size() - 1); + ITypeBinding typeBinding = svd.getType().resolveBinding(); + Block block = methodDeclaration.getBody(); + Set alreadySuggestedNames = new HashSet<>(); + if (block != null) { + suggestUndeclaredVariableNames(block, typeBinding, alreadySuggestedNames); + } else { + suggestUndeclaredVariableNames(typeBinding, alreadySuggestedNames); + } + suggestVariableNamesForType(typeBinding, alreadySuggestedNames); + suggestDefaultCompletions = false; + } else if (this.offset > methodDeclaration.getStartPosition() + methodDeclaration.getLength()) { + suggestModifierKeywords(0); + } + } + + private void completeNormalAnnotation(NormalAnnotation normalAnnotation) { + Bindings specificCompletionBindings = new Bindings(); + if (normalAnnotation.getTypeName().getStartPosition() + normalAnnotation.getTypeName().getLength() > this.offset) { + completeMarkerAnnotation(); + } else if (!this.requestor.isIgnored(CompletionProposal.ANNOTATION_ATTRIBUTE_REF)) { + completeNormalAnnotationParams(normalAnnotation, specificCompletionBindings); + } + suggestDefaultCompletions = false; + } + + private void completeSuperFieldAccess(SuperFieldAccess superFieldAccess) { + Bindings specificCompletionBindings = new Bindings(); + ITypeBinding superTypeBinding = superFieldAccess.resolveTypeBinding(); + processMembers(superFieldAccess, superTypeBinding, specificCompletionBindings, false); + publishFromScope(specificCompletionBindings); + suggestDefaultCompletions = false; + } + + private void completeQualifiedName(QualifiedName qualifiedName) throws JavaModelException { + Bindings specificCompletionBindings = new Bindings(); + ImportDeclaration importDecl = (ImportDeclaration)DOMCompletionUtils.findParent(qualifiedName, new int[] { ASTNode.IMPORT_DECLARATION }); + if (isParameterInNonParameterizedType(qualifiedName)) { + // do not complete + suggestDefaultCompletions = false; + } else 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)) { + int startPos = importDecl.getName().getStartPosition(); + int endPos = importDecl.getName().getStartPosition() + importDecl.getName().getLength(); + findModules((this.qualifiedPrefix + "." + this.prefix).toCharArray(), this.javaProject, this.assistOptions, Collections.emptySet(), startPos, endPos); //$NON-NLS-1$ + suggestDefaultCompletions = false; + } else { + suggestPackages(qualifiedName); + 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()) { + + boolean isTypeInVariableDeclaration = isTypeInVariableDeclaration(qualifiedName); + SwitchCase switchCase = (SwitchCase)DOMCompletionUtils.findParent(this.toComplete, new int[] { ASTNode.SWITCH_CASE }); + + processMembers(qualifiedName, qualifierTypeBinding, specificCompletionBindings, true); + if (this.extendsOrImplementsInfo == null && !isTypeInVariableDeclaration && switchCase == null) { + this.qualifyingType = qualifierTypeBinding; + publishFromScope(specificCompletionBindings); + } else if (switchCase != null) { + ITypeBinding switchBinding = null; + if (switchCase.getParent() instanceof SwitchStatement switchStatement) { + switchBinding = switchStatement.getExpression().resolveTypeBinding(); + } else if (switchCase.getParent() instanceof SwitchExpression switchExpression) { + switchBinding = switchExpression.getExpression().resolveTypeBinding(); + } + if (switchBinding == null || !switchBinding.isEnum()) { + final ITypeBinding finalizedSwitchBinding = switchBinding; + specificCompletionBindings.all() // + .filter(binding -> this.pattern.matchesName(this.prefix.toCharArray(), binding.getName().toCharArray())) // + .filter(binding -> { + if (binding instanceof ITypeBinding) { + return true; + } + if (binding instanceof IVariableBinding variableBinding + && Flags.isStatic(variableBinding.getModifiers()) + && Flags.isFinal(variableBinding.getModifiers())) { + if (SignatureUtils.isNumeric(SignatureUtils.getSignature(finalizedSwitchBinding)) + && SignatureUtils.isNumeric(SignatureUtils.getSignature(variableBinding.getType()))) { + return true; + } + return variableBinding.getType().getKey().equals(finalizedSwitchBinding.getKey()); + } + return false; + }) + .map(binding -> toProposal(binding)) + .map(proposal -> { + if (proposal.getKind() == CompletionProposal.FIELD_REF) { + proposal.setRelevance(proposal.getRelevance() + RelevanceConstants.R_FINAL + RelevanceConstants.R_QUALIFIED); + } + return proposal; + }) + .forEach(this.requestor::accept); + } + } else { + specificCompletionBindings.all() // + .filter(binding -> this.pattern.matchesName(this.prefix.toCharArray(), binding.getName().toCharArray())) // + .filter(ITypeBinding.class::isInstance) + .filter(type -> filterBasedOnExtendsOrImplementsInfo((IType)type.getJavaElement(), this.extendsOrImplementsInfo)) + .map(binding -> toProposal(binding)) + .forEach(this.requestor::accept); + } + 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 = DOMCompletionUtils.findParentTypeDeclaration(qualifiedName); + MethodDeclaration methodDecl = (MethodDeclaration)DOMCompletionUtils.findParent(this.toComplete, new int[] { ASTNode.METHOD_DECLARATION }); + if (parentTypeDeclaration != null && methodDecl != null && (methodDecl.getModifiers() & Flags.AccStatic) == 0) { + if (completionContext.getCurrentTypeBinding().isSubTypeCompatible(qualifierTypeBinding)) { + if (!isFailedMatch(this.prefix.toCharArray(), Keywords.THIS)) { + CompletionProposal res = createKeywordProposal(Keywords.THIS, startPos, endPos); + res.setRelevance(res.getRelevance() + RelevanceConstants.R_NON_INHERITED); + this.requestor.accept(res); + } + 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() || (qualifiedName.getParent() instanceof Type && qualifiedName.getParent().getParent() instanceof VariableDeclaration)) { + // 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(); + tempScope.scrapeAccessibleBindings(); + 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); + if (this.extendsOrImplementsInfo == null) { + publishFromScope(specificCompletionBindings); + } else { + specificCompletionBindings.all() // + .filter(binding -> this.pattern.matchesName(this.prefix.toCharArray(), binding.getName().toCharArray())) // + .filter(ITypeBinding.class::isInstance) + .filter(type -> filterBasedOnExtendsOrImplementsInfo((IType)type.getJavaElement(), this.extendsOrImplementsInfo)) + .map(binding -> toProposal(binding)) + .forEach(this.requestor::accept); + } + suggestDefaultCompletions = false; + } else { + // maybe it is actually a package? + if (shouldSuggestPackages(qualifiedName)) { + suggestPackages(qualifiedName); + } + // suggests types in the package + suggestTypesInPackage(qualifierPackageBinding.getName()); + suggestDefaultCompletions = false; + } + } + } else if (qualifiedNameBinding instanceof IVariableBinding variableBinding) { + ITypeBinding typeBinding = variableBinding.getType(); + if ((typeBinding == null || typeBinding.isRecovered()) && unit.findDeclaringNode(variableBinding) instanceof VariableDeclaration decl) { + Type type = null; + if (decl instanceof SingleVariableDeclaration single) { + type = single.getType(); + } else if (decl instanceof VariableDeclarationFragment fragment) { + if (fragment.getParent() instanceof FieldDeclaration field) { + type = field.getType(); + } else if (fragment.getParent() instanceof VariableDeclarationExpression expr) { + type = expr.getType(); + } else if (fragment.getParent() instanceof VariableDeclarationStatement stmt) { + type = stmt.getType(); + } + } + if (type != null) { + typeBinding = type.resolveBinding(); + } + if (typeBinding == null || typeBinding.isRecovered()) { + completeMissingType(type); + } + } + processMembers(qualifiedName, typeBinding, specificCompletionBindings, false); + publishFromScope(specificCompletionBindings); + suggestDowncastedFieldsAndMethods(specificCompletionBindings, qualifiedName.getQualifier(), variableBinding); + suggestDefaultCompletions = false; + } else { + Bindings tempScope = new Bindings(); + String simpleName = qualifiedName.getQualifier() instanceof SimpleName simple ? simple.getIdentifier() : ""; + if (!simpleName.isEmpty()) { + tempScope.scrapeAccessibleBindings(); + } + IVariableBinding v = tempScope.variables().filter(variable -> simpleName.equals(variable.getName())).findFirst().orElse(null); + if (v != null) { + processMembers(qualifiedName, v.getType(), specificCompletionBindings, false); + publishFromScope(specificCompletionBindings); + } else { + // UnimportedType.| + List foundTypes = findTypes(qualifiedName.getQualifier().toString(), null).map(TypeNameMatch::getType).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.setWorkingCopyOwner(this.workingCopyOwner); + 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); + specificCompletionBindings.toProposals().map(prop -> { + int rating = prop.getRelevance() + RelevanceConstants.R_NON_INHERITED + RelevanceConstants.R_NO_PROBLEMS; + LinkedList proposals = new LinkedList<>(); + proposals.add(prop); + while (!proposals.isEmpty()) { + CompletionProposal p = proposals.pop(); + p.setRelevance(rating); + if (p.getRequiredProposals() != null) { + proposals.addAll(Arrays.asList(p.getRequiredProposals())); + } + } + return prop; + }).forEach(this.requestor::accept); + int startPos = qualifiedName.getName().getStartPosition(); + int endPos = startPos + qualifiedName.getName().getLength(); + if (!(this.toComplete instanceof Type) && !isFailedMatch(this.prefix.toCharArray(), Keywords.CLASS)) { + var prop = createClassKeywordProposal(qualifierTypeBinding, startPos, endPos); + prop.setRelevance(prop.getRelevance() + RelevanceConstants.R_NO_PROBLEMS); + var typeProposal = toProposal(qualifierTypeBinding); + typeProposal.setReplaceRange(qualifiedName.getQualifier().getStartPosition(), qualifiedName.getQualifier().getStartPosition() + qualifiedName.getQualifier().getLength()); + typeProposal.setRelevance(prop.getRelevance()); + CompletionProposal[] requires = prop.getRequiredProposals() == null ? new CompletionProposal[1] : Arrays.copyOf(prop.getRequiredProposals(), prop.getRequiredProposals().length + 1); + requires[requires.length - 1] = typeProposal; + prop.setRequiredProposals(requires); + this.requestor.accept(prop); + } + } + } + suggestDefaultCompletions = false; + } + } + } + } + + private void completeAnonymousClassDeclaration(AnonymousClassDeclaration anonymousClassDeclaration) { + suggestTypeKeywords(true); + suggestModifierKeywords(0); + ITypeBinding typeDeclBinding = anonymousClassDeclaration.resolveBinding(); + findOverridableMethods(typeDeclBinding, this.javaProject, null); + suggestDefaultCompletions = false; + } + + private void completeTypeDeclaration(AbstractTypeDeclaration typeDecl) { + if (this.textContent != null) { + if (this.extendsOrImplementsInfo != null) { + // keyword present, but no simple or qualified name + // class MyClass implements | { + Bindings typeCompletionBindings = new Bindings(); + topLevelTypes(typeCompletionBindings); + typeCompletionBindings.all() + .filter(ITypeBinding.class::isInstance) + .filter(typeBinding -> filterBasedOnExtendsOrImplementsInfo((IType) typeBinding.getJavaElement(), this.extendsOrImplementsInfo)) + .map(this::toProposal) + .forEach(this.requestor::accept); + } else { + 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); + int implementsOffset = extendsOrImplementsContent.indexOf("implements"); + int extendsOffset = extendsOrImplementsContent.indexOf("extends"); + if (implementsOffset < 0 && extendsOffset < 0) { + // public class Foo | { + // + // } + boolean isInterface = typeDecl instanceof TypeDeclaration realTypeDecl && realTypeDecl.isInterface(); + boolean isEnumOrRecord = typeDecl instanceof RecordDeclaration || typeDecl instanceof EnumDeclaration; + if (!isEnumOrRecord && 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 (implementsOffset < 0 + && (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); + suggestTypeKeywords(true); + suggestModifierKeywords(0); + } + } + suggestDefaultCompletions = false; + } + } + + private void completeSingleMemberAnnotation(SingleMemberAnnotation singleMemberAnnotation) { + Bindings specificCompletionBindings = new Bindings(); + if (singleMemberAnnotation.getTypeName().getStartPosition() + singleMemberAnnotation.getTypeName().getLength() > this.offset) { + completeMarkerAnnotation(); + } else if (!this.requestor.isIgnored(CompletionProposal.ANNOTATION_ATTRIBUTE_REF)) { + completeAnnotationParams(singleMemberAnnotation, Collections.emptySet(), specificCompletionBindings); + } + suggestDefaultCompletions = false; + } + + private void completeSimpleName(SimpleName simpleName) { + Bindings specificCompletionBindings = new Bindings(); + if (simpleName.getParent() instanceof SimpleType simpleType + && (simpleType.getParent() instanceof FieldDeclaration || (simpleType.getParent() instanceof MethodDeclaration methodDecl && methodDecl.getReturnType2() == simpleType)) + && (simpleType.getParent().getParent() instanceof AbstractTypeDeclaration || simpleType.getParent().getParent() instanceof AnonymousClassDeclaration)) { + // eg. + // public class Foo { + // ba| + // } + BodyDeclaration bodyDeclaration = (BodyDeclaration)simpleType.getParent(); + + ITypeBinding typeDeclBinding; + if (simpleType.getParent().getParent() instanceof AbstractTypeDeclaration typeDecl) { + typeDeclBinding = typeDecl.resolveBinding(); + } else { + typeDeclBinding = ((AnonymousClassDeclaration)simpleType.getParent().getParent()).resolveBinding(); + } + + if (!typeDeclBinding.isAnnotation()) { + findOverridableMethods(typeDeclBinding, this.javaProject, simpleName); + } + suggestClassDeclarationLikeKeywords(); + suggestTypeKeywords(true); + suggestModifierKeywords(bodyDeclaration.getModifiers()); + if (!this.requestor.isIgnored(CompletionProposal.TYPE_REF)) { + findTypes(this.prefix, null) + // don't care about annotations + .filter(typeMatch -> { + try { + return !typeMatch.getType().isAnnotation(); + } catch (JavaModelException e) { + return true; + } + }) + // no need to filter out defaults, since we aren't performing default completion + .filter(typeMatch -> this.pattern.matchesName(this.prefix.toCharArray(), typeMatch.getType().getElementName().toCharArray())) + .filter(typeMatch -> filterBasedOnExtendsOrImplementsInfo(typeMatch.getType(), this.extendsOrImplementsInfo)) + .map(this::toProposal) + .forEach(this.requestor::accept); + } + if (!this.requestor.isIgnored(CompletionProposal.POTENTIAL_METHOD_DECLARATION) && !typeDeclBinding.isAnnotation()) { + 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 (DOMCompletionUtils.isJavaFieldOrMethodModifier(potentialModifier)) { + suggest = false; + } + } + if (suggest) { + this.requestor.accept(toNewMethodProposal(typeDeclBinding, this.prefix)); + } + } + suggestDefaultCompletions = false; + } + if (simpleName.getParent() instanceof DoStatement doStatement) { + if (doStatement.getBody().getStartPosition() + doStatement.getBody().getLength() < this.offset) { + String needsWhileTextArea = this.textContent.substring(doStatement.getBody().getStartPosition() + doStatement.getBody().getLength(), + doStatement.getStartPosition() + doStatement.getLength()); + if (!needsWhileTextArea.contains("while")) { + if (!isFailedMatch(this.prefix.toCharArray(), Keywords.WHILE)) { + this.requestor.accept(createKeywordProposal(Keywords.WHILE, -1, -1)); + } + suggestDefaultCompletions = false; + } + } + } + if (simpleName.getLocationInParent() == MemberValuePair.NAME_PROPERTY && simpleName.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 (simpleName.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 (simpleName.getParent() instanceof MethodDeclaration) { + suggestDefaultCompletions = false; + } + if (simpleName.getParent() instanceof SimpleType simpleType && simpleType.getParent() instanceof MethodDeclaration + && simpleType.getLocationInParent().getId().equals(MethodDeclaration.THROWN_EXCEPTION_TYPES_PROPERTY.getId())) { + findTypes(completionContext.getTokenString(), null) + .filter(typeMatch -> this.pattern.matchesName(this.prefix.toCharArray(), + typeMatch.getType().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(typeMatch -> { + try { + return !typeMatch.getType().isAnnotation() && !typeMatch.getType().isInterface(); + } catch (JavaModelException e) { + return true; + } + }) + .map(this::toProposal).forEach(this.requestor::accept); + suggestDefaultCompletions = false; + } + if (simpleName.getLocationInParent() == QualifiedName.QUALIFIER_PROPERTY && simpleName.getParent() instanceof QualifiedName) { + IBinding incorrectBinding = simpleName.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(); + localBindings.scrapeAccessibleBindings(); + Optional realBinding = localBindings.all() // + .filter(IVariableBinding.class::isInstance) + .map(IVariableBinding.class::cast) + .filter(varBind -> varBind.getName().equals(incorrectBinding.getName())) + .findFirst(); + if (realBinding.isPresent()) { + processMembers(simpleName, realBinding.get().getType(), specificCompletionBindings, false); + this.prefix = ""; //$NON-NLS-1$ + publishFromScope(specificCompletionBindings); + suggestDefaultCompletions = false; + } + } + } + if (simpleName.getParent() instanceof ImportDeclaration importDeclaration + && simpleName.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) { + int startPos = importDeclaration.getName().getStartPosition(); + int endPos = importDeclaration.getName().getStartPosition() + importDeclaration.getName().getLength(); + findModules(this.qualifiedPrefix.toCharArray(), this.javaProject, this.assistOptions, Collections.emptySet(), startPos, endPos); + suggestDefaultCompletions = false; + break; + } + } + } + if (simpleName.getParent() instanceof PackageDeclaration) { + suggestDefaultCompletions = false; + } + } + + private void completeModuleDeclaration(ModuleDeclaration moduleDeclaration) { + ImportDeclaration importDecl = (ImportDeclaration) DOMCompletionUtils.findParent(this.toComplete, new int[] {ASTNode.IMPORT_DECLARATION}); + int startPos= importDecl.getName().getStartPosition(); + int endPos = importDecl.getName().getStartPosition() + importDecl.getName().getLength(); + findModules(this.prefix.toCharArray(), this.javaProject, this.assistOptions, Set.of(moduleDeclaration.getName().toString()), startPos, endPos); + } + + private void completeMethodInvocation(MethodInvocation methodInvocation) { + Bindings specificCompletionBindings = new Bindings(); + Expression expression = methodInvocation.getExpression(); + if ((this.offset >= methodInvocation.getName().getStartPosition() && this.offset <= methodInvocation.getName().getStartPosition() + methodInvocation.getName().getLength()) // on method name + || (this.toComplete == methodInvocation && expression != null && this.offset > expression.getStartPosition() + expression.getLength()) /* after dot */) { + ITypeBinding type = expression == null + ? completionContext.getCurrentTypeBinding() + : expression.resolveTypeBinding(); + processMembers(methodInvocation, type, specificCompletionBindings, false); + specificCompletionBindings.all() + .filter(binding -> this.pattern.matchesName(this.prefix.toCharArray(), binding.getName().toCharArray())) + .map(binding -> toProposal(binding)) + .forEach(this.requestor::accept); + suggestDefaultCompletions = false; + } else if (methodInvocation.getStartPosition() + methodInvocation.getLength() <= this.offset && this.prefix.isEmpty()) { + // handle `myMethod().|` + IMethodBinding methodBinding = methodInvocation.resolveMethodBinding(); + if (methodBinding != null) { + ITypeBinding returnType = methodBinding.getReturnType(); + processMembers(methodInvocation, returnType, specificCompletionBindings, false); + specificCompletionBindings.all() + .map(binding -> toProposal(binding)) + .forEach(this.requestor::accept); + } + suggestDefaultCompletions = false; + } else if (methodInvocation.arguments().size() == 1 && ((ASTNode)methodInvocation.arguments().get(0)).getLength() == 0) { + // actually unresolved method: eg myMethod(| + // leave it to default behavior: complete with methods in scope + } else { + // inside parens, but not on any specific argument + IMethodBinding methodBinding = methodInvocation.resolveMethodBinding(); + if (methodBinding == null && this.toComplete == methodInvocation) { + // myMethod(|), where myMethod does not exist + suggestDefaultCompletions = false; + } else if (this.toComplete == methodInvocation) { + for (ITypeBinding param : this.expectedTypes.getExpectedTypes()) { + IMethodBinding potentialLambda = param.getFunctionalInterfaceMethod(); + if (potentialLambda != null) { + this.requestor.accept(createLambdaExpressionProposal(potentialLambda)); + } + } + if (!methodBinding.isRecovered() && methodBinding.getParameterTypes().length == 0) { + CompletionProposal proposal = toProposal(methodBinding); + proposal.setCompletion(new char[0]); + proposal.setReplaceRange(this.offset, this.offset); + proposal.setTokenRange(this.offset, this.offset); + this.requestor.accept(proposal); + suggestDefaultCompletions = false; + } else { + // Rather than completing the method name, we are completing the first argument. + // The prefix currently contains the name of the funciton being invoked, + // so remove that, + // and perform the default completion + this.prefix = ""; + } + } + } + } + + private void completeFieldAccess(FieldAccess fieldAccess) throws JavaModelException { + Bindings specificCompletionBindings = new Bindings(); + Expression fieldAccessExpr = fieldAccess.getExpression(); + ITypeBinding fieldAccessType = fieldAccessExpr.resolveTypeBinding(); + if (fieldAccessType != null) { + if (!fieldAccessType.isRecovered()) { + processMembers(fieldAccess, fieldAccessExpr.resolveTypeBinding(), specificCompletionBindings, false); + publishFromScope(specificCompletionBindings); + if (fieldAccessExpr instanceof ThisExpression) { + // this.new can be used to instantiate non-static inner classes + // eg. MyInner myInner = this.new MyInner(12); + if (CharOperation.prefixEquals(this.prefix.toCharArray(), Keywords.NEW)) { + this.requestor.accept(createKeywordProposal(Keywords.NEW, -1, -1)); + } + } + IVariableBinding variableToCast = null; + if (fieldAccessExpr instanceof Name name && name.resolveBinding() instanceof IVariableBinding variableBinding) { + variableToCast = variableBinding; + } else if (fieldAccessExpr instanceof FieldAccess parentFieldAccess && parentFieldAccess.resolveFieldBinding() != null) { + variableToCast = parentFieldAccess.resolveFieldBinding(); + } + if (variableToCast != null) { + suggestDowncastedFieldsAndMethods(specificCompletionBindings, fieldAccessExpr, variableToCast); + } + } else if (fieldAccessExpr instanceof MethodInvocation method && + this.unit.findDeclaringNode(method.resolveMethodBinding()) instanceof MethodDeclaration decl) { + completeMissingType(decl.getReturnType2()); + } + } else if (DOMCompletionUtils.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; + } + + private CompletionProposal toLinkProposal(DOMInternalCompletionProposal typeProposal) { + DOMInternalCompletionProposal linkProposal = createProposal(CompletionProposal.JAVADOC_TYPE_REF); + + StringBuilder completion = new StringBuilder(); + completion.append("{@link "); + completion.append(typeProposal.getCompletion()); + completion.append("}"); + + linkProposal.setCompletion(completion.toString().toCharArray()); + linkProposal.setSignature(typeProposal.getSignature()); + linkProposal.setRelevance(typeProposal.getRelevance() + RelevanceConstants.R_INLINE_TAG); + linkProposal.setName(typeProposal.getName()); + linkProposal.setPackageName(typeProposal.getPackageName()); + linkProposal.setDeclarationSignature(typeProposal.getDeclarationSignature()); + linkProposal.setTokenRange(typeProposal.getTokenStart(), typeProposal.getTokenEnd()); + linkProposal.setReplaceRange(typeProposal.getReplaceStart(), typeProposal.getReplaceEnd()); + + return linkProposal; + } + + private void suggestClassDeclarationLikeKeywords() { + if (!this.isFailedMatch(this.prefix.toCharArray(), Keywords.CLASS)) { + this.requestor.accept(createKeywordProposal(Keywords.CLASS, -1, -1)); + } + if (!this.isFailedMatch(this.prefix.toCharArray(), Keywords.INTERFACE)) { + this.requestor.accept(createKeywordProposal(Keywords.INTERFACE, -1, -1)); + } + if (!this.isFailedMatch(this.prefix.toCharArray(), Keywords.ENUM)) { + this.requestor.accept(createKeywordProposal(Keywords.ENUM, -1, -1)); + } + if (this.unit.getAST().apiLevel() >= AST.JLS14) { + if (!this.isFailedMatch(this.prefix.toCharArray(), RestrictedIdentifiers.RECORD)) { + this.requestor.accept(createKeywordProposal(RestrictedIdentifiers.RECORD, -1, -1)); + } + } + } + + private void suggestUndeclaredVariableNames(ITypeBinding typeBinding, Set alreadySuggested) { + ASTNode pastCursor = this.toComplete; + ASTNode cursor = this.toComplete.getParent(); + + while (cursor != null && !(cursor instanceof Block)) { + pastCursor = cursor; + cursor = cursor.getParent(); + } + + if (cursor instanceof Block block) { + List names = new ArrayList<>(); + int indexOfPast = block.statements().indexOf(pastCursor); + for (int i = indexOfPast + 1; i < block.statements().size(); i++) { + DOMCompletionUtils.visitChildren((ASTNode)block.statements().get(i), ASTNode.SIMPLE_NAME, node -> { + SimpleName simpleName = (SimpleName) node; + if (!(simpleName.getParent() instanceof SimpleType) + && !(simpleName.getParent() instanceof FieldAccess fieldAccess && fieldAccess.getName() == simpleName) + && !(simpleName.getParent() instanceof QualifiedName qualifiedName && qualifiedName.getName() == simpleName) + && simpleName.resolveBinding().isRecovered()) { + names.add(simpleName.toString()); + } + }); + } + for (String name : names) { + if (!this.isFailedMatch(this.prefix.toCharArray(), name.toCharArray()) && !alreadySuggested.contains(name)) { + alreadySuggested.add(name); + + CompletionProposal res = createProposal(CompletionProposal.VARIABLE_DECLARATION); + res.setName(name.toCharArray()); + res.setCompletion(name.toCharArray()); + res.setSignature(SignatureUtils.getSignatureChar(typeBinding)); + res.setRelevance(RelevanceConstants.R_DEFAULT + + RelevanceConstants.R_INTERESTING + + RelevanceConstants.R_NAME_FIRST_SUFFIX + + RelevanceConstants.R_NAME_FIRST_PREFIX + + RelevanceConstants.R_NAME_LESS_NEW_CHARACTERS + + RelevanceUtils.computeRelevanceForCaseMatching(this.prefix.toCharArray(), name.toCharArray(), this.assistOptions) + + RelevanceConstants.R_NON_RESTRICTED); + res.setSignature(SignatureUtils.getSignatureChar(typeBinding)); + setRange(res); + this.requestor.accept(res); + } + } + } + } + + private void suggestUndeclaredVariableNames(Block block, ITypeBinding typeBinding, Set alreadySuggested) { + List names = new ArrayList<>(); + for (Statement statement : (List)block.statements()) { + DOMCompletionUtils.visitChildren(statement, ASTNode.SIMPLE_NAME, node -> { + SimpleName simpleName = (SimpleName) node; + if (!(simpleName.getParent() instanceof SimpleType) + && !(simpleName.getParent() instanceof FieldAccess fieldAccess && fieldAccess.getName() == simpleName) + && !(simpleName.getParent() instanceof QualifiedName qualifiedName && qualifiedName.getName() == simpleName) + && !(simpleName.getParent() instanceof MethodInvocation) + && !(simpleName.getParent() instanceof MethodDeclaration) + && !(simpleName.getParent() instanceof AbstractTypeDeclaration) + && simpleName.resolveBinding().isRecovered()) { + names.add(simpleName.toString()); + } + }); + } + for (String name : names) { + if (!this.isFailedMatch(this.prefix.toCharArray(), name.toCharArray()) && !alreadySuggested.contains(name)) { + alreadySuggested.add(name); + + CompletionProposal res = createProposal(CompletionProposal.VARIABLE_DECLARATION); + res.setName(name.toCharArray()); + res.setCompletion(name.toCharArray()); + res.setSignature(SignatureUtils.getSignatureChar(typeBinding)); + res.setRelevance(RelevanceConstants.R_DEFAULT + + RelevanceConstants.R_INTERESTING + + RelevanceConstants.R_NAME_FIRST_SUFFIX + + RelevanceConstants.R_NAME_FIRST_PREFIX + + RelevanceConstants.R_NAME_LESS_NEW_CHARACTERS + + RelevanceUtils.computeRelevanceForCaseMatching(this.prefix.toCharArray(), name.toCharArray(), this.assistOptions) + + RelevanceConstants.R_NON_RESTRICTED); + res.setSignature(SignatureUtils.getSignatureChar(typeBinding)); + setRange(res); + this.requestor.accept(res); + } + } + } + + private static boolean isCollection(ITypeBinding type) { + if (type == null) { + return false; + } + if (Collection.class.getName().equals(type.getErasure().getQualifiedName())) { + return true; + } + return isCollection(type.getSuperclass()) || Arrays.stream(type.getInterfaces()).anyMatch(DOMCompletionEngine::isCollection); + } + + private void suggestVariableNamesForType(ITypeBinding typeBinding, Set alreadySuggestedNames) { + if (typeBinding.isPrimitive() || typeBinding.isRecovered()) { + return; + } + Set possibleNames = new LinkedHashSet<>(); + + String simpleName = ""; + boolean multiple = false; + if (typeBinding.isArray()) { + simpleName = typeBinding.getElementType().getName(); + multiple = true; + } else if (typeBinding.isParameterizedType() && isCollection(typeBinding)) { + possibleNames.add(typeBinding.getErasure().getName().toLowerCase()); + multiple = true; + if (typeBinding.getTypeArguments().length > 0) { + simpleName = typeBinding.getTypeArguments()[0].getErasure().getName(); + } + } else { + simpleName = typeBinding.getErasure().getName(); + } + + List nameSegments = Stream.of(simpleName.split("(?=_)|(?<=_)")).flatMap(nameSegment -> Stream.of(nameSegment.split("(?<=[a-z0-9])(?=[A-Z])"))).filter(str -> !str.isEmpty()).toList(); + + if (nameSegments.isEmpty()) { + return; + } + + List variablePortionsOfName = new ArrayList<>(); + + for (int i = 0; i < nameSegments.size(); i++) { + if (nameSegments.get(i).equals("_")) { + continue; + } + StringBuilder variablePortionOfName = new StringBuilder(); + String lowerCaseSegment = nameSegments.get(i).toLowerCase(); + variablePortionOfName.append(lowerCaseSegment); + for (int j = i + 1; j < nameSegments.size(); j++) { + variablePortionOfName.append(nameSegments.get(j)); + } + if (multiple) { + if (variablePortionOfName.toString().endsWith("s")) { + variablePortionOfName.append("es"); + } else { + variablePortionOfName.append("s"); + } + } + variablePortionsOfName.add(variablePortionOfName.toString()); + } + + List prefixes = new ArrayList<>(); + List suffixes = new ArrayList<>(); + if (this.assistOptions.localPrefixes != null) { + for (char[] localPrefix : this.assistOptions.localPrefixes) { + prefixes.add(new String(localPrefix)); + } + } + if (this.assistOptions.localSuffixes != null) { + for (char[] localSuffix : this.assistOptions.localSuffixes) { + suffixes.add(new String(localSuffix)); + } + } + // there is always the implicit suffix of "" + suffixes.add(""); + Map additionalRelevances = new HashMap<>(); + boolean firstPrefix = true; + boolean firstSuffix = true; + boolean realPrefixUsed = false; + for (String localPrefix : prefixes) { + int shortest = localPrefix.length(); + if (this.prefix.length() < shortest) { + shortest = this.prefix.length(); + } + + if (localPrefix.substring(0, shortest).equals(this.prefix.substring(0, shortest))) { + int outerAdditionalRelevance = 0; + if (firstPrefix) { + outerAdditionalRelevance += RelevanceConstants.R_NAME_FIRST_PREFIX; + } else if (!localPrefix.isEmpty()) { + outerAdditionalRelevance += RelevanceConstants.R_NAME_PREFIX; + } + for (String localSuffix : suffixes) { + int innerAdditionalRelevance = outerAdditionalRelevance; + if (firstSuffix && !localSuffix.isEmpty()) { + innerAdditionalRelevance += RelevanceConstants.R_NAME_FIRST_SUFFIX; + } else if (!localSuffix.isEmpty()) { + innerAdditionalRelevance += RelevanceConstants.R_NAME_SUFFIX; + } + firstSuffix = false; + for (String variablePortionOfName : variablePortionsOfName) { + int moreInnerAdditionalRelevance = innerAdditionalRelevance; + StringBuilder possibleName = new StringBuilder(); + possibleName.append(localPrefix); + if (!localPrefix.isEmpty()) { + variablePortionOfName = capitalizeFirstCodepoint(variablePortionOfName); + } + if (localPrefix.length() < this.prefix.length()) { + String leftovers = capitalizeFirstCodepoint(this.prefix.substring(localPrefix.length())); + boolean inserted = false; + for (int i = 0 ; i < leftovers.length(); i++) { + if (variablePortionOfName.startsWith(leftovers.substring(i))) { + inserted = true; + possibleName.append(leftovers.substring(0, i)); + possibleName.append(variablePortionOfName); + moreInnerAdditionalRelevance += RelevanceConstants.R_NAME_LESS_NEW_CHARACTERS; + break; + } + } + if (!inserted) { + possibleName.append(leftovers); + possibleName.append(variablePortionOfName); + } + } else { + possibleName.append(variablePortionOfName); + } + possibleName.append(localSuffix); + possibleNames.add(possibleName.toString()); + additionalRelevances.put(possibleName.toString(), moreInnerAdditionalRelevance); + } + } + realPrefixUsed = true; + } + } + if (!realPrefixUsed) { + firstSuffix = true; + for (String localSuffix : suffixes) { + int outerAdditionalRelevance = 0; + if (firstSuffix && !localSuffix.isEmpty()) { + outerAdditionalRelevance += RelevanceConstants.R_NAME_FIRST_SUFFIX; + } else if (!localSuffix.isEmpty()) { + outerAdditionalRelevance += RelevanceConstants.R_NAME_SUFFIX; + } + firstSuffix = false; + for (String variablePortionOfName : variablePortionsOfName) { + int innerAdditionalRelevance = outerAdditionalRelevance; + StringBuilder possibleName = new StringBuilder(); + boolean inserted = false; + if (!this.prefix.isEmpty()) { + if (variablePortionOfName.startsWith(this.prefix)) { + possibleName.append(variablePortionOfName); + innerAdditionalRelevance += RelevanceConstants.R_NAME_LESS_NEW_CHARACTERS; + } else { + variablePortionOfName = capitalizeFirstCodepoint(variablePortionOfName); + for (int i = 1; i < this.prefix.length(); i++) { + if (variablePortionOfName.startsWith(this.prefix.substring(i))) { + inserted = true; + possibleName.append(this.prefix.substring(0, i)); + possibleName.append(variablePortionOfName); + innerAdditionalRelevance += RelevanceConstants.R_NAME_LESS_NEW_CHARACTERS; + break; + } + } + if (!inserted) { + possibleName.append(this.prefix); + possibleName.append(variablePortionOfName); + } + } + } else { + possibleName.append(variablePortionOfName); + } + possibleName.append(localSuffix); + possibleNames.add(possibleName.toString()); + additionalRelevances.put(possibleName.toString(), innerAdditionalRelevance); + } + } + } + + for (String possibleName : possibleNames) { + if (!alreadySuggestedNames.contains(possibleName)) { + alreadySuggestedNames.add(possibleName); + CompletionProposal res = createProposal(CompletionProposal.VARIABLE_DECLARATION); + + int additionalRelevance = additionalRelevances.getOrDefault(possibleName, 0); + if (SourceVersion.isKeyword(possibleName)) { + possibleName += "1"; + } + + res.setName(possibleName.toCharArray()); + res.setCompletion(possibleName.toCharArray()); + + res.setRelevance(RelevanceConstants.R_DEFAULT + + RelevanceConstants.R_INTERESTING + + RelevanceUtils.computeRelevanceForCaseMatching(this.prefix.toCharArray(), possibleName.toCharArray(), this.assistOptions) + + additionalRelevance + + RelevanceConstants.R_NON_RESTRICTED); + res.setSignature(SignatureUtils.getSignatureChar(typeBinding)); + setRange(res); + this.requestor.accept(res); + } + } + } + + /** + * Returns the string with the first codepoint capitalized + * + * @param str the string to capitalize + * @return the string with the first codepoint capitalized + */ + private static String capitalizeFirstCodepoint(String str) { + int firstCodepoint = str.codePointAt(0); + return Character.toString(Character.toUpperCase(firstCodepoint)) + str.substring(Character.charCount(firstCodepoint)); + } + + private void suggestModifierKeywords(int existingModifiers) { + List viableMods = new ArrayList<>(); + boolean isDefinitelyMethod = Flags.isAbstract(existingModifiers) + || Flags.isDefaultMethod(existingModifiers) + || Flags.isNative(existingModifiers) + || Flags.isSynchronized(existingModifiers) + || Flags.isStrictfp(existingModifiers); + boolean isDefinitelyField = Flags.isTransient(existingModifiers) + || Flags.isVolatile(existingModifiers); + + if (!Flags.isAbstract(existingModifiers) && !Flags.isFinal(existingModifiers)) { + if (!isDefinitelyField) { + viableMods.add(Modifier.ModifierKeyword.ABSTRACT_KEYWORD.toString().toCharArray()); + } + viableMods.add(Modifier.ModifierKeyword.FINAL_KEYWORD.toString().toCharArray()); + } + if (!Flags.isAbstract(existingModifiers)) { + if (!isDefinitelyMethod) { + if (!Flags.isVolatile(existingModifiers)) { + viableMods.add(Modifier.ModifierKeyword.VOLATILE_KEYWORD.toString().toCharArray()); + } + if (!Flags.isTransient(existingModifiers)) { + viableMods.add(Modifier.ModifierKeyword.TRANSIENT_KEYWORD.toString().toCharArray()); + } + } + if (!isDefinitelyField) { + if (!Flags.isNative(existingModifiers)) { + viableMods.add(Modifier.ModifierKeyword.NATIVE_KEYWORD.toString().toCharArray()); + } + if (!Flags.isSynchronized(existingModifiers)) { + viableMods.add(Modifier.ModifierKeyword.SYNCHRONIZED_KEYWORD.toString().toCharArray()); + } + if (!Flags.isStrictfp(existingModifiers)) { + viableMods.add(Modifier.ModifierKeyword.STRICTFP_KEYWORD.toString().toCharArray()); + } + if (!Flags.isDefaultMethod(existingModifiers) && DOMCompletionUtils.findParentTypeDeclaration(toComplete) instanceof TypeDeclaration decl && decl.isInterface()) { + viableMods.add(Modifier.ModifierKeyword.DEFAULT_KEYWORD.toString().toCharArray()); + } + } + if (!Flags.isStatic(existingModifiers)) { + viableMods.add(Modifier.ModifierKeyword.STATIC_KEYWORD.toString().toCharArray()); + } + } + if (!Flags.isPublic(existingModifiers) && !Flags.isProtected(existingModifiers) && !Flags.isPrivate(existingModifiers)) { + viableMods.add(Modifier.ModifierKeyword.PUBLIC_KEYWORD.toString().toCharArray()); + viableMods.add(Modifier.ModifierKeyword.PROTECTED_KEYWORD.toString().toCharArray()); + viableMods.add(Modifier.ModifierKeyword.PRIVATE_KEYWORD.toString().toCharArray()); + } + for (char[] keyword : viableMods) { + if (!this.isFailedMatch(this.prefix.toCharArray(), keyword)) { + this.requestor.accept(createKeywordProposal(keyword, -1, -1)); + } + } + } + + private void suggestDowncastedFieldsAndMethods(Bindings specificCompletionBindings, Expression fieldAccessExpr, + IVariableBinding variableToCast) { + if (this.extendsOrImplementsInfo == null && (!this.requestor.isIgnored(CompletionProposal.METHOD_REF_WITH_CASTED_RECEIVER) + || !this.requestor.isIgnored(CompletionProposal.FIELD_REF_WITH_CASTED_RECEIVER))) { + Set alreadySuggestedBindingKeys = specificCompletionBindings.all() + .map(IBinding::getKey).collect(Collectors.toSet()); + List castedTypes = findInstanceofChecks(this.toComplete, variableToCast); + for (ITypeBinding castedType : castedTypes) { + Bindings castedBindings = new Bindings(); + processMembers(this.toComplete, castedType, castedBindings, false); + castedBindings.all().forEach(castedBinding -> { + if (alreadySuggestedBindingKeys.contains(castedBinding.getKey())) { + return; + } + if (!this.pattern.matchesName(this.prefix.toCharArray(), castedBinding.getName().toCharArray())) { + return; + } + this.requestor.accept(toCastedReceiverProposal(castedBinding, castedType, fieldAccessExpr)); + }); + } + } + } + + private CompletionProposal toCastedReceiverProposal(IBinding castedBinding, ITypeBinding castedType, ASTNode toCast) { + DOMInternalCompletionProposal template = (DOMInternalCompletionProposal)toProposal(castedBinding); + DOMInternalCompletionProposal res = null; + if (castedBinding instanceof IMethodBinding) { + res = createProposal(CompletionProposal.METHOD_REF_WITH_CASTED_RECEIVER); + } else if (castedBinding instanceof IVariableBinding) { + res = createProposal(CompletionProposal.FIELD_REF_WITH_CASTED_RECEIVER); + } else { + return template; + } + + int templateEnd = template.getReplaceEnd(); + res.setReplaceRange(toCast.getStartPosition(), templateEnd); + res.setReceiverRange(toCast.getStartPosition(), template.getReplaceStart() - 1); + res.setTokenRange(template.getReplaceStart(), templateEnd); + + String existingQualifierContent = this.textContent.substring(toCast.getStartPosition(), toCast.getStartPosition() + toCast.getLength()); + + // usually just the `.`, but could include comments + // see CompletionTests.testCompletionAfterInstanceof19 + String existingPostQualifierPreTokenContent = this.textContent.substring(toCast.getStartPosition() + toCast.getLength(), template.getReplaceStart()); + + // build completion + StringBuilder completion = new StringBuilder(); + completion.append("(("); + completion.append(castedType.getName()); + completion.append(")"); + completion.append(existingQualifierContent); + completion.append(")"); + completion.append(existingPostQualifierPreTokenContent); + completion.append(template.getCompletion()); + + res.setCompletion(completion.toString().toCharArray()); + + // copy props from the template proposal + if (!castedType.isArray()) { + res.setDeclarationSignature(template.getDeclarationSignature()); + } else { + // length of an array type + res.setDeclarationSignature(SignatureUtils.getSignatureChar(castedType)); + } + res.setSignature(template.getSignature()); + res.setOriginalSignature(template.originalSignature); + res.setReceiverSignature(SignatureUtils.getSignatureChar(castedType)); + res.setDeclarationPackageName(template.getDeclarationPackageName()); + res.setDeclarationTypeName(template.getDeclarationTypeName()); + res.setParameterPackageNames(template.getParameterPackageNames()); + res.setParameterTypeNames(template.getParameterTypeNames()); + res.setPackageName(template.getPackageName()); + res.setTypeName(template.getTypeName()); + res.setName(template.getName()); + res.setFlags(template.getFlags()); + res.setRelevance(template.getRelevance()); + + return res; + } + + private static List findInstanceofChecks(ASTNode toComplete2, IVariableBinding variableBinding) { + List castedTypes = new ArrayList<>(); + ASTNode cursor = toComplete2; + ASTNode cursorParent = cursor.getParent(); + while (cursorParent != null) { + if (cursorParent instanceof IfStatement ifStatement) { + TrueFalseCasts trueFalseCasts = DOMCompletionUtils.collectTrueFalseCasts(ifStatement.getExpression(), variableBinding); + if (ifStatement.getThenStatement().equals(cursor)) { + castedTypes.addAll(trueFalseCasts.trueCasts()); + } else if (ifStatement.getElseStatement() != null && ifStatement.getElseStatement().equals(cursor)) { + castedTypes.addAll(trueFalseCasts.falseCasts()); + } + } else if (cursorParent instanceof WhileStatement whileStatement) { + TrueFalseCasts trueFalseCasts = DOMCompletionUtils.collectTrueFalseCasts(whileStatement.getExpression(), variableBinding); + if (whileStatement.getBody().equals(cursor)) { + castedTypes.addAll(trueFalseCasts.trueCasts()); + } + } else if (cursorParent instanceof ForStatement forStatement && forStatement.getExpression() != null) { + TrueFalseCasts trueFalseCasts = DOMCompletionUtils.collectTrueFalseCasts(forStatement.getExpression(), variableBinding); + if (forStatement.getBody().equals(cursor)) { + castedTypes.addAll(trueFalseCasts.trueCasts()); + } + } else if (cursorParent instanceof Block block) { + int lastIndex = block.statements().indexOf(cursor); + for (int i = 0; i < lastIndex; i++) { + if (block.statements().get(i) instanceof IfStatement ifStatement) { + if (ifStatement.getElseStatement() == null + && (ifStatement.getThenStatement() instanceof ReturnStatement + || (ifStatement.getThenStatement() instanceof Block nestedBlock && nestedBlock.statements().stream().anyMatch(ReturnStatement.class::isInstance)))) { + // with a non conditional return + TrueFalseCasts trueFalseCasts = DOMCompletionUtils.collectTrueFalseCasts(ifStatement.getExpression(), variableBinding); + castedTypes.addAll(trueFalseCasts.falseCasts()); + } + } + } + } else if (cursorParent instanceof InfixExpression infixExpression) { + if (infixExpression.getRightOperand().equals(cursor)) { + if (infixExpression.getOperator().equals(Operator.CONDITIONAL_AND)) { + TrueFalseCasts trueFalseCasts = DOMCompletionUtils.collectTrueFalseCasts(infixExpression.getLeftOperand(), variableBinding); + castedTypes.addAll(trueFalseCasts.trueCasts()); + } else if (infixExpression.getOperator().equals(Operator.CONDITIONAL_OR)) { + TrueFalseCasts trueFalseCasts = DOMCompletionUtils.collectTrueFalseCasts(infixExpression.getLeftOperand(), variableBinding); + castedTypes.addAll(trueFalseCasts.falseCasts()); + } + } + } + cursor = cursorParent; + cursorParent = cursorParent.getParent(); + } + return castedTypes; + } + + private boolean isTypeInVariableDeclaration(ASTNode context) { + ASTNode cursor = context.getParent(); + ASTNode childCursor = context; + while (cursor != null + && !(cursor instanceof FieldDeclaration) + && !(cursor instanceof VariableDeclarationStatement) + && !(cursor instanceof SingleVariableDeclaration)) { + childCursor = cursor; + cursor = cursor.getParent(); + } + if (cursor instanceof FieldDeclaration fieldDecl) { + return fieldDecl.getType() == childCursor; + } else if (cursor instanceof VariableDeclarationStatement varDecl) { + return varDecl.getType() == childCursor; + } else if (cursor instanceof SingleVariableDeclaration varDecl) { + return varDecl.getType() == childCursor; + } + return false; + } + + private void completeMissingType(Type type) throws JavaModelException { + Type simpleType = type instanceof ParameterizedType parameterized ? + parameterized.getType() : type; + var scope = SearchEngine.createJavaSearchScope(new IJavaElement[] { this.javaProject }); + SearchEngine searchEngine = new SearchEngine(this.workingCopyOwner); + List types = new ArrayList<>(); + searchEngine.searchAllTypeNames(null, SearchPattern.R_PREFIX_MATCH, simpleType.toString().toCharArray(), SearchPattern.R_EXACT_MATCH, IJavaSearchConstants.TYPE, scope, new TypeNameMatchRequestor() { + @Override + public void acceptTypeNameMatch(TypeNameMatch match) { + types.add(match.getType()); + } + }, IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, monitor); + StringBuilder builder = new StringBuilder(); + if (type instanceof ParameterizedType parameterized) { + builder.append(Signature.C_GENERIC_START); + for (Type typeParam : (List)parameterized.typeArguments()) { + builder.append(SignatureUtils.createSignature(typeParam, searchEngine, scope, monitor)); + } + builder.append(Signature.C_GENERIC_END); + } + for (IType matchedType : types) { + processMembers(matchedType, searchEngine, scope) + .map(member -> { + StringBuilder declaringSignature = new StringBuilder(); + declaringSignature.append(member.getDeclarationSignature()); + declaringSignature.deleteCharAt(declaringSignature.length() - 1); // `;` + declaringSignature.append(builder); + declaringSignature.append(';'); + member.setDeclarationSignature(declaringSignature.toString().toCharArray()); + CompletionProposal typeProposal = toProposal(matchedType); + typeProposal.setReplaceRange(simpleType.getStartPosition(), simpleType.getStartPosition() + simpleType.getLength()); + typeProposal.setRelevance(member.getRelevance()); + type.accept(new ASTVisitor() { + @Override + public boolean visit(SimpleType simpleType) { + ITypeBinding binding = simpleType.resolveBinding(); + if (binding == null || binding.isRecovered()) { + List matchingITypes = new ArrayList<>(); + try { + searchEngine.searchAllTypeNames(null, SearchPattern.R_PREFIX_MATCH, simpleType.toString().toCharArray(), SearchPattern.R_EXACT_MATCH, IJavaSearchConstants.TYPE, scope, new TypeNameMatchRequestor() { + @Override + public void acceptTypeNameMatch(TypeNameMatch match) { + matchingITypes.add(match.getType()); + } + }, IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, monitor); + } catch (JavaModelException ex) { + ILog.get().error(ex.getMessage(), ex); + } + if (matchingITypes.size() == 1) { + CompletionProposal typeProposal = toProposal(matchingITypes.get(0)); + typeProposal.setReplaceRange(simpleType.getStartPosition(), simpleType.getStartPosition() + simpleType.getLength()); + typeProposal.setRelevance(member.getRelevance()); + } + } + return true; + } + }); + member.setRequiredProposals(new CompletionProposal[] { typeProposal }); + return member; + }).forEach(DOMCompletionEngine.this.requestor::accept); + } + } + + private void suggestSuperConstructors() { + if (this.requestor.isIgnored(CompletionProposal.METHOD_REF) || this.requestor.isIgnored(CompletionProposal.CONSTRUCTOR_INVOCATION)) { + return; + } + ASTNode methodOrTypeDeclaration = DOMCompletionUtils.findParent(toComplete, + new int[] { ASTNode.METHOD_DECLARATION, ASTNode.TYPE_DECLARATION, ASTNode.ANNOTATION_TYPE_DECLARATION, ASTNode.ENUM_DECLARATION, ASTNode.RECORD_DECLARATION }); + if (this.pattern.matchesName(this.prefix.toCharArray(), + Keywords.SUPER) && methodOrTypeDeclaration instanceof MethodDeclaration methodDecl && methodDecl.isConstructor()) { + ITypeBinding parentTypeBinding = completionContext.getCurrentTypeBinding(); + ITypeBinding superclassBinding = parentTypeBinding.getSuperclass(); + if (superclassBinding != null) { + for (IMethodBinding superclassMethod : superclassBinding.getDeclaredMethods()) { + if (superclassMethod.isConstructor() && isVisible(superclassMethod)) { + this.requestor.accept(toSuperConstructorProposal(superclassMethod)); + } + } + } + } + } + + private boolean isParameterInNonParameterizedType(ASTNode context) { + if (context instanceof ParameterizedType) { + return true; + } else if (DOMCompletionUtils.findParent(context, new int[] { ASTNode.PARAMETERIZED_TYPE }) != null) { + ASTNode cursor1 = context; + ASTNode cursor2 = context.getParent(); + while (!(cursor2 instanceof ParameterizedType paramType)) { + cursor1 = cursor2; + cursor2 = cursor2.getParent(); + } + ITypeBinding paramTypeBinding = paramType.resolveBinding().getTypeDeclaration(); + if (paramTypeBinding.getTypeParameters().length <= paramType.typeArguments().indexOf(cursor1)) { + return true; + } + } + return false; + } + + private void suggestAccessibleConstructorsForType(ITypeBinding typeBinding) { + CompilationUnit cu = (CompilationUnit)DOMCompletionUtils.findParent(toComplete, new int[] { ASTNode.COMPILATION_UNIT }); + String currentPackageName = cu.getPackage() == null ? "" : cu.getPackage().getName().toString(); + ITypeBinding parentTypeBinding = completionContext.getCurrentTypeBinding(); + 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 = parentTypeBinding == null ? false : DOMCompletionUtils.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(currentPackageName); + } + 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 topLevelTypes(Bindings scope) { + Queue toCollect = new ArrayDeque(); + toCollect.addAll(this.unit.types()); + while (!toCollect.isEmpty()) { + AbstractTypeDeclaration current = toCollect.poll(); + scope.add(current.resolveBinding()); + for (ASTNode bodyDecl : (List)current.bodyDeclarations()) { + if (bodyDecl instanceof AbstractTypeDeclaration childTypeDecl) { + toCollect.add(childTypeDecl); + } + } + } + } + + 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.requestor.isIgnored(CompletionProposal.TYPE_REF)) { + // FIXME: + // JDT expects completion to never throw in this case since its normally very fast. + // To fix this we need to: + // - making completion faster by skipping most steps when not really completing anything + // - consider catching the OperationCanceledException() in the cases where it's expected to be thrown upstream (jdt.ui) + return; + } + if (this.monitor != null && this.monitor.isCanceled()) { + throw new OperationCanceledException(); + } + } + + private void statementLikeKeywords() { + if (this.toComplete instanceof Block block && block.statements().isEmpty()) { + return; + } + List keywords = new ArrayList<>(); + boolean isExpressionExpected = + (this.toComplete.getParent() instanceof IfStatement ifStatement + && (IfStatement.EXPRESSION_PROPERTY.getId().equals(this.toComplete.getLocationInParent().getId()) + || FAKE_IDENTIFIER.equals(ifStatement.getExpression().toString()))) + || + (this.toComplete.getParent() instanceof WhileStatement whileStatement + && (WhileStatement.EXPRESSION_PROPERTY.getId().equals(this.toComplete.getLocationInParent().getId()) + || FAKE_IDENTIFIER.equals(whileStatement.getExpression().toString()))) + || + this.toComplete instanceof MethodInvocation + || + (this.toComplete.getParent() instanceof Assignment) + || + (this.toComplete instanceof Assignment) + ; + if (!isExpressionExpected) { + keywords.add(Keywords.ASSERT); + keywords.add(Keywords.RETURN); + keywords.add(Keywords.DO); + keywords.add(Keywords.WHILE); + keywords.add(Keywords.FOR); + keywords.add(Keywords.IF); + keywords.add(Keywords.SWITCH); + keywords.add(Keywords.SYNCHRONIZED); + keywords.add(Keywords.THROW); + keywords.add(Keywords.TRY); + keywords.add(Keywords.FINAL); + keywords.add(Keywords.CLASS); + } + keywords.add(Keywords.SUPER); + keywords.add(Keywords.NEW); + + { + // instanceof must be preceeded by an expression + int instanceofCursor = this.toComplete.getStartPosition(); + while (instanceofCursor > 0 && Character.isWhitespace(this.textContent.charAt(instanceofCursor - 1))) { + instanceofCursor--; + } + ASTNode preceedingNode = NodeFinder.perform(this.unit, instanceofCursor, 0); + while (preceedingNode != null && !(preceedingNode instanceof Expression)) { + preceedingNode = preceedingNode.getParent(); + } + if (preceedingNode != null) { + keywords.add(Keywords.INSTANCEOF); + } + } + + if (!this.expectedTypes.getExpectedTypes().isEmpty()) { + keywords.add(Keywords.NULL); + } + if (DOMCompletionUtils.findParent(this.toComplete, + new int[] { ASTNode.WHILE_STATEMENT, ASTNode.DO_STATEMENT, ASTNode.FOR_STATEMENT }) != null) { + if (!isExpressionExpected) { + keywords.add(Keywords.BREAK); + keywords.add(Keywords.CONTINUE); + } + } else if (DOMCompletionUtils.findParent(this.toComplete, + new int[] { ASTNode.SWITCH_EXPRESSION, ASTNode.SWITCH_STATEMENT }) != null) { + if (!isExpressionExpected) { + keywords.add(Keywords.BREAK); + } + } + if (DOMCompletionUtils.findParent(this.toComplete, + new int[] { ASTNode.SWITCH_EXPRESSION }) != null) { + if (!isExpressionExpected) { + keywords.add(Keywords.YIELD); + } + } + Statement statement = (Statement) DOMCompletionUtils.findParent(this.toComplete, new int[] {ASTNode.EXPRESSION_STATEMENT, ASTNode.VARIABLE_DECLARATION_STATEMENT}); + if (statement != null) { + + ASTNode statementParent = statement.getParent(); + if (statementParent instanceof Block block) { + int exprIndex = block.statements().indexOf(statement); + if (exprIndex > 0) { + ASTNode prevStatement = (ASTNode)block.statements().get(exprIndex - 1); + if (prevStatement.getNodeType() == ASTNode.IF_STATEMENT) { + keywords.add(Keywords.ELSE); + } + } + } + } + MethodDeclaration methodDecl = (MethodDeclaration) DOMCompletionUtils.findParent(this.toComplete, new int[] { ASTNode.METHOD_DECLARATION }); + Initializer initializer = (Initializer) DOMCompletionUtils.findParent(this.toComplete, new int[] { ASTNode.INITIALIZER }); + if (methodDecl != null && (methodDecl.getModifiers() & Flags.AccStatic) == 0 + || initializer != null && !STATIC.equals(this.textContent.substring(initializer.getStartPosition(), initializer.getStartPosition() + STATIC.length()))) { + keywords.add(Keywords.THIS); + } + if (!this.requestor.isIgnored(CompletionProposal.KEYWORD)) { + for (char[] keyword : keywords) { + if (!isFailedMatch(this.prefix.toCharArray(), keyword)) { + this.requestor.accept(createKeywordProposal(keyword, this.toComplete.getStartPosition(), this.offset)); + } + } + } + // Z means boolean + if (this.expectedTypes.getExpectedTypes().stream().anyMatch(type -> "Z".equals(type.getKey()))) { + if (!isFailedMatch(this.prefix.toCharArray(), Keywords.TRUE)) { + CompletionProposal completionProposal = createKeywordProposal(Keywords.TRUE, this.toComplete.getStartPosition(), this.offset); + completionProposal.setRelevance(completionProposal.getRelevance() + RelevanceConstants.R_EXACT_EXPECTED_TYPE + RelevanceConstants.R_UNQUALIFIED); + this.requestor.accept(completionProposal); + } + if (!isFailedMatch(this.prefix.toCharArray(), Keywords.FALSE)) { + CompletionProposal completionProposal = createKeywordProposal(Keywords.FALSE, this.toComplete.getStartPosition(), this.offset); + completionProposal.setRelevance(completionProposal.getRelevance() + RelevanceConstants.R_EXACT_EXPECTED_TYPE + RelevanceConstants.R_UNQUALIFIED); + this.requestor.accept(completionProposal); + } + } + } + + private void completeJavadocBlockTags(TagElement tagNode) { + if (this.requestor.isIgnored(CompletionProposal.JAVADOC_BLOCK_TAG)) { + return; + } + char[] atlessPrefix = this.prefix.startsWith("@") ? this.prefix.substring(1).toCharArray() : this.prefix.toCharArray(); + ASTNode nodeSearchNode = NodeFinder.perform(this.unit, this.offset, 0); + for (char[] blockTag : DOMCompletionEngineJavadocUtil.getJavadocBlockTags(this.javaProject, tagNode, nodeSearchNode)) { + if (!isFailedMatch(atlessPrefix, blockTag)) { + this.requestor.accept(toJavadocBlockTagProposal(blockTag)); + } + } + } + + private void completeJavadocInlineTags(TagElement tagNode) { + if (this.requestor.isIgnored(CompletionProposal.JAVADOC_INLINE_TAG)) { + return; + } + ASTNode nodeSearchNode = NodeFinder.perform(this.unit, this.offset, 0); + char[] atlessPrefix = this.prefix.startsWith("@") ? this.prefix.substring(1).toCharArray() : this.prefix.toCharArray(); + for (char[] inlineTag : DOMCompletionEngineJavadocUtil.getJavadocInlineTags(this.javaProject, tagNode, nodeSearchNode)) { + if (!isFailedMatch(atlessPrefix, inlineTag)) { + this.requestor.accept(toJavadocInlineTagProposal(inlineTag)); + } + } + } + + private void completeThrowsClause(MethodDeclaration methodDeclaration, Bindings scope) { + if (methodDeclaration.thrownExceptionTypes().size() == 0) { + int startScanIndex = -1; + if (methodDeclaration.getReturnType2() != null) { + startScanIndex = methodDeclaration.getReturnType2().getStartPosition() + methodDeclaration.getReturnType2().getLength(); + } + if (methodDeclaration.getName() != null) { + startScanIndex = methodDeclaration.getName().getStartPosition() + methodDeclaration.getName().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 = prefix.replace(FAKE_IDENTIFIER, ""); + if (!prefix.isBlank()) { + // IMO we should provide our own ISearchRequestor so that we don't have to mess with CompletionEngine internal state + // but this hack should hopefully fix the range + if (context instanceof Name name) { + this.nestedEngine.startPosition = name.getStartPosition(); + this.nestedEngine.endPosition = name.getStartPosition() + name.getLength(); + } + if (prefix.contains(".")) { + this.nestedEngine.qualifiedCompletionToken = prefix.toCharArray(); + this.nestedEngine.insideQualifiedReference = true; + } + this.nameEnvironment.findPackages(prefix.toCharArray(), this.nestedEngine); + } + } + } + + private void suggestTypesInPackage(String packageName) { + if (!this.requestor.isIgnored(CompletionProposal.TYPE_REF)) { + List foundTypes = findTypes(this.prefix, packageName).toList(); + for (TypeNameMatch foundType : foundTypes) { + if (this.pattern.matchesName(this.prefix.toCharArray(), foundType.getType().getElementName().toCharArray())) { + if (filterBasedOnExtendsOrImplementsInfo(foundType.getType(), this.extendsOrImplementsInfo)) { + this.requestor.accept(this.toProposal(foundType)); + } + } + } + } + } + + private void suggestPackagesInModule(IPackageFragmentRoot moduleContext, String packagePrefix, int startPos, int endPos) { + if (moduleContext == null) { + return; + } + if (!this.requestor.isIgnored(CompletionProposal.PACKAGE_REF)) { + IModuleDescription moduleDescription = moduleContext.getModuleDescription(); + String moduleName = moduleDescription.getElementName(); + try { + // use the IJavaElement model to collect the available packages + Set allowedPackages = Set.of(moduleDescription.getExportedPackageNames(this.javaProject.getModuleDescription())); + for (String packageName : allowedPackages) { + if (packageName.isEmpty()) + continue; + if (!this.pattern.matchesName(packagePrefix.toCharArray(), packageName.toCharArray())) { + continue; + } + this.requestor.accept(toJavadocModulePackageProposal(packageName, moduleName, startPos, endPos, packagePrefix)); + } + } catch (JavaModelException e) { + // we could fall back to the name environment, but in practice I don't think this is needed? + } + } + } + + private DOMInternalCompletionProposal toJavadocModulePackageProposal(String packageName, String moduleName, int startPos, int endPos, String packagePrefix) { + int relevance = RelevanceConstants.R_DEFAULT; + relevance += RelevanceConstants.R_RESOLVED; + relevance += RelevanceConstants.R_INTERESTING; + relevance += RelevanceUtils.computeRelevanceForCaseMatching(packagePrefix.toCharArray(), packageName.toCharArray(), this.assistOptions); + relevance += RelevanceUtils.computeRelevanceForQualification(true, packagePrefix, packagePrefix); + relevance += RelevanceConstants.R_NON_RESTRICTED; + + DOMInternalCompletionProposal proposal = createProposal(CompletionProposal.PACKAGE_REF); + StringBuilder signature = new StringBuilder(); + signature.append(packageName); + signature.append(" - "); + signature.append(moduleName); + signature.append("/"); + proposal.setDeclarationSignature(signature.toString().toCharArray()); + StringBuilder completion = new StringBuilder(); + completion.append(moduleName); + completion.append("/"); + completion.append(packageName); + proposal.setCompletion(completion.toString().toCharArray()); + proposal.setPackageName(packageName.toCharArray()); + proposal.setReplaceRange(startPos, endPos); + proposal.setTokenRange(startPos, endPos); + proposal.setRelevance(relevance); + return proposal; + } + + 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; + } + { + IType cursor = toFilter; + String invocationKey = info.typeDecl.resolveBinding().getKey(); + while (cursor != null) { + if (cursor.getKey().equals(invocationKey)) { + return false; + } + cursor = cursor.getDeclaringType(); + } + } + if (toFilter.isEnum() || toFilter.isRecord()) { + // cannot extend or implement + return false; + } + if (toFilter.isSealed()) { + String currentTypeName = info.typeDecl.getName().toString(); + boolean permitted = false; + for (var elt : toFilter.getPermittedSubtypeNames()) { + if (elt.equals(currentTypeName)) { + permitted = true; + } + } + if (!permitted) { + 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 ExtendsOrImplementsInfo isInExtendsOrImplements(ASTNode completion) { + if (completion instanceof AbstractTypeDeclaration typeDecl) { + // string manipulation + 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--; + } + if (nameEndOffset < this.offset && this.offset <= bodyStart) { + String extendsOrImplementsContent = this.textContent.substring(nameEndOffset, this.offset); + int implementsOffset = extendsOrImplementsContent.indexOf("implements"); + int extendsOffset = extendsOrImplementsContent.indexOf("extends"); + if (implementsOffset >= 0 || extendsOffset >= 0) { + boolean isInterface = implementsOffset >= 0 && this.offset > implementsOffset; + return new ExtendsOrImplementsInfo(typeDecl, isInterface); + } + } + return null; + } + 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() { + findTypes(completionContext.getTokenString(), -1, IJavaSearchConstants.ANNOTATION_TYPE, null) + .filter(typeMatch -> this.pattern.matchesName(this.prefix.toCharArray(), + typeMatch.getType().getElementName().toCharArray())) + .map(this::toProposal).forEach(this.requestor::accept); + suggestDefaultCompletions = false; + } + + 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.toProposals().forEach(this.requestor::accept); + } + + private void completeConstructor(ITypeBinding typeBinding, ASTNode referencedFrom, IJavaProject javaProject, boolean exactType) { + // compute type hierarchy + boolean isArray = typeBinding.isArray(); + AbstractTypeDeclaration enclosingType = (AbstractTypeDeclaration) DOMCompletionUtils.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 (typeBinding.getJavaElement() instanceof IType typeHandle) { + try { + ITypeHierarchy newTypeHierarchy = typeHandle.newTypeHierarchy(javaProject, null); + ASTParser parser = ASTParser.newParser(AST.getJLSLatest()); + parser.setProject(javaProject); + List subtypes = new ArrayList<>(); + for (IType subtype : newTypeHierarchy.getSubtypes(typeHandle)) { + subtypes.add(subtype); + } + // Include the current type as a possible constructor suggestion + // if the current text doesn't exactly match an existing type. + // This is suggested regardless if it's appropriate according to the expected type. + // FIXME: I feel like this is a misfeature of JDT + // I want to fix it upstream as well as here + if (enclosingTypeElement != null && !exactType) { + String enclosingRawSigature = Signature.getTypeErasure(SignatureUtils.getSignatureForTypeKey(enclosingTypeElement.getKey())); + boolean alreadyThere = false; + if (Signature.getTypeErasure(SignatureUtils.getSignatureForTypeKey(typeHandle.getKey())).equals(enclosingRawSigature)) { + alreadyThere = true; + } + for (IType subtype : subtypes) { + if (Signature.getTypeErasure(SignatureUtils.getSignatureForTypeKey(subtype.getKey())).equals(enclosingRawSigature)) { + alreadyThere = true; + } + } + if (!alreadyThere) { + subtypes.add(enclosingTypeElement); + } + } + + if (isArray) { + if (!this.isFailedMatch(this.prefix.toCharArray(), typeHandle.getElementName().toCharArray())) { + InternalCompletionProposal typeProposal = (InternalCompletionProposal) toProposal(typeHandle); + typeProposal.setArrayDimensions(typeBinding.getDimensions()); + typeProposal.setRelevance(typeProposal.getRelevance() + RelevanceConstants.R_EXACT_EXPECTED_TYPE); + this.requestor.accept(typeProposal); + } + 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 { + { + List proposals = toConstructorProposals(typeBinding, referencedFrom, true); + for (CompletionProposal proposal : proposals) { + this.requestor.accept(proposal); + } + } + 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(); + + String currentPackageName = this.unit.getPackage() != null ? this.unit.getPackage().getName().toString() : ""; + List importedTypes = this.unit.imports().stream() // + .map(importDecl -> ((ImportDeclaration)importDecl).resolveBinding()) // + .filter(ITypeBinding.class::isInstance).map(ITypeBinding.class::cast) // + .toList(); + Set alreadySuggestedMethodKeys = new HashSet<>(); + for (IMethodBinding declaredMethod : typeBinding.getDeclaredMethods()) { + alreadySuggestedMethodKeys.add(KeyUtils.getMethodKeyWithOwnerTypeAndReturnTypeRemoved(declaredMethod.getKey())); + } + if (typeBinding.getSuperclass() != null) { + findOverridableMethods0(typeBinding, typeBinding.getSuperclass(), alreadySuggestedMethodKeys, javaProject, originalPackageKey, currentPackageName, importedTypes, toReplace); + } + for (ITypeBinding superInterface : typeBinding.getInterfaces()) { + findOverridableMethods0(typeBinding, superInterface, alreadySuggestedMethodKeys, javaProject, originalPackageKey, currentPackageName, importedTypes, toReplace); + } + } + + private void findOverridableMethods0(ITypeBinding currentType, ITypeBinding typeBinding, Set alreadySuggestedKeys, IJavaProject javaProject, String originalPackageKey, + String currentPackageName, List importedTypes, ASTNode toReplace) { + next : for (IMethodBinding method : typeBinding.getDeclaredMethods()) { + String cleanedMethodKey = KeyUtils.getMethodKeyWithOwnerTypeAndReturnTypeRemoved(method.getKey()); + if (alreadySuggestedKeys.contains(cleanedMethodKey)) { + continue next; + } + if (method.isSynthetic() || method.isConstructor() + || Modifier.isPrivate(method.getModifiers()) + || (this.assistOptions.checkDeprecation && isDeprecated(method.getJavaElement())) + || Modifier.isStatic(method.getModifiers()) + || ((method.getModifiers() & (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED)) == 0) && !typeBinding.getPackage().getKey().equals(originalPackageKey)) { + continue next; + } + alreadySuggestedKeys.add(cleanedMethodKey); + 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(SignatureUtils.getSignatureChar(method.getDeclaringClass())); + proposal.setKey(method.getKey().toCharArray()); + proposal.setSignature(SignatureUtils.getSignatureChar(method)); + + try { + if (method.getJavaElement() != null) { + proposal.setParameterNames(Stream.of(((IMethod)method.getJavaElement()).getParameterNames()).map(name -> name.toCharArray()).toArray(char[][]::new)); + } else { + proposal.setParameterNames(Stream.of(method.getParameterNames()).map(name -> name.toCharArray()).toArray(char[][]::new)); + } + } catch (JavaModelException e) { + 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 + + RelevanceUtils.computeRelevanceForCaseMatching(this.prefix.toCharArray(), method.getName().toCharArray(), this.assistOptions); + proposal.setRelevance(relevance); + + StringBuilder completion = new StringBuilder(); + DOMCompletionEngineBuilder.createMethod(method, completion, currentType, importedTypes, currentPackageName); + proposal.setCompletion(completion.toString().toCharArray()); + this.requestor.accept(proposal); + } + if (typeBinding.getSuperclass() != null) { + findOverridableMethods0(currentType, typeBinding.getSuperclass(), alreadySuggestedKeys, javaProject, originalPackageKey, currentPackageName, importedTypes, toReplace); + } + for (ITypeBinding superInterface : typeBinding.getInterfaces()) { + findOverridableMethods0(currentType, superInterface, alreadySuggestedKeys, javaProject, originalPackageKey, currentPackageName, importedTypes, toReplace); + } + } + + private Stream findTypes(String namePrefix, String packageName) { + return findTypes(namePrefix, -1, IJavaSearchConstants.TYPE, packageName); + } + + private Stream findTypes(String namePrefix, int typeMatchRule, int searchFor, 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(TypeNameMatch match) { + if (isVisible(match)) { + types.add(match); + } + } + private boolean isVisible(TypeNameMatch match) { + if (!DOMCompletionEngine.this.settings.get(JavaCore.COMPILER_PB_FORBIDDEN_REFERENCE).equals(JavaCore.IGNORE) + && DOMCompletionEngine.this.assistOptions.checkForbiddenReference + && match.getAccessibility() == IAccessRule.K_NON_ACCESSIBLE) { + return false; + } + if (!DOMCompletionEngine.this.settings.get(JavaCore.COMPILER_PB_DISCOURAGED_REFERENCE).equals(JavaCore.IGNORE) + && DOMCompletionEngine.this.assistOptions.checkDiscouragedReference + && match.getAccessibility() == IAccessRule.K_DISCOURAGED) { + return false; + } + if (match.getPackageName().isEmpty() && !completionContext.getCurrentTypeBinding().getPackage().getName().isEmpty()) { + // can only access classes in the default package from the default package + return false; + } + if (Flags.isPublic(match.getModifiers())) { + return true; + } + if (Flags.isPrivate(match.getModifiers())) { + return modelUnit.equals(match.getType().getTypeRoot()); + } + if (Flags.isProtected(match.getModifiers())) { + IType nestingType = match.getType().getDeclaringType(); + return nestingType != null && DOMCompletionUtils.findInSupers(completionContext.getCurrentTypeBinding(), nestingType.getKey()); + } + return match.getPackageName().equals(modelUnit.getAncestor(IJavaElement.PACKAGE_FRAGMENT).getElementName()); + } + }; + try { + new SearchEngine(this.modelUnit instanceof ICompilationUnit modelCU ? modelCU.getOwner() : this.workingCopyOwner).searchAllTypeNames( + packageName == null ? null : packageName.toCharArray(), SearchPattern.R_EXACT_MATCH, + namePrefix.toCharArray(), + typeMatchRule >= 0 ? typeMatchRule : + 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), + searchFor, 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) { + if (typeBinding == null) { + return; + } + CompilationUnit cu = ((CompilationUnit)DOMCompletionUtils.findParent(referencedFrom, new int[] {ASTNode.COMPILATION_UNIT})); + String packageKey = cu.getPackage() != null ? cu.getPackage().resolveBinding() != null ? cu.getPackage().resolveBinding().getKey() : "" : ""; + ASTNode parentType = DOMCompletionUtils.findParent(referencedFrom, new int[] {ASTNode.ANNOTATION_TYPE_DECLARATION, ASTNode.TYPE_DECLARATION, ASTNode.ENUM_DECLARATION, ASTNode.RECORD_DECLARATION, ASTNode.ANONYMOUS_CLASS_DECLARATION}); + ITypeBinding referencedFromBinding = null; + if (parentType instanceof AbstractTypeDeclaration abstractTypeDeclaration) { + referencedFromBinding = abstractTypeDeclaration.resolveBinding(); + } else if (parentType instanceof AnonymousClassDeclaration anonymousClassDeclaration) { + referencedFromBinding = anonymousClassDeclaration.resolveBinding(); + } + boolean includePrivate = parentType == null ? false : referencedFromBinding.getKey().equals(typeBinding.getKey()); + MethodDeclaration methodDeclaration = (MethodDeclaration)DOMCompletionUtils.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 != null && referencedFromBinding.getKey().equals(typeBinding.getKey())) { + includeProtected = true; + } else if (methodDeclaration != null + && (methodDeclaration.getModifiers() & Flags.AccStatic) != 0) { + includeProtected = false; + } else if (referencedFromBinding != null) { + includeProtected = DOMCompletionUtils.findInSupers(referencedFromBinding, typeBinding); + } else { + includeProtected = false; + } + processMembers(typeBinding, scope, includePrivate, includeProtected, packageKey, isStaticContext, typeBinding.isInterface(), + new HashSet<>(), 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, + Set impossibleClasses) { + if (typeBinding == null) { + return; + } + + Predicate accessFilter = binding -> { + if (binding == null) { + return false; + } + if (binding instanceof IVariableBinding variableBinding) { + if (impossibleFields.contains(variableBinding.getName())) { + return false; + } + } else if (binding instanceof IMethodBinding methodBinding) { + if (impossibleMethods.contains(getSignature(methodBinding))) { + return false; + } + if (methodBinding.isConstructor()) { + return false; + } + } else { + if (impossibleClasses.contains(binding.getName())) { + return false; + } + if (canUseAbstract && isStaticContext) { + // must be accessed directly + // eg. given class OuterClass extends class SuperOuterClass, and SuperOuterClass contains class InnerClass + // - SuperOuterClass.InnerClass is valid + // - OuterClass.InnerClass is not valid + 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 && !(binding instanceof ITypeBinding))) + // check abstract + || (!canUseAbstract && (binding.getModifiers() & Flags.AccAbstract) != 0) + ) { + if (binding instanceof IVariableBinding) { + impossibleFields.add(binding.getName()); + } else if (binding instanceof IMethodBinding method) { + impossibleMethods.add(getSignature(method)); + } else { + impossibleClasses.add(binding.getName()); + } + return false; + } + return true; + }; + + ASTNode foundDecl = DOMCompletionUtils.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)DOMCompletionUtils.findParent(this.toComplete, new int[] {ASTNode.VARIABLE_DECLARATION_FRAGMENT}); + ASTNode typeDecl = ((CompilationUnit)this.toComplete.getRoot()).findDeclaringNode(typeBinding); + int indexOfField = -1; + if (typeDecl instanceof AbstractTypeDeclaration abstractTypeDecl) { + indexOfField = abstractTypeDecl.bodyDeclarations().indexOf(fieldDeclaration); + } else if (typeDecl instanceof AnonymousClassDeclaration anonymousTypeDeclaration) { + indexOfField = anonymousTypeDeclaration.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 { + List bodyDeclarations = null; + if (typeDecl instanceof AbstractTypeDeclaration abstractTypeDecl) { + bodyDeclarations = abstractTypeDecl.bodyDeclarations(); + } else if (typeDecl instanceof AnonymousClassDeclaration anonymousTypeDeclaration) { + bodyDeclarations = anonymousTypeDeclaration.bodyDeclarations(); + } + boolean isCursorFieldStatic = Flags.isStatic(fieldDeclaration.getModifiers()); + for (int i = 0; i < bodyDeclarations.size(); i++) { + if (bodyDeclarations.get(i) instanceof FieldDeclaration fieldDecl) { + // static field declarations after the current declaration can be used in completion, + // since they will be assigned during object creation + if (isCursorFieldStatic + ? (Flags.isStatic(fieldDecl.getModifiers()) && i < indexOfField + 1) + : (Flags.isStatic(fieldDecl.getModifiers()) || i < indexOfField + 1)) { + 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); + Arrays.stream(typeBinding.getDeclaredTypes()) // + .filter(accessFilter) // + .forEach(scope::add); + if (typeBinding.getInterfaces() != null) { + for (ITypeBinding superinterfaceBinding : typeBinding.getInterfaces()) { + processMembers(superinterfaceBinding, scope, false, includeProtected, originalPackageKey, isStaticContext, true, impossibleMethods, impossibleFields, impossibleClasses); + } + } + ITypeBinding superclassBinding = typeBinding.getSuperclass(); + if (superclassBinding != null ) { + processMembers(superclassBinding, scope, false, includeProtected, originalPackageKey, isStaticContext, true, impossibleMethods, impossibleFields, impossibleClasses); + } else if (!Object.class.getName().equals(typeBinding.getQualifiedName()) && !typeBinding.isPrimitive()) { + ITypeBinding objectType = this.unit.getAST().resolveWellKnownType(Object.class.getName()); + processMembers(objectType, scope, false, includeProtected, originalPackageKey, isStaticContext, true, impossibleMethods, impossibleFields, impossibleClasses); + } + } + + private Stream processMembers(IType type, SearchEngine searchEngine, IJavaSearchScope scope) { + IJavaElement[] children; + try { + children = type.getChildren(); + } catch (JavaModelException ex) { + ILog.get().error(ex.getMessage(), ex); + children = new IJavaElement[0]; + } + Stream current = Arrays.stream(children) + .filter(element -> element.getElementType() == IJavaElement.FIELD || element.getElementType() == IJavaElement.METHOD) + .filter(element -> !assistOptions.checkDeprecation || !isDeprecated(element)) + .filter(this::isVisible) + .map(this::toProposal); + List superTypes = new ArrayList<>(); + try { + superTypes.add(type.getSuperclassName()); + superTypes.addAll(Arrays.asList(type.getSuperInterfaceNames())); + } catch (JavaModelException ex) { + ILog.get().error(ex.getMessage(), ex); + } + return Stream.concat(current, superTypes.stream() + .filter(Objects::nonNull) + .map(typeName -> { + int index = typeName.lastIndexOf('.'); + char[] packageName = index >= 0 ? typeName.substring(0, index).toCharArray() : null; + char[] simpleName = index >= 0 ? typeName.substring(index + 1, typeName.length()).toCharArray() : typeName.toCharArray(); + List types = new ArrayList<>(); + try { + searchEngine.searchAllTypeNames(packageName, SearchPattern.R_EXACT_MATCH, simpleName, SearchPattern.R_EXACT_MATCH, IJavaSearchConstants.TYPE, scope, new TypeNameMatchRequestor() { + @Override + public void acceptTypeNameMatch(TypeNameMatch match) { + types.add(match.getType()); + } + }, IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, monitor); + } catch (JavaModelException ex) { + ILog.get().error(ex.getMessage(), ex); + } + return types; + }).flatMap(List::stream) + .map(t -> processMembers(t, searchEngine, scope)) + .flatMap(Function.identity())); + } + + private String getSignature(IMethodBinding method) { + return method.getName() + '(' + + Arrays.stream(method.getParameterTypes()).map(paramType -> paramType != null ? paramType.getName() : "Object").collect(Collectors.joining(",")) + + ')'; + } + + 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, IAccessRule.K_ACCESSIBLE); + } + + int kind = -1; + boolean inJavadoc = DOMCompletionUtils.findParent(this.toComplete, new int [] { ASTNode.JAVADOC }) != null; + if (binding instanceof ITypeBinding) { + kind = CompletionProposal.TYPE_REF; + } else if (binding instanceof IMethodBinding m) { + if (DOMCompletionUtils.findParent(this.toComplete, new int[] { ASTNode.EXPRESSION_METHOD_REFERENCE, ASTNode.TYPE_METHOD_REFERENCE, ASTNode.SUPER_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()); + int endOffset = this.toComplete instanceof SimpleName name && name.getStartPosition() >= 0 ? + name.getStartPosition() + name.getLength() : -1; + if (kind == CompletionProposal.METHOD_REF + && (endOffset < 0 || this.textContent.length() <= endOffset || this.textContent.charAt(endOffset) != '(')) { + completion += "()"; //$NON-NLS-1$ + } + if (toComplete instanceof MethodInvocation method && Objects.equals(binding.getName(), method.getName().getIdentifier())) { + completion = ""; // myMethod(| + } + 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 != null) { + 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(SignatureUtils.getSignatureChar(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(); + if (completionContext.getCurrentTypeBinding() != null) { + ITypeBinding completionContextTypeBinding = completionContext.getCurrentTypeBinding(); + 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 if (this.qualifiedPrefix.equals(this.prefix)) { + 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 { + QualifiedName qualifiedName = (QualifiedName)DOMCompletionUtils.findParent(this.toComplete, new int[] { ASTNode.QUALIFIED_NAME }); + if (qualifiedName != null) { + Name name = qualifiedName.getQualifier(); + ITypeBinding directParentClass = methodBinding.getDeclaringClass(); + if (name.toString().equals(directParentClass.getName())) { + CompletionProposal typeProposal = toProposal(directParentClass); + typeProposal.setReplaceRange(name.getStartPosition(), name.getStartPosition() + name.getLength()); + typeProposal.setTokenRange(name.getStartPosition(), name.getStartPosition() + name.getLength()); + typeProposal.setRequiredProposals(null); + res.setRequiredProposals(new CompletionProposal[] { typeProposal }); + } + } else { + ILog.get().error("expected there to be a parent type, since this is a qualified static method completion"); + } + } + } + } + } else if (kind == CompletionProposal.LOCAL_VARIABLE_REF) { + var variableBinding = (IVariableBinding) binding; + res.setSignature(SignatureUtils.getSignatureChar(variableBinding.getType())); + } else if (kind == CompletionProposal.FIELD_REF) { + var variableBinding = (IVariableBinding) binding; + ITypeBinding declaringClass = variableBinding.getDeclaringClass(); + res.setSignature(SignatureUtils.getSignatureChar(variableBinding.getType())); + if (declaringClass != null && !declaringClass.getQualifiedName().isEmpty()) { + char[] declSignature = SignatureUtils.getSignatureChar(declaringClass); + res.setDeclarationSignature(declSignature); + } else if (declaringClass != null && declaringClass.isAnonymous()) { + res.setDeclarationSignature(SignatureUtils.getSignatureChar(declaringClass.getSuperclass())); + } else { + res.setDeclarationSignature(new char[0]); + } + + if ((variableBinding.getModifiers() & Flags.AccStatic) != 0 || variableBinding.isEnumConstant()) { + ITypeBinding topLevelClass = variableBinding.getDeclaringClass(); + while (topLevelClass.getDeclaringClass() != null) { + topLevelClass = topLevelClass.getDeclaringClass(); + } + final ITypeBinding finalizedTopLevelClass = topLevelClass; + ITypeBinding variableTypeBinding = variableBinding.getDeclaringClass(); + if (completionContext.getCurrentTypeBinding() != null) { + ITypeBinding completionContextTypeBinding = completionContext.getCurrentTypeBinding(); + 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 if (this.qualifiedPrefix.equals(this.prefix)) { + ITypeBinding directParentClass = variableBinding.getDeclaringClass(); + res.setRequiredProposals(new CompletionProposal[] { toStaticImportProposal(directParentClass) }); + } else { + QualifiedName qualifiedName = (QualifiedName)DOMCompletionUtils.findParent(this.toComplete, new int[] { ASTNode.QUALIFIED_NAME }); + if (qualifiedName != null) { + Name name = qualifiedName.getQualifier(); + ITypeBinding directParentClass = variableBinding.getDeclaringClass(); + if (name.toString().equals(directParentClass.getName())) { + CompletionProposal typeProposal = toProposal(directParentClass); + typeProposal.setReplaceRange(name.getStartPosition(), name.getStartPosition() + name.getLength()); + typeProposal.setTokenRange(name.getStartPosition(), name.getStartPosition() + name.getLength()); + typeProposal.setRequiredProposals(null); + res.setRequiredProposals(new CompletionProposal[] { typeProposal }); + } + } else { + ILog.get().error("expected there to be a parent type, since this is a qualified static method completion"); + } + } + } + } + if (variableBinding.isEnumConstant() + && !DOMCompletionUtils.isInQualifiedName(toComplete) + && !(toComplete instanceof SwitchCase) + && toComplete.getLocationInParent() != SwitchCase.EXPRESSION_PROPERTY + && toComplete.getLocationInParent() != SwitchCase.EXPRESSIONS2_PROPERTY) { + res.setCompletion((variableBinding.getDeclaringClass().getName() + '.' + variableBinding.getName()).toCharArray()); + } + } else if (kind == CompletionProposal.TYPE_REF) { + var typeBinding = (ITypeBinding) binding; + res.setSignature(SignatureUtils.getSignatureChar(typeBinding)); + } 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(SignatureUtils.getSignatureChar(methodBinding.getReturnType())); + res.setReceiverSignature(SignatureUtils.getSignatureChar(methodBinding.getDeclaringClass())); + res.setDeclarationSignature(SignatureUtils.getSignatureChar(methodBinding.getDeclaringClass())); + } else { + res.setSignature(new char[] {}); + res.setReceiverSignature(new char[] {}); + res.setDeclarationSignature(new char[] {}); + } + + if (Modifier.isStatic(binding.getModifiers()) && + this.toComplete.getLocationInParent() == QualifiedName.NAME_PROPERTY && + ((QualifiedName)this.toComplete.getParent()).getQualifier().resolveBinding() == null && + binding.getJavaElement() != null && + binding.getJavaElement().getParent() instanceof IType resolvedType && + resolvedType.exists()) { + // unresolved type for qualifier, suggest qualification + var typeProposal = toProposal(resolvedType); + var qualifier = ((QualifiedName)this.toComplete.getParent()).getQualifier(); + typeProposal.setReplaceRange(qualifier.getStartPosition(), qualifier.getStartPosition() + qualifier.getLength()); + var requiredProposals = res.getRequiredProposals(); + requiredProposals = requiredProposals == null ? new CompletionProposal[1] : Arrays.copyOf(requiredProposals, requiredProposals.length + 1); + requiredProposals[requiredProposals.length - 1] = typeProposal; + res.setRequiredProposals(requiredProposals); + } + + if (this.toComplete instanceof SimpleName + && !Set.of(QualifiedName.QUALIFIER_PROPERTY, ExpressionMethodReference.NAME_PROPERTY).contains(this.toComplete.getLocationInParent()) + && !this.prefix.isEmpty() + && !inJavadoc) { + res.setReplaceRange(this.toComplete.getStartPosition(), endOffset); + res.setTokenRange(this.toComplete.getStartPosition(), endOffset); + } else if (this.toComplete instanceof MethodInvocation) { + res.setReplaceRange(this.offset, this.offset); + res.setTokenRange(this.offset, 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 = DOMCompletionUtils.isInQualifiedName(this.toComplete); + int relevance = RelevanceConstants.R_DEFAULT; + relevance += RelevanceConstants.R_RESOLVED; + relevance += RelevanceConstants.R_INTERESTING; + relevance += (res.isConstructor() ? 0 : RelevanceUtils.computeRelevanceForCaseMatching(this.prefix.toCharArray(), binding.getName().toCharArray(), this.assistOptions)); + // TODO: I think this is a bug + if (!(this.toComplete instanceof ClassInstanceCreation)) { + relevance += 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); + } + relevance += (isInQualifiedName || res.getRequiredProposals() != null || inJavadoc ? 0 : RelevanceUtils.computeRelevanceForQualification(new String(res.getCompletion()).indexOf('.') >= 0, this.prefix, this.qualifiedPrefix)); + relevance += RelevanceConstants.R_NON_RESTRICTED; + relevance += RelevanceUtils.computeRelevanceForInheritance(this.qualifyingType, binding); + relevance += !Modifier.isStatic(binding.getModifiers()) && ((isInQualifiedName && !staticOnly()) || (inJavadoc && !res.isConstructor())) ? RelevanceConstants.R_NON_STATIC : 0; + relevance += binding instanceof IVariableBinding field && field.isEnumConstant() ? RelevanceConstants.R_ENUM + RelevanceConstants.R_ENUM_CONSTANT : 0; + res.setRelevance(relevance); + 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 staticOnly() { + if (this.toComplete.getLocationInParent() == QualifiedName.NAME_PROPERTY) { + return DOMCodeSelector.resolveBinding(((QualifiedName)this.toComplete.getParent()).getQualifier()) instanceof ITypeBinding; + } + return false; + } + + private CompletionProposal toProposal(TypeNameMatch typeNameMatch) { + return toProposal(typeNameMatch.getType(), typeNameMatch.getAccessibility(), typeNameMatch.getModifiers()); + } + + private CompletionProposal toProposal(IType type, int access) { + try { + return toProposal(type, access, type.getFlags()); + } catch (JavaModelException ex) { + return toProposal(type, access, 0); + } + } + + private CompletionProposal toProposal(IType type, int access, int modifiers) { + DOMInternalCompletionProposal res = createProposal(CompletionProposal.TYPE_REF); + char[] simpleName = type.getElementName().toCharArray(); + char[] signature = SignatureUtils.createSignature(type).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(); + if (completionContext.getCurrentTypeBinding() != null + && completionContext.getCurrentTypeBinding().getJavaElement() != null + && type.getFullyQualifiedName().equals(((IType)completionContext.getCurrentTypeBinding().getJavaElement()).getFullyQualifiedName())) { + completion.insert(0, cursor.getElementName()); + } else { + ASTNode currentName = this.toComplete instanceof QualifiedName qn && FAKE_IDENTIFIER.equals(qn.getName().toString()) ? qn.getName() : this.toComplete instanceof Name ? this.toComplete : null; + while (cursor instanceof IType currentType && (completion.isEmpty() || currentName == null || (!Objects.equals(currentName.toString(), currentType.getElementName()) && !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; + } + } + } + + Javadoc javadoc = (Javadoc) DOMCompletionUtils.findParent(this.toComplete, new int[] { ASTNode.JAVADOC }); + AbstractTypeDeclaration parentTypeDeclaration = DOMCompletionUtils.findParentTypeDeclaration(this.toComplete); + if (parentTypeDeclaration != null || javadoc != null) { + String fullTypeName = type.getFullyQualifiedName(); + boolean inImports = ((List)this.unit.imports()).stream().anyMatch(improt -> fullTypeName.equals(improt.getName().toString())); + boolean isInJavaLang = type.getFullyQualifiedName().startsWith("java.lang.") && !type.getFullyQualifiedName().substring("java.lang.".length()).contains("."); + IPackageBinding currentPackageBinding = completionContext.getCurrentTypeBinding() == null ? null : completionContext.getCurrentTypeBinding().getPackage(); + // TODO: what about qualified references to inner classes? + if (packageFrag != null && (currentPackageBinding == null + || (this.qualifiedPrefix.startsWith(packageFrag.getElementName()) && javadoc != null) + || (!packageFrag.getElementName().equals(currentPackageBinding.getName()) + && !packageFrag.getElementName().equals("java.lang"))) && !inImports && !isInJavaLang) { //$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.completionContext.isInJavadoc()) { + if (this.qualifiedPrefix.equals(this.prefix)) { + setRange(res); + } else { + setQualifiedRange(res); + } + } 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 if (this.toComplete instanceof ThisExpression thisExpression + && thisExpression.getQualifier() != null + && this.offset > (thisExpression.getQualifier().getStartPosition() + thisExpression.getQualifier().getLength())) { + setRange(res); + } else if (this.toComplete instanceof MethodRefParameter || this.toComplete instanceof MethodRef || this.toComplete instanceof TextElement){ + if (this.qualifiedPrefix.equals(this.prefix)) { + setRange(res); + } else { + setQualifiedRange(res); + } + } else { + res.setReplaceRange(this.toComplete.getStartPosition(), this.offset); + } + res.setFlags(modifiers); + 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); + } else { + setTokenRange(res); + } + boolean nodeInImports = DOMCompletionUtils.findParent(this.toComplete, new int[] { ASTNode.IMPORT_DECLARATION }) != null; + + IType topLevelClass = type; + while (topLevelClass.getDeclaringType() 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(); + } + boolean isExceptionExpected = DOMCompletionUtils.findParent(this.toComplete, new int[] { ASTNode.CATCH_CLAUSE }) != null // + || (DOMCompletionUtils.findParent(this.toComplete, new int[] { ASTNode.TAG_ELEMENT }) instanceof TagElement te && TagElement.TAG_THROWS.equals(te.getTagName())); + int relevance = RelevanceConstants.R_DEFAULT; + relevance += RelevanceConstants.R_RESOLVED; + relevance += RelevanceUtils.computeRelevanceForInteresting(type, expectedTypes); + relevance += RelevanceUtils.computeRelevanceForRestrictions(access, this.settings); + relevance += (isExceptionExpected && DOMCompletionUtils.findInSupers(type, "Ljava/lang/Exception;", this.workingCopyOwner, this.typeHierarchyCache) ? RelevanceConstants.R_EXCEPTION : 0); + relevance += RelevanceUtils.computeRelevanceForInheritance(this.qualifyingType, type); + relevance += RelevanceUtils.computeRelevanceForQualification(!"java.lang".equals(type.getPackageFragment().getElementName()) && !nodeInImports && !fromCurrentCU && !inSamePackage && !typeIsImported, this.prefix, this.qualifiedPrefix); + relevance += (type.getFullyQualifiedName().startsWith("java.") ? RelevanceConstants.R_JAVA_LIBRARY : 0); + // sometimes subclasses and superclasses are considered, sometimes they aren't + relevance += (isExceptionExpected ? RelevanceUtils.computeRelevanceForExpectingType(type, expectedTypes, this.workingCopyOwner, this.typeHierarchyCache) : RelevanceUtils.simpleComputeRelevanceForExpectingType(type, expectedTypes)); + relevance += 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 (parentTypeDeclaration != null) { + String packageName = ""; //$NON-NLS-1$ + PackageDeclaration packageDecl = this.unit.getPackage(); + if (packageDecl != null) { + packageName = packageDecl.getName().toString(); + } + if (!packageName.equals(type.getPackageFragment().getElementName()) && !new String(res.getCompletion()).equals(type.getFullyQualifiedName('.'))) { + // 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 toSuperConstructorProposal(IMethodBinding superConstructor) { + CompletionProposal res = toProposal(superConstructor); + res.setName(Keywords.SUPER); + res.setCompletion(CharOperation.concat(Keywords.SUPER, new char[] {'(', ')'})); + res.setTokenRange(res.getReplaceStart(), res.getReplaceEnd()); + + res.setRelevance(RelevanceConstants.R_DEFAULT + + RelevanceConstants.R_RESOLVED + + RelevanceConstants.R_INTERESTING + + RelevanceUtils.computeRelevanceForCaseMatching(this.prefix.toCharArray(), Keywords.SUPER, this.assistOptions) + + RelevanceConstants.R_NON_RESTRICTED + ); + + return res; + } + + private CompletionProposal toProposal(IJavaElement element) { + if (element instanceof IType type) { + return toProposal(type, IAccessRule.K_ACCESSIBLE); + } + DOMInternalCompletionProposal res = null; + IType parentType = (IType)element.getAncestor(IJavaElement.TYPE); + if (element instanceof IField field) { + res = createProposal(CompletionProposal.FIELD_REF); + res.setName(field.getElementName().toCharArray()); + try { + res.setSignature(field.getTypeSignature().toCharArray()); + } catch (JavaModelException ex) { + ILog.get().error(ex.getMessage(), ex); + } + res.setCompletion(field.getElementName().toCharArray()); + setRange(res); + res.setRelevance(RelevanceConstants.R_DEFAULT + + RelevanceConstants.R_RESOLVED + + RelevanceConstants.R_INTERESTING + + RelevanceConstants.R_CASE + + RelevanceConstants.R_NON_STATIC + + RelevanceConstants.R_NON_RESTRICTED + + RelevanceConstants.R_NO_PROBLEMS); + } + if (element instanceof IMethod method) { + res = createProposal(CompletionProposal.METHOD_REF); + try { + res.setSignature(method.getSignature().toCharArray()); + res.setParameterNames(Arrays.stream(method.getParameterNames()).map(String::toCharArray).toArray(char[][]::new)); + res.setParameterTypeNames(Arrays.stream(method.getParameterTypes()).map(String::toCharArray).toArray(char[][]::new)); + } catch (JavaModelException ex) { + ILog.get().error(ex.getMessage(), ex); + } + res.setName(method.getElementName().toCharArray()); + res.setCompletion((method.getElementName() + "()").toCharArray()); + setRange(res); + res.setRelevance(RelevanceConstants.R_DEFAULT + + RelevanceConstants.R_RESOLVED + + RelevanceConstants.R_INTERESTING + + RelevanceConstants.R_CASE + + RelevanceConstants.R_NON_STATIC + + RelevanceConstants.R_NON_RESTRICTED + + RelevanceConstants.R_NO_PROBLEMS); + } + if (res != null) { + if (element instanceof IMember member) { + try { + res.setFlags(member.getFlags()); + } catch (JavaModelException ex) { + ILog.get().error(ex.getMessage(), ex); + } + } + res.setDeclarationSignature(SignatureUtils.createSignature(parentType).toCharArray()); + res.setDeclarationTypeName(parentType.getFullyQualifiedName().toCharArray()); + res.setDeclarationPackageName(element.getAncestor(IJavaElement.PACKAGE_FRAGMENT).getElementName().toCharArray()); + } + return res; + } + + private CompletionProposal toNewMethodProposal(ITypeBinding parentType, String newMethodName) { + DOMInternalCompletionProposal res = createProposal(CompletionProposal.POTENTIAL_METHOD_DECLARATION); + res.setDeclarationSignature(SignatureUtils.getSignatureChar(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(ITypeBinding typeBinding, ASTNode referencedFrom, boolean exactType) { + + List proposals = new ArrayList<>(); + + AbstractTypeDeclaration parentType = (AbstractTypeDeclaration)DOMCompletionUtils.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(typeBinding.getKey()); + MethodDeclaration methodDeclaration = (MethodDeclaration)DOMCompletionUtils.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 = DOMCompletionUtils.findInSupers(referencedFromBinding, typeBinding.getKey()); + } + + String packageKey = typeBinding.getPackage().getKey(); + + boolean isInterface = typeBinding.isInterface(); + + boolean isExactName = this.toComplete instanceof ClassInstanceCreation cic && cic.getType().getStartPosition() + cic.getType().getLength() < this.offset; + + if (!this.requestor.isIgnored(CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION) && isInterface) { + // create an anonymous declaration: `new MyInterface() { }`; + proposals.add(toAnonymousConstructorProposal(typeBinding)); + // 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 { + if (!this.requestor.isIgnored(CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION) && isExactName) { + List constructors = Stream.of(typeBinding.getDeclaredMethods()).filter(IMethodBinding::isConstructor).toList(); + if (constructors.isEmpty()) { + IMethodBinding constructorBinding = ((ClassInstanceCreation)this.toComplete).resolveConstructorBinding(); + if (constructorBinding != null) { + constructors.add(constructorBinding); + } + } + for (IMethodBinding constructorBinding : constructors) { + proposals.add(toAnonymousClassProposal(typeBinding, constructorBinding)); + } + } + try { + List constructors = Stream.of(typeBinding.getErasure().getDeclaredMethods()).filter(IMethodBinding::isConstructor).toList(); + if (!constructors.isEmpty()) { + for (IMethodBinding constructor: constructors) { + if ( + // public + (constructor.getModifiers() & Modifier.PUBLIC) != 0 + // protected + || (includeProtected && (constructor.getModifiers() & Modifier.PROTECTED) != 0) + // private + || (includePrivate && (constructor.getModifiers() & Modifier.PRIVATE) != 0) + // package private + ||((constructor.getModifiers() & (Modifier.PRIVATE | Modifier.PROTECTED | Modifier.PUBLIC)) == 0 && packageKey.equals(referencedFromBinding.getPackage().getKey()))) { + proposals.add(toConstructorProposal(constructor, exactType, isExactName)); + } + } + } else { + proposals.add(toDefaultConstructorProposal(typeBinding, exactType, isExactName)); + } + } catch (JavaModelException e) { + ILog.get().error("Model exception while trying to collect constructors for completion", e); //$NON-NLS-1$ + } + } + + return proposals; + } + + private List toConstructorProposals(IType type, ASTNode referencedFrom, boolean exactType) { + + List proposals = new ArrayList<>(); + + AbstractTypeDeclaration parentType = (AbstractTypeDeclaration)DOMCompletionUtils.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)DOMCompletionUtils.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 = DOMCompletionUtils.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 + } + + boolean isExactName = this.toComplete instanceof ClassInstanceCreation cic && cic.getType().getStartPosition() + cic.getType().getLength() < this.offset; + + 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 { + if (!this.requestor.isIgnored(CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION) && isExactName) { + List constructors = new ArrayList<>(); + Stream.of(((ClassInstanceCreation)this.toComplete).resolveTypeBinding().getDeclaredMethods()).filter(IMethodBinding::isConstructor).forEach(constructors::add); + Collections.sort(constructors, (o1, o2) -> SignatureUtils.getSignature(o2).compareTo(SignatureUtils.getSignature(o1))); + if (constructors.isEmpty()) { + IMethodBinding constructorBinding = ((ClassInstanceCreation)this.toComplete).resolveConstructorBinding(); + if (constructorBinding != null) { + constructors.add(constructorBinding); + } + } + for (IMethodBinding constructorBinding : constructors) { + proposals.add(toAnonymousClassProposal(type, constructorBinding)); + } + } + 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, isExactName)); + } + } + } else { + proposals.add(toDefaultConstructorProposal(type, exactType, isExactName)); + } + } 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, boolean isExactName) throws JavaModelException { + DOMInternalCompletionProposal res; + if (isExactName) { + res = createProposal(CompletionProposal.METHOD_REF); + } else { + res = createProposal(CompletionProposal.CONSTRUCTOR_INVOCATION); + } + IType declaringClass = method.getDeclaringType(); + char[] simpleName = method.getElementName().toCharArray(); + if (isExactName) { + res.setCompletion(new char[0]); + } else { + res.setCompletion(new char[] {'(', ')'}); + } + res.setName(simpleName); + + char[] signature = SignatureUtils.getSignatureChar(method); + res.setSignature(signature); + res.setOriginalSignature(signature); + + IPackageFragment packageFragment = (IPackageFragment)declaringClass.getAncestor(IJavaElement.PACKAGE_FRAGMENT); + + if (declaringClass.getTypeParameters().length > 0) { + // we need to massage the signature; the signature doesn't include the type parameters + res.setDeclarationSignature(SignatureUtils.createSignature(declaringClass).replace(";", "<>;").toCharArray()); + } else { + res.setDeclarationSignature(SignatureUtils.createSignature(declaringClass).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 && !isExactName ? RelevanceConstants.R_EXACT_EXPECTED_TYPE : 0) + + (!isExactName ? RelevanceConstants.R_UNQUALIFIED : 0) + + RelevanceConstants.R_NON_RESTRICTED + + (!isExactName ? RelevanceConstants.R_CONSTRUCTOR : 0) + + (!isExactName ? RelevanceUtils.computeRelevanceForCaseMatching(this.prefix.toCharArray(), simpleName, this.assistOptions) : 0); + res.setRelevance(relevance); + + CompletionProposal typeProposal = toProposal(declaringClass); + typeProposal.setSignature(Signature.getTypeErasure(SignatureUtils.getSignatureForTypeKey(declaringClass.getKey()).toCharArray())); + 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 toConstructorProposal(IMethodBinding method, boolean isExactType, boolean isExactName) throws JavaModelException { + DOMInternalCompletionProposal res; + if (isExactName) { + res = createProposal(CompletionProposal.METHOD_REF); + } else { + res = createProposal(CompletionProposal.CONSTRUCTOR_INVOCATION); + } + ITypeBinding declaringClass = method.getDeclaringClass(); + char[] simpleName = method.getName().toCharArray(); + if (isExactName) { + res.setCompletion(new char[0]); + } else { + res.setCompletion(new char[] {'(', ')'}); + } + res.setName(simpleName); + + char[] signature = SignatureUtils.getSignatureChar(method); + res.setSignature(signature); + res.setOriginalSignature(signature); + + if (declaringClass.getTypeArguments().length == 0 + && declaringClass.getErasure().getTypeParameters().length > 0) { + // we need to massage the signature; the signature doesn't include the type parameters + res.setDeclarationSignature(Signature.getTypeErasure(SignatureUtils.getSignature(declaringClass)).replace(";", "<>;").toCharArray()); + } else if (declaringClass.getErasure().getTypeParameters().length > 0) { + res.setDeclarationSignature(SignatureUtils.getSignatureChar(declaringClass.getErasure())); + } else { + res.setDeclarationSignature(SignatureUtils.getSignature(declaringClass).toCharArray()); + } + res.setDeclarationTypeName(simpleName); + res.setDeclarationPackageName(declaringClass.getPackage().getName().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.getName().toCharArray()).toArray(char[][]::new)); + } + res.setCompatibleProposal(true); + + res.setReplaceRange(this.offset, this.offset); + res.setTokenRange(this.toComplete.getStartPosition(), this.offset); + res.setFlags(method.getModifiers()); + + int relevance = RelevanceConstants.R_DEFAULT + + RelevanceConstants.R_RESOLVED + + RelevanceConstants.R_INTERESTING + + (isExactType && !isExactName ? RelevanceConstants.R_EXACT_EXPECTED_TYPE : 0) + + (!isExactName ? RelevanceConstants.R_UNQUALIFIED : 0) + + RelevanceConstants.R_NON_RESTRICTED + + (!isExactName ? RelevanceConstants.R_CONSTRUCTOR : 0) + + (!isExactName ? RelevanceUtils.computeRelevanceForCaseMatching(this.prefix.toCharArray(), simpleName, this.assistOptions) : 0); + res.setRelevance(relevance); + + CompletionProposal typeProposal = toProposal(declaringClass.getErasure()); + typeProposal.setSignature(Signature.getTypeErasure(SignatureUtils.getSignatureChar(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, boolean isExactName) throws JavaModelException { + DOMInternalCompletionProposal res; + if (isExactName) { + res = createProposal(CompletionProposal.METHOD_REF); + } else { + 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(SignatureUtils.createSignature(type).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 && !isExactName ? RelevanceConstants.R_EXACT_EXPECTED_TYPE : 0) + + (!isExactName ? RelevanceConstants.R_UNQUALIFIED : 0) + + RelevanceConstants.R_NON_RESTRICTED + + (!isExactName ? RelevanceConstants.R_CONSTRUCTOR : 0); + 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 toDefaultConstructorProposal(ITypeBinding type, boolean isExactType, boolean isExactName) throws JavaModelException { + DOMInternalCompletionProposal res; + if (isExactName) { + res = createProposal(CompletionProposal.METHOD_REF); + } else { + res = createProposal(CompletionProposal.CONSTRUCTOR_INVOCATION); + } + char[] simpleName = type.getName().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); + + String packageName = type.getPackage().getName(); + + res.setDeclarationSignature(SignatureUtils.getSignatureChar(type)); + res.setDeclarationTypeName(simpleName); + res.setDeclarationPackageName(packageName.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.getName().toCharArray()).toArray(char[][]::new)); + } + res.setCompatibleProposal(true); + + res.setReplaceRange(this.offset, this.offset); + res.setTokenRange(this.toComplete.getStartPosition(), this.offset); + res.setFlags(type.getModifiers() & (Flags.AccPublic | Flags.AccPrivate | Flags.AccProtected)); + + int relevance = RelevanceConstants.R_DEFAULT + + RelevanceConstants.R_RESOLVED + + RelevanceConstants.R_INTERESTING + + (isExactType && !isExactName ? RelevanceConstants.R_EXACT_EXPECTED_TYPE : 0) + + (!isExactName ? RelevanceConstants.R_UNQUALIFIED : 0) + + RelevanceConstants.R_NON_RESTRICTED + + (!isExactName ? RelevanceConstants.R_CONSTRUCTOR : 0); + 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(SignatureUtils.createSignature(type).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 += RelevanceConstants.R_NON_RESTRICTED; + relevance += RelevanceUtils.computeRelevanceForCaseMatching(this.prefix.toCharArray(), type.getElementName().toCharArray(), this.assistOptions); + relevance += RelevanceConstants.R_EXACT_EXPECTED_TYPE; + relevance += RelevanceConstants.R_UNQUALIFIED; + 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(SignatureUtils.createSignature(type).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 toAnonymousConstructorProposal(ITypeBinding typeBinding) { + DOMInternalCompletionProposal res = createProposal(CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION); + res.setDeclarationSignature(SignatureUtils.getSignature(typeBinding).toCharArray()); + res.setDeclarationKey(typeBinding.getKey().toCharArray()); + res.setSignature( + CompletionEngine.createMethodSignature( + CharOperation.NO_CHAR_CHAR, + CharOperation.NO_CHAR_CHAR, + CharOperation.NO_CHAR, + CharOperation.NO_CHAR)); + + String packageName = typeBinding.getPackage().getName(); + + res.setDeclarationPackageName(packageName.toCharArray()); + res.setDeclarationTypeName(typeBinding.getName().toCharArray()); + res.setName(typeBinding.getName().toCharArray()); + + int relevance = RelevanceConstants.R_DEFAULT; + relevance += RelevanceConstants.R_RESOLVED; + relevance += RelevanceConstants.R_INTERESTING; + relevance += RelevanceConstants.R_NON_RESTRICTED; + relevance += RelevanceUtils.computeRelevanceForCaseMatching(this.prefix.toCharArray(), typeBinding.getName().toCharArray(), this.assistOptions); + relevance += RelevanceConstants.R_EXACT_EXPECTED_TYPE; + relevance += RelevanceConstants.R_UNQUALIFIED; + if (typeBinding.getPackage().getName().startsWith("java.")) { //$NON-NLS-1$ + relevance += RelevanceConstants.R_JAVA_LIBRARY; + } + + if(typeBinding.isClass()) { + relevance += RelevanceConstants.R_CLASS; +// relevance += computeRelevanceForException(typeName); // TODO: + } else if(typeBinding.isEnum()) { + relevance += RelevanceConstants.R_ENUM; + } else if(typeBinding.isInterface()) { + relevance += RelevanceConstants.R_INTERFACE; + } + + DOMInternalCompletionProposal typeProposal = createProposal(CompletionProposal.TYPE_REF); + typeProposal.setDeclarationSignature(typeBinding.getPackage().getName().toCharArray()); + typeProposal.setSignature(SignatureUtils.getSignatureChar(typeBinding)); + typeProposal.setPackageName(packageName.toCharArray()); + typeProposal.setTypeName(typeBinding.getName().toCharArray()); + typeProposal.setCompletion(typeBinding.getName().toCharArray()); + typeProposal.setFlags(typeBinding.getModifiers()); + 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 toAnonymousClassProposal(IType type, IMethodBinding existingConstructor) { + DOMInternalCompletionProposal res = createProposal(CompletionProposal.ANONYMOUS_CLASS_DECLARATION); + + res.setDeclarationSignature(SignatureUtils.stripTypeArgumentsFromKey(SignatureUtils.getSignature(existingConstructor.getDeclaringClass())).toCharArray()); + res.setDeclarationKey(existingConstructor.getDeclaringClass().getKey().toCharArray()); + res.setSignature(SignatureUtils.getSignatureChar(existingConstructor)); + res.setParameterNames(Stream.of(existingConstructor.getParameterNames()).map(String::toCharArray).toArray(char[][]::new)); + + IPackageFragment packageFragment = (IPackageFragment)type.getAncestor(IJavaElement.PACKAGE_FRAGMENT); + + res.setDeclarationPackageName(packageFragment.getElementName().toCharArray()); + res.setDeclarationTypeName(type.getElementName().toCharArray()); + + int relevance = RelevanceConstants.R_DEFAULT; + relevance += RelevanceConstants.R_RESOLVED; + relevance += RelevanceConstants.R_INTERESTING; + relevance += RelevanceConstants.R_NON_RESTRICTED; + + DOMInternalCompletionProposal typeProposal = createProposal(CompletionProposal.TYPE_REF); + typeProposal.setDeclarationSignature(packageFragment.getElementName().toCharArray()); + typeProposal.setSignature(SignatureUtils.createSignature(type).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.setFlags(Flags.AccPublic); + res.setReplaceRange(this.offset, this.offset); + res.setTokenRange(this.toComplete.getStartPosition(), this.offset); + res.setRelevance(relevance); + return res; + } + + private CompletionProposal toAnonymousClassProposal(ITypeBinding type, IMethodBinding existingConstructor) { + DOMInternalCompletionProposal res = createProposal(CompletionProposal.ANONYMOUS_CLASS_DECLARATION); + + res.setDeclarationSignature(SignatureUtils.stripTypeArgumentsFromKey(SignatureUtils.getSignature(existingConstructor.getDeclaringClass())).toCharArray()); + res.setDeclarationKey(existingConstructor.getDeclaringClass().getKey().toCharArray()); + res.setSignature(SignatureUtils.getSignatureChar(existingConstructor)); + res.setParameterNames(Stream.of(existingConstructor.getParameterNames()).map(String::toCharArray).toArray(char[][]::new)); + + String packageName = type.getPackage().getName(); + + res.setDeclarationPackageName(packageName.toCharArray()); + res.setDeclarationTypeName(type.getName().toCharArray()); + + int relevance = RelevanceConstants.R_DEFAULT; + relevance += RelevanceConstants.R_RESOLVED; + relevance += RelevanceConstants.R_INTERESTING; + relevance += RelevanceConstants.R_NON_RESTRICTED; + + DOMInternalCompletionProposal typeProposal = createProposal(CompletionProposal.TYPE_REF); + typeProposal.setDeclarationSignature(packageName.toCharArray()); + typeProposal.setSignature(SignatureUtils.getSignatureChar(type)); + typeProposal.setPackageName(packageName.toCharArray()); + typeProposal.setTypeName(type.getName().toCharArray()); + typeProposal.setCompletion(type.getName().toCharArray()); + typeProposal.setFlags(type.getModifiers()); + setRange(typeProposal); + typeProposal.setRelevance(relevance); + res.setRequiredProposals( new CompletionProposal[]{typeProposal}); + + 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(SignatureUtils.getSignatureChar(methodBinding)); + + res.setDeclarationSignature(SignatureUtils.getSignatureChar(methodBinding.getDeclaringClass())); + res.setSignature(SignatureUtils.getSignatureChar(methodBinding)); + if(methodBinding != methodBinding.getMethodDeclaration()) { + res.setOriginalSignature(SignatureUtils.getSignatureChar(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(SignatureUtils.getSignatureChar(variableBinding.getDeclaringClass())); + res.setSignature(SignatureUtils.getSignatureChar(variableBinding.getType()));; + 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(SignatureUtils.getSignatureChar(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(SignatureUtils.getSignatureChar(method.getDeclaringClass())); + proposal.setSignature(SignatureUtils.getSignatureChar(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()); + setTokenRange(res); + ASTNode replaceNode = this.toComplete; + if (replaceNode instanceof TextElement) { + replaceNode = replaceNode.getParent(); + } + boolean isAlreadyInline = this.textContent.charAt(this.completionContext.getTokenStart() - 1) == '{'; + boolean isInlineWithClosing = isAlreadyInline && this.textContent.length() > this.completionContext.getTokenEnd() + 1 && this.textContent.charAt(this.completionContext.getTokenEnd() + 1) == '}'; + res.setReplaceRange(this.completionContext.getTokenStart() - (isAlreadyInline ? 1 : 0), this.completionContext.getTokenEnd() + (isInlineWithClosing ? 2 : 1)); + 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, int startPos, int endPos) { + 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, startPos, endPos))); + } + + /** + * 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, int startPos, int endPos) { + + StringBuilder completion = new StringBuilder(moduleName); + if (DOMCompletionUtils.findParent(this.toComplete, new int[] {ASTNode.JAVADOC}) != null) { + completion.append("/"); + } + char[] completionChar = completion.toString().toCharArray(); + char[] moduleNameChar = moduleName.toCharArray(); + int relevance = RelevanceConstants.R_DEFAULT; + relevance += RelevanceConstants.R_RESOLVED; + relevance += RelevanceConstants.R_INTERESTING; + relevance += RelevanceUtils.computeRelevanceForCaseMatching(prefix, moduleNameChar, 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(moduleNameChar); + proposal.setDeclarationSignature(moduleNameChar); + proposal.setCompletion(completionChar); + + proposal.setReplaceRange(startPos, endPos); + + 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); + 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.setTokenRange(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_NON_INHERITED + + RelevanceUtils.computeRelevanceForCaseMatching(this.prefix.toCharArray(), Keywords.CLASS, assistOptions) +// + RelevanceUtils.computeRelevanceForExpectingType(typeBinding, this.expectedTypes) + ; + + DOMInternalCompletionProposal keywordProposal = createProposal(CompletionProposal.FIELD_REF); + keywordProposal.setCompletion(Keywords.CLASS); + + if (startPos == -1 && endPos == -1) { + setRange(keywordProposal); + } else { + keywordProposal.setReplaceRange(startPos, endPos); + keywordProposal.setTokenRange(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 Bindings defaultCompletionBindings; + 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(SignatureUtils.getSignatureChar(method.getDeclaringClass())); + res.setSignature(SignatureUtils.getSignatureChar(method)); + + IMethodBinding original = method.getMethodDeclaration(); + if (original != method) { + res.setOriginalSignature(SignatureUtils.getSignatureChar(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); + } + + /** + * Sets the replace and token ranges of a qualified completion based on the contents of the buffer. + * + * eg. the buffer content before the cursor is java.util.Li + * and you are replacing all of it with java.util.List + * + * @param completionProposal the proposal whose range to set + */ + private void setQualifiedRange(CompletionProposal completionProposal) { + int startPos = this.offset - this.prefix.length() - 1 - this.qualifiedPrefix.length(); + int cursor = this.offset; + while (cursor < this.textContent.length() + && (Character.isJavaIdentifierPart(this.textContent.charAt(cursor)) || this.textContent.charAt(cursor) == '.')) { + cursor++; + } + completionProposal.setReplaceRange(startPos, cursor); + completionProposal.setTokenRange(startPos, cursor); + } + + /** + * Sets the token range 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 setTokenRange(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.setTokenRange(startPos, cursor); + } + + private boolean isDeprecated(IJavaElement element) { + if (!this.assistOptions.checkDeprecation) { + return false; + } + if (Objects.equals(element.getAncestor(IJavaElement.COMPILATION_UNIT), this.modelUnit)) { + return false; + } + while (element instanceof IMember member) { + try { + if (Flags.isDeprecated(member.getFlags())) { + return true; + } + } catch (JavaModelException e) { + ILog.get().error(e.getMessage(), e); + } + element = element.getParent(); + } + return false; + } + + private boolean isVisible(IBinding binding) { + if (binding == null) { + return false; + } + if (!assistOptions.checkVisibility) { + return true; + } + if (Modifier.isPublic(binding.getModifiers())) { + return true; + } + if (Modifier.isPrivate(binding.getModifiers())) { + return binding.isEqualTo(completionContext.getCurrentTypeBinding()); + } + ITypeBinding declaringClass = getDeclaringClass(binding); + if (declaringClass == null) { + return false; + } + if (Modifier.isProtected(binding.getModifiers())) { + return DOMCompletionUtils.findInSupers(completionContext.getCurrentTypeBinding(), declaringClass); + } + return declaringClass.getPackage().isEqualTo(completionContext.getCurrentTypeBinding().getPackage()); + } + + private boolean isVisible(IJavaElement element) { + if (element == null) { + return false; + } + if (!assistOptions.checkVisibility) { + return true; + } + int flags; + try { + flags = element instanceof IType type ? type.getFlags() : + element instanceof IMethod method ? method.getFlags() : + element instanceof IField field ? field.getFlags() : + 0; + } catch (JavaModelException ex) { + ILog.get().error(ex.getMessage(), ex); + flags = 0; + } + if (element instanceof IType type) { + if (Modifier.isPublic(flags)) { + return true; + } + if (Modifier.isPrivate(flags)) { + return completionContext.getCurrentTypeBinding() != null && type.getKey().equals(completionContext.getCurrentTypeBinding().getKey()); + } + if (Modifier.isProtected(flags)) { + // TODO + } + return Objects.equals(type.getPackageFragment().getElementName(), this.unit.getPackage().getName().toString()); + } else { + IType type = element instanceof IMethod method ? method.getDeclaringType() : + element instanceof IField field ? field.getDeclaringType() : + null; + if (!isVisible(type)) { + return false; + } + if (Modifier.isPublic(flags)) { + return true; + } + if (Modifier.isPrivate(flags)) { + return completionContext.getCurrentTypeBinding() != null && type.getKey().equals(completionContext.getCurrentTypeBinding().getKey()); + } + if (Modifier.isProtected(flags)) { + // TODO + } + return Objects.equals(type.getPackageFragment().getElementName(), this.unit.getPackage().getName().toString()); + } + } + + 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; + } + + private CompletionProposal toLabelProposal(String label) { + DOMInternalCompletionProposal res = createProposal(CompletionProposal.LABEL_REF); + res.setCompletion(label.toCharArray()); + res.setName(label.toCharArray()); + setRange(res); + int relevance = RelevanceConstants.R_DEFAULT; + relevance += RelevanceConstants.R_RESOLVED; + relevance += RelevanceConstants.R_INTERESTING; + relevance += RelevanceConstants.R_NON_RESTRICTED; + relevance += RelevanceUtils.computeRelevanceForCaseMatching(completionContext.getToken(), label.toCharArray(), assistOptions); + res.setRelevance(relevance); + return res; + } + +} 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..f998b223f0c --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionEngineBuilder.java @@ -0,0 +1,234 @@ +/******************************************************************************* + * 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 java.util.List; + +import org.eclipse.jdt.core.Flags; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.JavaModelException; +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$ + + private static final String JAVA_LANG_PKG = "java.lang."; + + static void createMethod(IMethodBinding methodBinding, StringBuilder completion, ITypeBinding currentType, List importedTypes, String currentPackage) { + + // 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, currentType, importedTypes, currentPackage); + } + completion.append('>'); + completion.append(' '); + } + + // Return type + createType(methodBinding.getReturnType(), completion, currentType, importedTypes, currentPackage); + completion.append(' '); + + // Selector (name) + completion.append(methodBinding.getName()); + + completion.append('('); + + // Parameters + ITypeBinding[] parameterTypes = methodBinding.getParameterTypes(); + String[] parameterNames; + try { + if (methodBinding.getJavaElement() != null) { + parameterNames = ((IMethod)methodBinding.getJavaElement()).getParameterNames(); + } else { + parameterNames = methodBinding.getParameterNames(); + } + } catch (JavaModelException e) { + 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, currentType, importedTypes, currentPackage); + 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, currentType, importedTypes, currentPackage); + } + } + } + + static void createType(ITypeBinding type, StringBuilder completion, ITypeBinding currentType, List importedTypes, String currentPackage) { + if (type.isWildcardType() || type.isIntersectionType()) { + completion.append('?'); + if (type.isUpperbound() && type.getBound() != null) { + completion.append(' '); + completion.append(EXTENDS); + completion.append(' '); + createType(type.getBound(), completion, currentType, importedTypes, currentPackage); + if (type.getTypeBounds() != null) { + for (ITypeBinding bound : type.getTypeBounds()) { + completion.append(' '); + completion.append('&'); + completion.append(' '); + createType(bound, completion, currentType, importedTypes, currentPackage); + } + } + } else if (type.getBound() != null) { + completion.append(' '); + completion.append(SUPER); + completion.append(' '); + createType(type.getBound(), completion, currentType, importedTypes, currentPackage); + } + } else if (type.isArray()) { + createType(type.getElementType(), completion, currentType, importedTypes, currentPackage); + 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, currentType, importedTypes, currentPackage); + 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, currentType, importedTypes, currentPackage); + } + completion.append('>'); + } + } else { + boolean appended = false; + for (ITypeBinding importedType : importedTypes) { + if (importedType.getKey().equals(type.getErasure().getKey())) { + completion.append(type.getName()); + appended = true; + break; + } + } + ITypeBinding firstMatchingInheritedMemberType = getFirstMatchingInheritedMemberType(currentType, type.getName()); + if (firstMatchingInheritedMemberType != null && firstMatchingInheritedMemberType.getKey().equals(type.getKey())) { + completion.append(type.getName()); + appended = true; + } + if (!appended) { + String qualifiedName = type.getQualifiedName(); + String packageName = type.getPackage() != null ? type.getPackage().getName() : ""; + if (qualifiedName.startsWith(JAVA_LANG_PKG)) { + qualifiedName = qualifiedName.substring(JAVA_LANG_PKG.length()); + } else if (!packageName.isEmpty() && currentPackage.startsWith(packageName)) { + qualifiedName = qualifiedName.substring(packageName.length() + 1); + } + completion.append(qualifiedName); + } + } + } + + static void createTypeVariable(ITypeBinding typeVariable, StringBuilder completion, ITypeBinding currentType, List importedTypes, String currentPackage) { + 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, currentType, importedTypes, currentPackage); + } + 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, currentType, importedTypes, currentPackage); + } + } + } + + static ITypeBinding getFirstMatchingInheritedMemberType(ITypeBinding typeBinding, String typeName) { + return getFirstMatchingInheritedMemberType(typeBinding, typeName, true); + } + + static ITypeBinding getFirstMatchingInheritedMemberType(ITypeBinding typeBinding, String typeName, boolean canUsePrivate) { + for (ITypeBinding memberType : typeBinding.getDeclaredTypes()) { + if (typeName.equals(memberType.getName()) && (canUsePrivate || !Flags.isPrivate(memberType.getModifiers()))) { + return memberType; + } + } + if (typeBinding.getSuperclass() != null) { + return getFirstMatchingInheritedMemberType(typeBinding.getSuperclass(), typeName, false); + } + return null; + } + +} 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..202b2fab336 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionEngineJavadocUtil.java @@ -0,0 +1,215 @@ +/******************************************************************************* + * 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.Javadoc; +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()); + + /** + * + * @param project + * @param tagNode + * @param nodeSearchNode if this comment is unparented, this node is used to determine whether tags applicable to methods and fields should be suggested + * @return + */ + public static List getJavadocBlockTags(IJavaProject project, TagElement tagNode, ASTNode nodeSearchNode) { + 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, nodeSearchNode); + } + + /** + * + * @param project + * @param tagNode + * @param nodeSearchNode if this comment is unparented, this node is used to determine whether tags applicable to methods and fields should be suggested + * @return + */ + public static List getJavadocInlineTags(IJavaProject project, TagElement tagNode, ASTNode nodeSearchNode) { + 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, nodeSearchNode); + } + + private static List tagsForNode(List tagsForVersion, TagElement tagNode, ASTNode nodeSearchNode) { + boolean isField = DOMCompletionUtils.findParent(tagNode, new int[]{ ASTNode.FIELD_DECLARATION }) != null; + if (isField) { + return tagsForVersion.stream() // + .filter(tag -> FIELD_TAGS_SET.contains(tag)) // + .toList(); + } + + ASTNode astNode = DOMCompletionUtils.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 = DOMCompletionUtils.findParent(tagNode, new int[] {ASTNode.PACKAGE_DECLARATION}) != null; + if (isPackage) { + return tagsForVersion.stream() // + .filter(tag -> PACKAGE_TAGS_SET.contains(tag)) // + .toList(); + } + + boolean isUnparented = DOMCompletionUtils.findParent(tagNode, new int[] {ASTNode.JAVADOC}) instanceof Javadoc javadoc && javadoc.getParent() == null; + if (isUnparented) { + boolean onlyClassTags = DOMCompletionUtils.findParentTypeDeclaration(nodeSearchNode) == null; + return tagsForVersion.stream() // + .filter(tag -> !onlyClassTags || CLASS_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..eb0a020d5ac --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionEngineMethodDeclHandler.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * 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 java.util.stream.IntStream; + +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; +import org.eclipse.jdt.internal.compiler.classfmt.MethodInfo; +import org.eclipse.jdt.internal.core.BinaryMethod; + +/** + * 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 { + var res = List.of(m.getParameterNames()); + if (!res.isEmpty() && m instanceof BinaryMethod binary && binary.getElementInfo() instanceof MethodInfo info + && IntStream.range(0, res.size()).mapToObj(n -> "arg" + n).toList().equals(res) + && (info.getArgumentNames() == null || info.getArgumentNames().length == 0)) { + return null; + } + return res; + } catch (JavaModelException ex) { + ILog.get().warn(ex.getMessage(), ex); + } + } + if (binding.getDeclaringClass().isFromSource() || binding.getDeclaringClass().isArray()) { + return List.of(binding.getParameterNames()); + } + return null; + } +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionUtils.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionUtils.java new file mode 100644 index 00000000000..6ef99664bf2 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMCompletionUtils.java @@ -0,0 +1,317 @@ +/******************************************************************************* + * 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.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.function.Consumer; + +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.ITypeHierarchy; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.Signature; +import org.eclipse.jdt.core.WorkingCopyOwner; +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.Expression; +import org.eclipse.jdt.core.dom.ExpressionMethodReference; +import org.eclipse.jdt.core.dom.FieldAccess; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.IVariableBinding; +import org.eclipse.jdt.core.dom.InfixExpression; +import org.eclipse.jdt.core.dom.InstanceofExpression; +import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.PrefixExpression; +import org.eclipse.jdt.core.dom.QualifiedName; +import org.eclipse.jdt.core.dom.SuperMethodReference; +import org.eclipse.jdt.core.dom.TypeMethodReference; +import org.eclipse.jdt.core.dom.TypePattern; +import org.eclipse.jdt.internal.SignatureUtils; + +public class DOMCompletionUtils { + + /** + * 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) DOMCompletionUtils.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); + } + + /** + * Returns true if toFind is a superclass of root. + * + * @param root the class to begin searching in + * @param toFind the class to find + * @return true if toFind is a superclass of root + */ + public static boolean findInSupers(ITypeBinding root, ITypeBinding toFind) { + ITypeBinding superFind = toFind.getErasure(); + if( superFind != null ) { + String keyToFind = superFind.getKey(); + return findInSupers(root, keyToFind); + } + return false; + } + + /** + * Returns true if the type indicated by keyOfTypeToFind is a superclass of root. + * + * @param root the class to begin searching in + * @param keyOfTypeToFind the key of the class to find + * @return true if the type indicated by keyOfTypeToFind is a superclass of root + */ + public 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(); + if (current == null) { + continue; + } + 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; + } + + /** + * Returns true if the type indicated by keyOfTypeToFind is a superclass of root. + * + * @param root the class to begin searching in + * @param keyOfTypeToFind the key of the class to find + * @param workignCopyOwner the working copy owner + * @param hierarchyCache the cache of existing type hierarchies (rebuilding + * the type hierarchy is very expensive) + * @return true if the type indicated by keyOfTypeToFind is a superclass of root + */ + public static boolean findInSupers(IType root, String keyOfTypeToFind, WorkingCopyOwner workingCopyOwner, Map hierarchyCache) { + if (root.getKey().equals(keyOfTypeToFind)) { + return true; + } + try { + String signature = SignatureUtils.getSignatureForTypeKey(keyOfTypeToFind); + IType typeToFind = root.getJavaProject().findType(Signature.getSignatureQualifier(signature)+ "." + Signature.getSignatureSimpleName(signature)); + if (typeToFind != null) { + ITypeHierarchy hierarchy; + if (hierarchyCache.containsKey(keyOfTypeToFind)) { + hierarchy = hierarchyCache.get(keyOfTypeToFind); + } else { + hierarchy = typeToFind.newTypeHierarchy(root.getJavaProject(), workingCopyOwner, new NullProgressMonitor()); + hierarchyCache.put(keyOfTypeToFind, hierarchy); + } + + for (IType subType : hierarchy.getAllSubtypes(typeToFind)) { + if (subType.getKey().equals(root.getKey())) { + return true; + } + } + } + return false; + } catch (JavaModelException e) { + return false; + } + } + + /** + * Returns true if the given node is in a qualified name and false otherwise. + * + * @return true if the given node is in a qualified name and false otherwise + */ + public static boolean isInQualifiedName(ASTNode node) { + return Set.of(QualifiedName.NAME_PROPERTY, + FieldAccess.NAME_PROPERTY, + ExpressionMethodReference.NAME_PROPERTY, + TypeMethodReference.NAME_PROPERTY, + SuperMethodReference.NAME_PROPERTY).contains(node.getLocationInParent()) + || node instanceof FieldAccess + || node instanceof QualifiedName + || node instanceof SuperMethodReference + || node instanceof TypeMethodReference; + } + + /** + * 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 + */ + public record TrueFalseBindings(List trueBindings, List falseBindings) {} + + /** + * Represents collections of type casts that might be safe 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 + */ + public record TrueFalseCasts(List trueCasts, List falseCasts) {} + + /** + * Returns a list of variable bindings defined by type patterns in the given boolean expression. + * + * The list is separated into variables declared when the expression is true and variables declared when the expression is false. + * + * @param e the expression to collect the bindings for + * @return a list of variable bindings defined by type patterns in the given boolean expression + */ + public static 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<>(); + DOMCompletionUtils.visitChildren(e, ASTNode.TYPE_PATTERN, (TypePattern patt) -> { + typePatternBindings.add(patt.getPatternVariable().resolveBinding()); + }); + return new TrueFalseBindings(typePatternBindings, Collections.emptyList()); + } + } + + /** + * Returns a list of safe casts to the given variable based on the type checks in the given boolean expression. + * + * The list is separated into the casts that are safe when the expression is true and the casts that are safe when the expression is false. + * + * @param e the expression to check for type checks in + * @param castedBinding the binding that will be casted + * @return a list of safe casts to the given variable based on the type checks in the given boolean expression + */ + public static TrueFalseCasts collectTrueFalseCasts(Expression e, IVariableBinding castedBinding) { + if (e instanceof PrefixExpression prefixExpression && prefixExpression.getOperator() == PrefixExpression.Operator.NOT) { + TrueFalseCasts notBindings = collectTrueFalseCasts(prefixExpression.getOperand(), castedBinding); + return new TrueFalseCasts(notBindings.falseCasts(), notBindings.trueCasts()); + } else if (e instanceof InfixExpression infixExpression && (infixExpression.getOperator() == InfixExpression.Operator.CONDITIONAL_AND || infixExpression.getOperator() == InfixExpression.Operator.AND )) { + TrueFalseCasts left = collectTrueFalseCasts(infixExpression.getLeftOperand(), castedBinding); + TrueFalseCasts right = collectTrueFalseCasts(infixExpression.getRightOperand(), castedBinding); + List combined = new ArrayList<>(); + combined.addAll(left.trueCasts()); + combined.addAll(right.trueCasts()); + return new TrueFalseCasts(combined, Collections.emptyList()); + } else if (e instanceof InfixExpression infixExpression && (infixExpression.getOperator() == InfixExpression.Operator.CONDITIONAL_OR || infixExpression.getOperator() == InfixExpression.Operator.OR)) { + TrueFalseCasts left = collectTrueFalseCasts(infixExpression.getLeftOperand(), castedBinding); + TrueFalseCasts right = collectTrueFalseCasts(infixExpression.getRightOperand(), castedBinding); + List combined = new ArrayList<>(); + combined.addAll(left.falseCasts()); + combined.addAll(right.falseCasts()); + return new TrueFalseCasts(Collections.emptyList(), combined); + } else { + List castedTypes = new ArrayList<>(); + DOMCompletionUtils.visitChildren(e, ASTNode.INSTANCEOF_EXPRESSION, (InstanceofExpression expr) -> { + Expression leftOperand= expr.getLeftOperand(); + if (leftOperand instanceof Name name && name.resolveBinding() != null && name.resolveBinding().getKey().equals(castedBinding.getKey())) { + castedTypes.add(expr.getRightOperand().resolveBinding()); + } else if (leftOperand instanceof FieldAccess fieldAccess && fieldAccess.resolveFieldBinding() != null && fieldAccess.resolveFieldBinding().getKey().equals(castedBinding.getKey())) { + castedTypes.add(expr.getRightOperand().resolveBinding()); + } + }); + return new TrueFalseCasts(castedTypes, Collections.emptyList()); + } + } + +} 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..9bc031eb9e2 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMInternalCompletionProposal.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * 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.Signature; +import org.eclipse.jdt.core.compiler.CharOperation; +import org.eclipse.jdt.internal.core.NameLookup; + +class DOMInternalCompletionProposal extends InternalCompletionProposal { + + public DOMInternalCompletionProposal(int kind, int completionLocation) { + super(kind, completionLocation); + } + + @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; + } + +} \ No newline at end of file diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMThrownExceptionFinder.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMThrownExceptionFinder.java new file mode 100644 index 00000000000..4a2917e7205 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/DOMThrownExceptionFinder.java @@ -0,0 +1,235 @@ +/******************************************************************************* + * 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.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.Stack; + +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; +import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; +import org.eclipse.jdt.core.dom.CatchClause; +import org.eclipse.jdt.core.dom.ClassInstanceCreation; +import org.eclipse.jdt.core.dom.EnumDeclaration; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.MethodInvocation; +import org.eclipse.jdt.core.dom.RecordDeclaration; +import org.eclipse.jdt.core.dom.ThrowStatement; +import org.eclipse.jdt.core.dom.TryStatement; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.core.dom.UnionType; + +/** + * Rewrite of {@link org.eclipse.jdt.internal.codeassist.ThrownExceptionFinder} + * to work with API AST and API bindings. + */ +public class DOMThrownExceptionFinder extends ASTVisitor { + + private Set thrownExceptions; + private Stack> exceptionsStack; + private Set caughtExceptions; + private Set discouragedExceptions; + + /** + * Finds the thrown exceptions minus the ones that are already caught in + * previous catch blocks. Exception is already caught even if its super type is + * being caught. Also computes, separately, a list comprising of (a)those + * exceptions that have been caught already and (b)those exceptions that are + * thrown by the method and whose super type has been caught already. + */ + public void processThrownExceptions(TryStatement tryStatement) { + this.thrownExceptions = new LinkedHashSet<>(); + this.exceptionsStack = new Stack<>(); + this.caughtExceptions = new LinkedHashSet<>(); + this.discouragedExceptions = new LinkedHashSet<>(); + tryStatement.accept(this); + removeCaughtExceptions(tryStatement, true /* remove unchecked exceptions this time */); + } + + private void acceptException(ITypeBinding binding) { + if (binding != null && !binding.isRecovered()) { + this.thrownExceptions.add(binding); + } + } + + @Override + public void endVisit(MethodInvocation node) { + endVisitMethodInvocation(node.resolveMethodBinding()); + super.visit(node); + } + + @Override + public void endVisit(ClassInstanceCreation node) { + endVisitMethodInvocation(node.resolveConstructorBinding()); + super.visit(node); + } + + @Override + public void endVisit(ThrowStatement node) { + acceptException(node.getExpression().resolveTypeBinding()); + super.visit(node); + } + + private void endVisitMethodInvocation(IMethodBinding methodBinding) { + if (methodBinding == null) { + return; + } + for (ITypeBinding thrownException : methodBinding.getExceptionTypes()) { + acceptException(thrownException); + } + } + + public ITypeBinding[] getAlreadyCaughtExceptions() { + return this.caughtExceptions.toArray(ITypeBinding[]::new); + } + + public ITypeBinding[] getThrownUncaughtExceptions() { + return this.thrownExceptions.toArray(ITypeBinding[]::new); + } + + /** + * Returns all exceptions that are discouraged to use because (a) they are + * already caught in some inner try-catch, or (b) their super exception has + * already been caught. + * + * @return all discouraged exceptions + */ + public ITypeBinding[] getDiscouragedExceptions() { + return this.discouragedExceptions.toArray(ITypeBinding[]::new); + } + + @Override + public boolean visit(TypeDeclaration typeDeclaration) { + return false; + } + + @Override + public boolean visit(EnumDeclaration enumDeclaration) { + return false; + } + + @Override + public boolean visit(RecordDeclaration recordDeclaration) { + return false; + } + + @Override + public boolean visit(AnnotationTypeDeclaration annotationTypeDeclaration) { + return false; + } + + @Override + public boolean visit(AnonymousClassDeclaration anonymousClassDeclaration) { + return false; + } + + @Override + public boolean visit(TryStatement tryStatement) { + this.exceptionsStack.push(this.thrownExceptions); + Set exceptionSet = new LinkedHashSet<>(); + this.thrownExceptions = exceptionSet; + tryStatement.getBody().accept(this); + + removeCaughtExceptions(tryStatement, false); + + this.thrownExceptions = this.exceptionsStack.pop(); + + for (ITypeBinding value : exceptionSet) { + this.thrownExceptions.add(value); + } + + for (CatchClause catchClause : (List) tryStatement.catchClauses()) { + catchClause.getBody().accept(this); + } + return false; + } + + private void removeCaughtExceptions(TryStatement tryStatement, boolean recordUncheckedCaughtExceptions) { + for (CatchClause catchClause : (List) tryStatement.catchClauses()) { + Type exceptionType = catchClause.getException().getType(); + if (exceptionType instanceof UnionType unionType) { + for (Type unionMemberType : (List) unionType.types()) { + ITypeBinding unionMemberTypeBinding = unionMemberType.resolveBinding(); + // if it's recovered, it may be the node being completed + if (!unionMemberTypeBinding.isRecovered()) { + if (recordUncheckedCaughtExceptions) { + // is in outermost try-catch. Remove all caught exceptions, unchecked or checked + removeCaughtException(unionMemberTypeBinding); + this.caughtExceptions.add(unionMemberTypeBinding); + } else { + // is in some inner try-catch. Discourage already caught checked exceptions + // from being proposed in an outer catch. + if (!isUncheckedException(unionMemberTypeBinding)) { + this.discouragedExceptions.add(unionMemberTypeBinding); + } + } + } + } + } else { + ITypeBinding exception = catchClause.getException().getType().resolveBinding(); + if (!exception.isRecovered()) { + if (recordUncheckedCaughtExceptions) { + // is in outermost try-catch. Remove all caught exceptions, unchecked or checked + removeCaughtException(exception); + this.caughtExceptions.add(exception); + } else { + // is in some inner try-catch. Discourage already caught checked exceptions + // from being proposed in an outer catch + if (!isUncheckedException(exception)) { + this.discouragedExceptions.add(exception); + } + } + } + } + } + } + + private void removeCaughtException(ITypeBinding caughtException) { + List toRemoves = new ArrayList<>(); + for (ITypeBinding exception : this.thrownExceptions) { + if (caughtException.getKey().equals(exception.getKey())) { + toRemoves.add(exception); + } else if (exception.isSubTypeCompatible(caughtException)) { + // catching the sub-exception when super has been caught already will give an + // error + // so remove it from thrown list and lower the relevance for cases when it is + // found + // from searchAllTypes(..) + toRemoves.add(exception); + this.discouragedExceptions.add(exception); + } + } + for (ITypeBinding toRemove : toRemoves) { + this.thrownExceptions.remove(toRemove); + } + } + + private boolean isUncheckedException(ITypeBinding binding) { + if (binding.isArray()) { + return false; + } + ITypeBinding cursor = binding; + while (cursor != null) { + if ("Ljava/lang/RuntimeException;".equals(cursor.getKey())) { + return true; + } else if ("Ljava/lang/Error;".equals(cursor.getKey())) { + return true; + } + cursor = cursor.getSuperclass(); + } + return false; + } +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/ExpectedTypes.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/ExpectedTypes.java new file mode 100644 index 00000000000..98ece4237f9 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/ExpectedTypes.java @@ -0,0 +1,788 @@ +/******************************************************************************* + * 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.Arrays; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ArrayAccess; +import org.eclipse.jdt.core.dom.ArrayInitializer; +import org.eclipse.jdt.core.dom.AssertStatement; +import org.eclipse.jdt.core.dom.Assignment; +import org.eclipse.jdt.core.dom.Block; +import org.eclipse.jdt.core.dom.CastExpression; +import org.eclipse.jdt.core.dom.ClassInstanceCreation; +import org.eclipse.jdt.core.dom.ConditionalExpression; +import org.eclipse.jdt.core.dom.DoStatement; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.ForStatement; +import org.eclipse.jdt.core.dom.IMemberValuePairBinding; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.IfStatement; +import org.eclipse.jdt.core.dom.InfixExpression; +import org.eclipse.jdt.core.dom.InstanceofExpression; +import org.eclipse.jdt.core.dom.Javadoc; +import org.eclipse.jdt.core.dom.LambdaExpression; +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.Modifier; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.ParameterizedType; +import org.eclipse.jdt.core.dom.PrefixExpression; +import org.eclipse.jdt.core.dom.PrimitiveType; +import org.eclipse.jdt.core.dom.ReturnStatement; +import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; +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.TryStatement; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.dom.TypeParameter; +import org.eclipse.jdt.core.dom.VariableDeclaration; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jdt.core.dom.WhileStatement; +import org.eclipse.jdt.core.dom.YieldStatement; +import org.eclipse.jdt.internal.codeassist.impl.AssistOptions; + +/** + * Utility to evaluate what particular types are expected or not at a given position, used to + * compute completion item relevance. + * @implNote This is extracted and partly adapted from CompletionEngine. The current implementation + * is incomplete, further work is needed to support all constructs. + */ +public class ExpectedTypes { + private static enum TypeFilter { + SUPERTYPE, SUBTYPE; + } + + private static final Set CONDITION_LOCATIONS = Set.of( + IfStatement.EXPRESSION_PROPERTY, + WhileStatement.EXPRESSION_PROPERTY, + DoStatement.EXPRESSION_PROPERTY, + ForStatement.EXPRESSION_PROPERTY, + ConditionalExpression.EXPRESSION_PROPERTY + ); + + private final int offset; + private Collection expectedTypesFilters = Set.of(TypeFilter.SUPERTYPE, TypeFilter.SUBTYPE); + private final Collection expectedTypes = new LinkedHashSet<>(); + private final Collection uninterestingBindings = new LinkedHashSet<>(); + private final Collection forbiddenBindings = new LinkedHashSet<>(); + private final AssistOptions options; + private final ASTNode node; + private boolean isReady; + + public ExpectedTypes(AssistOptions options, ASTNode toComplete, int offset) { + this.offset = offset; + this.options = options; + this.node = toComplete; + } + + private void computeExpectedTypes(){ + ASTNode parent2 = this.node; + // find the parent that contains type information + while (parent2 != null) { + if (parent2 instanceof VariableDeclarationFragment fragment && this.offset > fragment.getName().getStartPosition() + fragment.getName().getLength()) { + var variable = fragment.resolveBinding(); + if (variable != null) { + this.expectedTypes.add(variable.getType()); + } + } + if (parent2 instanceof MethodInvocation method && this.offset > method.getName().getStartPosition() + method.getName().getLength()) { + if (method.arguments().size() == 1 && ((ASTNode)method.arguments().get(0)).getLength() == 0) { + if (method.getExpression() != null) { + var receiverType = method.getExpression().resolveTypeBinding(); + if (receiverType != null) { + Arrays.stream(receiverType.getDeclaredMethods()) + .filter(decl -> Objects.equals(decl.getName(), method.getName())) + .filter(decl -> decl.getParameterTypes().length > 0) + .map(decl -> decl.getParameterTypes()[0]) + .forEach(this.expectedTypes::add); + break; + } + } + // just methodName( => consider type of requestor + // continue + } else { + // consider params, implemented out of this loop + break; + } + } + if (parent2 instanceof InfixExpression) { + break; + } + if (parent2 instanceof ReturnStatement) { + break; + } + if (parent2 instanceof YieldStatement) { + parent2 = DOMCompletionUtils.findParent(parent2, new int[] {ASTNode.SWITCH_EXPRESSION}); + } + if (parent2 instanceof Block) { + break; + } + if (parent2 instanceof LambdaExpression) { + break; + } + if (parent2 instanceof TryStatement) { + break; + } + if (parent2 instanceof Assignment assign && this.offset > assign.getLeftHandSide().getStartPosition() + assign.getLeftHandSide().getLength()) { + this.expectedTypes.add(assign.resolveTypeBinding()); + return; + } + if (parent2 instanceof ClassInstanceCreation newObj && this.offset > newObj.getType().getStartPosition() + newObj.getType().getLength()) { + ITypeBinding binding = newObj.resolveTypeBinding(); + + if(binding != null) { + computeExpectedTypesForAllocationExpression( + binding, + newObj.arguments(), + newObj); + } + break; + } + if (parent2 instanceof CastExpression cast && this.offset > cast.getType().getStartPosition() + cast.getType().getLength()) { + this.expectedTypes.add(cast.getType().resolveBinding()); + return; + } + if (parent2.getLocationInParent() != null && CONDITION_LOCATIONS.contains(parent2.getLocationInParent())) { + this.expectedTypes.add(parent2.getAST().resolveWellKnownType(PrimitiveType.BOOLEAN.toString())); + return; + } + if (parent2.getLocationInParent() == ParameterizedType.TYPE_ARGUMENTS_PROPERTY && parent2.getParent() instanceof ParameterizedType parameterized) { + int index = parameterized.typeArguments().indexOf(parent2); + ITypeBinding parameterizedType = parameterized.resolveBinding(); + if (parameterizedType != null && index >= 0) { + if (parameterizedType.getTypeDeclaration() != null) { + parameterizedType = parameterizedType.getTypeDeclaration(); + } + ITypeBinding[] typeParameters = parameterizedType.getTypeParameters(); + if (typeParameters.length > index) { + ITypeBinding expectedType = typeParameters[index]; + if (expectedType != null) { + if (expectedType.isTypeVariable() || expectedType.isWildcardType()) { + if (expectedType.getSuperclass() != null) { + expectedTypes.add(expectedType.getSuperclass()); + } + expectedTypes.addAll(Arrays.asList(expectedType.getInterfaces())); + } else { + expectedTypes.add(expectedType); + } + } + if (this.expectedTypes.isEmpty()) { + this.expectedTypes.add(parent2.getAST().resolveWellKnownType(Object.class.getName())); + } + } + } + return; + } + if (parent2 instanceof SwitchCase switchCase) { + if (switchCase.getParent() instanceof SwitchStatement stmt) { + this.expectedTypes.add(stmt.getExpression().resolveTypeBinding()); + return; + } + if (switchCase.getParent() instanceof SwitchExpression expr) { + this.expectedTypes.add(expr.getExpression().resolveTypeBinding()); + return; + } + } + if (parent2 instanceof ArrayInitializer array) { + var arrayType = array.resolveTypeBinding(); + if (arrayType != null && arrayType.isArray()) { + this.expectedTypes.add(arrayType.getElementType()); + } + break; + } + if (parent2 instanceof ParameterizedType parameterizedType) { + ITypeBinding typeBinding = parameterizedType.getType().resolveBinding().getTypeDeclaration(); + // TODO: does this ever happen for other entries? + if (typeBinding.getTypeParameters()[0].isWildcardType() && typeBinding.getTypeParameters()[0].getBound() != null) { + this.expectedTypes.add(typeBinding.getTypeParameters()[0].getBound()); + } else { + this.expectedTypes.add(parent2.getAST().resolveWellKnownType(Object.class.getName())); + } + return; + } + if (parent2.getLocationInParent() == MemberValuePair.VALUE_PROPERTY && parent2.getParent() instanceof MemberValuePair mvp) { + var binding = mvp.resolveMemberValuePairBinding(); + if (binding != null) { + var methodBinding = binding.getMethodBinding(); + if (methodBinding != null) { + this.expectedTypes.add(methodBinding.getReturnType()); + break; + } + } + } + if (parent2.getLocationInParent() == LambdaExpression.BODY_PROPERTY && parent2.getParent() instanceof LambdaExpression lambda && !(parent2 instanceof Block)) { + var binding = lambda.resolveMethodBinding(); + if (binding != null) { + this.expectedTypes.add(binding.getReturnType()); + break; + } + } + if (parent2 instanceof TagElement te) { + if (TagElement.TAG_THROWS.equals(te.getTagName())) { + Javadoc javadoc = (Javadoc)DOMCompletionUtils.findParent(te, new int[] { ASTNode.JAVADOC }); + if (javadoc.getParent() instanceof MethodDeclaration methodDecl) { + this.expectedTypes.addAll(((List)methodDecl.thrownExceptionTypes()).stream().map(Type::resolveBinding).toList()); + } + } + break; + } + parent2 = parent2.getParent(); + } + ASTNode parent = parent2; + if (parent == null) { + return; // no construct to infer possible types + } + // default filter + this.expectedTypesFilters = Set.of(TypeFilter.SUBTYPE); + + // find types from parent + if(parent instanceof VariableDeclaration variable && !(parent instanceof TypeParameter) + && this.offset > variable.getName().getStartPosition() + variable.getName().getLength()) { + ITypeBinding binding = variable.resolveBinding().getType(); + if(binding != null) { + if(!(variable.getInitializer() instanceof ArrayInitializer)) { + this.expectedTypes.add(binding); + } else { + this.expectedTypes.add(binding.getComponentType()); + } + } + } else if(parent instanceof Assignment assignment) { + ITypeBinding binding = assignment.resolveTypeBinding(); + if(binding != null) { + this.expectedTypes.add(binding); + } + } else if (parent instanceof ReturnStatement) { + findLambda(parent) + .map(LambdaExpression::resolveMethodBinding) + .or(() -> findMethod(parent).map(MethodDeclaration::resolveBinding)) + .map(IMethodBinding::getReturnType) + .ifPresent(this.expectedTypes::add); + } else if (parent instanceof LambdaExpression lambda) { + if (lambda.getBody() == this.node) { + Optional.ofNullable(lambda.resolveMethodBinding()) + .map(IMethodBinding::getReturnType) + .ifPresent(this.expectedTypes::add); + } + } else if(parent instanceof CastExpression castExpression) { + ITypeBinding binding = castExpression.resolveTypeBinding(); + if(binding != null){ + this.expectedTypes.add(binding); + this.expectedTypesFilters = Set.of(TypeFilter.SUBTYPE, TypeFilter.SUPERTYPE); + } + } else if (parent instanceof MethodInvocation messageSend) { + if (messageSend.getExpression() != null) { + final ITypeBinding initialBinding = messageSend.getExpression().resolveTypeBinding(); + ITypeBinding currentBinding = initialBinding; // messageSend.actualReceiverType + boolean isStatic = messageSend.getExpression() instanceof Name name && name.resolveBinding() instanceof ITypeBinding; + while(currentBinding != null) { + computeExpectedTypesForMessageSend( + currentBinding, + messageSend.getName().toString(), + messageSend.arguments(), + initialBinding, + messageSend, + isStatic); + computeExpectedTypesForMessageSendForInterface( + currentBinding, + messageSend.getName().toString(), + messageSend.arguments(), + initialBinding, + messageSend, + isStatic); + currentBinding = currentBinding.getSuperclass(); + } + } else { + // find the param + IMethodBinding methodBinding = messageSend.resolveMethodBinding(); + if (this.node != parent) { + ASTNode cursor = this.node; + while (cursor != null && cursor.getParent() != messageSend) { + cursor = cursor.getParent(); + } + if (cursor != null && methodBinding != null) { + int i = messageSend.arguments().indexOf(cursor); + if (0 <= i && i < methodBinding.getParameterTypes().length) { + this.expectedTypes.add(methodBinding.getParameterTypes()[i]); + } else if (0 <= i && methodBinding.isVarargs()) { + this.expectedTypes.add(methodBinding.getParameterTypes()[methodBinding.getParameterTypes().length - 1]); + } + } + } else { + if (methodBinding.getParameterTypes().length > 0) { + this.expectedTypes.add(methodBinding.getParameterTypes()[0]); + } + } + } + } else if(parent instanceof ClassInstanceCreation allocationExpression) { + ITypeBinding binding = allocationExpression.resolveTypeBinding(); + if(binding != null) { + computeExpectedTypesForAllocationExpression( + binding, + allocationExpression.arguments(), + allocationExpression); + } + } else if(parent instanceof InstanceofExpression e) { + ITypeBinding binding = e.getLeftOperand().resolveTypeBinding(); + /*if (binding == null) { + if (scope instanceof BlockScope) + binding = e.expression.resolveType((BlockScope) scope); + else if (scope instanceof ClassScope) + binding = e.expression.resolveType((ClassScope) scope); + }*/ + if(binding != null){ + this.expectedTypes.add(binding); + this.expectedTypesFilters = Set.of(TypeFilter.SUBTYPE, TypeFilter.SUPERTYPE); + } + } else if(parent instanceof InfixExpression binaryExpression) { + var operator = binaryExpression.getOperator(); + if (operator == InfixExpression.Operator.EQUALS || operator == InfixExpression.Operator.NOT_EQUALS) { + ITypeBinding binding = binaryExpression.getLeftOperand().resolveTypeBinding(); + if (binding != null) { + this.expectedTypes.add(binding); + this.expectedTypesFilters = Set.of(TypeFilter.SUBTYPE, TypeFilter.SUPERTYPE); + } + } else if (operator == InfixExpression.Operator.PLUS) { + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.SHORT.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.INT.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.LONG.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.FLOAT.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.DOUBLE.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.CHAR.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.BYTE.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(String.class.getName())); + } else if (operator == InfixExpression.Operator.CONDITIONAL_AND + || operator == InfixExpression.Operator.CONDITIONAL_OR + || operator == InfixExpression.Operator.XOR) { + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.BOOLEAN.toString())); + } else { + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.SHORT.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.INT.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.LONG.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.FLOAT.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.DOUBLE.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.CHAR.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.BYTE.toString())); + } + if(operator == InfixExpression.Operator.LESS) { + if(binaryExpression.getLeftOperand() instanceof Name name){ + // TODO port further code to IBinding + /*Binding b = scope.getBinding(name.token, Binding.VARIABLE | Binding.TYPE, name, false); + if(b instanceof ReferenceBinding) { + TypeVariableBinding[] typeVariableBindings =((ReferenceBinding)b).typeVariables(); + if(typeVariableBindings != null && typeVariableBindings.length > 0) { + this.expectedTypes.add(typeVariableBindings[0]); + } + }*/ + } + } + } else if(parent instanceof PrefixExpression prefixExpression) { + var operator = prefixExpression.getOperator(); + if (operator == PrefixExpression.Operator.NOT) { + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.BOOLEAN.toString())); + } else if (operator == PrefixExpression.Operator.COMPLEMENT) { + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.SHORT.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.INT.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.LONG.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.CHAR.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.BYTE.toString())); + } else if (operator == PrefixExpression.Operator.PLUS + || operator == PrefixExpression.Operator.MINUS + || operator == PrefixExpression.Operator.INCREMENT + || operator == PrefixExpression.Operator.DECREMENT) { + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.SHORT.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.INT.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.LONG.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.FLOAT.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.DOUBLE.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.CHAR.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.BYTE.toString())); + } + } else if(parent instanceof ArrayAccess) { + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.SHORT.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.INT.toString())); + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.LONG.toString())); + } else if (parent instanceof DoStatement) { + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.BOOLEAN.toString())); + } // TODO port next code to IBinding + /*else if(parent instanceof ParameterizedSingleTypeReference ref) { + ITypeBinding expected = null; + if (this.parser.enclosingNode instanceof AbstractVariableDeclaration || + this.parser.enclosingNode instanceof ReturnStatement) { + // completing inside the diamond + if (this.parser.enclosingNode instanceof AbstractVariableDeclaration) { + AbstractVariableDeclaration abstractVariableDeclaration = (AbstractVariableDeclaration) this.parser.enclosingNode; + expected = abstractVariableDeclaration.initialization != null ? abstractVariableDeclaration.initialization.expectedType() : null; + } else { + ReturnStatement returnStatement = (ReturnStatement) this.parser.enclosingNode; + if (returnStatement.getExpression() != null) { + expected = returnStatement.getExpression().expectedType(); + } + } + this.expectedTypes.add(expected); + } else { + TypeVariableBinding[] typeVariables = ((ReferenceBinding)ref.resolvedType).typeVariables(); + int length = ref.typeArguments == null ? 0 : ref.typeArguments.length; + if(typeVariables != null && typeVariables.length >= length) { + int index = length - 1; + while(index > -1 && ref.typeArguments[index] != node) index--; + + TypeBinding bound = typeVariables[index].firstBound; + addExpectedType(bound == null ? scope.getJavaLangObject() : bound, scope); + } + } + } else if(parent instanceof ParameterizedQualifiedTypeReference ref) { + TypeReference[][] arguments = ref.typeArguments; + ITypeBinding expected = null; + if (this.parser.enclosingNode instanceof AbstractVariableDeclaration || + this.parser.enclosingNode instanceof ReturnStatement) { + // completing inside the diamond + if (this.parser.enclosingNode instanceof AbstractVariableDeclaration) { + AbstractVariableDeclaration abstractVariableDeclaration = (AbstractVariableDeclaration) this.parser.enclosingNode; + expected = abstractVariableDeclaration.initialization != null ? abstractVariableDeclaration.initialization.expectedType() : null; + } else { + ReturnStatement returnStatement = (ReturnStatement) this.parser.enclosingNode; + if (returnStatement.getExpression() != null) { + expected = returnStatement.getExpression().expectedType(); + } + } + this.expectedTypes.add(expected); + } else { + TypeVariableBinding[] typeVariables = ((ReferenceBinding)ref.resolvedType).typeVariables(); + if(typeVariables != null) { + int iLength = arguments == null ? 0 : arguments.length; + done: for (int i = 0; i < iLength; i++) { + int jLength = arguments[i] == null ? 0 : arguments[i].length; + for (int j = 0; j < jLength; j++) { + if(arguments[i][j] == node && typeVariables.length > j) { + TypeBinding bound = typeVariables[j].firstBound; + addExpectedType(bound == null ? scope.getJavaLangObject() : bound, scope); + break done; + } + } + } + } + } + } */ else if(parent instanceof MemberValuePair pair) { + Optional.ofNullable(pair.resolveMemberValuePairBinding()) + .map(IMemberValuePairBinding::getMethodBinding) + .map(IMethodBinding::getReturnType) + .map(ITypeBinding::getComponentType) + .ifPresent(this.expectedTypes::add); + // TODO port next code to IBinding + /*} else if (parent instanceof NormalAnnotation annotation) { + List memberValuePairs = annotation.values(); + if(memberValuePairs == null || memberValuePairs.isEmpty()) { + ITypeBinding annotationType = annotation.resolveTypeBinding(); + if(annotationType != null) { + IMethodBinding[] methodBindings = annotationType.getDeclaredMethods(); // TODO? Missing super interface methods? + if (methodBindings != null && + methodBindings.length > 0 && + CharOperation.equals(methodBindings[0].selector, VALUE)) { + boolean canBeSingleMemberAnnotation = true; + done : for (int i = 1; i < methodBindings.length; i++) { + if((methodBindings[i].getModifiers() & ClassFileConstants.AccAnnotationDefault) == 0) { + canBeSingleMemberAnnotation = false; + break done; + } + } + if (canBeSingleMemberAnnotation) { + this.assistNodeCanBeSingleMemberAnnotation = canBeSingleMemberAnnotation; + this.expectedTypes.add(methodBindings[0].getReturnType().getComponentType()); + } + } + } + } + } else if (parent instanceof AssistNodeParentAnnotationArrayInitializer parent1) { + if(parent1.type.resolvedType instanceof ReferenceBinding) { + MethodBinding[] methodBindings = + ((ReferenceBinding)parent1.type.resolvedType).availableMethods(); + if (methodBindings != null) { + for (MethodBinding methodBinding : methodBindings) { + if(CharOperation.equals(methodBinding.selector, parent1.name)) { + addExpectedType(methodBinding.returnType.leafComponentType(), scope); + break; + } + } + } + } + */ + } else if (parent instanceof TryStatement) { + DOMThrownExceptionFinder thrownExceptionFinder = new DOMThrownExceptionFinder(); + thrownExceptionFinder.processThrownExceptions((TryStatement) parent); + ITypeBinding[] bindings = thrownExceptionFinder.getThrownUncaughtExceptions(); + ITypeBinding[] alreadyCaughtExceptions = thrownExceptionFinder.getAlreadyCaughtExceptions(); + ITypeBinding[] discouragedExceptions = thrownExceptionFinder.getDiscouragedExceptions(); + if (bindings != null && bindings.length > 0) { + for (ITypeBinding binding : bindings) { + this.expectedTypes.add(binding); + } + this.expectedTypesFilters = Set.of(TypeFilter.SUPERTYPE); + } + if (alreadyCaughtExceptions != null && alreadyCaughtExceptions.length > 0) { + for (ITypeBinding alreadyCaughtException : alreadyCaughtExceptions) { + this.forbiddenBindings.add(alreadyCaughtException); + } + } + if (discouragedExceptions != null && discouragedExceptions.length > 0) { + for (ITypeBinding discouragedException : discouragedExceptions) { + this.uninterestingBindings.add(discouragedException); + // do not insert into known types. We do need these types to come from + // searchAllTypes(..) albeit with lower relevance + } + } + /* + } else if (parent instanceof SwitchStatement switchStatement) { + this.assistNodeIsInsideCase = assistNodeIsInsideCase(node, parent); + if (switchStatement.getExpression() != null && + switchStatement.getExpression().resolveTypeBinding() != null) { + if (this.assistNodeIsInsideCase && + switchStatement.getExpression().resolveTypeBinding().getName() == String.class.getName() && + this.compilerOptions.complianceLevel >= ClassFileConstants.JDK1_7) { + // set the field to true even though the expected types array will contain String as + // expected type to avoid traversing the array in every case later on. + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=343476 + this.assistNodeIsString = true; + } + this.expectedTypes.add(switchStatement.getExpression().resolveTypeBinding()); + } + */ + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=253008, flag boolean as the expected + // type if we are completing inside if(), for (; ;), while() and do while() + } else if (parent instanceof WhileStatement) { // covers both while and do-while loops + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.BOOLEAN.toString())); + } else if (parent instanceof IfStatement) { + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.BOOLEAN.toString())); + } else if (parent instanceof AssertStatement assertStatement) { + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=274466 + // If the assertExpression is same as the node , then the assistNode is the conditional part of the assert statement + if (assertStatement.getExpression() == this.node) { + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.BOOLEAN.toString())); + } + } else if (parent instanceof ForStatement forStatement) { + if (forStatement.getExpression().equals(this.node)) { + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.BOOLEAN.toString())); + } + } else if (parent instanceof Javadoc) { // Expected types for javadoc + findMethod(parent) + .map(MethodDeclaration::resolveBinding) + .map(IMethodBinding::getExceptionTypes) + .map(Arrays::stream) + .orElseGet(Stream::of) + .forEach(this.expectedTypes::add); + } else if (parent instanceof ConditionalExpression conditionalExpr) { + if (conditionalExpr.getExpression() == this.node) { + this.expectedTypes.add(this.node.getAST().resolveWellKnownType(PrimitiveType.BOOLEAN.toString())); + } else { + ITypeBinding typeBinding = conditionalExpr.resolveTypeBinding(); + if (typeBinding != null && !typeBinding.isRecovered()) { + this.expectedTypes.add(typeBinding); + } + } + } + + // Guard it, otherwise we end up with a empty array which cause issues down the line +// if((this.expectedTypesPtr > -1) && ((this.expectedTypesPtr + 1) != this.expectedTypes.length)) { +// System.arraycopy(this.expectedTypes, 0, this.expectedTypes = new TypeBinding[this.expectedTypesPtr + 1], 0, this.expectedTypesPtr + 1); +// } + } + + private void computeExpectedTypesForAllocationExpression( + ITypeBinding binding, + List arguments, + ASTNode invocationSite) { + + if (arguments == null) + return; + + IMethodBinding[] methods = avaiableMethods(binding).toArray(IMethodBinding[]::new); + nextMethod : for (IMethodBinding method : methods) { + if (!method.isConstructor()) continue nextMethod; + + if (method.isSynthetic()) continue nextMethod; + + //if (this.options.checkVisibility && !method.canBeSeenBy(invocationSite, scope)) continue nextMethod; + + ITypeBinding[] parameters = method.getParameterTypes(); + if(parameters.length < arguments.size()) + continue nextMethod; + + int length = arguments.size() - 1; + + for (int j = 0; j < length; j++) { + Expression argument = arguments.get(j); + ITypeBinding argType = argument.resolveTypeBinding(); + if(argType != null && !argType.isSubTypeCompatible(parameters[j])) + continue nextMethod; + } + + if (arguments.size() > 0) { + ITypeBinding expectedType = method.getParameterTypes()[arguments.size() - 1]; + if(expectedType != null) { + this.expectedTypes.add(expectedType); + } + } else if (arguments.isEmpty() && parameters.length > 0 && parameters[0] != null) { + this.expectedTypes.add(parameters[0]); + } + } + } + private void computeExpectedTypesForMessageSend( + ITypeBinding binding, + String selector, + List arguments, + ITypeBinding receiverType, + ASTNode invocationSite, + boolean isStatic) { + + if (arguments == null) + return; + + IMethodBinding[] methods = avaiableMethods(binding).toArray(IMethodBinding[]::new); + nextMethod : for (IMethodBinding method : methods) { + if (method.isSynthetic()) continue nextMethod; + + //if (method.isDefaultAbstract()) continue nextMethod; + + if (method.isConstructor()) continue nextMethod; + + if (isStatic && !Modifier.isStatic(method.getModifiers())) continue nextMethod; + + //if (this.options.checkVisibility && !method.canBeSeenBy(receiverType, invocationSite, scope)) continue nextMethod; + + if(!Objects.equals(method.getName(), selector)) continue nextMethod; + + ITypeBinding[] parameters = method.getParameterTypes(); + if(parameters.length < arguments.size()) + continue nextMethod; + + if (arguments.isEmpty() && parameters.length > 0) { + this.expectedTypes.add(parameters[0]); + } else { + int length = arguments.size() - 1; + int completionArgIndex = arguments.size() - 1; + + for (int j = 0; j < length; j++) { + Expression argument = arguments.get(j); + ITypeBinding argType = argument.resolveTypeBinding(); + if(argType != null && !argType.getErasure().isSubTypeCompatible(parameters[j].getErasure())) + continue nextMethod; + + /*if((argument.getStartPosition() >= this.startPosition) + && (argument.getStartPosition() + argument.getLength() <= this.endPosition)) { + completionArgIndex = j; + }*/ + } + if (completionArgIndex >= 0) { + ITypeBinding expectedType = method.getParameterTypes()[completionArgIndex]; + if(expectedType != null) { + this.expectedTypes.add(expectedType); + } + } + } + } + } + private void computeExpectedTypesForMessageSendForInterface( + ITypeBinding binding, + String selector, + List arguments, + ITypeBinding receiverType, + ASTNode invocationSite, + boolean isStatic) { + + ITypeBinding[] itsInterfaces = binding.getInterfaces(); + int itsLength = itsInterfaces.length; + ITypeBinding[] interfacesToVisit = itsInterfaces; + int nextPosition = interfacesToVisit.length; + + for (int i = 0; i < nextPosition; i++) { + ITypeBinding currentType = interfacesToVisit[i]; + computeExpectedTypesForMessageSend( + currentType, + selector, + arguments, + receiverType, + invocationSite, + isStatic); + + itsInterfaces = currentType.getInterfaces(); + itsLength = itsInterfaces.length; + if (nextPosition + itsLength >= interfacesToVisit.length) { + System.arraycopy(interfacesToVisit, 0, interfacesToVisit = new ITypeBinding[nextPosition + itsLength + 5], 0, nextPosition); + } + nextInterface : for (int a = 0; a < itsLength; a++) { + ITypeBinding next = itsInterfaces[a]; + for (int b = 0; b < nextPosition; b++) { + if (Objects.equals(next, interfacesToVisit[b])) continue nextInterface; + } + interfacesToVisit[nextPosition++] = next; + } + } + } + + + private static Optional findMethod(ASTNode node) { + while (node != null && !(node instanceof MethodDeclaration)) { + node = node.getParent(); + } + return Optional.ofNullable((MethodDeclaration)node); + } + private static Optional findLambda(ASTNode node) { + while (node != null && !(node instanceof LambdaExpression)) { + node = node.getParent(); + } + return Optional.ofNullable((LambdaExpression)node); + } + + private LinkedHashSet avaiableMethods(ITypeBinding typeBinding) { + LinkedHashSet res = new LinkedHashSet<>(); + res.addAll(Arrays.asList(typeBinding.getDeclaredMethods())); + for (ITypeBinding interfac : typeBinding.getInterfaces()) { + res.addAll(avaiableMethods(interfac)); + } + if (typeBinding.getSuperclass() != null) { + res.addAll(avaiableMethods(typeBinding.getSuperclass())); + } + return res; + } + + public List getExpectedTypes() { + if (!this.isReady) { + computeExpectedTypes(); + this.expectedTypes.removeIf(ITypeBinding::isNullType); + this.isReady = true; + } + return new ArrayList<>(this.expectedTypes); + } + + public Collection getUninterestingTypes() { + return this.uninterestingBindings; + } + + public boolean allowsSubtypes() { + return this.expectedTypesFilters.contains(TypeFilter.SUBTYPE); + } + public boolean allowsSupertypes() { + return this.expectedTypesFilters.contains(TypeFilter.SUPERTYPE); + } +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/KeyUtils.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/KeyUtils.java new file mode 100644 index 00000000000..1cca7b8b02a --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/KeyUtils.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * 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 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.codeassist; + +/** + * Utility methods for manipulating binding keys. + */ +public class KeyUtils { + + public static final String OBJECT_KEY = "Ljava/lang/Object;"; + + /** + * Returns the given method key with the owner type and return type removed. + * + * @param methodKey the method key to remove the owner type and return type from + * @return the given method key with the owner type and return type removed + */ + public static final String getMethodKeyWithOwnerTypeAndReturnTypeRemoved(String methodKey) { + return methodKey.substring(methodKey.indexOf('.'), methodKey.lastIndexOf(')') + 1); + } + +} 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..bfec6cdaaaa --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/codeassist/RelevanceUtils.java @@ -0,0 +1,284 @@ +/******************************************************************************* + * 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.Map; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.jdt.core.IAccessRule; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.ITypeHierarchy; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.WorkingCopyOwner; +import org.eclipse.jdt.core.compiler.CharOperation; +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.PrimitiveType; +import org.eclipse.jdt.internal.codeassist.impl.AssistOptions; +import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; + +/** + * 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 (expectedType.isRawType() && Objects.equals(expectedType.getErasure().getQualifiedName(), proposalType.getErasure().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. + var oneErasure = expectedType.getErasure(); + var otherErasure = proposalType.getErasure(); + if ((oneErasure.isPrimitive() && otherErasure.getQualifiedName().startsWith("java.lang.")) || + (otherErasure.isPrimitive() && oneErasure.getQualifiedName().startsWith("java.lang."))) { + if (oneErasure.getName().equalsIgnoreCase(otherErasure.getName()) || + Set.of(oneErasure.getName().toLowerCase(), otherErasure.getName().toLowerCase()).equals(Set.of("char", "character"))) { + relevance = CompletionEngine.R_EXPECTED_TYPE; + } + } + if (KeyUtils.OBJECT_KEY.equals(expectedType.getKey()) && proposalType.isPrimitive()) { + // can be autoboxed + relevance = CompletionEngine.R_EXPECTED_TYPE; + } + } + return relevance; + } + return 0; + } + + static int computeRelevanceForExpectingType(IType proposalType, ExpectedTypes expectedTypes, WorkingCopyOwner workingCopyOwner, Map typeHierarchyCache) { + if (proposalType != null) { + IPackageFragment packageFragment = (IPackageFragment)proposalType.getAncestor(IJavaElement.PACKAGE_FRAGMENT); + 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.getElementName())) { + return RelevanceConstants.R_VOID; + } + for (ITypeBinding expectedType : expectedTypes.getExpectedTypes()) { + if(expectedTypes.allowsSubtypes() + && DOMCompletionUtils.findInSupers(proposalType, expectedType.getKey(), workingCopyOwner, typeHierarchyCache)) { + if (expectedType.getKey().equals(proposalType.getKey())) { + return RelevanceConstants.R_EXACT_EXPECTED_TYPE; + // ??? I'm just guessing on the default packages name here, it might be the empty string + } else if (packageFragment != null && packageFragment.getElementName().equals("")) { + return RelevanceConstants.R_PACKAGE_EXPECTED_TYPE; + } + relevance = RelevanceConstants.R_EXPECTED_TYPE; + + } + if(expectedTypes.allowsSupertypes() && DOMCompletionUtils.findInSupers(expectedType, proposalType.getKey())) { + if (expectedType.getKey().equals(proposalType.getKey())) { + 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. + var oneErasure = expectedType.getErasure(); + if ((oneErasure.isPrimitive() && proposalType.getFullyQualifiedName().startsWith("java.lang."))) { + if (oneErasure.getName().equalsIgnoreCase(proposalType.getElementName()) || + Set.of(oneErasure.getName().toLowerCase(), proposalType.getElementName().toLowerCase()).equals(Set.of("char", "character"))) { + relevance = CompletionEngine.R_EXPECTED_TYPE; + } + } + } + return relevance; + } + return 0; + } + + /** + * Returns the appropriate relevance based on if the given type is expected, without taking into account sub/super classes. + * + * @param type the type completion to get the relevance of + * @param expectedTypes the information on the expected type + * @return the appropriate relevance based on if the given type is expected, without taking into account sub/super classes + */ + static int simpleComputeRelevanceForExpectingType(IType type, ExpectedTypes expectedTypes) { + return (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); + } + + /** + * Returns the appropriate relevance number based on if the given member is directly implemented in the qualifying type. + * + * @see CompletionEngine#computeRelevanceForInheritance(ReferenceBinding, ReferenceBinding) + * @param qualifyingType the qualifying type + * @param member the member to check if it's directly or indirectly inherited + * @return the appropriate relevance number based on if the given member is directly implemented in the qualifying type + */ + static int computeRelevanceForInheritance(ITypeBinding qualifyingType, IBinding member) { + if (qualifyingType == null) { + return 0; + } + if (member instanceof IMethodBinding methodBinding) { + if (methodBinding.getDeclaringClass().getKey().equals(qualifyingType.getKey())) { + return RelevanceConstants.R_NON_INHERITED; + } + } else if (member instanceof IVariableBinding varBinding) { + if (varBinding.getDeclaringClass().getKey().equals(qualifyingType.getKey())) { + return RelevanceConstants.R_NON_INHERITED; + } + } else if (member instanceof ITypeBinding typeBinding) { + if (typeBinding.getDeclaringClass().getKey().equals(qualifyingType.getKey())) { + return RelevanceConstants.R_NON_INHERITED; + } + } + return 0; + } + + /** + * Returns the appropriate relevance number based on if the given member type is directly implemented in the qualifying type. + * + * @see CompletionEngine#computeRelevanceForInheritance(ReferenceBinding, ReferenceBinding) + * @param qualifyingType the qualifying type + * @param memberType the member type to check if it's directly or indirectly inherited + * @return the appropriate relevance number based on if the given member is directly implemented in the qualifying type + */ + static int computeRelevanceForInheritance(ITypeBinding qualifyingType, IType memberType) { + if (qualifyingType == null || memberType.getDeclaringType() == null) { + return 0; + } + if (memberType.getDeclaringType().getKey().equals(qualifyingType.getKey())) { + return RelevanceConstants.R_NON_INHERITED; + } + return 0; + } + + static int computeRelevanceForInteresting(IType potentiallyInterestingType, ExpectedTypes expectedTypes) { + String typeKey = potentiallyInterestingType.getKey(); + for (ITypeBinding uninterestingType : expectedTypes.getUninterestingTypes()) { + if (uninterestingType.getKey().equals(typeKey)) { + return 0; + } + } + return RelevanceConstants.R_INTERESTING; + } + + /** + * Returns the appropriate relevance number based on the access rule, taking into account if access warnings/errors are disabled. + * + * i.e. if the errors for accessing an inaccessible class are disabled, + * the relevance number is the same as if there are no access restrictions. + * + * @param accessRuleKind the access rule + * @param settings the compiler settings + * @return the appropriate relevance number + */ + static int computeRelevanceForRestrictions(int accessRuleKind, Map settings) { + if (accessRuleKind == IAccessRule.K_ACCESSIBLE) { + return RelevanceConstants.R_NON_RESTRICTED; + } + if (accessRuleKind == IAccessRule.K_NON_ACCESSIBLE + && settings.get(JavaCore.COMPILER_PB_FORBIDDEN_REFERENCE).equals(JavaCore.IGNORE)) { + return RelevanceConstants.R_NON_RESTRICTED; + } + if (accessRuleKind == IAccessRule.K_DISCOURAGED + && settings.get(JavaCore.COMPILER_PB_DISCOURAGED_REFERENCE).equals(JavaCore.IGNORE)) { + return RelevanceConstants.R_NON_RESTRICTED; + } + return 0; + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/DOMASTNodeUtils.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/DOMASTNodeUtils.java new file mode 100644 index 00000000000..47e811a6ce8 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/DOMASTNodeUtils.java @@ -0,0 +1,271 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jdt.internal.core.search; + +import java.util.List; + +import org.eclipse.jdt.core.IInitializer; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IParent; +import org.eclipse.jdt.core.ISourceRange; +import org.eclipse.jdt.core.ISourceReference; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; +import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; +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.ConstructorInvocation; +import org.eclipse.jdt.core.dom.EnumConstantDeclaration; +import org.eclipse.jdt.core.dom.FieldAccess; +import org.eclipse.jdt.core.dom.FieldDeclaration; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.IVariableBinding; +import org.eclipse.jdt.core.dom.ImportDeclaration; +import org.eclipse.jdt.core.dom.Initializer; +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.MethodReference; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.PackageDeclaration; +import org.eclipse.jdt.core.dom.SuperConstructorInvocation; +import org.eclipse.jdt.core.dom.SuperFieldAccess; +import org.eclipse.jdt.core.dom.SuperMethodInvocation; +import org.eclipse.jdt.core.dom.SuperMethodReference; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.dom.TypeParameter; +import org.eclipse.jdt.core.dom.VariableDeclaration; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jdt.core.dom.VariableDeclarationStatement; + +public class DOMASTNodeUtils { + + /** + * @param node + * @return an existing (in model) Java element enclosing the node + */ + public static IJavaElement getEnclosingJavaElement(ASTNode node) { + if (node == null) { + return null; + } + if (node instanceof AbstractTypeDeclaration + || node instanceof MethodDeclaration + || node instanceof FieldDeclaration + || node instanceof Initializer + || node instanceof ImportDeclaration + || node instanceof PackageDeclaration + || node instanceof CompilationUnit + || node instanceof AnnotationTypeMemberDeclaration + || node instanceof Initializer + || node.getLocationInParent() == FieldDeclaration.FRAGMENTS_PROPERTY) { + return getDeclaringJavaElement(node); + } + return getEnclosingJavaElement(node.getParent()); + } + + /** + * @param node + * @return an existing (in model) Java element enclosing the node + */ + public static IJavaElement getLocalJavaElement(ASTNode node) { + if (node == null) { + return null; + } + if (node instanceof VariableDeclaration variable) { + IVariableBinding vb = variable.resolveBinding(); + if( vb != null ) + return vb.getJavaElement(); + } + if (node instanceof VariableDeclarationStatement variable && !variable.fragments().isEmpty()) { + IVariableBinding vb = ((List)variable.fragments()).iterator().next().resolveBinding(); + if( vb != null ) + return vb.getJavaElement(); + } + if (node instanceof TypeParameter typeParam) { + return typeParam.resolveBinding().getJavaElement(); + } + if (node instanceof AbstractTypeDeclaration + || node instanceof MethodDeclaration + || node instanceof FieldDeclaration + || node instanceof Initializer + || node instanceof ImportDeclaration + || node instanceof PackageDeclaration + || node instanceof CompilationUnit + || node instanceof AnnotationTypeMemberDeclaration + || node instanceof Initializer + || node.getLocationInParent() == FieldDeclaration.FRAGMENTS_PROPERTY) { + return getEnclosingJavaElement(node); + } + return getLocalJavaElement(node.getParent()); + } + + public static IJavaElement getDeclaringJavaElement(ASTNode key) { + if (key instanceof CompilationUnit unit) { + return unit.getJavaElement(); + } + IJavaElement je = findElementForNodeViaDirectBinding(key); + if( je != null ) { + return je; + } + IJavaElement je2 = findElementForNodeCustom(key); + return je2; + } + + private static IJavaElement findElementForNodeCustom(ASTNode key) { + if( key instanceof FieldDeclaration fd ) { + List fragments = fd.fragments(); + if( fragments.size() > 0 ) { + VariableDeclarationFragment vdf = (VariableDeclarationFragment)fragments.get(0); + if( vdf != null ) { + IJavaElement ret = findElementForNodeViaDirectBinding(vdf); + return ret; + } + } + } + if( key instanceof Initializer i) { + ASTNode parentNode = i.getParent(); + int domOccurance = -1; + if( parentNode instanceof AbstractTypeDeclaration typeDecl) { + domOccurance = typeDecl.bodyDeclarations() + .stream() + .filter(Initializer.class::isInstance) + .toList() + .indexOf(key) + 1; + } + IJavaElement parentEl = findElementForNodeViaDirectBinding(parentNode); + if( parentEl instanceof IParent parentElement) { + try { + for (IJavaElement child : parentElement.getChildren()) { + if (child instanceof IInitializer init) { + int count = init.getOccurrenceCount(); + if( count == domOccurance ) { + return init; + } + } + } + } catch( JavaModelException jme) { + // ignore + } + } + } + if( key instanceof ImportDeclaration id) { + ASTNode parentNode = id.getParent(); + if( parentNode instanceof CompilationUnit unit) { + IJavaElement parentEl = unit.getJavaElement(); + return ((org.eclipse.jdt.internal.core.CompilationUnit) parentEl).getImport(id.getName().toString()); + } + } + if (key instanceof PackageDeclaration pack && pack.getParent() instanceof CompilationUnit unit) { + IJavaElement parentEl = unit.getJavaElement(); + return ((org.eclipse.jdt.internal.core.CompilationUnit) parentEl).getPackageDeclaration(pack.getName().getFullyQualifiedName()); + } + return null; + } + + private static IJavaElement findElementForNodeViaDirectBinding(ASTNode key) { + if( key != null ) { + IBinding b = DOMASTNodeUtils.getBinding(key); + if( b != null ) { + IJavaElement el = b.getJavaElement(); + return el; + } + } + return null; + } + + public static IBinding getBinding(ASTNode astNode) { + if (astNode instanceof Name name) { + return name.resolveBinding(); + } + if (astNode instanceof VariableDeclaration variable) { + return variable.resolveBinding(); + } + if (astNode instanceof EnumConstantDeclaration enumConstantDeclaration) { + return enumConstantDeclaration.resolveVariable(); + } + if (astNode instanceof FieldAccess fieldAcces) { + return fieldAcces.resolveFieldBinding(); + } + if (astNode instanceof MethodInvocation method) { + return method.resolveMethodBinding(); + } + if (astNode instanceof Type type) { + return type.resolveBinding(); + } + if (astNode instanceof AbstractTypeDeclaration type) { + return type.resolveBinding(); + } + if (astNode instanceof MethodDeclaration method) { + return method.resolveBinding(); + } + if (astNode instanceof SuperFieldAccess superField) { + return superField.resolveFieldBinding(); + } + if (astNode instanceof SuperMethodInvocation superMethod) { + return superMethod.resolveMethodBinding(); + } + if (astNode instanceof SuperMethodReference superRef) { + return superRef.resolveMethodBinding(); + } + if (astNode instanceof ConstructorInvocation superRef) { + return superRef.resolveConstructorBinding(); + } + if (astNode instanceof SuperConstructorInvocation superRef) { + return superRef.resolveConstructorBinding(); + } + if (astNode instanceof MethodRef methodRef) { + return methodRef.resolveBinding(); + } + if (astNode instanceof MethodReference methodRef) { + return methodRef.resolveMethodBinding(); + } + if (astNode instanceof AnnotationTypeMemberDeclaration methodRef) { + return methodRef.resolveBinding(); + } + if (astNode instanceof ClassInstanceCreation ref) { + return ref.resolveConstructorBinding(); + } + if (astNode instanceof TypeParameter ref) { + return ref.resolveBinding(); + } + if (astNode instanceof MemberValuePair ref) { + return ref.resolveMemberValuePairBinding(); + } + // TODO more... + return null; + } + + public static boolean insideDocComment(org.eclipse.jdt.core.dom.ASTNode node) { + return node.getRoot() instanceof org.eclipse.jdt.core.dom.CompilationUnit unit && + ((List)unit.getCommentList()).stream().anyMatch(comment -> comment.getStartPosition() <= node.getStartPosition() && comment.getStartPosition() + comment.getLength() >= node.getStartPosition() + node.getLength()); + } + + public static boolean isWithinRange(org.eclipse.jdt.core.dom.ASTNode node, IJavaElement el) { + if( el instanceof ISourceReference isr) { + try { + ISourceRange r = isr.getSourceRange(); + if( r != null ) { + int astStart = node.getStartPosition(); + int astLen = node.getLength(); + int rangeStart = r.getOffset(); + int rangeLen = r.getLength(); + return astStart >= rangeStart && (astStart + astLen) <= (rangeStart + rangeLen); + } + } catch(JavaModelException jme) { + // Ignore + } + } + return false; + } +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/DOMJavaSearchDelegate.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/DOMJavaSearchDelegate.java new file mode 100644 index 00000000000..53d945b466c --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/DOMJavaSearchDelegate.java @@ -0,0 +1,543 @@ +/******************************************************************************* + * Copyright (c) 2000, 2021 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Stephan Herrmann - Contribution for + * Bug 377883 - NPE on open Call Hierarchy + * Microsoft Corporation - Contribution for bug 575562 - improve completion search performance + *******************************************************************************/ +package org.eclipse.jdt.internal.core.search; + +import static org.eclipse.jdt.internal.core.JavaModelManager.trace; +import static org.eclipse.jdt.internal.core.search.DOMASTNodeUtils.insideDocComment; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.zip.ZipFile; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.ILog; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.core.IClassFile; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaModelStatusConstants; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IOpenable; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.ISourceRange; +import org.eclipse.jdt.core.ITypeRoot; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.SourceRange; +import org.eclipse.jdt.core.WorkingCopyOwner; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.ASTRequestor; +import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; +import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; +import org.eclipse.jdt.core.dom.ClassInstanceCreation; +import org.eclipse.jdt.core.dom.ConstructorInvocation; +import org.eclipse.jdt.core.dom.CreationReference; +import org.eclipse.jdt.core.dom.EnumConstantDeclaration; +import org.eclipse.jdt.core.dom.FieldDeclaration; +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.MethodDeclaration; +import org.eclipse.jdt.core.dom.MethodInvocation; +import org.eclipse.jdt.core.dom.MethodRef; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.ParameterizedType; +import org.eclipse.jdt.core.dom.QualifiedName; +import org.eclipse.jdt.core.dom.SimpleType; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jdt.core.dom.SuperConstructorInvocation; +import org.eclipse.jdt.core.dom.SuperMethodInvocation; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.dom.VariableDeclaration; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jdt.core.dom.VariableDeclarationStatement; +import org.eclipse.jdt.core.search.FieldDeclarationMatch; +import org.eclipse.jdt.core.search.FieldReferenceMatch; +import org.eclipse.jdt.core.search.IJavaSearchDelegate; +import org.eclipse.jdt.core.search.LocalVariableReferenceMatch; +import org.eclipse.jdt.core.search.MethodReferenceMatch; +import org.eclipse.jdt.core.search.PackageReferenceMatch; +import org.eclipse.jdt.core.search.SearchMatch; +import org.eclipse.jdt.core.search.SearchParticipant; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.jdt.core.search.TypeParameterReferenceMatch; +import org.eclipse.jdt.core.search.TypeReferenceMatch; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; +import org.eclipse.jdt.internal.compiler.env.IBinaryType; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; +import org.eclipse.jdt.internal.core.BinaryType; +import org.eclipse.jdt.internal.core.ClassFile; +import org.eclipse.jdt.internal.core.JarPackageFragmentRoot; +import org.eclipse.jdt.internal.core.JavaModelManager; +import org.eclipse.jdt.internal.core.JavaProject; +import org.eclipse.jdt.internal.core.LocalVariable; +import org.eclipse.jdt.internal.core.ModularClassFile; +import org.eclipse.jdt.internal.core.NamedMember; +import org.eclipse.jdt.internal.core.PackageFragment; +import org.eclipse.jdt.internal.core.PackageFragmentRoot; +import org.eclipse.jdt.internal.core.search.matching.ClassFileMatchLocator; +import org.eclipse.jdt.internal.core.search.matching.DOMLocalVariableLocator; +import org.eclipse.jdt.internal.core.search.matching.DOMPatternLocator; +import org.eclipse.jdt.internal.core.search.matching.MatchLocator; +import org.eclipse.jdt.internal.core.search.matching.MatchingNodeSet; +import org.eclipse.jdt.internal.core.search.matching.MethodPattern; +import org.eclipse.jdt.internal.core.search.matching.ModularClassFileMatchLocator; +import org.eclipse.jdt.internal.core.search.matching.NodeSetWrapper; +import org.eclipse.jdt.internal.core.search.matching.PatternLocator; +import org.eclipse.jdt.internal.core.search.matching.PossibleMatch; +import org.eclipse.jdt.internal.core.search.processing.JobManager; +import org.eclipse.jdt.internal.core.util.Util; + +public class DOMJavaSearchDelegate implements IJavaSearchDelegate { + private Map matchToWrapper = new HashMap<>(); + public DOMJavaSearchDelegate() { + + } + + @Override + public void locateMatches(MatchLocator locator, IJavaProject javaProject, PossibleMatch[] possibleMatches, int start, + int length) throws CoreException { + locator.initialize((JavaProject)javaProject, length); + for( int i = 0; i < possibleMatches.length; i++ ) { + matchToWrapper.put(possibleMatches[i], wrapNodeSet(possibleMatches[i].nodeSet)); + } + + + Map map = javaProject.getOptions(true); + map.put(CompilerOptions.OPTION_TaskTags, org.eclipse.jdt.internal.compiler.util.Util.EMPTY_STRING); + locator.options = new CompilerOptions(map); + + // Original implementation used Map throughout, however, + // PossibleMatch was determined to be a bad / non-unique key where the + // hashCode and equals methods would let two different matches overlap. + // So we have to use Arrays and Lists, like a bunch of barbarians. + org.eclipse.jdt.core.ICompilationUnit[] unitArray = new org.eclipse.jdt.core.ICompilationUnit[possibleMatches.length]; + + Map cuToMatch = new HashMap<>(); + for (int i = 0; i < possibleMatches.length; i++) { + var currentPossibleMatch = possibleMatches[i]; + locator.currentPossibleMatch = currentPossibleMatch; + if (!skipMatch(locator, javaProject, currentPossibleMatch)) { + org.eclipse.jdt.core.ICompilationUnit u = findUnitForPossibleMatch(locator, javaProject, possibleMatches[i]); + unitArray[i] = u; + if (u != null) { + cuToMatch.put(u, possibleMatches[i]); + } else if (currentPossibleMatch.openable instanceof IClassFile classFile) { + locateMatchesForBinary(currentPossibleMatch, locator); + } + } + } + org.eclipse.jdt.core.ICompilationUnit[] nonNullUnits = Arrays.asList(unitArray).stream().filter(Objects::nonNull) + .toArray(org.eclipse.jdt.core.ICompilationUnit[]::new); + if (nonNullUnits.length == 0) { + return; + } + + Set ownerSet = new HashSet<>(); + for (int i = 0; i < nonNullUnits.length; i++) { + if (nonNullUnits[i].getOwner() != null) { + ownerSet.add(nonNullUnits[i].getOwner()); + } + } + WorkingCopyOwner owner = null; + if (ownerSet.size() == 1) { + owner = ownerSet.toArray(new WorkingCopyOwner[ownerSet.size()])[0]; + } + + ASTParser astParser = ASTParser.newParser(AST.getJLSLatest()); + astParser.setCompilerOptions(javaProject.getOptions(true)); + astParser.setProject(javaProject); + astParser.setResolveBindings(true); + astParser.setBindingsRecovery(true); + if (owner != null) + astParser.setWorkingCopyOwner(owner); + + org.eclipse.jdt.core.dom.CompilationUnit[] domUnits = new org.eclipse.jdt.core.dom.CompilationUnit[possibleMatches.length]; + + List nonNullDomIndexes = new ArrayList<>(); + astParser.createASTs(nonNullUnits, new String[0], new ASTRequestor() { + @Override + public void acceptAST(org.eclipse.jdt.core.ICompilationUnit source, + org.eclipse.jdt.core.dom.CompilationUnit ast) { + PossibleMatch pm = cuToMatch.get(source); + if (pm != null) { + for (int i = 0; i < possibleMatches.length; i++) { + if (possibleMatches[i] == pm) { +// String s = CharOperation.toString(pm.compoundName); +// if( "g1.t.s.ref.R1".equals(s)) { +// if( "g3.t.ref.R4".equals(s)) { +// int z = 5; z++; if( z == 3 ) {} +// } + domUnits[i] = ast; + nonNullDomIndexes.add(i); + locator.currentPossibleMatch = pm; + NodeSetWrapper wrapper = matchToWrapper.get(possibleMatches[i]); + ast.accept(new PatternLocatorVisitor(locator, wrapper)); + return; + } + } + } + } + // todo, use a subprogressmonitor or slice it + }, locator.progressMonitor); + + Collections.sort(nonNullDomIndexes); + for (int x : nonNullDomIndexes) { + PossibleMatch possibleMatch = possibleMatches[x]; + locator.currentPossibleMatch = possibleMatch; + NodeSetWrapper wrapper = this.matchToWrapper.get(possibleMatch); + for (org.eclipse.jdt.core.dom.ASTNode node : wrapper.trustedASTNodeLevels.keySet()) { + int level = wrapper.trustedASTNodeLevels.get(node); + SearchMatch match = toMatch(locator, node, level, possibleMatch); + if (match != null && match.getElement() != null) { + DOMPatternLocator locator2 = DOMPatternLocatorFactory.createWrapper(locator.patternLocator, locator.pattern); + locator2.reportSearchMatch(locator, node, match); + } + } + } + } + + private NodeSetWrapper wrapNodeSet(MatchingNodeSet nodeSet) { + return new NodeSetWrapper(nodeSet.mustResolve); + } + + private boolean skipMatch(MatchLocator locator, IJavaProject javaProject, PossibleMatch possibleMatch) { + if (locator.options.sourceLevel >= ClassFileConstants.JDK9) { + char[] pModuleName = possibleMatch.getModuleName(); + if (pModuleName != null && locator.lookupEnvironment.getModule(pModuleName) == null) + return true; + } + return false; + } + + private org.eclipse.jdt.core.ICompilationUnit findUnitForPossibleMatch(MatchLocator locator, IJavaProject jp, PossibleMatch match) { + if (!skipMatch(locator, jp, match)) { + if (match.openable instanceof org.eclipse.jdt.core.ICompilationUnit cu) { + return cu; + } else if (match.openable instanceof ITypeRoot tr) { + ITypeRoot toOpen = tr; + try { + // If this is a nested class like p/X$Y, it won't work. When it gets to + // the bindings for p/X, it thinks it is in p/X$Y.class file. :| + String n = tr.getElementName(); + if (n.toLowerCase().endsWith(".class") && n.contains("$")) { + String enclosingSourceFile = n.substring(0, n.indexOf("$")) + ".class"; + IJavaElement parent = tr.getParent(); + if (parent instanceof IPackageFragment ipf) { + IOpenable open2 = ipf.getClassFile(enclosingSourceFile); + if (open2 instanceof ITypeRoot tr2) { + toOpen = tr2; + } + } + } + if (toOpen instanceof IClassFile classFile && classFile.getBuffer() == null) { + return null; + } + org.eclipse.jdt.core.ICompilationUnit ret = toOpen.getWorkingCopy(null, new NullProgressMonitor()); + return ret; + } catch (JavaModelException jme) { + // Ignore for now + } + } + } + return null; + } + + private SearchMatch toMatch(MatchLocator locator, org.eclipse.jdt.core.dom.ASTNode node, int accuracy, PossibleMatch possibleMatch) { + if (node == null) { + return null; + } + SearchMatch sm = toCoreMatch(locator, node, accuracy, possibleMatch); + if( accuracy == SearchPattern.R_ERASURE_MATCH) { + sm.setRule(SearchPattern.R_ERASURE_MATCH); + } + return sm; + } + + private SearchMatch toCoreMatch(MatchLocator locator, org.eclipse.jdt.core.dom.ASTNode node, int accuracy, PossibleMatch possibleMatch) { + IResource resource = possibleMatch.resource; + if (node instanceof MethodDeclaration || node instanceof AbstractTypeDeclaration + || node instanceof VariableDeclaration || node instanceof AnnotationTypeMemberDeclaration) { + IJavaElement javaElement = DOMASTNodeUtils.getDeclaringJavaElement(node); + if (javaElement != null) { + ISourceRange range = new SourceRange(node.getStartPosition(), node.getLength()); + if (javaElement instanceof NamedMember named) { + try { + range = named.getNameRange(); + } catch (JavaModelException ex) { + ILog.get().error(ex.getMessage(), ex); + } + } + return locator.newDeclarationMatch(javaElement, null, accuracy, range.getOffset(), range.getLength()); + } + } + if (node instanceof MethodInvocation method) { + IJavaElement enclosing = DOMASTNodeUtils.getEnclosingJavaElement(node.getParent()); + IMethodBinding mb = method.resolveMethodBinding(); + boolean isSynthetic = mb != null && mb.isSynthetic(); + var res = new MethodReferenceMatch(enclosing, accuracy, method.getName().getStartPosition(), + method.getStartPosition() + method.getLength() - method.getName().getStartPosition(), false, + isSynthetic, (accuracy & PatternLocator.SUPER_INVOCATION_FLAVOR) != 0, insideDocComment(node), getParticipant(locator), resource); + res.setRaw(mb != null && mb.isRawMethod()); + res.setLocalElement(DOMASTNodeUtils.getLocalJavaElement(node)); + return res; + } + if (node instanceof SuperMethodInvocation method) { + IMethodBinding mb = method.resolveMethodBinding(); + var res = new MethodReferenceMatch(DOMASTNodeUtils.getEnclosingJavaElement(node.getParent()), accuracy, + method.getName().getStartPosition(), + method.getStartPosition() + method.getLength() - method.getName().getStartPosition(), false, + mb.isSynthetic(), (accuracy & PatternLocator.SUPER_INVOCATION_FLAVOR) != 0, insideDocComment(node), getParticipant(locator), + resource); + res.setRaw(mb != null && mb.isRawMethod()); + res.setLocalElement(DOMASTNodeUtils.getLocalJavaElement(node)); + return res; + } + if (node instanceof ClassInstanceCreation newInstance) { + IMethodBinding mb = newInstance.resolveConstructorBinding(); + var res = new MethodReferenceMatch( + DOMASTNodeUtils.getEnclosingJavaElement( + node.getParent().getParent()) /* we don't want the variable decl */, + accuracy, newInstance.getStartPosition(), newInstance.getLength(), true, + mb.isSynthetic(), (accuracy & PatternLocator.SUPER_INVOCATION_FLAVOR) != 0, insideDocComment(node), + getParticipant(locator), resource); + res.setRaw(mb != null && mb.isRawMethod()); + res.setLocalElement(DOMASTNodeUtils.getLocalJavaElement(node)); + return res; + } + if (node instanceof ConstructorInvocation newInstance) { + IMethodBinding mb = newInstance.resolveConstructorBinding(); + var res = new MethodReferenceMatch(DOMASTNodeUtils.getEnclosingJavaElement(node), accuracy, + newInstance.getStartPosition(), newInstance.getLength(), true, + mb.isSynthetic(), (accuracy & PatternLocator.SUPER_INVOCATION_FLAVOR) != 0, insideDocComment(node), + getParticipant(locator), resource); + res.setRaw(mb != null && mb.isRawMethod()); + res.setLocalElement(DOMASTNodeUtils.getLocalJavaElement(node)); + return res; + } + if (node instanceof MethodRef method && method.resolveBinding() instanceof IMethodBinding mb) { + IJavaElement enclosing = DOMASTNodeUtils.getEnclosingJavaElement(node.getParent()); + boolean isSynthetic = mb != null && mb.isSynthetic(); + var res = new MethodReferenceMatch(enclosing, accuracy, method.getName().getStartPosition(), + method.getStartPosition() + method.getLength() - method.getName().getStartPosition(), false, + isSynthetic, (accuracy & PatternLocator.SUPER_INVOCATION_FLAVOR) != 0, insideDocComment(node), getParticipant(locator), resource); + res.setRaw(mb != null && mb.isRawMethod()); + res.setLocalElement(DOMASTNodeUtils.getLocalJavaElement(node)); + return res; + } + if (node instanceof SuperConstructorInvocation newInstance) { + IMethodBinding mb = newInstance.resolveConstructorBinding(); + var res = new MethodReferenceMatch(DOMASTNodeUtils.getEnclosingJavaElement(node), accuracy, + newInstance.getStartPosition(), newInstance.getLength(), true, + mb.isSynthetic(), (accuracy & PatternLocator.SUPER_INVOCATION_FLAVOR) != 0, insideDocComment(node), + getParticipant(locator), resource); + res.setRaw(mb != null && mb.isRawMethod()); + res.setLocalElement(DOMASTNodeUtils.getLocalJavaElement(node)); + return res; + } + if (node instanceof CreationReference constructorRef) { + IMethodBinding mb = constructorRef.resolveMethodBinding(); + var res = new MethodReferenceMatch(DOMASTNodeUtils.getEnclosingJavaElement(node), accuracy, + constructorRef.getStartPosition(), constructorRef.getLength(), true, + mb.isSynthetic(), (accuracy & PatternLocator.SUPER_INVOCATION_FLAVOR) != 0, insideDocComment(node), getParticipant(locator), + resource); + res.setRaw(mb != null && mb.isRawMethod()); + res.setLocalElement(DOMASTNodeUtils.getLocalJavaElement(node)); + return res; + } + if (node.getLocationInParent() == SingleMemberAnnotation.VALUE_PROPERTY && locator.pattern instanceof MethodPattern) { + var res = new MethodReferenceMatch(DOMASTNodeUtils.getEnclosingJavaElement(node), accuracy, + node.getStartPosition(), node.getLength(), true, + false, (accuracy & PatternLocator.SUPER_INVOCATION_FLAVOR) != 0, insideDocComment(node), getParticipant(locator), + resource); + res.setLocalElement(DOMASTNodeUtils.getLocalJavaElement(node)); + return res; + } + if (node instanceof EnumConstantDeclaration enumConstantDeclaration) { + int start = enumConstantDeclaration.getStartPosition(); + int len = enumConstantDeclaration.getLength(); + if (enumConstantDeclaration.getAnonymousClassDeclaration() != null) { + len = enumConstantDeclaration.getAnonymousClassDeclaration().getStartPosition() - start; + } + return new FieldDeclarationMatch(DOMASTNodeUtils.getDeclaringJavaElement(node), accuracy, start, len, + getParticipant(locator), resource); + } + if (node instanceof Type nt) { + //IBinding b = DOMASTNodeUtils.getBinding(nt); + IJavaElement element = DOMASTNodeUtils.getEnclosingJavaElement(node); + if (element instanceof LocalVariable) { + element = element.getParent(); + } + TypeReferenceMatch ret = new TypeReferenceMatch(element, accuracy, node.getStartPosition(), + node.getLength(), DOMASTNodeUtils.insideDocComment(node), getParticipant(locator), resource); + if (nt.isParameterizedType()) { + if (((ParameterizedType) nt).typeArguments().size() == 0) { + ret.setRaw(true); + } + } + IJavaElement localJavaElement = DOMASTNodeUtils.getLocalJavaElement(node); + if (!Objects.equals(localJavaElement, element)) { + ret.setLocalElement(localJavaElement); + } + if (node.getLocationInParent() == VariableDeclarationStatement.TYPE_PROPERTY && node.getParent() instanceof VariableDeclarationStatement stmt && stmt.fragments().size() > 1) { + ret.setOtherElements(((List)stmt.fragments()) + .subList(1, stmt.fragments().size()) + .stream() + .map(VariableDeclarationFragment::resolveBinding) + .map(IVariableBinding::getJavaElement) + .toArray(IJavaElement[]::new)); + } + if (node.getLocationInParent() == FieldDeclaration.TYPE_PROPERTY && node.getParent() instanceof FieldDeclaration stmt && stmt.fragments().size() > 1) { + ret.setOtherElements(((List)stmt.fragments()) + .subList(1, stmt.fragments().size()) + .stream() + .map(VariableDeclarationFragment::resolveBinding) + .map(IVariableBinding::getJavaElement) + .toArray(IJavaElement[]::new)); + } + return ret; + } + if (node instanceof org.eclipse.jdt.core.dom.TypeParameter nodeTP) { + IJavaElement element = DOMASTNodeUtils.getEnclosingJavaElement(node); + return new TypeParameterReferenceMatch(element, accuracy, nodeTP.getName().getStartPosition(), + nodeTP.getName().getLength(), DOMASTNodeUtils.insideDocComment(node), getParticipant(locator), resource); + } + if (node instanceof Name name) { + IBinding b = name.resolveBinding(); + IJavaElement enclosing = DOMASTNodeUtils.getEnclosingJavaElement(node); +// if( b == null ) { +// // This fixes some issues but causes even more failures +// return new SearchMatch(enclosing, accuracy, node.getStartPosition(), node.getLength(), getParticipant(locator), resource); +// } + if (b instanceof ITypeBinding btb) { + TypeReferenceMatch ref = new TypeReferenceMatch(enclosing, accuracy, node.getStartPosition(), + node.getLength(), insideDocComment(node), getParticipant(locator), resource); + if (btb.isRawType()) + ref.setRaw(true); + ref.setLocalElement(DOMASTNodeUtils.getLocalJavaElement(node)); + return ref; + } + if (b instanceof IVariableBinding variable) { + if (variable.isField()) { + var res = new FieldReferenceMatch(enclosing, accuracy, node.getStartPosition(), node.getLength(), + DOMLocalVariableLocator.isRead(name), DOMLocalVariableLocator.isWrite(name), + insideDocComment(node), getParticipant(locator), resource); + res.setLocalElement(DOMASTNodeUtils.getLocalJavaElement(node)); + return res; + } + return new LocalVariableReferenceMatch(enclosing, accuracy, node.getStartPosition(), node.getLength(), + DOMLocalVariableLocator.isRead(name), DOMLocalVariableLocator.isWrite(name), + insideDocComment(node), getParticipant(locator), resource); + } + if (b instanceof IPackageBinding) { + var res = new PackageReferenceMatch(enclosing, accuracy, name.getStartPosition(), name.getLength(), + insideDocComment(name), getParticipant(locator), resource); + res.setLocalElement(DOMASTNodeUtils.getLocalJavaElement(node)); + return res; + } + if (b instanceof IMethodBinding) { + var res = new MethodReferenceMatch(enclosing, accuracy, node.getStartPosition(), node.getLength(), + insideDocComment(node), getParticipant(locator), resource); + res.setLocalElement(DOMASTNodeUtils.getLocalJavaElement(node)); + return res; + } + // more...? + } + if (node.getLocationInParent() == SimpleType.NAME_PROPERTY + || node.getLocationInParent() == QualifiedName.NAME_PROPERTY) { + // more...? + return toMatch(locator, node.getParent(), accuracy, possibleMatch); + } + return null; + } + public SearchParticipant getParticipant(MatchLocator locator) { + return locator.currentPossibleMatch.document.getParticipant(); + } + + private void locateMatchesForBinary(PossibleMatch possibleMatch, MatchLocator locator) { + if (possibleMatch.openable instanceof ClassFile classFile) { + IBinaryType info = null; + try { + info = getBinaryInfo(classFile, classFile.resource()); + } catch (CoreException ce) { + ILog.get().error(ce.getMessage(), ce); + } + if (info != null) { + try { + new ClassFileMatchLocator().locateMatches(locator, classFile, info); + } catch (CoreException e) { + ILog.get().error(e.getMessage(), e); + } + } + } else if (possibleMatch.openable instanceof ModularClassFile modularClassFile) { // no source + try { + new ModularClassFileMatchLocator().locateMatches(locator, modularClassFile); + } catch (CoreException e) { + ILog.get().error(e.getMessage(), e); + } + } + } + + protected IBinaryType getBinaryInfo(ClassFile classFile, IResource resource) throws CoreException { + BinaryType binaryType = (BinaryType) classFile.getType(); + if (classFile.isOpen()) + return binaryType.getElementInfo(); // reuse the info from the java model cache + + // create a temporary info + IBinaryType info; + try { + PackageFragment pkg = (PackageFragment) classFile.getParent(); + PackageFragmentRoot root = (PackageFragmentRoot) pkg.getParent(); + if (root.isArchive()) { + // class file in a jar + String classFileName = classFile.getElementName(); + String classFilePath = Util.concatWith(pkg.names, classFileName, '/'); + ZipFile zipFile = null; + try { + zipFile = ((JarPackageFragmentRoot) root).getJar(); + info = ClassFileReader.read(zipFile, classFilePath); + } finally { + JavaModelManager.getJavaModelManager().closeZipFile(zipFile); + } + } else { + // class file in a directory + info = Util.newClassFileReader(resource); + } + if (info == null) throw binaryType.newNotPresentException(); + return info; + } catch (ClassFormatException e) { + if (JobManager.VERBOSE) { + trace("", e); //$NON-NLS-1$ + } + return null; + } catch (java.io.IOException e) { + throw new JavaModelException(e, IJavaModelStatusConstants.IO_EXCEPTION); + } + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/DOMPatternLocatorFactory.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/DOMPatternLocatorFactory.java new file mode 100644 index 00000000000..c29bd49ad7f --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/DOMPatternLocatorFactory.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * 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.core.search; + +import org.eclipse.core.runtime.ILog; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.jdt.internal.core.search.matching.ConstructorLocator; +import org.eclipse.jdt.internal.core.search.matching.DOMConstructorLocator; +import org.eclipse.jdt.internal.core.search.matching.DOMFieldLocator; +import org.eclipse.jdt.internal.core.search.matching.DOMLocalVariableLocator; +import org.eclipse.jdt.internal.core.search.matching.DOMMethodLocator; +import org.eclipse.jdt.internal.core.search.matching.DOMOrLocator; +import org.eclipse.jdt.internal.core.search.matching.DOMPackageReferenceLocator; +import org.eclipse.jdt.internal.core.search.matching.DOMPatternLocator; +import org.eclipse.jdt.internal.core.search.matching.DOMSuperTypeReferenceLocator; +import org.eclipse.jdt.internal.core.search.matching.DOMTypeDeclarationLocator; +import org.eclipse.jdt.internal.core.search.matching.DOMTypeParameterLocator; +import org.eclipse.jdt.internal.core.search.matching.DOMTypeReferenceLocator; +import org.eclipse.jdt.internal.core.search.matching.FieldLocator; +import org.eclipse.jdt.internal.core.search.matching.LocalVariableLocator; +import org.eclipse.jdt.internal.core.search.matching.MethodLocator; +import org.eclipse.jdt.internal.core.search.matching.OrLocator; +import org.eclipse.jdt.internal.core.search.matching.OrPattern; +import org.eclipse.jdt.internal.core.search.matching.PackageReferenceLocator; +import org.eclipse.jdt.internal.core.search.matching.PatternLocator; +import org.eclipse.jdt.internal.core.search.matching.SuperTypeReferenceLocator; +import org.eclipse.jdt.internal.core.search.matching.TypeDeclarationLocator; +import org.eclipse.jdt.internal.core.search.matching.TypeParameterLocator; +import org.eclipse.jdt.internal.core.search.matching.TypeReferenceLocator; + +public class DOMPatternLocatorFactory { + + public static DOMPatternLocator createWrapper(PatternLocator locator, SearchPattern pattern) { + // pattern is not always accessible from locator, so pass it explicitly + // TODO implement all this. + if( locator instanceof FieldLocator fl) { + return new DOMFieldLocator(fl); + } + if( locator instanceof ConstructorLocator cl) { + return new DOMConstructorLocator(cl); + } + if( locator instanceof LocalVariableLocator lcl) { + return new DOMLocalVariableLocator(lcl); + } + if( locator instanceof MethodLocator ml) { + return new DOMMethodLocator(ml); + } + if( locator instanceof PackageReferenceLocator prl) { + return new DOMPackageReferenceLocator(prl); + } + if( locator instanceof SuperTypeReferenceLocator strl) { + return new DOMSuperTypeReferenceLocator(strl); + } + if( locator instanceof TypeDeclarationLocator tdl) { + return new DOMTypeDeclarationLocator(tdl); + } + if( locator instanceof TypeParameterLocator tpl) { + return new DOMTypeParameterLocator(tpl); + } + if( locator instanceof TypeReferenceLocator trl) { + return new DOMTypeReferenceLocator(trl); + } + if (locator instanceof OrLocator orLocator && pattern instanceof OrPattern orPattern) { + return new DOMOrLocator(orLocator, orPattern); + } + ILog.get().warn("Cannot map pattern locator/pattern to DOMPatternLocator"); + return new DOMPatternLocator(null); // stub + } +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/LocatorResponse.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/LocatorResponse.java new file mode 100644 index 00000000000..4c0a95a82a4 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/LocatorResponse.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * 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.core.search; + +import org.eclipse.jdt.core.dom.ASTNode; + +/** + * + */ +public record LocatorResponse(int level, boolean replacementNodeFound, + ASTNode replacement, boolean added, boolean canVisitChildren) { +} \ No newline at end of file diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/PatternLocatorVisitor.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/PatternLocatorVisitor.java new file mode 100644 index 00000000000..8ce81235a51 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/PatternLocatorVisitor.java @@ -0,0 +1,380 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jdt.internal.core.search; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; + +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.AnnotationTypeDeclaration; +import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; +import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; +import org.eclipse.jdt.core.dom.ArrayInitializer; +import org.eclipse.jdt.core.dom.ArrayType; +import org.eclipse.jdt.core.dom.BooleanLiteral; +import org.eclipse.jdt.core.dom.CharacterLiteral; +import org.eclipse.jdt.core.dom.ClassInstanceCreation; +import org.eclipse.jdt.core.dom.ConstructorInvocation; +import org.eclipse.jdt.core.dom.CreationReference; +import org.eclipse.jdt.core.dom.EnumConstantDeclaration; +import org.eclipse.jdt.core.dom.EnumDeclaration; +import org.eclipse.jdt.core.dom.ExpressionMethodReference; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.ImportDeclaration; +import org.eclipse.jdt.core.dom.IntersectionType; +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.Name; +import org.eclipse.jdt.core.dom.NameQualifiedType; +import org.eclipse.jdt.core.dom.NullLiteral; +import org.eclipse.jdt.core.dom.NumberLiteral; +import org.eclipse.jdt.core.dom.PackageDeclaration; +import org.eclipse.jdt.core.dom.ParameterizedType; +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.SingleVariableDeclaration; +import org.eclipse.jdt.core.dom.StringLiteral; +import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; +import org.eclipse.jdt.core.dom.SuperConstructorInvocation; +import org.eclipse.jdt.core.dom.SuperMethodInvocation; +import org.eclipse.jdt.core.dom.SuperMethodReference; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.core.dom.TypeLiteral; +import org.eclipse.jdt.core.dom.TypeParameter; +import org.eclipse.jdt.core.dom.UnionType; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jdt.internal.core.search.matching.DOMPatternLocator; +import org.eclipse.jdt.internal.core.search.matching.MatchLocator; +import org.eclipse.jdt.internal.core.search.matching.NodeSetWrapper; +import org.eclipse.jdt.internal.core.search.matching.PatternLocator; + +/** + * Visits an AST to feel the possible match with nodes + */ +class PatternLocatorVisitor extends ASTVisitor { + + private final DOMPatternLocator domPatternLocator; + private final NodeSetWrapper nodeSet; + private MatchLocator locator; + + public PatternLocatorVisitor(MatchLocator locator, NodeSetWrapper nodeSet) { + super(true); + this.nodeSet = nodeSet; + this.locator = locator; + this.domPatternLocator = DOMPatternLocatorFactory.createWrapper(this.locator.patternLocator, locator.pattern); + if (this.domPatternLocator != null) { + domPatternLocator.initializePolymorphicSearch(locator); + } + } + private boolean defaultVisitImplementation(T node, BiFunction levelFunc) { + defaultVisitImplementationWithFunc(node, levelFunc, DOMASTNodeUtils::getBinding); + return true; + } + + /* + * TODO + * We really need to change this BiFunction into something that returns + * a record with way more information. Simply knowing if the current node matches + * is nowhere near sufficient. + * + * We need to know if: + * 1) the current node matches or does not match + * 2) We found a replacement node to consider + * 3) We found a replacement node and added it already + * 4) Should we continue considering children or have they been ruled out? + */ + private LocatorResponse defaultVisitImplementationWithFunc( + T node, + BiFunction levelFunc, + Function bindingFunc) { + LocatorResponse resp = levelFunc.apply(node, this.domPatternLocator); + boolean mustResolve = (this.nodeSet.mustResolve() || this.locator.patternLocator.isMustResolve()); + boolean nodeReplaced = resp.replacementNodeFound(); + ASTNode n2 = nodeReplaced ? resp.replacement() : node; + n2 = n2 == null ? node : n2; + if (resp.level() == PatternLocator.POSSIBLE_MATCH && mustResolve) { + LocatorResponse resp2 = this.domPatternLocator.resolveLevel(n2, bindingFunc.apply(n2), this.locator); + n2 = resp2.replacementNodeFound() ? resp2.replacement() : n2; + resp = new LocatorResponse(resp2.level(), resp.replacementNodeFound() || resp2.replacementNodeFound(), n2, resp2.added(), resp2.canVisitChildren()); + } + boolean added = resp.added(); + if( !added ) { + this.nodeSet.addMatch(n2, resp.level()); + } + return resp; + } + + + @Override + public boolean visit(AnnotationTypeDeclaration node) { + return defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + } + @Override + public boolean visit(TypeParameter node) { + return defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + } + @Override + public boolean visit(MethodDeclaration node) { + return defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + } + @Override + public boolean visit(AnnotationTypeMemberDeclaration node) { + return defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + } + @Override + public boolean visit(MethodInvocation node) { + return defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + } + @Override + public boolean visit(MethodRef node) { + return defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + } + @Override + public boolean visit(ExpressionMethodReference node) { + return defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + } + @Override + public boolean visit(SuperMethodReference node) { + return defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + } + @Override + public boolean visit(SuperMethodInvocation node) { + return defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + } + + private boolean visitAbstractTypeDeclaration(AbstractTypeDeclaration node) { + return defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + } + @Override + public boolean visit(EnumDeclaration node) { + return visitAbstractTypeDeclaration(node); + } + @Override + public boolean visit(TypeDeclaration node) { + return visitAbstractTypeDeclaration(node); + } + @Override + public boolean visit(RecordDeclaration node) { + return visitAbstractTypeDeclaration(node); + } + @Override + public boolean visit(AnonymousClassDeclaration node) { + return defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + } + + private boolean visitType(Type node) { + LocatorResponse resp = defaultVisitImplementationWithFunc(node, (x,y) -> y.match(node, this.nodeSet, this.locator), DOMASTNodeUtils::getBinding); + return resp.level() == 0 && resp.canVisitChildren(); + } + + @Override + public boolean visit(SimpleType type) { + visitType(type); + Name n = type.getName(); + if( n instanceof QualifiedName qn ) { + Name qualifier = qn.getQualifier(); + if( qualifier instanceof SimpleName sn1 ) { + sn1.accept(this); + } else if( qualifier instanceof QualifiedName qn1) { + qn1.accept(this); + } + } + return false; // No need to visit single name child + } + @Override + public boolean visit(QualifiedType type) { + boolean ret = visitType(type); + if( !ret ) { + visitAllDescendentTypeArguments(type); + } + return ret; + } + @Override + public boolean visit(NameQualifiedType type) { + boolean ret = visitType(type); + if( !ret ) { + visitAllDescendentTypeArguments(type); + } + return ret; + } + @Override + public boolean visit(ParameterizedType node) { + LocatorResponse resp = defaultVisitImplementationWithFunc(node, (x,y) -> y.match(node, this.nodeSet, this.locator), DOMASTNodeUtils::getBinding); + if( resp.level() == 0 && resp.canVisitChildren() ) { + return true; + } + // always visit the type arguments though + visitAllDescendentTypeArguments(node); + return false; + } + + private void visitAllDescendentTypeArguments(Type node) { + // This feel suspect to me... maybe repeats nodes... idk yet + node.accept(new ASTVisitor() { + @Override + public boolean visit(ParameterizedType node) { + visitTypeArgumentList(node.typeArguments()); + return true; + } + }); + } + + protected void visitTypeArgumentList(List typeArgs) { + ArrayList args = new ArrayList(typeArgs); + for( Object t : args ) { + ((Type)t).accept(this); + } + } + + @Override + public boolean visit(PrimitiveType node) { + return visitType(node); + } + @Override + public boolean visit(ArrayType node) { + return visitType(node); + } + @Override + public boolean visit(IntersectionType node) { + return visitType(node); + } + @Override + public boolean visit(UnionType node) { + return visitType(node); + } + @Override + public boolean visit(ClassInstanceCreation node) { + return defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + } + @Override + public boolean visit(CreationReference node) { + return defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + } + @Override + public boolean visit(ConstructorInvocation node) { + return defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + } + @Override + public boolean visit(SuperConstructorInvocation node) { + return defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + } + @Override + public boolean visit(SimpleName node) { + StructuralPropertyDescriptor loc = node.getLocationInParent(); + if ( + loc == VariableDeclarationFragment.NAME_PROPERTY || + loc == SingleVariableDeclaration.NAME_PROPERTY || + loc == TypeDeclaration.NAME_PROPERTY || + loc == EnumDeclaration.NAME_PROPERTY || + loc == EnumConstantDeclaration.NAME_PROPERTY || + loc == AnnotationTypeDeclaration.NAME_PROPERTY || + loc == MethodDeclaration.NAME_PROPERTY) { + return false; // skip as parent was most likely already matched + } + LocatorResponse resp = defaultVisitImplementationWithFunc(node, (x,y) -> y.match(node, this.nodeSet, this.locator), DOMASTNodeUtils::getBinding); + return resp.level() == 0 && resp.canVisitChildren(); + } + + @Override + public boolean visit(VariableDeclarationFragment node) { + return defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + } + @Override + public boolean visit(SingleVariableDeclaration node) { + return defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + } + @Override + public boolean visit(EnumConstantDeclaration node) { + LocatorResponse response = this.domPatternLocator.match(node, this.nodeSet, this.locator); + boolean mustResolve = (this.nodeSet.mustResolve() || this.locator.patternLocator.isMustResolve()); + int retLevel = response.level(); + if ((response.level() & PatternLocator.MATCH_LEVEL_MASK) == PatternLocator.POSSIBLE_MATCH && mustResolve) { + LocatorResponse l1 = this.domPatternLocator.resolveLevel(node, node.resolveVariable(), this.locator); + LocatorResponse l2 = this.domPatternLocator.resolveLevel(node, node.resolveConstructorBinding(), this.locator); + retLevel = Math.max(l1.level(), l2.level()); + } + this.nodeSet.addMatch(node, retLevel); + return true; + } + @Override + public boolean visit(QualifiedName node) { + if (node.getLocationInParent() == SimpleType.NAME_PROPERTY) { + return false; // type was already checked + } + LocatorResponse resp = defaultVisitImplementationWithFunc(node, (x,y) -> y.match(node, this.nodeSet, this.locator), DOMASTNodeUtils::getBinding); + return resp.level() == 0 && resp.canVisitChildren(); + } + + @Override + public boolean visit(ImportDeclaration node) { + defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + return true; + } + + @Override + public boolean visit(PackageDeclaration node) { + defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + return true; + } + @Override + public boolean visit(MemberValuePair node) { + defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + return true; + } + + // following ones are for values in SingleMemberAnnotation + @Override + public boolean visit(NumberLiteral node) { + defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + return true; + } + @Override + public boolean visit(StringLiteral node) { + defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + return true; + } + @Override + public boolean visit(CharacterLiteral node) { + defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + return true; + } + @Override + public boolean visit(BooleanLiteral node) { + defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + return true; + } + @Override + public boolean visit(ArrayInitializer node) { + defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + return true; + } + @Override + public boolean visit(TypeLiteral node) { + defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + return true; + } + @Override + public boolean visit(NullLiteral node) { + defaultVisitImplementation(node, (x,y) -> y.match(node, this.nodeSet, this.locator)); + return true; + } +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMConstructorLocator.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMConstructorLocator.java new file mode 100644 index 00000000000..4d32f50208a --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMConstructorLocator.java @@ -0,0 +1,258 @@ +/******************************************************************************* + * Copyright (c) 2023, 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.core.search.matching; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; +import org.eclipse.jdt.core.dom.ClassInstanceCreation; +import org.eclipse.jdt.core.dom.ConstructorInvocation; +import org.eclipse.jdt.core.dom.CreationReference; +import org.eclipse.jdt.core.dom.EnumConstantDeclaration; +import org.eclipse.jdt.core.dom.EnumDeclaration; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.SuperConstructorInvocation; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.internal.codeassist.DOMCompletionUtils; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; +import org.eclipse.jdt.internal.core.search.DOMASTNodeUtils; +import org.eclipse.jdt.internal.core.search.LocatorResponse; + +public class DOMConstructorLocator extends DOMPatternLocator { + + private ConstructorLocator locator; + + public DOMConstructorLocator(ConstructorLocator cl) { + super(cl.pattern); + this.locator = cl; + } + + @Override + public LocatorResponse match(MethodDeclaration node, NodeSetWrapper nodeSet, MatchLocator locator) { + if (!node.isConstructor()) { + return toResponse(IMPOSSIBLE_MATCH); + } + if (this.locator.pattern.fineGrain != 0 && !this.locator.pattern.findDeclarations) + return toResponse(IMPOSSIBLE_MATCH); + int referencesLevel = this.locator.pattern.findReferences ? matchLevelForReference(node) : IMPOSSIBLE_MATCH; + int declarationsLevel = this.locator.pattern.findDeclarations ? this.matchLevelForDeclarations(node) : IMPOSSIBLE_MATCH; + + // use the stronger match + int level = nodeSet.addMatch(node, referencesLevel >= declarationsLevel ? referencesLevel : declarationsLevel); + return toResponse(level, true); + } + + protected int matchLevelForReference(MethodDeclaration constructor) { + if ((this.locator.pattern.parameterSimpleNames == null || this.locator.pattern.parameterSimpleNames.length == 0) && + constructor.isConstructor() && + constructor.getBody() != null && + constructor.getBody().statements().stream().noneMatch(st -> st instanceof SuperConstructorInvocation || st instanceof ConstructorInvocation)) { + return this.locator.pattern.mustResolve ? POSSIBLE_MATCH : ACCURATE_MATCH; + } + return IMPOSSIBLE_MATCH; + } + + @Override + public LocatorResponse match(org.eclipse.jdt.core.dom.Expression node, NodeSetWrapper nodeSet, MatchLocator locator) { // interested + if (!this.locator.pattern.findReferences) + return toResponse(IMPOSSIBLE_MATCH); + if (node instanceof CreationReference creationRef && (this.locator.pattern.declaringSimpleName == null + || this.matchesTypeReference(this.locator.pattern.declaringSimpleName, creationRef.getType()))) { + int level = this.locator.pattern.mustResolve ? POSSIBLE_MATCH : INACCURATE_MATCH; + return toResponse(level); + } + if (node instanceof ClassInstanceCreation newInstance) { + int level = (this.locator.pattern.declaringSimpleName == null + || this.matchesTypeReference(this.locator.pattern.declaringSimpleName, newInstance.getType())) + && matchParametersCount(node, newInstance.arguments()) ? POSSIBLE_MATCH : IMPOSSIBLE_MATCH; + return toResponse(level); + } + return toResponse(IMPOSSIBLE_MATCH); + } + + @Override + public LocatorResponse match(org.eclipse.jdt.core.dom.ASTNode node, NodeSetWrapper nodeSet, MatchLocator locator) { + if (!this.locator.pattern.findReferences) + return toResponse(IMPOSSIBLE_MATCH); + if (node instanceof ConstructorInvocation constructorInvocation) { + if (!matchParametersCount(node, constructorInvocation.arguments())) { + return toResponse(IMPOSSIBLE_MATCH); + } + if (this.locator.pattern.declaringSimpleName != null) { + if (!this.matchesName(this.locator.pattern.declaringSimpleName, DOMCompletionUtils.findParentTypeDeclaration(node).getName().getIdentifier().toCharArray())) { + return toResponse(IMPOSSIBLE_MATCH); + } + } + int level = this.locator.pattern.mustResolve ? POSSIBLE_MATCH : INACCURATE_MATCH; + return toResponse(level, true); + } else if (node instanceof SuperConstructorInvocation superRef) { + if (!matchParametersCount(node, superRef.arguments())) { + return toResponse(IMPOSSIBLE_MATCH); + } + if (this.locator.pattern.declaringSimpleName != null) { + Type superType = null; + var current = superRef.getParent(); + while (current != null && !(current instanceof AbstractTypeDeclaration) + && !(current instanceof CreationReference)) { + current = current.getParent(); + } + if (current instanceof org.eclipse.jdt.core.dom.TypeDeclaration typeDecl) { + superType = typeDecl.getSuperclassType(); + } + if (current instanceof CreationReference newInstance) { + superType = newInstance.getType(); + } + if (!this.matchesTypeReference(this.locator.pattern.declaringSimpleName, superType)) { + return toResponse(IMPOSSIBLE_MATCH); + } + } + int level = this.locator.pattern.mustResolve ? POSSIBLE_MATCH : INACCURATE_MATCH; + return toResponse(level, true); + } + if (node instanceof EnumConstantDeclaration enumConstantDecl + && node.getParent() instanceof EnumDeclaration enumDeclaration + && this.locator.matchesName(this.locator.pattern.declaringSimpleName, + enumDeclaration.getName().getIdentifier().toCharArray()) + && matchParametersCount(enumConstantDecl, enumConstantDecl.arguments())) { + int level = nodeSet.addMatch(node, this.locator.pattern.mustResolve ? POSSIBLE_MATCH : ACCURATE_MATCH); + return toResponse(level, true); + } + return toResponse(IMPOSSIBLE_MATCH); + } + + @Override + public LocatorResponse resolveLevel(org.eclipse.jdt.core.dom.ASTNode node, IBinding binding, MatchLocator locator) { + if (node instanceof MethodDeclaration decl) { + if (this.locator.pattern.findReferences) { + if (matchLevelForReference(decl) > IMPOSSIBLE_MATCH) { + if (binding instanceof IMethodBinding currentConstructor) { + var superType = currentConstructor.getDeclaringClass().getSuperclass(); + if (superType != null) { + int superConstructorLevel = Arrays.stream(superType.getDeclaredMethods()) + .filter(IMethodBinding::isConstructor) + .filter(superConstructor -> superConstructor.getParameterTypes().length == 0) + .mapToInt(superConstructor -> { + int level = matchConstructor(superConstructor); + if (level == IMPOSSIBLE_MATCH && superConstructor != superConstructor.getMethodDeclaration()) { + level = matchConstructor(superConstructor.getMethodDeclaration()); + } + return level; + }).findAny() + .orElse(IMPOSSIBLE_MATCH); + if (superConstructorLevel > IMPOSSIBLE_MATCH) { + return toResponse(superConstructorLevel); + } + } + } + } + } + if (!this.locator.pattern.findDeclarations) { + return toResponse(IMPOSSIBLE_MATCH); + } + } + if (binding instanceof IMethodBinding constructor) { + int level = matchConstructor(constructor); + if (level == IMPOSSIBLE_MATCH) { + if (constructor != constructor.getMethodDeclaration()) { + level = matchConstructor(constructor.getMethodDeclaration()); + } + } + return toResponse(level); + } + return toResponse(IMPOSSIBLE_MATCH); + } + + boolean matchParametersCount(org.eclipse.jdt.core.dom.ASTNode node, + List args) { + if (this.locator.pattern.parameterSimpleNames != null + && (!this.locator.pattern.varargs || DOMASTNodeUtils.insideDocComment(node))) { + int length = this.locator.pattern.parameterCount; + if (length < 0) + length = this.locator.pattern.parameterSimpleNames.length; + int argsLength = args == null ? 0 : args.size(); + if (length != argsLength) { + return false; + } + } + return true; + } + protected int matchLevelForDeclarations(ConstructorDeclaration constructor) { + // constructor name is stored in selector field + if (this.locator.pattern.declaringSimpleName != null && !this.locator.matchesName(this.locator.pattern.declaringSimpleName, constructor.selector)) + return IMPOSSIBLE_MATCH; + + if (this.locator.pattern.parameterSimpleNames != null) { + int length = this.locator.pattern.parameterSimpleNames.length; + Argument[] args = constructor.arguments; + int argsLength = args == null ? 0 : args.length; + if (length != argsLength) return IMPOSSIBLE_MATCH; + } + + // Verify type arguments (do not reject if pattern has no argument as it can be an erasure match) + if (this.locator.pattern.hasConstructorArguments()) { + if (constructor.typeParameters == null || constructor.typeParameters.length != this.locator.pattern.constructorArguments.length) return IMPOSSIBLE_MATCH; + } + + return this.locator.pattern.mustResolve ? POSSIBLE_MATCH : ACCURATE_MATCH; + } + protected int matchConstructor(IMethodBinding constructor) { + if (!constructor.isConstructor()) return IMPOSSIBLE_MATCH; + + // declaring type, simple name has already been matched by matchIndexEntry() + int level = this.resolveLevelForType(this.locator.pattern.declaringSimpleName, this.locator.pattern.declaringQualification, constructor.getDeclaringClass()); + if (level == IMPOSSIBLE_MATCH) return IMPOSSIBLE_MATCH; + + // parameter types + int parameterCount = this.locator.pattern.parameterCount; + if (parameterCount > -1) { + if (parameterCount != constructor.getParameterTypes().length) return IMPOSSIBLE_MATCH; + for (int i = 0; i < parameterCount; i++) { + // TODO (frederic) use this call to refine accuracy on parameter types +// int newLevel = resolveLevelForType(this.pattern.parameterSimpleNames[i], this.pattern.parameterQualifications[i], this.pattern.parametersTypeArguments[i], 0, constructor.parameters[i]); + int newLevel = this.resolveLevelForType(this.locator.pattern.parameterSimpleNames[i], this.locator.pattern.parameterQualifications[i], constructor.getParameterTypes()[i]); + if (level > newLevel) { + if (newLevel == IMPOSSIBLE_MATCH) { +// if (isErasureMatch) { +// return ERASURE_MATCH; +// } + return IMPOSSIBLE_MATCH; + } + level = newLevel; // can only be downgraded + } + } + } + return level; + } + + protected int matchLevelForDeclarations(MethodDeclaration constructor) { + // constructor name is stored in selector field + if (this.locator.pattern.declaringSimpleName != null && !this.locator.matchesName(this.locator.pattern.declaringSimpleName, constructor.getName().toString().toCharArray())) + return IMPOSSIBLE_MATCH; + + if (this.locator.pattern.parameterSimpleNames != null) { + int length = this.locator.pattern.parameterSimpleNames.length; + var args = constructor.parameters(); + int argsLength = args == null ? 0 : args.size(); + if (length != argsLength) return IMPOSSIBLE_MATCH; + } + + // Verify type arguments (do not reject if pattern has no argument as it can be an erasure match) + if (this.locator.pattern.hasConstructorArguments()) { + if (constructor.typeParameters() == null || constructor.typeParameters().size() != this.locator.pattern.constructorArguments.length) return IMPOSSIBLE_MATCH; + } + + return this.locator.pattern.mustResolve ? POSSIBLE_MATCH : ACCURATE_MATCH; + } +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMFieldLocator.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMFieldLocator.java new file mode 100644 index 00000000000..d2444739ea6 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMFieldLocator.java @@ -0,0 +1,322 @@ +/******************************************************************************* + * Copyright (c) 2023, 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.core.search.matching; + +import java.util.Set; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.ISourceRange; +import org.eclipse.jdt.core.ISourceReference; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.compiler.CharOperation; +import org.eclipse.jdt.core.dom.EnumConstantDeclaration; +import org.eclipse.jdt.core.dom.EnumDeclaration; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.FieldAccess; +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.Name; +import org.eclipse.jdt.core.dom.QualifiedName; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; +import org.eclipse.jdt.core.dom.SuperFieldAccess; +import org.eclipse.jdt.core.dom.ThisExpression; +import org.eclipse.jdt.core.dom.VariableDeclaration; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jdt.core.search.FieldDeclarationMatch; +import org.eclipse.jdt.core.search.IJavaSearchConstants; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding; +import org.eclipse.jdt.internal.core.search.DOMASTNodeUtils; +import org.eclipse.jdt.internal.core.search.LocatorResponse; + +public class DOMFieldLocator extends DOMPatternLocator { + + private FieldLocator fieldLocator; + + public DOMFieldLocator(FieldLocator locator) { + super(locator.pattern); + this.fieldLocator = locator; + } + + @Override + public LocatorResponse match(org.eclipse.jdt.core.dom.ASTNode node, NodeSetWrapper nodeSet, MatchLocator locator) { + int declarationsLevel = PatternLocator.IMPOSSIBLE_MATCH; + if (node instanceof EnumConstantDeclaration enumConstant) { + return match(enumConstant, nodeSet); + } + if (this.fieldLocator.pattern.findReferences) { + if (node instanceof ImportDeclaration importRef) { + // With static import, we can have static field reference in import reference + if (importRef.isStatic() && !importRef.isOnDemand() + && this.fieldLocator.matchesName(this.fieldLocator.pattern.name, + importRef.getName().toString().toCharArray()) + && this.fieldLocator.pattern instanceof FieldPattern fieldPattern) { + char[] declaringType = CharOperation.concat(fieldPattern.declaringQualification, + fieldPattern.declaringSimpleName, '.'); + if (this.fieldLocator.matchesName(declaringType, importRef.getName().toString().toCharArray())) { + declarationsLevel = this.fieldLocator.pattern.mustResolve ? PatternLocator.POSSIBLE_MATCH + : PatternLocator.ACCURATE_MATCH; + } + } + } + } + int level = nodeSet.addMatch(node, declarationsLevel); + return toResponse(level, true); + } + + @Override + public LocatorResponse match(Name name, NodeSetWrapper nodeSet, MatchLocator locator) { + if (name.getLocationInParent() == SingleVariableDeclaration.NAME_PROPERTY || name.getLocationInParent() == VariableDeclarationFragment.NAME_PROPERTY) { + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); // already caught by match(VariableDeclaration) + } + + if (this.fieldLocator.matchesName(this.fieldLocator.pattern.name, name.toString().toCharArray())) { + if (this.fieldLocator.isDeclarationOfAccessedFieldsPattern + && this.fieldLocator.pattern instanceof DeclarationOfAccessedFieldsPattern doafp) { + if (doafp.enclosingElement != null) { + // we have an enclosing element to check + if (!DOMASTNodeUtils.isWithinRange(name, doafp.enclosingElement)) { + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + // We need to report the declaration, not the usage + // TODO testDeclarationOfAccessedFields2 + IBinding b = name.resolveBinding(); + IJavaElement je = b == null ? null : b.getJavaElement(); + if (je != null && doafp.knownFields.includes(je)) { + doafp.knownFields.remove(je); + ISourceReference sr = je instanceof ISourceReference ? (ISourceReference) je : null; + IResource r = null; + ISourceRange srg = null; + String elName = je.getElementName(); + try { + srg = sr.getSourceRange(); + IJavaElement ancestor = je.getAncestor(IJavaElement.COMPILATION_UNIT); + r = ancestor == null ? null : ancestor.getCorrespondingResource(); + } catch (JavaModelException jme) { + // ignore + } + if (srg != null) { + int accuracy = this.fieldLocator.pattern.mustResolve ? PatternLocator.POSSIBLE_MATCH + : PatternLocator.ACCURATE_MATCH; + FieldDeclarationMatch fdMatch = new FieldDeclarationMatch(je, accuracy, + srg.getOffset() + srg.getLength() - elName.length() - 1, elName.length(), + locator.getParticipant(), r); + try { + locator.report(fdMatch); + } catch (CoreException ce) { + // ignore + } + } + } + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + } + + if (!matchesFineGrain(name)) { + return toResponse(IMPOSSIBLE_MATCH); + } + + if (!this.fieldLocator.pattern.readAccess && this.fieldLocator.pattern.fineGrain == 0 && DOMLocalVariableLocator.isRead(name)) { + return toResponse(IMPOSSIBLE_MATCH); + } + if (!this.fieldLocator.pattern.writeAccess && DOMLocalVariableLocator.isWrite(name)) { + return toResponse(IMPOSSIBLE_MATCH); + } + int level = nodeSet.addMatch(name, this.fieldLocator.pattern.mustResolve ? PatternLocator.POSSIBLE_MATCH + : PatternLocator.ACCURATE_MATCH); + return toResponse(level, true); + } + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + + private boolean matchesFineGrain(Name name) { + int fineGrain = this.fieldLocator.pattern.fineGrain; + if (fineGrain == 0) { + return true; + } + if ((fineGrain & IJavaSearchConstants.SUPER_REFERENCE) != 0 && name.getLocationInParent() == SuperFieldAccess.NAME_PROPERTY) { + return true; + } + if ((fineGrain & IJavaSearchConstants.QUALIFIED_REFERENCE) != 0 && name.getLocationInParent() == QualifiedName.NAME_PROPERTY) { + return true; + } + if (name.getLocationInParent() == FieldAccess.NAME_PROPERTY) { + Expression expr = ((FieldAccess)name.getParent()).getExpression(); + if ((fineGrain & IJavaSearchConstants.THIS_REFERENCE) != 0 && expr instanceof ThisExpression) { + return true; + } + if ((fineGrain & IJavaSearchConstants.QUALIFIED_REFERENCE) != 0 && expr != null && !(expr instanceof ThisExpression)) { + return true; + } + } + if ((fineGrain & IJavaSearchConstants.IMPLICIT_THIS_REFERENCE) != 0 && + !Set.of(SuperFieldAccess.NAME_PROPERTY, FieldAccess.NAME_PROPERTY, QualifiedName.NAME_PROPERTY).contains(name.getLocationInParent())) { + return true; + } + return false; + } + + @Override + public LocatorResponse resolveLevel(org.eclipse.jdt.core.dom.ASTNode node, IBinding binding, MatchLocator locator) { + if (binding == null) + return toResponse(PatternLocator.ACCURATE_MATCH); + if (binding instanceof IVariableBinding variableBinding) { + if (variableBinding.isRecordComponent()) { + // for matching the component in constructor of a record + if (!this.fieldLocator.matchesName(this.fieldLocator.pattern.name, + variableBinding.getName().toCharArray())) + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + FieldPattern fieldPattern = (FieldPattern) this.fieldLocator.pattern; + IMethodBinding declaring = variableBinding == null ? null : variableBinding.getDeclaringMethod(); + ITypeBinding tb = declaring == null ? null : declaring.getDeclaringClass(); + int level = this.resolveLevelForType(fieldPattern.declaringSimpleName, + fieldPattern.declaringQualification, tb); + return toResponse(level); + } + if (variableBinding.isField()) { + return toResponse(this.matchField(variableBinding, true)); + } + } + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + + @Override + public LocatorResponse match(VariableDeclaration node, NodeSetWrapper nodeSet, MatchLocator locator) { + if (!this.fieldLocator.pattern.findDeclarations && !this.fieldLocator.isDeclarationOfAccessedFieldsPattern) { + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + if (node.getLocationInParent() != org.eclipse.jdt.core.dom.FieldDeclaration.FRAGMENTS_PROPERTY) { + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + int referencesLevel = PatternLocator.IMPOSSIBLE_MATCH; + if (this.fieldLocator.pattern.findReferences) + // must be a write only access with an initializer + if (this.fieldLocator.pattern.writeAccess && !this.fieldLocator.pattern.readAccess + && node.getInitializer() != null) + if (this.fieldLocator.matchesName(this.fieldLocator.pattern.name, + node.getName().getIdentifier().toCharArray())) + referencesLevel = this.fieldLocator.pattern.mustResolve ? PatternLocator.POSSIBLE_MATCH + : PatternLocator.ACCURATE_MATCH; + + int declarationsLevel = PatternLocator.IMPOSSIBLE_MATCH; + if ((this.fieldLocator.pattern.findDeclarations || this.fieldLocator.isDeclarationOfAccessedFieldsPattern) + && this.fieldLocator.matchesName(this.fieldLocator.pattern.name, + node.getName().getIdentifier().toCharArray()) + && this.fieldLocator.pattern instanceof FieldPattern fieldPattern + && this.matchesTypeReference(fieldPattern.typeSimpleName, + ((org.eclipse.jdt.core.dom.FieldDeclaration) node.getParent()).getType())) { + declarationsLevel = this.fieldLocator.pattern.mustResolve ? PatternLocator.POSSIBLE_MATCH + : PatternLocator.ACCURATE_MATCH; + } + // use the stronger match + int level = nodeSet.addMatch(node, referencesLevel >= declarationsLevel ? referencesLevel : declarationsLevel); + return toResponse(level, true); + } + + private LocatorResponse match(EnumConstantDeclaration node, NodeSetWrapper nodeSet) { + int referencesLevel = PatternLocator.IMPOSSIBLE_MATCH; + if (this.fieldLocator.pattern.findReferences) + // must be a write only access with an initializer + if (this.fieldLocator.pattern.writeAccess && !this.fieldLocator.pattern.readAccess) + if (this.fieldLocator.matchesName(this.fieldLocator.pattern.name, + node.getName().getIdentifier().toCharArray())) + referencesLevel = this.fieldLocator.pattern.mustResolve ? PatternLocator.POSSIBLE_MATCH + : PatternLocator.ACCURATE_MATCH; + + int declarationsLevel = PatternLocator.IMPOSSIBLE_MATCH; + if (this.fieldLocator.pattern.findDeclarations + && this.fieldLocator.matchesName(this.fieldLocator.pattern.name, + node.getName().getIdentifier().toCharArray()) + && this.fieldLocator.pattern instanceof FieldPattern fieldPattern + && this.fieldLocator.matchesName(fieldPattern.typeSimpleName, + ((EnumDeclaration) node.getParent()).getName().getIdentifier().toCharArray())) { + declarationsLevel = this.fieldLocator.pattern.mustResolve ? PatternLocator.POSSIBLE_MATCH + : PatternLocator.ACCURATE_MATCH; + } + // use the stronger match + int level = nodeSet.addMatch(node, referencesLevel >= declarationsLevel ? referencesLevel : declarationsLevel); + return toResponse(level, true); + } + + protected int matchField(IVariableBinding field, boolean matchName) { + if (field == null) + return INACCURATE_MATCH; + if (!field.isField()) + return IMPOSSIBLE_MATCH; + + if (matchName && !this.fieldLocator.matchesName(this.fieldLocator.pattern.name, field.getName().toCharArray())) + return IMPOSSIBLE_MATCH; + + FieldPattern fieldPattern = (FieldPattern) this.fieldLocator.pattern; + ITypeBinding receiverBinding = field.getDeclaringClass(); + if (receiverBinding == null) { + if (field == ArrayBinding.ArrayLength) + // optimized case for length field of an array + return fieldPattern.declaringQualification == null && fieldPattern.declaringSimpleName == null + ? ACCURATE_MATCH + : IMPOSSIBLE_MATCH; + int mode = fieldPattern.getMatchMode(); + if (mode == SearchPattern.R_EXACT_MATCH) { + return IMPOSSIBLE_MATCH; + } + return INACCURATE_MATCH; + } + + // Note there is no dynamic lookup for field access + int declaringLevel = this.resolveLevelForType(fieldPattern.declaringSimpleName, + fieldPattern.declaringQualification, receiverBinding); + if (declaringLevel == IMPOSSIBLE_MATCH) + return IMPOSSIBLE_MATCH; + + // look at field type only if declaring type is not specified + if (fieldPattern.declaringSimpleName == null) { + if (this.fieldLocator.isDeclarationOfAccessedFieldsPattern + && this.fieldLocator.pattern instanceof DeclarationOfAccessedFieldsPattern doafp) { + IJavaElement je = field.getJavaElement(); + if (je != null) { + doafp.knownFields.add(je); + } + } else { + return declaringLevel; + } + return IMPOSSIBLE_MATCH; + } + + int typeLevel = resolveLevelForType(field.getType()); + int ret = declaringLevel > typeLevel ? typeLevel : declaringLevel; // return the weaker match + if (this.fieldLocator.isDeclarationOfAccessedFieldsPattern + && this.fieldLocator.pattern instanceof DeclarationOfAccessedFieldsPattern doafp) { + IJavaElement je = field.getJavaElement(); + if (je != null) { + doafp.knownFields.add(je); + } + } else { + return ret; + } + return IMPOSSIBLE_MATCH; + } + + protected int resolveLevelForType(ITypeBinding typeBinding) { + FieldPattern fieldPattern = (FieldPattern) this.fieldLocator.pattern; + ITypeBinding fieldTypeBinding = typeBinding; + if (fieldTypeBinding != null && fieldTypeBinding.isParameterizedType()) { + fieldTypeBinding = typeBinding.getErasure(); + } + int fieldNameMatch = this.resolveLevelForType(fieldPattern.typeSimpleName, + fieldPattern.typeQualification, fieldTypeBinding); + return fieldNameMatch; + } +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMLocalVariableLocator.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMLocalVariableLocator.java new file mode 100644 index 00000000000..e9bc0b56394 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMLocalVariableLocator.java @@ -0,0 +1,176 @@ +/******************************************************************************* + * Copyright (c) 2023, 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.core.search.matching; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.Assignment; +import org.eclipse.jdt.core.dom.ChildPropertyDescriptor; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.IVariableBinding; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.PostfixExpression; +import org.eclipse.jdt.core.dom.PrefixExpression; +import org.eclipse.jdt.core.dom.QualifiedName; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.VariableDeclaration; +import org.eclipse.jdt.core.dom.Assignment.Operator; +import org.eclipse.jdt.core.search.SearchMatch; +import org.eclipse.jdt.internal.core.LocalVariable; +import org.eclipse.jdt.internal.core.search.LocatorResponse; + +public class DOMLocalVariableLocator extends DOMPatternLocator { + + private LocalVariableLocator locator; + private List foundDeclarations = new ArrayList<>(); + + public DOMLocalVariableLocator(LocalVariableLocator lcl) { + super(lcl.pattern); + this.locator = lcl; + } + + private LocalVariable getLocalVariable() { + if( this.locator.pattern instanceof LocalVariablePattern lvp ) + return lvp.localVariable; + return null; + } + + @Override + public LocatorResponse match(Name node, NodeSetWrapper nodeSet, MatchLocator locator) { + if (node.getLocationInParent() instanceof ChildPropertyDescriptor descriptor + // local variable refs are either expressions as children + && (descriptor.getChildType() == Expression.class + // or dereferenced names + || descriptor == QualifiedName.QUALIFIER_PROPERTY) + // local variables cannot be qualified + && node instanceof SimpleName simple + && Objects.equals(getLocalVariable() == null ? null : getLocalVariable().getElementName(), simple.getIdentifier())) { + if (!this.locator.pattern.readAccess && isRead(node)) { + return toResponse(IMPOSSIBLE_MATCH); + } + if (!this.locator.pattern.writeAccess && isWrite(node)) { + return toResponse(IMPOSSIBLE_MATCH); + } + return toResponse(POSSIBLE_MATCH); + } + return toResponse(IMPOSSIBLE_MATCH); + } + + @Override + public LocatorResponse resolveLevel(org.eclipse.jdt.core.dom.ASTNode node, IBinding binding, MatchLocator locator) { + if (!(binding instanceof IVariableBinding)) { + return toResponse(IMPOSSIBLE_MATCH); + } + Object bindingElement = binding.getJavaElement(); + LocalVariable localVar = getLocalVariable(); + if (Objects.equals(bindingElement, localVar)) { + // We need to know if this is a reference request or a declaration request + if (this.locator.pattern.findReferences) { + return new LocatorResponse(ACCURATE_MATCH, false, node, false, false); + } else if (this.locator.pattern.findDeclarations) { + // we need to make sure the node has a VariableDeclaration in its ancestry + boolean isDecl = hasVariableDeclarationAncestor(node); + if( isDecl) { + if( !alreadyFound(localVar)) { + foundDeclarations.add(localVar); + return new LocatorResponse(ACCURATE_MATCH, false, node, false, false); + } + } + return toResponse(IMPOSSIBLE_MATCH); + } + } + return toResponse(IMPOSSIBLE_MATCH); + } + + private boolean hasVariableDeclarationAncestor(ASTNode node) { + ASTNode working = node; + while(working != null ) { + if( working instanceof VariableDeclaration) { + return true; + } + working = working.getParent(); + } + return false; + } + + @Override + public LocatorResponse match(VariableDeclaration node, NodeSetWrapper nodeSet, MatchLocator locator) { + int defaultLevelOnMatch = this.locator.pattern.mustResolve ? POSSIBLE_MATCH : ACCURATE_MATCH; + if (this.locator.pattern.findReferences) { + // must be a write only access with an initializer + if (this.locator.pattern.writeAccess && !this.locator.pattern.readAccess && node.getInitializer() != null) { + if (this.locator.matchesName(this.locator.pattern.name, node.getName().getIdentifier().toCharArray())) { + return toResponse(defaultLevelOnMatch, false); + } + } + } + + if (this.locator.pattern.findDeclarations) { + if (this.locator.matchesName(this.locator.pattern.name, node.getName().getIdentifier().toCharArray())) { + LocalVariable lvFromPattern = getLocalVariable(); + if (lvFromPattern != null ) { + if(node.getStartPosition() == lvFromPattern.declarationSourceStart) { + return toResponse(defaultLevelOnMatch, false); + } else if( node.getName().getStartPosition() == lvFromPattern.nameStart) { + return new LocatorResponse(defaultLevelOnMatch, true, node.getName(), false, false); + } + } + } + } + return toResponse(0, false); + } + + @Override + public void reportSearchMatch(MatchLocator locator, ASTNode node, SearchMatch match) throws CoreException { + SearchMatch matchToReport = match; + if(this.locator.pattern.findDeclarations && hasVariableDeclarationAncestor(node) ) { + LocalVariable localVariable = getLocalVariable(); + int offset = localVariable.nameStart; + int length = localVariable.nameEnd-offset+1; + IJavaElement element = localVariable; + matchToReport = locator.newDeclarationMatch(element, null, match.getAccuracy(), offset, length); + } + if( matchToReport != null) { + SearchMatchingUtility.reportSearchMatch(locator, matchToReport); + } + } + + private boolean alreadyFound(LocalVariable matchToReport) { + boolean found = foundDeclarations.stream().filter(x -> matchToReport.toString().equals(x.toString())).findFirst().isPresent(); + return found; + } + + public static boolean isRead(Name name) { + if (name.getLocationInParent() == QualifiedName.NAME_PROPERTY) { + name = (QualifiedName)name.getParent(); + } + if (name.getLocationInParent() == Assignment.LEFT_HAND_SIDE_PROPERTY) { + Assignment assign = (Assignment)name.getParent(); + return assign.getOperator() != Operator.ASSIGN; + } + return true; + } + + public static boolean isWrite(Name name) { + if (name.getLocationInParent() == QualifiedName.NAME_PROPERTY) { + name = (QualifiedName)name.getParent(); + } + return Set.of(Assignment.LEFT_HAND_SIDE_PROPERTY, PostfixExpression.OPERAND_PROPERTY, PrefixExpression.OPERAND_PROPERTY) + .contains(name.getLocationInParent()); + } +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMMethodLocator.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMMethodLocator.java new file mode 100644 index 00000000000..53916264079 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMMethodLocator.java @@ -0,0 +1,1167 @@ +/******************************************************************************* + * Copyright (c) 2023, 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.core.search.matching; + +import static org.eclipse.jdt.internal.core.JavaModelManager.trace; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.JavaModelException; +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.Annotation; +import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; +import org.eclipse.jdt.core.dom.ConstructorInvocation; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.ExpressionMethodReference; +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.JdtCoreDomPackagePrivateUtility; +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.MethodReference; +import org.eclipse.jdt.core.dom.Modifier; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; +import org.eclipse.jdt.core.dom.SuperMethodInvocation; +import org.eclipse.jdt.core.dom.SuperMethodReference; +import org.eclipse.jdt.core.dom.ThisExpression; +import org.eclipse.jdt.core.dom.TypeMethodReference; +import org.eclipse.jdt.core.search.IJavaSearchConstants; +import org.eclipse.jdt.core.search.SearchMatch; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.jdt.internal.core.BinaryMethod; +import org.eclipse.jdt.internal.core.search.BasicSearchEngine; +import org.eclipse.jdt.internal.core.search.DOMASTNodeUtils; +import org.eclipse.jdt.internal.core.search.LocatorResponse; + +public class DOMMethodLocator extends DOMPatternLocator { + + private MethodLocator locator; + private MethodPattern pattern; + private char[][][] allSuperDeclaringTypeNames; + private char[][][] samePkgSuperDeclaringTypeNames; + private Map methodDeclarationsWithInvalidParam = new HashMap<>(); + public DOMMethodLocator(MethodLocator locator) { + super(locator.pattern); + this.locator = locator; + this.pattern = locator.pattern; + } + + private IMethodBinding getDOMASTMethodBinding(ITypeBinding type, String methodName, ITypeBinding[] argumentTypes) { + if( type == null ) + return null; + return Stream.of(type.getDeclaredMethods()) + .filter(method -> Objects.equals(method.getName(), methodName)) + .filter(method -> compatibleByErasure(method.getParameterTypes(), argumentTypes)) + .findAny().orElse(null); + } + // can be replaced with `Arrays.equals(method.getParameterTypes(), argumentTypes, Comparator.comparing(ITypeBinding::getErasure))` + // but JDT bugs + private static boolean compatibleByErasure(ITypeBinding[] one, ITypeBinding[] other) { + if (Objects.equals(one, other)) { + return true; + } + if (one == null || other == null || one.length != other.length) { + return false; + } + for (int i = 0; i < one.length; i++) { + if (!Objects.equals(one[i].getErasure(), other[i].getErasure())) { + return false; + } + } + return true; + } + + @Override + public LocatorResponse match(org.eclipse.jdt.core.dom.MethodDeclaration node, NodeSetWrapper nodeSet, MatchLocator locator) { + if (!this.locator.pattern.findDeclarations) + return toResponse(IMPOSSIBLE_MATCH); + + // Verify method name + if (!this.locator.matchesName(this.locator.pattern.selector, node.getName().getIdentifier().toCharArray())) + return toResponse(IMPOSSIBLE_MATCH); + + // Verify parameters types + boolean resolve = this.locator.pattern.mustResolve; + if (this.locator.pattern.parameterSimpleNames != null) { + int length = this.locator.pattern.parameterSimpleNames.length; + List args = node.parameters(); + int argsLength = args == null ? 0 : args.size(); + if (length != argsLength) + return toResponse(IMPOSSIBLE_MATCH); + for (int i = 0; i < argsLength; i++) { + var arg = args.get(i); + if (!this.matchesTypeReference(this.locator.pattern.parameterSimpleNames[i], arg.getType(), arg.isVarargs())) { + if (!this.locator.pattern.mustResolve) { + // Set resolution flag on node set in case of types was inferred in parameterized types from generic ones... + // (see bugs https://bugs.eclipse.org/bugs/show_bug.cgi?id=79990, 96761, 96763) + nodeSet.setMustResolve(true); + resolve = true; + } + this.methodDeclarationsWithInvalidParam.put(node, null); + } + } + } + + // Verify type arguments (do not reject if pattern has no argument as it can be an erasure match) + if (this.locator.pattern.hasMethodArguments()) { + if (node.typeParameters() == null || node.typeParameters().size() != this.locator.pattern.methodArguments.length) + return toResponse(IMPOSSIBLE_MATCH); + } + + // Method declaration may match pattern + int level = nodeSet.addMatch(node, resolve ? POSSIBLE_MATCH : ACCURATE_MATCH); + return toResponse(level, true); + } + + @Override + public LocatorResponse match(AnnotationTypeMemberDeclaration node, NodeSetWrapper nodeSet, MatchLocator locator) { + return toResponse(this.locator.pattern.findDeclarations && this.locator.matchesName(this.locator.pattern.selector, node.getName().getIdentifier().toCharArray()) ? + POSSIBLE_MATCH : IMPOSSIBLE_MATCH); + } + + @Override + public LocatorResponse match(Annotation node, NodeSetWrapper nodeSet, MatchLocator locator) { + return toResponse(this.locator.pattern.findReferences && node instanceof SingleMemberAnnotation singleMemberAnnot ? + POSSIBLE_MATCH : IMPOSSIBLE_MATCH); + } + + private int matchReference(SimpleName name, List args) { + if (!this.locator.pattern.findReferences) return IMPOSSIBLE_MATCH; + + if (!this.locator.matchesName(this.locator.pattern.selector, name.getIdentifier().toCharArray())) return IMPOSSIBLE_MATCH; + if (this.locator.pattern.parameterSimpleNames != null && (!this.locator.pattern.varargs || DOMASTNodeUtils.insideDocComment(name))) { + int length = this.locator.pattern.parameterSimpleNames.length; + int argsLength = args == null ? 0 : args.size(); + if (length != argsLength) return IMPOSSIBLE_MATCH; + } + return this.locator.pattern.mustResolve ? POSSIBLE_MATCH : ACCURATE_MATCH; + } + @Override + public LocatorResponse match(MethodInvocation node, NodeSetWrapper nodeSet, MatchLocator locator) { + int level = this.matchReference(node.getName(), node.arguments()); + if( level == IMPOSSIBLE_MATCH ) + return toResponse(IMPOSSIBLE_MATCH); + int fineGrain = this.locator.pattern.fineGrain; + if (fineGrain != 0) { + Expression expr = node.getExpression(); + if ((fineGrain & IJavaSearchConstants.IMPLICIT_THIS_REFERENCE) == 0 && expr == null) { + return toResponse(IMPOSSIBLE_MATCH); + } + if ((fineGrain & IJavaSearchConstants.QUALIFIED_REFERENCE) == 0 && expr != null && !(expr instanceof ThisExpression)) { + return toResponse(IMPOSSIBLE_MATCH); + } + if ((fineGrain & IJavaSearchConstants.THIS_REFERENCE) == 0 && expr instanceof ThisExpression) { + return toResponse(IMPOSSIBLE_MATCH); + } + } + return toResponse(nodeSet.addMatch(node, level), true); + } + @Override + public LocatorResponse match(MethodRef node, NodeSetWrapper nodeSet, MatchLocator locator) { + int level = this.matchReference(node.getName(), node.parameters()); + if( level == IMPOSSIBLE_MATCH ) + return toResponse(IMPOSSIBLE_MATCH); + return toResponse(nodeSet.addMatch(node, level), true); + } + @Override + public LocatorResponse match(MethodReference node, NodeSetWrapper nodeSet, MatchLocator locator) { + if ((this.locator.pattern.fineGrain & IJavaSearchConstants.METHOD_REFERENCE_EXPRESSION) == 0) { + return toResponse(IMPOSSIBLE_MATCH); + } + SimpleName name = node instanceof TypeMethodReference typeMethodRef ? typeMethodRef.getName() : + node instanceof SuperMethodReference superMethodRef ? superMethodRef.getName() : + node instanceof ExpressionMethodReference exprMethodRef ? exprMethodRef.getName() : + null; + if (name == null) { + return toResponse(IMPOSSIBLE_MATCH); + } + if (this.locator.matchesName(this.locator.pattern.selector, name.getIdentifier().toCharArray())) { + nodeSet.setMustResolve(true); + return toResponse(nodeSet.addMatch(node, POSSIBLE_MATCH), true); + } else { + return toResponse(IMPOSSIBLE_MATCH); + } + + } + @Override + public LocatorResponse match(org.eclipse.jdt.core.dom.Expression expression, NodeSetWrapper nodeSet, MatchLocator locator) { + int level = IMPOSSIBLE_MATCH; + if (expression instanceof SuperMethodInvocation node) { + if (this.pattern.fineGrain != 0 && (this.pattern.fineGrain & IJavaSearchConstants.SUPER_REFERENCE) == 0) { + return toResponse(IMPOSSIBLE_MATCH); + } + level = this.matchReference(node.getName(), node.arguments()); + } + if (expression.getLocationInParent() == SingleMemberAnnotation.VALUE_PROPERTY + && this.locator.pattern.matchesName(this.locator.pattern.selector, "value".toCharArray()) + && this.locator.pattern.parameterCount == 0) { + // TODO: also check annotation name matches pattern (if available) + level = POSSIBLE_MATCH; + } + if( level == IMPOSSIBLE_MATCH) + return toResponse(IMPOSSIBLE_MATCH); + return toResponse(nodeSet.addMatch(expression, level), true); + } + + @Override + public LocatorResponse match(Name node, NodeSetWrapper nodeSet, MatchLocator locator) { + if( node.getParent() instanceof MethodInvocation mi && mi.getName() == node) { +// if( nodeSet.getTrustedMatch(mi) > IMPOSSIBLE_MATCH ) { + return toResponse(IMPOSSIBLE_MATCH); +// } + } + + if (node.getLocationInParent() == MemberValuePair.NAME_PROPERTY + && this.locator.pattern.parameterCount == 0 + && node instanceof SimpleName simpleName) { + return toResponse(matchReference(simpleName, null)); + } + + String name = node.toString(); + String[] segments = name.split("\\."); //$NON-NLS-1$ + String lastSegment = segments == null || segments.length == 0 ? null : segments[segments.length-1]; + boolean matchesLastSegment = this.locator.pattern.selector == null ? true : + this.locator.matchesName(this.locator.pattern.selector, (lastSegment == null ? "" : lastSegment).toCharArray()); //$NON-NLS-1$ + boolean matchesPrefix = this.locator.pattern.declaringPackageName == null ? true : + name.startsWith(new String(this.locator.pattern.declaringPackageName)); + int level = matchesLastSegment && matchesPrefix ? POSSIBLE_MATCH : IMPOSSIBLE_MATCH; + return toResponse(level); + } + + protected int matchMethod(ASTNode node, IMethodBinding method, boolean skipImpossibleArg, boolean bindingIsDeclaration) { + if (!this.locator.matchesName(this.locator.pattern.selector, method.getName().toCharArray())) return IMPOSSIBLE_MATCH; + + int level = matchMethodBindingReturn(method); + if (level == IMPOSSIBLE_MATCH) + return level; + + if (this.pattern.declaringSimpleName == null && this.locator.pattern.returnSimpleName != null) { + // look at return type only if declaring type is not specified + level = resolveLevelForType(this.locator.pattern.returnSimpleName, this.locator.pattern.returnQualification, method.getReturnType()); + if (level == IMPOSSIBLE_MATCH) + return level; + } + + level = matchMethodParametersTypes(node, method, skipImpossibleArg, level); + if (level == IMPOSSIBLE_MATCH) + return level; + + level = matchMethodTypeArguments(node, method, skipImpossibleArg, level, bindingIsDeclaration); + if (level == IMPOSSIBLE_MATCH) + return level; + + int typeParamMatches = validateReceiverTypeArguments(node, method, level, bindingIsDeclaration); + if( typeParamMatches == DOMTypeReferenceLocator.TYPE_PARAMS_NO_MATCH) level = IMPOSSIBLE_MATCH; + if( typeParamMatches == DOMTypeReferenceLocator.TYPE_PARAMS_COUNT_MATCH) level = ERASURE_MATCH; + if( isPatternExactMatch()) { + if( typeParamMatches == DOMTypeReferenceLocator.TYPE_PARAMS_NO_MATCH) { + return IMPOSSIBLE_MATCH; + } + } + boolean isErasurePattern = isPatternErasureMatch(); + boolean isEquivPattern = isPatternEquivalentMatch(); + if( level == ERASURE_MATCH && !isErasurePattern && !isEquivPattern) + level = IMPOSSIBLE_MATCH; + + return level; + } + + private int validateReceiverTypeArguments(ASTNode node, IMethodBinding method, int level, + boolean bindingIsDeclaration) { + // This method is substantially copied from DOMTypeReferenceLocator + boolean erasureMatch = isPatternErasureMatch(); + boolean equivMatch = isPatternEquivalentMatch(); + boolean exactMatch = isPatternExactMatch(); + boolean patternHasTypeArgs = this.locator.pattern.hasTypeArguments(); + + if( patternHasTypeArgs && !(erasureMatch || equivMatch || exactMatch )) { + return DOMTypeReferenceLocator.TYPE_PARAMS_NO_MATCH; + } + if( patternHasTypeArgs && !(erasureMatch || equivMatch || exactMatch )) { + return DOMTypeReferenceLocator.TYPE_PARAMS_NO_MATCH; + } + + char[][][] fromPattern = this.locator.pattern.getTypeArguments(); + if( fromPattern == null ) { + return DOMTypeReferenceLocator.TYPE_PARAMS_MATCH; + } + if( node instanceof MethodInvocation mi && mi.getExpression() != null ) { + ASTNode expr = mi.getExpression(); + IBinding b = DOMASTNodeUtils.getBinding(expr); + if( b instanceof IVariableBinding vb) { + b = vb.getType(); + } + if( b instanceof ITypeBinding tb ) { + boolean bindingIsRaw = tb.isRawType(); + ITypeBinding[] typeArgs = tb.getTypeArguments(); + if( fromPattern.length == 0 ) { + if( typeArgs == null || typeArgs.length == 0 ) { + return DOMTypeReferenceLocator.TYPE_PARAMS_MATCH; + } + return DOMTypeReferenceLocator.TYPE_PARAMS_NO_MATCH; + } + char[][] thisLevelTypeParams = fromPattern[0]; + boolean emptyPatternParams = thisLevelTypeParams == null || thisLevelTypeParams.length == 0; + if( emptyPatternParams) { + if( exactMatch && emptyPatternParams && (typeArgs != null && typeArgs.length > 0) ) { + return DOMTypeReferenceLocator.TYPE_PARAMS_NO_MATCH; + } + } else { + if( typeArgs == null || typeArgs.length != thisLevelTypeParams.length) { + if( thisLevelTypeParams.length == 0 ) { + return DOMTypeReferenceLocator.TYPE_PARAMS_COUNT_MATCH; + } + if (typeArgs.length==0) { + if( equivMatch && bindingIsRaw) { + return DOMTypeReferenceLocator.TYPE_PARAMS_MATCH; + } + if( !bindingIsRaw && !(equivMatch || erasureMatch)) { + return DOMTypeReferenceLocator.TYPE_PARAMS_NO_MATCH; + } + if( !equivMatch || bindingIsRaw) + return DOMTypeReferenceLocator.TYPE_PARAMS_MATCH; + } + return DOMTypeReferenceLocator.TYPE_PARAMS_NO_MATCH; + } + for( int j = 0; j < thisLevelTypeParams.length; j++ ) { + ITypeBinding domBinding = typeArgs[j]; + String patternSig = new String(thisLevelTypeParams[j]); + IBinding patternBinding = JdtCoreDomPackagePrivateUtility.findBindingForType(node, patternSig); + if( patternBinding == null ) { + boolean plusOrMinus = patternSig.startsWith("+") || patternSig.startsWith("-"); + String safePatternString = plusOrMinus ? patternSig.substring(1) : patternSig; + if( safePatternString.startsWith("Q")) { + patternBinding = JdtCoreDomPackagePrivateUtility.findUnresolvedBindingForType(node, patternSig); + } else { + patternBinding = JdtCoreDomPackagePrivateUtility.findBindingForType(node, safePatternString); + } + } + boolean singleTypeArgMatches = TypeArgumentMatchingUtility.validateSingleTypeArgMatches(exactMatch, patternSig, patternBinding, domBinding, this.locator); + if( !singleTypeArgMatches ) { + return DOMTypeReferenceLocator.TYPE_PARAMS_COUNT_MATCH; + } + + } + } + } + } + return DOMTypeReferenceLocator.TYPE_PARAMS_MATCH; + } + + private ITypeBinding findPossiblyUnresolvedBindingForType(ASTNode node, String patternSig) { + ITypeBinding patternBinding = JdtCoreDomPackagePrivateUtility.findBindingForType(node, patternSig) instanceof ITypeBinding ptb + ? ptb : null; + if( patternBinding == null ) { + boolean plusOrMinus = patternSig.startsWith("+") || patternSig.startsWith("-"); + String safePatternString = plusOrMinus ? patternSig.substring(1) : patternSig; + if( safePatternString.startsWith("Q")) { + patternBinding = JdtCoreDomPackagePrivateUtility.findBindingForType(node, patternSig) instanceof ITypeBinding ptb + ? ptb : null; + } + } + return patternBinding; + } + + private boolean isPatternErasureMatch() { + int r = this.locator.pattern.getMatchRule(); + return (r & SearchPattern.R_ERASURE_MATCH) == SearchPattern.R_ERASURE_MATCH; + } + private boolean isPatternEquivalentMatch() { + int r = this.locator.pattern.getMatchRule(); + return (r & SearchPattern.R_EQUIVALENT_MATCH) == SearchPattern.R_EQUIVALENT_MATCH; + } + private boolean isPatternExactMatch() { + int r = this.locator.pattern.getMatchRule(); + return (r & SearchPattern.R_FULL_MATCH) == SearchPattern.R_FULL_MATCH; + } + + private int matchMethodTypeArguments(ASTNode node, IMethodBinding method, + boolean skipImpossibleArg, int level, boolean bindingIsDeclaration) { + boolean potentialMatchOnly = false; + if (this.locator.pattern.hasMethodArguments()) { + boolean isExactPattern = isPatternExactMatch(); + boolean isErasurePattern = isPatternErasureMatch(); + boolean isEquivPattern = isPatternEquivalentMatch(); + boolean methodIsRaw = method.isRawMethod(); + ITypeBinding[] argBindings = method.getTypeArguments(); + char[][] goal = this.locator.pattern.methodArguments; + + if( goal.length > 0 && methodIsRaw && isExactPattern) { + return IMPOSSIBLE_MATCH; + } + + + if( goal == null || goal.length == 0) { + return level; + } + if( argBindings == null || argBindings.length == 0 ) { + // just check from the node real quick + List typeArgsFromNode = null; + if( node instanceof MethodInvocation mi) { + typeArgsFromNode = mi.typeArguments(); + potentialMatchOnly = !bindingIsDeclaration ? true : false; + } else if( node instanceof MethodDeclaration md) { + typeArgsFromNode = md.typeParameters(); + } + if(typeArgsFromNode != null && typeArgsFromNode.size() > 0 ) { + // Something is wrong with the binding. Maybe an error node + List tmp = typeArgsFromNode.stream().map(DOMASTNodeUtils::getBinding). + filter(x -> x instanceof ITypeBinding). + map(x -> (ITypeBinding)x). + collect(Collectors.toList()); + argBindings = tmp.toArray(new ITypeBinding[tmp.size()]); + } + } + + if( argBindings == null || argBindings.length == 0 ) { + if( goal == null || goal.length == 0 ) + return level; + // we have a raw usage of this method with no type params in use + if( isExactPattern || (!isErasurePattern && !isEquivPattern)) { + return IMPOSSIBLE_MATCH; + } + if( !bindingIsDeclaration ) { + return IMPOSSIBLE_MATCH; + } + // Now we have only declarations, so you need to check type Params instead of type args. + ITypeBinding[] tmp = method.getTypeParameters(); + if( tmp != null && tmp.length > 0 ) { + return goal != null && goal.length == tmp.length ? ERASURE_MATCH : IMPOSSIBLE_MATCH; + } + return ERASURE_MATCH; + } + + // Now we need to do the hard work of comparing one to another + if( argBindings.length != goal.length ) + return IMPOSSIBLE_MATCH; + + boolean inaccurateFound = false; + boolean erasureFound = false; + for( int i = 0; i < argBindings.length; i++ ) { + // Compare each + String goaliString = new String(goal[i]); + IBinding patternBinding = findPossiblyUnresolvedBindingForType(node, goaliString); + if( argBindings[i].isTypeVariable() && patternBinding == null ) { + continue; + } + boolean match = TypeArgumentMatchingUtility.validateSingleTypeArgMatches(isExactPattern, goaliString, patternBinding, argBindings[i]); + if( !match ) { + if( isExactPattern || (!isErasurePattern && !isEquivPattern)) { + return IMPOSSIBLE_MATCH; + } + if( potentialMatchOnly ) { + inaccurateFound = true; + } else { + erasureFound = true; + } + } + } + if(inaccurateFound) + return INACCURATE_MATCH; + if( erasureFound ) { + return ERASURE_MATCH; + } + } + return level; + } + + protected int matchMethodBindingReturn(IMethodBinding binding) { + // look at return type only if declaring type is not specified + if (this.locator.pattern.declaringSimpleName == null) { + // TODO (frederic) use this call to refine accuracy on return type + // int newLevel = resolveLevelForType(this.locator.pattern.returnSimpleName, this.locator.pattern.returnQualification, this.locator.pattern.returnTypeArguments, 0, method.returnType); + if (resolveLevelForType(this.locator.pattern.returnSimpleName, this.locator.pattern.returnQualification, + binding.getReturnType()) == IMPOSSIBLE_MATCH) + return IMPOSSIBLE_MATCH; + } + return ACCURATE_MATCH; + } + + private int matchMethodParametersTypes(ASTNode node, IMethodBinding method, boolean skipImpossibleArg, int level) { + // parameter types + boolean isExactPattern = isPatternExactMatch(); + int parameterCount = this.locator.pattern.parameterSimpleNames == null ? -1 : this.locator.pattern.parameterSimpleNames.length; + if (parameterCount > -1) { + // global verification + if (method.getParameterTypes() == null) return INACCURATE_MATCH; + if (parameterCount != method.getParameterTypes().length) return IMPOSSIBLE_MATCH; + if (method.isRecovered()) { + // return inaccurate match for ambiguous call (bug 80890) + return INACCURATE_MATCH; + } + boolean foundTypeVariable = false; + IMethodBinding focusMethodBinding = null; + boolean checkedFocus = false; + boolean isBinary = this.locator.pattern!= null && this.locator.pattern.focus instanceof BinaryMethod; + // verify each parameter + ITypeBinding[] paramTypes = method.getParameterTypes(); + for (int i = 0; i < parameterCount; i++) { + ITypeBinding argType = paramTypes[i]; + int newLevel = IMPOSSIBLE_MATCH; + boolean foundLevel = false; + if (argType.isMember() || this.locator.pattern.parameterQualifications[i] != null) { + if (!checkedFocus) { + focusMethodBinding = getDOMASTMethodBinding(this.locator.pattern, node.getAST()); + checkedFocus = true; + } + if (focusMethodBinding != null) {// textual comparison insufficient + ITypeBinding[] parameters = focusMethodBinding.getParameterTypes(); + if (parameters.length >= parameterCount) { + newLevel = (isBinary ? argType.getErasure().isEqualTo(parameters[i].getErasure()) : argType.isEqualTo((parameters[i]))) ? + ACCURATE_MATCH : IMPOSSIBLE_MATCH; + foundLevel = true; + } + } + } else { + // TODO (frederic) use this call to refine accuracy on parameter types +// newLevel = resolveLevelForType(this.locator.pattern.parameterSimpleNames[i], this.locator.pattern.parameterQualifications[i], this.locator.pattern.parametersTypeArguments[i], 0, argType); + newLevel = this.resolveLevelForType(this.locator.pattern.parameterSimpleNames[i], this.locator.pattern.parameterQualifications[i], argType); + // TODO testMethodReferencesElementPatternSingleTypeParameter04 says should be potential match only + if( argType.isGenericType() ) { + // this param is also a generic and has its own nested types, but we don't know what they are + if( newLevel == ACCURATE_MATCH ) { + ITypeBinding[] nestedParams = argType.getTypeParameters(); + char[][][][] ptaAll = this.locator.pattern.parametersTypeArguments; + char[][][] ptaThisLevel = ptaAll == null || ptaAll.length == 0 ? null : ptaAll[0]; + boolean patternHasTypeArgs = ptaThisLevel == null || ptaThisLevel.length <= i ? false : true; + if( patternHasTypeArgs) { + char[][] thisParamTypeArgs = ptaThisLevel[i]; + for( int q = 0; q < thisParamTypeArgs.length; q++ ) { + char[] fromPattern = thisParamTypeArgs[q]; + ITypeBinding fromBinding = nestedParams == null || nestedParams.length == 0 ? null : nestedParams[q]; + if( fromBinding == null ) { + // pattern expects, binding doesn't have + newLevel = INACCURATE_MATCH; + } else { + //see if they match? + String fromPatternString = new String(fromPattern); + IBinding patternBinding = findPossiblyUnresolvedBindingForType(node, fromPatternString); + boolean match = TypeArgumentMatchingUtility.validateSingleTypeArgMatches(isExactPattern, fromPatternString, patternBinding, fromBinding); + if( !match ) { + newLevel = INACCURATE_MATCH; + } + } + } + } + } + } + } + if (level > newLevel) { + if (newLevel == IMPOSSIBLE_MATCH) { + if (skipImpossibleArg) { + // Do not consider match as impossible while finding declarations and source level >= 1.5 + // (see bugs https://bugs.eclipse.org/bugs/show_bug.cgi?id=79990, 96761, 96763) + if (!foundLevel) { + newLevel = level; + } + } else if (argType.isTypeVariable()) { + newLevel = level; + foundTypeVariable = true; + } else { + return IMPOSSIBLE_MATCH; + } + } + level = newLevel; // can only be downgraded + } + } + if (foundTypeVariable) { + if (!Modifier.isStatic(method.getModifiers()) && !Modifier.isPrivate(method.getModifiers())) { + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=123836, No point in textually comparing type variables, captures etc with concrete types. + if (!checkedFocus) + focusMethodBinding = getDOMASTMethodBinding(this.locator.pattern, node.getAST()); + if (focusMethodBinding != null + /* && matchOverriddenMethod(focusMethodBinding.getDeclaringClass(), focusMethodBinding, method)*/ + && (focusMethodBinding.overrides(method) || method.overrides(focusMethodBinding))) { + return ACCURATE_MATCH; + } + } + return IMPOSSIBLE_MATCH; + } + } + return level; + } + + public IMethodBinding getDOMASTMethodBinding(MethodPattern methodPattern, AST ast) { + char[] typeName = PatternLocator.qualifiedPattern(methodPattern.declaringSimpleName, methodPattern.declaringQualification); + if( typeName != null ) { + var type = ast.resolveWellKnownType(new String(typeName)); + if (type != null) { + for (IMethodBinding method : type.getDeclaredMethods()) { + if (Objects.equals(method.getJavaElement(), methodPattern.focus)) { + return method; + } + } + } + } + return null; + } + + private int computeResolveLevel(org.eclipse.jdt.core.dom.ASTNode node, IBinding binding, MatchLocator locator) { + if (node.getLocationInParent() == SingleMemberAnnotation.VALUE_PROPERTY + && node.getParent() instanceof SingleMemberAnnotation singleMemberAnnotation) { + var valuePairs = singleMemberAnnotation.resolveAnnotationBinding().getDeclaredMemberValuePairs(); + if (valuePairs != null && valuePairs.length > 0) { + binding = valuePairs[0].getMethodBinding(); + } + } + + if (binding instanceof IMethodBinding method) { + boolean skipVerif = this.locator.pattern.findDeclarations /*&& this.locator.mayBeGeneric*/; + return resolveLevelForNodeWithMethodBinding(node, method, null, skipVerif, true); + } + return IMPOSSIBLE_MATCH; + } + + protected int resolveLevel(MethodInvocation messageSend) { + IMethodBinding invocationBinding = messageSend.resolveMethodBinding(); + ITypeBinding initialReceiverType = messageSend.getExpression() != null ? messageSend.getExpression().resolveTypeBinding() : null; + return resolveLevelForNodeWithMethodBinding(messageSend, invocationBinding, initialReceiverType, false, false); + } + + private List arguments(ASTNode node) { + return (List) + (node instanceof MethodInvocation method ? method.arguments() : + node instanceof ConstructorInvocation constr ? constr.arguments() : + List.of()); + } + + protected int resolveLevelForNodeWithMethodBinding(ASTNode messageSend, + IMethodBinding invocationBinding, ITypeBinding initialReceiverType, + boolean skipVerif, boolean nullParamsForSubTypeCheck) { + boolean isExactPattern = isPatternExactMatch(); + boolean isErasurePattern = isPatternErasureMatch(); + boolean isEquivPattern = isPatternEquivalentMatch(); + + IMethodBinding invocationOrDeclarationBinding = invocationBinding; + IMethodBinding declarationBinding = null; + if (invocationOrDeclarationBinding == null) { + return INACCURATE_MATCH; + } + + int invocationLevel = matchMethod(messageSend, invocationBinding, skipVerif, false); + int declarationLevel = IMPOSSIBLE_MATCH; + if (invocationLevel == IMPOSSIBLE_MATCH) { + declarationBinding = invocationBinding.getMethodDeclaration(); + if (invocationBinding != declarationBinding) + declarationLevel = matchMethod(messageSend, declarationBinding, skipVerif, true); + if (declarationLevel == IMPOSSIBLE_MATCH) + return IMPOSSIBLE_MATCH; + invocationOrDeclarationBinding = declarationBinding; + } + + if (invocationBinding.getParameterTypes().length == arguments(messageSend).size()) { + for (int i = 0; i < invocationBinding.getParameterTypes().length; i++) { + var source = arguments(messageSend).get(i).resolveTypeBinding(); + var resolved = invocationBinding.getParameterTypes()[i]; + if (source != null && !source.isAssignmentCompatible(resolved)) { + return INACCURATE_MATCH; + } + } + } + + int invocOrDeclLevel = invocationLevel == IMPOSSIBLE_MATCH ? declarationLevel : invocationLevel; + // receiver type + if (this.pattern.declaringSimpleName == null && this.pattern.declaringQualification == null) { + // since any declaring class will do + return invocOrDeclLevel; + } + +// ITypeBinding invocationDeclClass = invocationBinding.getDeclaringClass(); +// ITypeBinding declBindingClass = declarationBinding == null ? null : declarationBinding.getDeclaringClass(); +// String k1 = invocationDeclClass == null ? null : invocationDeclClass.getKey(); +// String k2 = declBindingClass == null ? null : declBindingClass.getKey(); +// String q1 = invocationDeclClass == null ? null : invocationDeclClass.getQualifiedName(); +// String q2 = declBindingClass == null ? null : declBindingClass.getQualifiedName(); +// String b1 = invocationDeclClass == null ? null : invocationDeclClass.getBinaryName(); +// String b2 = declBindingClass == null ? null : declBindingClass.getBinaryName(); + int declaringLevel; + ITypeBinding receiverType = initialReceiverType != null ? initialReceiverType : invocationOrDeclarationBinding.getDeclaringClass(); + if (shouldResolveSubSuperLevel(messageSend, receiverType, invocationOrDeclarationBinding, invocOrDeclLevel)) { + declaringLevel = resolveSubSuperLevel(messageSend, receiverType, invocationOrDeclarationBinding, invocOrDeclLevel, nullParamsForSubTypeCheck); + } else { + declaringLevel = resolveLevelForType(this.pattern.declaringSimpleName, this.pattern.declaringQualification, invocationOrDeclarationBinding.getDeclaringClass()); + } + + int declaringFlavors = declaringLevel & FLAVORS_MASK; + + int noFlavorInvocOrDecl = invocOrDeclLevel & MATCH_LEVEL_MASK; + int noFlavorDeclaringLevel = declaringLevel & MATCH_LEVEL_MASK; + int weakerMethod = findWeakerLevel(noFlavorInvocOrDecl, noFlavorDeclaringLevel); + int matchLevel = (weakerMethod & PatternLocator.MATCH_LEVEL_MASK); + + int retval = weakerMethod; + if( matchLevel != ACCURATE_MATCH) { + boolean isDefault = Modifier.isDefault(invocationOrDeclarationBinding.getModifiers()); + boolean nullFocus = this.pattern.focus == null; + char[][][] superTypeNames = isDefault && nullFocus ? this.samePkgSuperDeclaringTypeNames: this.allSuperDeclaringTypeNames; + if (superTypeNames != null && resolveLevelAsSuperInvocation(invocationOrDeclarationBinding.getDeclaringClass(), invocationOrDeclarationBinding.getParameterTypes(), superTypeNames, true)) { + // since this is an ACCURATE_MATCH so return the possibly weaker match + // this is an overridden method => add flavor to returned level + declaringLevel = invocOrDeclLevel | SUPER_INVOCATION_FLAVOR; + } + if ((declaringLevel & FLAVORS_MASK) != 0) { + // level got some flavors => return it + retval = declaringLevel; + } else if( isExactPattern || (!isErasurePattern && !isEquivPattern)) { + retval = IMPOSSIBLE_MATCH; + } else if( isEquivPattern && matchLevel == ERASURE_MATCH) { + retval = IMPOSSIBLE_MATCH; + } + } else if (declaringFlavors != 0) { + // level got some flavors => return it + retval = declaringLevel; + } + + return retval; + } + + private boolean shouldResolveSubSuperLevel(ASTNode messageSend, ITypeBinding receiverType, IMethodBinding invocationOrDeclarationBinding, int invocOrDeclLevel) { + if (receiverType != null && receiverType.isArray()) { + receiverType = messageSend.getAST().resolveWellKnownType(Object.class.getName()); + } + boolean isVirtuallyInvoked = isVirtualInvoke(invocationOrDeclarationBinding); + boolean excluded = receiverType == null || receiverType.isArray() || receiverType.isIntersectionType(); + if (isVirtuallyInvoked && !excluded) { + return true; + } + return false; + } + + private int resolveSubSuperLevel(ASTNode messageSend, ITypeBinding receiverType, IMethodBinding invocationOrDeclarationBinding, + int invocOrDeclLevel, boolean useNullParameterTypes) { + int retLevel; + if (receiverType == null || receiverType.isArray()) { + receiverType = messageSend.getAST().resolveWellKnownType(Object.class.getName()); + } + var packageBinding = receiverType.getPackage(); + boolean isDefault = Modifier.isDefault(invocationOrDeclarationBinding.getModifiers()); + String packageBindingName = packageBinding != null ? packageBinding.getName() : null; + ITypeBinding[] parameterTypes = useNullParameterTypes ? null : invocationOrDeclarationBinding.getParameterTypes(); + String bindingName = invocationOrDeclarationBinding.getName(); + retLevel = resolveLevelAsSubtype(this.pattern.declaringSimpleName, this.pattern.declaringQualification, + receiverType, bindingName, parameterTypes, packageBindingName, isDefault, invocationOrDeclarationBinding); + if (retLevel == IMPOSSIBLE_MATCH) { + if (invocationOrDeclarationBinding.getDeclaringClass() == null || this.allSuperDeclaringTypeNames == null) { + retLevel = INACCURATE_MATCH; + } else { + boolean nullFocusDefault = Modifier.isDefault(invocationOrDeclarationBinding.getModifiers()) && this.pattern.focus == null; + char[][][] superTypeNames = nullFocusDefault ? this.samePkgSuperDeclaringTypeNames: this.allSuperDeclaringTypeNames; + if (superTypeNames != null && resolveLevelAsSuperInvocation(receiverType, invocationOrDeclarationBinding.getParameterTypes(), superTypeNames, true)) { + retLevel = invocOrDeclLevel // since this is an ACCURATE_MATCH so return the possibly weaker match + | SUPER_INVOCATION_FLAVOR; // this is an overridden method => add flavor to returned level + } + } + } + return retLevel; + } + + protected boolean isVirtualInvoke(IMethodBinding method) { + // This method makes absolutely zero sense to me. + String t = method == null ? null : + method.getDeclaringClass() == null ? null : + method.getDeclaringClass().getPackage() == null ? null : + method.getDeclaringClass().getPackage().getName(); + boolean notStatic = !Modifier.isStatic(method.getModifiers()); + boolean notPrivate = !Modifier.isPrivate(method.getModifiers()); + boolean isDefault = Modifier.isDefault(method.getModifiers()); + boolean nonNullFocus = this.pattern.focus != null; + boolean packageMatch = CharOperation.equals(this.pattern.declaringPackageName, t.toCharArray()); + boolean r = notStatic && notPrivate && !(isDefault && nonNullFocus && !packageMatch); + return r; + } + + @Override + public LocatorResponse resolveLevel(org.eclipse.jdt.core.dom.ASTNode node, IBinding binding, MatchLocator locator) { + if (node instanceof MethodInvocation invocation) { + return toResponse(resolveLevel(invocation)); + } + int level = computeResolveLevel(node, binding, locator); + if (node instanceof MethodDeclaration declaration && binding != null) { + if( level == IMPOSSIBLE_MATCH ) { + return toResponse(IMPOSSIBLE_MATCH); + } + boolean matchesDecl = matchesDeclaration(node, binding.getJavaElement(), declaration.resolveBinding(), locator); + return toResponse(matchesDecl ? level : IMPOSSIBLE_MATCH); + } + return toResponse(level); + } + + private int findWeakerLevel(int i, int j) { + int levelI = i & PatternLocator.MATCH_LEVEL_MASK; + int levelJ = j & PatternLocator.MATCH_LEVEL_MASK; + int[] ints = {DOMPatternLocator.IMPOSSIBLE_MATCH, + DOMPatternLocator.POSSIBLE_MATCH, + DOMPatternLocator.INACCURATE_MATCH, + DOMPatternLocator.ERASURE_MATCH, + DOMPatternLocator.ACCURATE_MATCH}; + List list = Arrays.stream(ints).boxed().collect(Collectors.toList()); + int iIndex = list.indexOf(levelI); + int jIndex = list.indexOf(levelJ); + return iIndex > jIndex ? j : i; + } + + protected int resolveLevelAsSubtype_basic(char[] simplePattern, char[] qualifiedPattern, ITypeBinding type, + IMethodBinding method, String packageName, boolean isDefault, boolean methodIdenticalToOriginal) { + if (type == null) return INACCURATE_MATCH; + + int level = this.resolveLevelForType(simplePattern, qualifiedPattern, type); + if (level != IMPOSSIBLE_MATCH) { + if (isDefault && !Objects.equals(packageName, type.getPackage().getName())) { + return IMPOSSIBLE_MATCH; + } + + // if concrete, then method is overridden + if( !methodIdenticalToOriginal && isConcrete(method, type)) { + level |= PatternLocator.OVERRIDDEN_METHOD_FLAVOR; + } + return level; + } + return -1; + } + + private boolean isConcrete(IMethodBinding method, ITypeBinding type) { + boolean nullMethod = method == null; + boolean abstractMethod = method == null ? false : Modifier.isAbstract(method.getModifiers()); + boolean abstractType = Modifier.isAbstract(type.getModifiers()); + if ((!nullMethod && !abstractMethod || !abstractType) && !type.isInterface()) { + return true; + } + return false; + } + + protected int resolveLevelAsSubtype_super(char[] simplePattern, char[] qualifiedPattern, ITypeBinding type, + String methodName, ITypeBinding[] argumentTypes, IMethodBinding method, + String packageName, boolean isDefault, IMethodBinding originalQuery) { + // matches superclass + if (!type.isInterface() && !type.getQualifiedName().equals(Object.class.getName())) { + int level = resolveLevelAsSubtype(simplePattern, qualifiedPattern, type.getSuperclass(), methodName, argumentTypes, packageName, isDefault, originalQuery); + if (level != IMPOSSIBLE_MATCH) { + // need to verify if method may be overridden + if (method != null) { // one method match in hierarchy + if ((level & PatternLocator.OVERRIDDEN_METHOD_FLAVOR) != 0) { + // this method is already overridden on a super class, current match is impossible + // testMethodDeclaration01 requires we NOT return impossible here, wtf + return IMPOSSIBLE_MATCH; + } + if (isConcrete(method, type)) { + // store the fact that the method is overridden + level |= PatternLocator.OVERRIDDEN_METHOD_FLAVOR; + } + } + return level | PatternLocator.SUB_INVOCATION_FLAVOR; // add flavor to returned level + } + } + return -1; + } + + protected int resolveLevelAsSubtype_interfaces(char[] simplePattern, char[] qualifiedPattern, ITypeBinding type, + String methodName, ITypeBinding[] argumentTypes, IMethodBinding method, + String packageName, boolean isDefault, IMethodBinding originalQuery) { + // matches interfaces + boolean concrete = isConcrete(method, type); + ITypeBinding[] interfaces = type.getInterfaces(); + //if (interfaces == null) return INACCURATE_MATCH; + if (interfaces == null) return -1; + for (ITypeBinding ref : interfaces) { + int level = resolveLevelAsSubtype(simplePattern, qualifiedPattern, + ref, methodName, null, packageName, isDefault, originalQuery); + if (level != IMPOSSIBLE_MATCH) { + // if concrete class, then method is overridden + if (concrete) { + level |= PatternLocator.OVERRIDDEN_METHOD_FLAVOR; + } + return level | PatternLocator.SUB_INVOCATION_FLAVOR; // add flavor to returned level + } + } + return -1; + } + + private boolean compareDeclaringClass(IMethodBinding b1, IMethodBinding b2) { + if( b1 == null || b2 == null ) + return b1 == b2; + String b1ClassFqqn = b1 == null || b1.getDeclaringClass() == null ? null : b1.getDeclaringClass().getQualifiedName(); + String b2ClassFqqn = b2 == null || b2.getDeclaringClass() == null ? null : b2.getDeclaringClass().getQualifiedName(); + String b1TrimTypeParms = b1ClassFqqn.contains("<") ? b1ClassFqqn.substring(0,b1ClassFqqn.indexOf("<")) : b1ClassFqqn; + String b2TrimTypeParms = b2ClassFqqn.contains("<") ? b2ClassFqqn.substring(0,b2ClassFqqn.indexOf("<")) : b2ClassFqqn; + return b1TrimTypeParms.equals(b2TrimTypeParms); + } + + protected int resolveLevelAsSubtype(char[] simplePattern, + char[] qualifiedPattern, ITypeBinding type, + String methodName, ITypeBinding[] argumentTypes, + String packageName, boolean isDefault, + IMethodBinding originalQuery) { + + // This binding might be null, because as we search up the heirarchy tree, + // there might be intermediate classes or interfaces where the searched-for + // method does not exist. This is not grounds for disqualification. + IMethodBinding method = getDOMASTMethodBinding(type, methodName, argumentTypes); + + boolean methodIdenticalToOriginal = compareDeclaringClass(originalQuery, method); + int r1 = resolveLevelAsSubtype_basic(simplePattern, qualifiedPattern, type, method, packageName, isDefault, methodIdenticalToOriginal); + if( r1 != -1 ) + return r1; + + r1 = resolveLevelAsSubtype_super(simplePattern, qualifiedPattern, type, methodName, argumentTypes, method, packageName, isDefault, originalQuery); + if( r1 != -1 ) + return r1; + + r1 = resolveLevelAsSubtype_interfaces(simplePattern, qualifiedPattern, type, methodName, argumentTypes, method, packageName, isDefault, originalQuery); + if( r1 != -1 ) + return r1; + + return IMPOSSIBLE_MATCH; + } + /* + * Return whether the given type binding or one of its possible super interfaces + * matches a type in the declaring type names hierarchy. + */ + private boolean resolveLevelAsSuperInvocation(ITypeBinding type, ITypeBinding[] argumentTypes, char[][][] superTypeNames, boolean methodAlreadyVerified) { + char[][] compoundName = Arrays.stream(type.getQualifiedName().split("\\.")).map(String::toCharArray).toArray(char[][]::new); + for (char[][] superTypeName : superTypeNames) { + if (CharOperation.equals(superTypeName, compoundName)) { + // need to verify if the type implements the pattern method + if (methodAlreadyVerified) return true; // already verified before enter into this method (see resolveLevel(MessageSend)) + if (Arrays.stream(type.getDeclaredMethods()) + .filter(method -> this.locator.matchesName(this.locator.pattern.selector, method.getName().toCharArray())) + .anyMatch(method -> Arrays.equals(method.getParameterTypes(), argumentTypes, Comparator.comparing(t -> t.getErasure().getKey())))) { + return true; + } + } + } + + // If the given type is an interface then a common super interface may be found + // in a parallel branch of the super hierarchy, so we need to verify all super interfaces. + // If it's a class then there's only one possible branch for the hierarchy and + // this branch has been already verified by the test above + if (type.isInterface()) { + ITypeBinding[] interfaces = type.getInterfaces(); + if (interfaces == null) return false; + for (ITypeBinding ref : interfaces) { + if (resolveLevelAsSuperInvocation(ref, argumentTypes, superTypeNames, false)) { + return true; + } + } + } + return false; + } + + @Override + public void initializePolymorphicSearch(MatchLocator locator) { + long start = 0; + if (BasicSearchEngine.VERBOSE) { + start = System.currentTimeMillis(); + } + try { + SuperTypeNamesCollector namesCollector = + new SuperTypeNamesCollector( + this.pattern, + this.pattern.declaringSimpleName, + this.pattern.declaringQualification, + locator, + this.pattern.declaringType, + locator.progressMonitor); + this.allSuperDeclaringTypeNames = namesCollector.collect(); + this.samePkgSuperDeclaringTypeNames = namesCollector.getSamePackageSuperTypeNames(); + } catch (JavaModelException e) { + // inaccurate matches will be found + } + if (BasicSearchEngine.VERBOSE) { + trace("Time to initialize polymorphic search: "+(System.currentTimeMillis()-start)); //$NON-NLS-1$ + } + } + + public boolean matchesDeclaration(ASTNode reference, IJavaElement element, IMethodBinding methodBinding, MatchLocator locator) { + // If method parameters verification was not valid, then try to see if method arguments can match a method in hierarchy + if (this.methodDeclarationsWithInvalidParam.containsKey(reference)) { + // First see if this reference has already been resolved => report match if validated + Boolean report = this.methodDeclarationsWithInvalidParam.get(reference); + if (report != null) { + return report.booleanValue(); + } + if (matchOverriddenMethod(methodBinding.getDeclaringClass(), methodBinding, null)) { + this.methodDeclarationsWithInvalidParam.put(reference, Boolean.TRUE); + return true; + } + if (isTypeInSuperDeclaringTypeNames(methodBinding.getDeclaringClass().getQualifiedName())) { + IMethodBinding patternBinding = getMethodBindingFromPattern(reference.getAST()); + if (patternBinding != null) { + if (!matchOverriddenMethod(patternBinding.getDeclaringClass(), patternBinding, methodBinding)) { + this.methodDeclarationsWithInvalidParam.put(reference, Boolean.FALSE); + return false; + } + } + this.methodDeclarationsWithInvalidParam.put(reference, Boolean.TRUE); + return true; + } + this.methodDeclarationsWithInvalidParam.put(reference, Boolean.FALSE); + return false; + } + return true; + } + + private boolean isTypeInSuperDeclaringTypeNames(String qualifiedName) { + if (this.allSuperDeclaringTypeNames == null) { + return false; + } + char[][] compound = Arrays.stream(qualifiedName.split("\\.")).map(String::toCharArray).toArray(char[][]::new); + return Arrays.stream(this.allSuperDeclaringTypeNames).anyMatch(name -> CharOperation.equals(name, compound)); + } + + // This works for only methods of parameterized types. + private boolean matchOverriddenMethod(ITypeBinding type, IMethodBinding method, IMethodBinding matchMethod) { + if (type == null || this.pattern.selector == null) return false; + + List parents = new ArrayList<>(); + if (!type.isInterface() && !Objects.equals(type.getQualifiedName(), Object.class.getName())) { + parents.add(type.getSuperclass()); + } + parents.addAll(Arrays.asList(type.getInterfaces())); + // matches superclass + for (ITypeBinding superType : parents) { + if (superType.isParameterizedType()) { + for (IMethodBinding candidate : superType.getDeclaredMethods()) { + if (Arrays.equals(candidate.getName().toCharArray(), this.pattern.selector) && areParametersEqual(candidate, method)) { + if (matchMethod == null) { + if (methodParametersEqualsPattern(candidate.getMethodDeclaration() /* .original() with ECJ */)) return true; + } else { + if (areParametersEqual(candidate.getMethodDeclaration() /* .original() with ECJ */, matchMethod)) return true; + } + } + } + } + if (matchOverriddenMethod(superType, method, matchMethod)) { + return true; + } + } + return false; + } + + private boolean areParametersEqual(IMethodBinding one, IMethodBinding another) { + var first = one.getParameterTypes(); + var second = another.getParameterTypes(); + if (Objects.equals(first, second)) { + return true; + } + if (first == null || second == null) { + return false; + } + if (first.length != second.length) { + return false; + } + for (int i = 0; i < first.length; i++) { + if (!first[i].isEqualTo(second[i])) { + return false; + } + } + return true; + } + + /* + * Return whether method parameters are equals to pattern ones. + */ + private boolean methodParametersEqualsPattern(IMethodBinding method) { + ITypeBinding[] methodParameters = method.getParameterTypes(); + + int length = methodParameters.length; + if (length != this.pattern.parameterSimpleNames.length) return false; + + for (int i = 0; i < length; i++) { + char[] paramQualifiedName = qualifiedPattern(this.pattern.parameterSimpleNames[i], this.pattern.parameterQualifications[i]); + if (!CharOperation.match(paramQualifiedName, methodParameters[i].getName() /* readableName() with ECJ*/.toCharArray(), this.isCaseSensitive)) { + return false; + } + } + return true; + } + + private IMethodBinding getMethodBindingFromPattern(AST context) { + if (this.pattern.focus instanceof IMethod method) { + var type = method.getDeclaringType().getFullyQualifiedName('$'); + var typeBinding = context.resolveWellKnownType(type); + if (typeBinding != null) { + var res = Arrays.stream(typeBinding.getDeclaredMethods()) + .filter(child -> Objects.equals(method.getElementName(), child.getName())) + .filter(child -> method.getParameterTypes().length == child.getParameterTypes().length) + .filter(child -> Objects.equals(method, child.getJavaElement())) + .findFirst(); + if (res.isPresent()) { + return res.get(); + } + } + } + return null; + } + + /* + * Subclasses can override this if they want to make last minute changes to the match + */ + @Override + public void reportSearchMatch(MatchLocator locator, ASTNode node, SearchMatch match) throws CoreException { + if( preferParamaterizedNode() ) { + if( node instanceof MethodInvocation iv) { + List l = iv.typeArguments(); + if( l != null && l.size() > 0 ) { + int start = ((ASTNode)l.get(0)).getStartPosition(); + if( start > 0 ) { + int newStart = start - 1; + int currOffset = match.getOffset(); + if( newStart < currOffset) { + int diff = currOffset - newStart; + match.setOffset(newStart); + match.setLength(match.getLength() + diff); + } + } + } + } + } + SearchMatchingUtility.reportSearchMatch(locator, match); + } + + + private boolean preferParamaterizedNode() { + int patternRule = this.locator.pattern.getMatchRule(); + boolean patternIsErasureMatch = isPatternErasureMatch(); + boolean patternIsEquivMatch = isPatternEquivalentMatch(); + boolean patternIsExactMatch = isPatternExactMatch(); + + boolean hasMethodArgs = this.locator.pattern.hasMethodArguments(); + boolean hasMethodParams = this.locator.pattern.hasMethodParameters(); + boolean emptyTypeArgsPattern = this.locator.pattern.methodArguments == null || + this.locator.pattern.methodArguments.length == 0; + if( patternIsEquivMatch || patternIsExactMatch) + return hasMethodArgs; + if( patternIsErasureMatch) { + return false; + } + return true; + } +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMOrLocator.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMOrLocator.java new file mode 100644 index 00000000000..de062efd3b4 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMOrLocator.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * 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.core.search.matching; + +import java.util.Arrays; +import java.util.Comparator; + +import org.eclipse.core.runtime.ILog; +import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; +import org.eclipse.jdt.core.dom.FieldAccess; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.MethodInvocation; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.dom.VariableDeclaration; +import org.eclipse.jdt.internal.core.search.DOMPatternLocatorFactory; +import org.eclipse.jdt.internal.core.search.LocatorResponse; + +public class DOMOrLocator extends DOMPatternLocator { + + private final DOMPatternLocator[] children; + + public DOMOrLocator(OrLocator orLocator, OrPattern pattern) { + super(pattern); + children = Arrays.stream(children(orLocator)) + .map(child -> DOMPatternLocatorFactory.createWrapper(child, null)) // usually pattern would be inferred from locator so we can pass null + .toArray(DOMPatternLocator[]::new); + } + + private static PatternLocator[] children(OrLocator orLocator) { + try { + var locatorsField = OrLocator.class.getDeclaredField("patternLocators"); + locatorsField.setAccessible(true); + return (PatternLocator[])locatorsField.get(orLocator); + } catch (Exception ex) { + ILog.get().error(ex.getMessage(), ex); + return new PatternLocator[0]; + } + } + + public LocatorResponse match(org.eclipse.jdt.core.dom.Annotation node, NodeSetWrapper nodeSet, MatchLocator locator) { + return or(child -> child.match(node, nodeSet, locator)); + } + public LocatorResponse match(org.eclipse.jdt.core.dom.ASTNode node, NodeSetWrapper nodeSet, MatchLocator locator) { // needed for some generic nodes + return or(child -> child.match(node, nodeSet, locator)); + } + public LocatorResponse match(org.eclipse.jdt.core.dom.Expression node, NodeSetWrapper nodeSet, MatchLocator locator) { + return or(child -> child.match(node, nodeSet, locator)); + } + public LocatorResponse match(org.eclipse.jdt.core.dom.FieldDeclaration node, NodeSetWrapper nodeSet, MatchLocator locator) { + return or(child -> child.match(node, nodeSet, locator)); + } + public LocatorResponse match(org.eclipse.jdt.core.dom.LambdaExpression node, NodeSetWrapper nodeSet, MatchLocator locator) { + return or(child -> child.match(node, nodeSet, locator)); + } + public LocatorResponse match(VariableDeclaration node, NodeSetWrapper nodeSet, MatchLocator locator) { + return or(child -> child.match(node, nodeSet, locator)); + } + public LocatorResponse match(org.eclipse.jdt.core.dom.MethodDeclaration node, NodeSetWrapper nodeSet, MatchLocator locator) { + return or(child -> child.match(node, nodeSet, locator)); + } + public LocatorResponse match(org.eclipse.jdt.core.dom.MemberValuePair node, NodeSetWrapper nodeSet, MatchLocator locator) { + return or(child -> child.match(node, nodeSet, locator)); + } + public LocatorResponse match(MethodInvocation node, NodeSetWrapper nodeSet, MatchLocator locator) { + return or(child -> child.match(node, nodeSet, locator)); + } + protected LocatorResponse match(org.eclipse.jdt.core.dom.ModuleDeclaration node, NodeSetWrapper nodeSet, MatchLocator locator) { + return or(child -> child.match(node, nodeSet, locator)); + } + public LocatorResponse match(Name node, NodeSetWrapper nodeSet, MatchLocator locator) { + return or(child -> child.match(node, nodeSet, locator)); + } + public LocatorResponse match(FieldAccess node, NodeSetWrapper nodeSet, MatchLocator locator) { + return or(child -> child.match(node, nodeSet, locator)); + } + public LocatorResponse match(AbstractTypeDeclaration node, NodeSetWrapper nodeSet, MatchLocator locator) { + return or(child -> child.match(node, nodeSet, locator)); + } + public LocatorResponse match(org.eclipse.jdt.core.dom.TypeParameter node, NodeSetWrapper nodeSet, MatchLocator locator) { + return or(child -> child.match(node, nodeSet, locator)); + } + public LocatorResponse match(Type node, NodeSetWrapper nodeSet, MatchLocator locator) { + return or(child -> child.match(node, nodeSet, locator)); + } + public LocatorResponse resolveLevel(org.eclipse.jdt.core.dom.ASTNode node, MatchLocator locator) { + return or(child -> child.resolveLevel(node, locator)); + } + public LocatorResponse resolveLevel(org.eclipse.jdt.core.dom.ASTNode node, IBinding binding, MatchLocator locator) { + return or(child -> child.resolveLevel(node, binding, locator)); + } + + private LocatorResponse or(java.util.function.Function query) { + return Arrays.stream(this.children) + .map(query::apply) + .max(Comparator.comparingInt(LocatorResponse::level)) + .orElse(toResponse(PatternLocator.IMPOSSIBLE_MATCH)); + } + + @Override + public void initializePolymorphicSearch(MatchLocator locator) { + for (PatternLocator patternLocator : this.children) + patternLocator.initializePolymorphicSearch(locator); + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMPackageReferenceLocator.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMPackageReferenceLocator.java new file mode 100644 index 00000000000..371555d5202 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMPackageReferenceLocator.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2023, 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.core.search.matching; + +import java.util.Arrays; + +import org.eclipse.jdt.core.dom.ArrayType; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.IPackageBinding; +import org.eclipse.jdt.core.dom.ImportDeclaration; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.NameQualifiedType; +import org.eclipse.jdt.core.dom.QualifiedName; +import org.eclipse.jdt.core.dom.QualifiedType; +import org.eclipse.jdt.core.dom.SimpleType; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.internal.core.search.LocatorResponse; + +public class DOMPackageReferenceLocator extends DOMPatternLocator { + + private PackageReferenceLocator locator; + + public DOMPackageReferenceLocator(PackageReferenceLocator locator) { + super(locator.pattern); + this.locator = locator; + } + + @Override + public LocatorResponse match(org.eclipse.jdt.core.dom.Annotation node, NodeSetWrapper nodeSet, MatchLocator locator) { + return match(node.getTypeName(), nodeSet, locator); + } + + @Override + public LocatorResponse match(org.eclipse.jdt.core.dom.ASTNode node, NodeSetWrapper nodeSet, MatchLocator locator) { + // interested in ImportReference + if (node instanceof ImportDeclaration decl) { + return match(decl.getName(), nodeSet, locator); + } + return toResponse(IMPOSSIBLE_MATCH); + } + + @Override + public LocatorResponse match(Name node, NodeSetWrapper nodeSet, MatchLocator locator) { + // interested in QualifiedNameReference + char[][] arr = Arrays.stream(node.getFullyQualifiedName().split("\\.")).map(String::toCharArray) //$NON-NLS-1$ + .toArray(char[][]::new); + int level = nodeSet.addMatch(node, this.locator.matchLevelForTokens(arr)); + return toResponse(level, true); + } + + @Override + public LocatorResponse match(Type node, NodeSetWrapper nodeSet, MatchLocator locator) { // interested in QualifiedTypeReference + // only + if (node instanceof ArrayType att) { + return match(att.getElementType(), nodeSet, locator); + } + Name typePkg = null; + if (node instanceof SimpleType stt) { + Name n = stt.getName(); + typePkg = n instanceof QualifiedName qn ? qn.getQualifier() : n; + } else if (node instanceof QualifiedType qt3) { + Type t1 = qt3.getQualifier(); + typePkg = t1 instanceof SimpleType sttt ? sttt.getName() : null; + } else if (node instanceof NameQualifiedType qt) { + typePkg = qt.getQualifier(); + } + return typePkg != null ? match(typePkg, nodeSet, locator) : toResponse(IMPOSSIBLE_MATCH); + } + + @Override + public LocatorResponse resolveLevel(org.eclipse.jdt.core.dom.ASTNode node, IBinding binding, MatchLocator locator) { + if (binding instanceof IPackageBinding ipb) { + String n = ipb.getName(); + String patternName = new String(this.locator.pattern.pkgName); + if (patternName.equals(n)) { + return toResponse(ACCURATE_MATCH); + } + } + return toResponse(IMPOSSIBLE_MATCH); + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMPatternLocator.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMPatternLocator.java new file mode 100644 index 00000000000..a2389fccad6 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMPatternLocator.java @@ -0,0 +1,361 @@ +/******************************************************************************* + * Copyright (c) 2023, 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.core.search.matching; + +import java.util.Arrays; + +import org.eclipse.core.runtime.CoreException; +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.ArrayType; +import org.eclipse.jdt.core.dom.FieldAccess; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.MethodInvocation; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.ParameterizedType; +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.SimpleName; +import org.eclipse.jdt.core.dom.SimpleType; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.dom.VariableDeclaration; +import org.eclipse.jdt.core.search.SearchMatch; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.jdt.internal.compiler.lookup.IntersectionTypeBinding18; +import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; +import org.eclipse.jdt.internal.core.search.LocatorResponse; +import org.eclipse.jdt.internal.javac.dom.JavacTypeBinding; + +public class DOMPatternLocator extends PatternLocator { + public DOMPatternLocator(SearchPattern pattern) { + super(pattern); + } + + protected LocatorResponse toResponse(int val) { + return toResponse(val, false); + } + protected LocatorResponse toResponse(int val, boolean alreadyAdded) { + return new LocatorResponse(val, false, null, alreadyAdded, true); + } + + + // AST DOM Variants + public LocatorResponse match(org.eclipse.jdt.core.dom.Annotation node, NodeSetWrapper nodeSet, MatchLocator locator) { + // each subtype should override if needed + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + /** + * Check if the given ast node syntactically matches this pattern. + * If it does, add it to the match set. + * Returns the match level. + */ + public LocatorResponse match(org.eclipse.jdt.core.dom.ASTNode node, NodeSetWrapper nodeSet, MatchLocator locator) { // needed for some generic nodes + // each subtype should override if needed + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + public LocatorResponse match(org.eclipse.jdt.core.dom.Expression node, NodeSetWrapper nodeSet, MatchLocator locator) { + // each subtype should override if needed + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + public LocatorResponse match(org.eclipse.jdt.core.dom.FieldDeclaration node, NodeSetWrapper nodeSet, MatchLocator locator) { + // each subtype should override if needed + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + public LocatorResponse match(org.eclipse.jdt.core.dom.LambdaExpression node, NodeSetWrapper nodeSet, MatchLocator locator) { + // each subtype should override if needed + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + public LocatorResponse match(org.eclipse.jdt.core.dom.MethodReference node, NodeSetWrapper nodeSet, MatchLocator locator) { + // each subtype should override if needed + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + public LocatorResponse match(org.eclipse.jdt.core.dom.MethodRef node, NodeSetWrapper nodeSet, MatchLocator locator) { + // each subtype should override if needed + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + public LocatorResponse match(VariableDeclaration node, NodeSetWrapper nodeSet, MatchLocator locator) { + // each subtype should override if needed + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + public LocatorResponse match(org.eclipse.jdt.core.dom.MethodDeclaration node, NodeSetWrapper nodeSet, MatchLocator locator) { + // each subtype should override if needed + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + public LocatorResponse match(org.eclipse.jdt.core.dom.MemberValuePair node, NodeSetWrapper nodeSet, MatchLocator locator) { + // each subtype should override if needed + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + public LocatorResponse match(org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration node, NodeSetWrapper nodeSet, MatchLocator locator) { + // each subtype should override if needed + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + public LocatorResponse match(MethodInvocation node, NodeSetWrapper nodeSet, MatchLocator locator) { + // each subtype should override if needed + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + protected LocatorResponse match(org.eclipse.jdt.core.dom.ModuleDeclaration node, NodeSetWrapper nodeSet, MatchLocator locator) { + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + public LocatorResponse match(Name node, NodeSetWrapper nodeSet, MatchLocator locator) { + // each subtype should override if needed + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + public LocatorResponse match(FieldAccess node, NodeSetWrapper nodeSet, MatchLocator locator) { + // each subtype should override if needed + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + public LocatorResponse match(AbstractTypeDeclaration node, NodeSetWrapper nodeSet, MatchLocator locator) { + // each subtype should override if needed + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + public LocatorResponse match(org.eclipse.jdt.core.dom.TypeParameter node, NodeSetWrapper nodeSet, MatchLocator locator) { + // each subtype should override if needed + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + public LocatorResponse match(Type node, NodeSetWrapper nodeSet, MatchLocator locator) { + // each subtype should override if needed + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + public LocatorResponse resolveLevel(org.eclipse.jdt.core.dom.ASTNode node, MatchLocator locator) { + // each subtype should override if needed + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + public LocatorResponse resolveLevel(org.eclipse.jdt.core.dom.ASTNode node, IBinding binding, MatchLocator locator) { + // each subtype should override if needed + return toResponse(PatternLocator.IMPOSSIBLE_MATCH); + } + protected String getQualifiedSourceName(ITypeBinding binding) { + if (binding == null) { + return null; + } + ITypeBinding type = binding.isArray() ? binding.getComponentType() : binding; + String simpleName = type instanceof JavacTypeBinding ? ((JavacTypeBinding)type).getName(false) : binding.getName(); + String qualifier = qualifiedSourceName(type.getDeclaringClass()); + if( qualifier == null && type instanceof JavacTypeBinding jctb) { + String qualifiedName = jctb.getQualifiedName(false); + if( qualifiedName != null ) { + return qualifiedName; + } + } + if (type.isLocal()) { + return qualifier + ".1." + simpleName; //$NON-NLS-1$ + } else if (type.isMember()) { + return qualifier + '.' + simpleName; + } + return binding.getName(); + } + protected int resolveLevelForType(char[] simpleNamePattern, char[] qualificationPattern, ITypeBinding binding) { + return + binding == null && simpleNamePattern == null && qualificationPattern == null ? ACCURATE_MATCH : + binding != null && binding.isArray() && new String(simpleNamePattern).endsWith("[]") ? resolveLevelForType(Arrays.copyOf(simpleNamePattern, simpleNamePattern.length - 2), qualificationPattern, binding.getComponentType()) : + resolveLevelForTypeFQN(simpleNamePattern, qualificationPattern, binding, null); + } + + protected int resolveLevelForTypeFQN(char[] simpleNamePattern, char[] qualificationPattern, ITypeBinding binding, IImportDiscovery discovery) { + int level = 0; + if (simpleNamePattern == null) { + return ACCURATE_MATCH; + } + if (qualificationPattern == null && simpleNamePattern != null) { + level = resolveLevelForTypeSourceName(simpleNamePattern, (binding.isArray() ? binding : binding.getErasure()).getName().toCharArray(), binding); + } + if (level == ACCURATE_MATCH || level == ERASURE_MATCH) { + return level; + } + char[] qualifiedPattern = getQualifiedPattern(simpleNamePattern, qualificationPattern); + level = resolveLevelForTypeFQN(qualifiedPattern, binding, discovery); + if (level == ACCURATE_MATCH || binding == null) + return level; + + ITypeBinding type = binding.isArray() ? binding.getComponentType() : binding; + char[] sourceName = null; + if (type.isMember() || type.isLocal()) { + if (qualificationPattern != null) { + sourceName = getQualifiedSourceName(binding).toCharArray(); + } else { + sourceName = binding.getQualifiedName().toCharArray(); + } + } else if (qualificationPattern == null) { + sourceName = getQualifiedSourceName(binding).toCharArray(); + } + if (type.isRecovered()) { + if (qualificationPattern == null || !type.getQualifiedName().contains(".") ) { + level = resolveLevelForTypeSourceName(simpleNamePattern, binding.getName().toCharArray(), type); + if (level > IMPOSSIBLE_MATCH) { + return INACCURATE_MATCH; + } + } + } + if (sourceName == null) + return IMPOSSIBLE_MATCH; + return resolveLevelForTypeSourceName(qualifiedPattern, sourceName, type); + } + public static String qualifiedSourceName(ITypeBinding binding) { + if (binding == null) { + return null; + } + if (binding.isLocal()) { + return binding.isMember() + ? qualifiedSourceName(binding.getDeclaringClass()) + '.' + binding.getName() + : qualifiedSourceName(binding.getDeclaringClass()) + ".1." + binding.getName(); //$NON-NLS-1$ + } + return binding.getQualifiedName(); + } + private Name getBaseTypeName(Type type) { + if( type instanceof SimpleType simp) { + return simp.getName(); + } + if( type instanceof QualifiedType qn) { + return qn.getName(); + } + if( type instanceof ArrayType arr) { + return getBaseTypeName(arr.getElementType()); + } + if (type instanceof ParameterizedType parameterized) { + return getBaseTypeName(parameterized.getType()); + } + + return null; + } + + protected boolean matchesTypeReference(char[] pattern, Type type, boolean isVarargs) { + if (pattern == null) return true; // null is as if it was "*" + if (type == null) return true; // treat as an inexact match + + var name = getBaseTypeName(type); + var simpleName = name instanceof SimpleName simple ? simple.getIdentifier() : + name instanceof QualifiedName qName ? qName.getName().getIdentifier() : + type instanceof PrimitiveType primitive ? primitive.getPrimitiveTypeCode().toString() : + null; + if (simpleName == null) { + return true; + } + int dimensions = type instanceof ArrayType arrayType ? arrayType.dimensions().size() : 0; + if (isVarargs) { + dimensions++; + } + for (int i = 0; i < dimensions; i++) { + simpleName += "[]"; //$NON-NLS-1$ + } + return matchesName(pattern, simpleName.toCharArray()); + } + protected boolean matchesTypeReference(char[] pattern, Type type) { + return matchesTypeReference(pattern, type, false); + } + protected int resolveLevelForTypeSourceName(char[] qualifiedPattern, char[] sourceName, ITypeBinding type) { + switch (this.matchMode) { + case SearchPattern.R_PREFIX_MATCH: + if (CharOperation.prefixEquals(qualifiedPattern, sourceName, this.isCaseSensitive)) { + return ACCURATE_MATCH; + } + break; + case SearchPattern.R_CAMELCASE_MATCH: + if ((qualifiedPattern.length>0 && sourceName.length>0 && qualifiedPattern[0] == sourceName[0])) { + if (CharOperation.camelCaseMatch(qualifiedPattern, sourceName, false)) { + return ACCURATE_MATCH; + } + if (!this.isCaseSensitive && CharOperation.prefixEquals(qualifiedPattern, sourceName, false)) { + return ACCURATE_MATCH; + } + } + break; + case SearchPattern.R_CAMELCASE_SAME_PART_COUNT_MATCH: + if ((qualifiedPattern.length>0 && sourceName.length>0 && qualifiedPattern[0] == sourceName[0])) { + if (CharOperation.camelCaseMatch(qualifiedPattern, sourceName, true)) { + return ACCURATE_MATCH; + } + } + break; + default: + if( type != null && type.isLocal() ) { + if (CharOperation.prefixEquals(qualifiedPattern, sourceName, this.isCaseSensitive)) { + return ACCURATE_MATCH; + } + } + if (CharOperation.match(qualifiedPattern, sourceName, this.isCaseSensitive)) { + return ACCURATE_MATCH; + } + } + return IMPOSSIBLE_MATCH; + } + + public static interface IImportDiscovery { + public String findImportForString(String s); + } + + protected int resolveLevelForTypeFQN(char[] qualifiedPattern, ITypeBinding type) { + return resolveLevelForTypeFQN(qualifiedPattern, type, null); + } + protected int resolveLevelForTypeFQN(char[] qualifiedPattern, ITypeBinding type, IImportDiscovery discovery) { + + if (qualifiedPattern == null) return ACCURATE_MATCH; + if (type == null) return INACCURATE_MATCH; + + // Type variable cannot be specified through pattern => this kind of binding cannot match it (see bug 79803) + if (type.isTypeVariable()) return IMPOSSIBLE_MATCH; + + if (type instanceof IntersectionTypeBinding18) { + int result = IMPOSSIBLE_MATCH, prev = IMPOSSIBLE_MATCH; + IntersectionTypeBinding18 i18 = (IntersectionTypeBinding18) type; + for (ReferenceBinding ref : i18.intersectingTypes) { + result = resolveLevelForType(qualifiedPattern, ref); + if (result == ACCURATE_MATCH) return result; + if (result == IMPOSSIBLE_MATCH) continue; + if (prev == IMPOSSIBLE_MATCH) prev = result; + } + return prev; + } + // NOTE: if case insensitive search then qualifiedPattern is assumed to be lowercase + String qnfb = getQualifiedSourceName(type); + int qnfbLastDot = qnfb.lastIndexOf('.'); + int patternFirstDot = CharOperation.indexOf('.', qualifiedPattern); + if( qnfbLastDot != -1 && patternFirstDot == -1) { + // qnfb is actually qualified but the char array is not. + String qnfbLastSegment = qnfbLastDot == qnfb.length() - 1 ? null : qnfb.substring(qnfbLastDot+1); + if( qnfbLastSegment != null ) { + boolean match1 = CharOperation.match(qualifiedPattern, qnfbLastSegment.toCharArray(), this.isCaseSensitive); + return match1 ? ACCURATE_MATCH : IMPOSSIBLE_MATCH; + } + } + char[] qualifiedNameFromBinding = qnfb == null ? null : qnfb.toCharArray(); + if( qualifiedNameFromBinding == null || qualifiedNameFromBinding.length == 0 ) { + qualifiedNameFromBinding = type.getName().toCharArray(); + } + boolean match1 = CharOperation.match(qualifiedPattern, qualifiedNameFromBinding, this.isCaseSensitive); + if( match1 ) { + return ACCURATE_MATCH; + } + + // There's a chance our "qualified name" is not fully qualified. + if( patternFirstDot != -1 && discovery != null ) { + String firstSegment = new String(qualifiedPattern, 0, patternFirstDot); + String fqqnImport = discovery.findImportForString(firstSegment); + if( fqqnImport != null ) { + String fqqnPattern = fqqnImport.substring(0, fqqnImport.length() - firstSegment.length()) + new String(qualifiedPattern); + boolean match2 = CharOperation.match(fqqnPattern.toCharArray(), qualifiedNameFromBinding, this.isCaseSensitive); + if( match2 ) { + return ACCURATE_MATCH; + } + } + } + return IMPOSSIBLE_MATCH; + } + + /* + * Subclasses can override this if they want to make last minute changes to the match + */ + public void reportSearchMatch(MatchLocator locator, ASTNode node, SearchMatch match) throws CoreException { + SearchMatchingUtility.reportSearchMatch(locator, match); + } +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMSuperTypeReferenceLocator.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMSuperTypeReferenceLocator.java new file mode 100644 index 00000000000..b871770fa76 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMSuperTypeReferenceLocator.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (c) 2023, 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.core.search.matching; + +import java.util.Set; + +import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; +import org.eclipse.jdt.core.dom.ClassInstanceCreation; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.QualifiedName; +import org.eclipse.jdt.core.dom.QualifiedType; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.SimpleType; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.internal.core.search.LocatorResponse; + +public class DOMSuperTypeReferenceLocator extends DOMPatternLocator { + + private SuperTypeReferenceLocator locator; + + public DOMSuperTypeReferenceLocator(SuperTypeReferenceLocator locator) { + super(locator.pattern); + this.locator = locator; + } + + @Override + public LocatorResponse match(org.eclipse.jdt.core.dom.LambdaExpression node, NodeSetWrapper nodeSet, MatchLocator locator) { + if (this.locator.pattern.superRefKind != SuperTypeReferencePattern.ONLY_SUPER_INTERFACES) + return toResponse(IMPOSSIBLE_MATCH); + nodeSet.setMustResolve(true); + int level = nodeSet.addMatch(node, POSSIBLE_MATCH); + return toResponse(level, true); + } + + @Override + public LocatorResponse match(Type node, NodeSetWrapper nodeSet, MatchLocator locator) { + if (!Set.of(TypeDeclaration.SUPERCLASS_TYPE_PROPERTY, TypeDeclaration.SUPER_INTERFACE_TYPES_PROPERTY, ClassInstanceCreation.TYPE_PROPERTY).contains(node.getLocationInParent())) { + return toResponse(IMPOSSIBLE_MATCH); + } + if (node.getLocationInParent() == ClassInstanceCreation.TYPE_PROPERTY && node.getParent() instanceof ClassInstanceCreation newInst && newInst.getAnonymousClassDeclaration() == null) { + return toResponse(IMPOSSIBLE_MATCH); + } + if (node.getParent() instanceof AbstractTypeDeclaration decl && !DOMTypeDeclarationLocator.matchSearchForTypeSuffix(decl, this.locator.pattern.typeSuffix)) { + return toResponse(IMPOSSIBLE_MATCH); + } + if (this.locator.pattern.superSimpleName == null) { + int level = nodeSet.addMatch(node, this.locator.pattern.mustResolve ? POSSIBLE_MATCH : ACCURATE_MATCH); + return toResponse(level, true); + } + + char[] typeRefSimpleName = null; + if (node instanceof SimpleType simple) { + if (simple.getName() instanceof SimpleName name) { + typeRefSimpleName = name.getIdentifier().toCharArray(); + } + if (simple.getName() instanceof QualifiedName name) { + typeRefSimpleName = name.getName().getIdentifier().toCharArray(); + } + } else if (node instanceof QualifiedType qualified) { + typeRefSimpleName = qualified.getName().getIdentifier().toCharArray(); + } + if (this.locator.matchesName(this.locator.pattern.superSimpleName, typeRefSimpleName)) { + int level = nodeSet.addMatch(node, this.locator.pattern.mustResolve ? POSSIBLE_MATCH : ACCURATE_MATCH); + return toResponse(level, true); + } + + return toResponse(IMPOSSIBLE_MATCH); + } + + @Override + public LocatorResponse resolveLevel(org.eclipse.jdt.core.dom.ASTNode node, IBinding binding, MatchLocator locator) { + if (binding == null) + return toResponse(INACCURATE_MATCH); + if (!(binding instanceof ITypeBinding type)) + return toResponse(IMPOSSIBLE_MATCH); + + int level = IMPOSSIBLE_MATCH; + if (this.locator.pattern.superRefKind != SuperTypeReferencePattern.ONLY_SUPER_INTERFACES || node.getLocationInParent() == TypeDeclaration.SUPER_INTERFACE_TYPES_PROPERTY) { + level = this.resolveLevelForType(this.locator.pattern.superSimpleName, this.locator.pattern.superQualification, + type); + if (level > IMPOSSIBLE_MATCH) + return toResponse(level); + } + + if (this.locator.pattern.superRefKind != SuperTypeReferencePattern.ONLY_SUPER_CLASSES || node.getLocationInParent() == TypeDeclaration.SUPERCLASS_TYPE_PROPERTY) { + level = this.resolveLevelForType(this.locator.pattern.superSimpleName, this.locator.pattern.superQualification, + type); + if (level == ACCURATE_MATCH) + return toResponse(ACCURATE_MATCH); + } + return toResponse(binding.isRecovered() ? POSSIBLE_MATCH : level); + } +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMTypeDeclarationLocator.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMTypeDeclarationLocator.java new file mode 100644 index 00000000000..dc957f4ec3a --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMTypeDeclarationLocator.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * Copyright (c) 2023, 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.core.search.matching; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.core.compiler.CharOperation; +import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; +import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; +import org.eclipse.jdt.core.dom.EnumDeclaration; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.IModuleBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.internal.core.search.LocatorResponse; +import org.eclipse.jdt.internal.core.search.indexing.IIndexConstants; + +public class DOMTypeDeclarationLocator extends DOMPatternLocator { + + private TypeDeclarationLocator locator; + + public DOMTypeDeclarationLocator(TypeDeclarationLocator locator) { + super(locator.pattern); + this.locator = locator; + } + @Override + public LocatorResponse match(AbstractTypeDeclaration node, NodeSetWrapper nodeSet, MatchLocator locator) { + if (!matchSearchForTypeSuffix(node, this.locator.pattern.typeSuffix)) { + return toResponse(IMPOSSIBLE_MATCH); + } + if (this.locator.pattern.simpleName == null || this.locator.matchesName(this.locator.pattern.simpleName, node.getName().getIdentifier().toCharArray())) { + int level = nodeSet.addMatch(node, this.locator.pattern.mustResolve ? POSSIBLE_MATCH : ACCURATE_MATCH); + return toResponse(level, true); + } + + return toResponse(IMPOSSIBLE_MATCH); + } + + static boolean matchSearchForTypeSuffix(AbstractTypeDeclaration type, char typeSuffix) { + return switch (typeSuffix) { + case IIndexConstants.CLASS_SUFFIX -> type instanceof TypeDeclaration decl && !decl.isInterface(); + case IIndexConstants.CLASS_AND_INTERFACE_SUFFIX -> type instanceof TypeDeclaration; + case IIndexConstants.CLASS_AND_ENUM_SUFFIX -> (type instanceof TypeDeclaration decl && !decl.isInterface()) || type instanceof EnumDeclaration; + case IIndexConstants.INTERFACE_SUFFIX -> type instanceof TypeDeclaration decl && decl.isInterface(); + case IIndexConstants.INTERFACE_AND_ANNOTATION_SUFFIX -> (type instanceof TypeDeclaration decl && !decl.isInterface()) || type instanceof AnnotationTypeDeclaration; + case IIndexConstants.ENUM_SUFFIX -> type instanceof EnumDeclaration; + case IIndexConstants.ANNOTATION_TYPE_SUFFIX -> type instanceof AnnotationTypeDeclaration; + case IIndexConstants.TYPE_SUFFIX -> true; + default -> false; + }; + } + + static boolean matchSearchForTypeSuffix(ITypeBinding type, char typeSuffix) { + return switch (typeSuffix) { + case IIndexConstants.CLASS_SUFFIX -> type.isClass(); + case IIndexConstants.CLASS_AND_INTERFACE_SUFFIX -> type.isClass() || (type.isInterface() && !type.isAnnotation()); + case IIndexConstants.CLASS_AND_ENUM_SUFFIX -> type.isClass() || type.isEnum(); + case IIndexConstants.INTERFACE_SUFFIX -> type.isInterface() && !type.isAnnotation(); + case IIndexConstants.INTERFACE_AND_ANNOTATION_SUFFIX -> type.isInterface() || type.isAnnotation(); + case IIndexConstants.ENUM_SUFFIX -> type.isEnum(); + case IIndexConstants.ANNOTATION_TYPE_SUFFIX -> type.isAnnotation(); + case IIndexConstants.TYPE_SUFFIX -> true; + default -> false; + }; + } + + @Override + public LocatorResponse resolveLevel(org.eclipse.jdt.core.dom.ASTNode node, IBinding binding, MatchLocator locator) { + if (binding == null) return toResponse(INACCURATE_MATCH); + if (!(binding instanceof ITypeBinding)) return toResponse(IMPOSSIBLE_MATCH); + + ITypeBinding type = (ITypeBinding) binding; + + if (!matchSearchForTypeSuffix(type, this.locator.pattern.typeSuffix)) { + return toResponse(IMPOSSIBLE_MATCH); + } + + if (this.matchModule(this.locator.pattern, type) == IMPOSSIBLE_MATCH) { + return toResponse(IMPOSSIBLE_MATCH); + } + // fully qualified name + if (this.locator.pattern instanceof QualifiedTypeDeclarationPattern) { + QualifiedTypeDeclarationPattern qualifiedPattern = (QualifiedTypeDeclarationPattern) this.locator.pattern; + int level = this.resolveLevelForType(qualifiedPattern.simpleName, qualifiedPattern.qualification, type); + return toResponse(level); + } else { + char[] enclosingTypeName = this.locator.pattern.enclosingTypeNames == null ? null : CharOperation.concatWith(this.locator.pattern.enclosingTypeNames, '.'); + int level = resolveLevelForType(this.locator.pattern.simpleName, this.locator.pattern.pkg, enclosingTypeName, type); + return toResponse(level); + } + } + protected int resolveLevelForType(char[] simpleNamePattern, char[] qualificationPattern, char[] enclosingNamePattern, ITypeBinding type) { + if (enclosingNamePattern == null) + return this.resolveLevelForType(simpleNamePattern, qualificationPattern, type); + if (qualificationPattern == null) + return this.resolveLevelForType(simpleNamePattern, enclosingNamePattern, type); + + // pattern was created from a Java element: qualification is the package name. + char[] fullQualificationPattern = CharOperation.concat(qualificationPattern, enclosingNamePattern, '.'); + if (CharOperation.equals(this.locator.pattern.pkg, type.getPackage().getName().toCharArray())) + return this.resolveLevelForType(simpleNamePattern, fullQualificationPattern, type); + return IMPOSSIBLE_MATCH; + } + private int matchModule(TypeDeclarationPattern typePattern, ITypeBinding type) { + IModuleBinding module = type.getModule(); + if (module == null || module.getName() == null || typePattern.moduleNames == null) + return POSSIBLE_MATCH; //can't determine, say possible to all. + String bindModName = module.getName(); + + if (typePattern.modulePatterns == null) {// use 'normal' matching + char[][] moduleList = this.locator.getModuleList(typePattern); + for (char[] m : moduleList) { // match any in the list + int ret = this.locator.matchNameValue(m, bindModName.toCharArray()); + if (ret != IMPOSSIBLE_MATCH) return ret; + } + } else {// use pattern matching + for (Pattern p : typePattern.modulePatterns) { + Matcher matcher = p.matcher(bindModName); + if (matcher.matches()) return ACCURATE_MATCH; + } + } + return IMPOSSIBLE_MATCH; + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMTypeParameterLocator.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMTypeParameterLocator.java new file mode 100644 index 00000000000..141e20e9496 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/DOMTypeParameterLocator.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * Copyright (c) 2023, 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.core.search.matching; + +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.ISourceRange; +import org.eclipse.jdt.core.ISourceReference; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.SimpleType; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.internal.core.search.LocatorResponse; + +public class DOMTypeParameterLocator extends DOMPatternLocator { + + private TypeParameterLocator locator; + + public DOMTypeParameterLocator(TypeParameterLocator locator) { + super(locator.pattern); + this.locator = locator; + } + + private static boolean nodeSourceRangeMatchesElement(org.eclipse.jdt.core.dom.ASTNode node, IJavaElement focus) { + if( focus == null ) + return false; + + ISourceRange sr = null; + try { + if( focus instanceof ISourceReference isr2) { + sr = isr2.getSourceRange(); + } + } catch(JavaModelException jme3) { + // ignore + } + + if( sr == null ) + return false; + + if( sr.getOffset() == node.getStartPosition() && sr.getLength() == node.getLength()) { + return true; + } + return false; + } + + @Override + public LocatorResponse match(Type node, NodeSetWrapper nodeSet, MatchLocator locator) { + if (this.locator.pattern.findReferences) { + if (node instanceof SimpleType simple) { // Type parameter cannot be qualified + if (this.locator.matchesName(this.locator.pattern.name, simple.getName().toString().toCharArray())) { + int level = this.locator.pattern.mustResolve ? POSSIBLE_MATCH : ACCURATE_MATCH; + return toResponse(nodeSet.addMatch(node, level), true); + } + } + } + return toResponse(IMPOSSIBLE_MATCH); + } + + @Override + public LocatorResponse match(org.eclipse.jdt.core.dom.TypeParameter node, NodeSetWrapper nodeSet, MatchLocator locator) { + if (this.locator.pattern.findReferences) { + if (this.locator.matchesName(this.locator.pattern.name, node.getName().toString().toCharArray())) { + int level = this.locator.pattern.mustResolve ? POSSIBLE_MATCH : ACCURATE_MATCH; + return toResponse(nodeSet.addMatch(node, level), true); + } + } + if (this.locator.pattern.findDeclarations) { + if (this.locator.matchesName(this.locator.pattern.name, node.getName().toString().toCharArray())) { + int level = this.locator.pattern.mustResolve ? POSSIBLE_MATCH : ACCURATE_MATCH; + return toResponse(nodeSet.addMatch(node, level), true); + } + } + return toResponse(IMPOSSIBLE_MATCH); + } + @Override + public LocatorResponse resolveLevel(org.eclipse.jdt.core.dom.ASTNode node, IBinding binding, MatchLocator locator) { + if (binding == null) return toResponse(INACCURATE_MATCH); + if (!(binding instanceof ITypeBinding)) return toResponse(IMPOSSIBLE_MATCH); + ITypeBinding tb = (ITypeBinding)binding; + int ret = matchTypeParameter(tb, true); + if( ret == ACCURATE_MATCH) { + if( !this.locator.pattern.findDeclarations && nodeSourceRangeMatchesElement(node, this.locator.pattern.focus)) { + return toResponse(IMPOSSIBLE_MATCH); + } + } + return toResponse(ret); + } + protected int matchTypeParameter(ITypeBinding variable, boolean matchName) { + if (variable.getDeclaringMethod() != null) { + var methBinding = variable.getDeclaringMethod(); + if (this.locator.matchesName(methBinding.getDeclaringClass().getName().toCharArray(), this.locator.pattern.methodDeclaringClassName) && + (methBinding.isConstructor() || this.locator.matchesName(methBinding.getName().toCharArray(), this.locator.pattern.declaringMemberName))) { + int length = this.locator.pattern.methodArgumentTypes==null ? 0 : this.locator.pattern.methodArgumentTypes.length; + if (methBinding.getParameterTypes() == null) { + if (length == 0) return ACCURATE_MATCH; + } else if (methBinding.getParameterTypes().length == length){ + ITypeBinding[] p = methBinding.getParameterTypes(); + for (int i=0; i foundElements = new ArrayList<>(); + private Set imports = new HashSet<>(); + private MatchLocator matchLocator = null; + private String packageName; + + public DOMTypeReferenceLocator(TypeReferenceLocator locator) { + super(locator.pattern); + this.locator = locator; + } + private boolean hasPackageDeclarationAncestor(org.eclipse.jdt.core.dom.ASTNode node) { + if( node instanceof PackageDeclaration) { + return true; + } + return node == null ? false : hasPackageDeclarationAncestor(node.getParent()); + } + + @Override + public LocatorResponse match(org.eclipse.jdt.core.dom.Annotation node, NodeSetWrapper nodeSet, MatchLocator locator) { + return match(node.getTypeName(), nodeSet, locator); + } + @Override + public LocatorResponse match(Name name, NodeSetWrapper nodeSet, MatchLocator locator) { + this.matchLocator = locator; + if( name.getParent() instanceof LabeledStatement ls && ls.getLabel() == name) { + return toResponse(IMPOSSIBLE_MATCH); + } + if( name.getParent() instanceof BreakStatement bs && bs.getLabel() == name) { + return toResponse(IMPOSSIBLE_MATCH); + } + if (!matchFineGrain(name, this.locator.fineGrain())) { + return toResponse(IMPOSSIBLE_MATCH); + } + if (this.locator.pattern.simpleName == null) { + int v = this.locator.pattern.mustResolve ? POSSIBLE_MATCH : ACCURATE_MATCH; + return toResponse(v, false); + } + if( name instanceof SimpleName sn2 ) { + if( this.locator.pattern.qualification == null) + return toResponse(match(sn2, nodeSet)); + // searching for a qualified name but we are only simple + org.eclipse.jdt.core.dom.ASTNode parent3 = name.getParent(); + if( !(parent3 instanceof QualifiedName)) { + return toResponse(match(sn2, nodeSet)); + } + // Parent is a qualified name and we didn't match it... + // so we know the whole name was a failed match, but... + if( parent3 instanceof QualifiedName qn3 && qn3.getQualifier() == name) { + // Maybe the qualifier is the type we're looking for + if( match(sn2, nodeSet) == POSSIBLE_MATCH) { + return toResponse(POSSIBLE_MATCH); + } + } + + if( this.locator.pattern.getMatchMode() == SearchPattern.R_EXACT_MATCH) { + return toResponse(IMPOSSIBLE_MATCH); + } + if( match(sn2, nodeSet) == POSSIBLE_MATCH) { + return toResponse(POSSIBLE_MATCH); + } + return toResponse(IMPOSSIBLE_MATCH); + } + if( name instanceof QualifiedName qn2 ) { + return toResponse(match(qn2, nodeSet)); + } + return toResponse(IMPOSSIBLE_MATCH); + } + @Override + public LocatorResponse match(org.eclipse.jdt.core.dom.ASTNode node, NodeSetWrapper nodeSet, MatchLocator locator) { + this.matchLocator = locator; + if (!matchFineGrain(node, this.locator.fineGrain())) { + return toResponse(IMPOSSIBLE_MATCH); + } + if (node instanceof EnumConstantDeclaration enumConstantDecl + && node.getParent() instanceof EnumDeclaration enumDeclaration + && enumConstantDecl.getAnonymousClassDeclaration() != null) { + if (this.locator.pattern.simpleName == null) { + int v = nodeSet.addMatch(node, this.locator.pattern.mustResolve ? POSSIBLE_MATCH : ACCURATE_MATCH); + return toResponse(v, true); + } + if (this.locator.matchesName(this.locator.pattern.simpleName, enumDeclaration.getName().getIdentifier().toCharArray())) { + int v = nodeSet.addMatch(node, this.locator.pattern.mustResolve ? POSSIBLE_MATCH : ACCURATE_MATCH); + return toResponse(v, true); + } + } + if( node instanceof ImportDeclaration id) { + Name n = id.getName(); + imports.add(n); + } + if( node instanceof PackageDeclaration pd) { + this.packageName = pd.getName().toString(); + } + return toResponse(IMPOSSIBLE_MATCH); + } + + private LocatorResponse matchTypeNodeReturnComponent(Type node, String qualifiedNameFromNode, String fqqn, + int defaultLevel) { + if( this.locator.matchesName(qualifiedNameFromNode.toCharArray(), fqqn.toCharArray())) { + Type nodeToUse = node; + boolean replacementFound = false; + if( !preferParamaterizedNode() && node instanceof ParameterizedType pt) { + nodeToUse = pt.getType(); + replacementFound = true; + } + Type replacedNode = replacementFound ? nodeToUse : null; + int typeParamMatches = validateTypeParameters(node); + if( typeParamMatches == TYPE_PARAMS_MATCH) { + return new LocatorResponse(defaultLevel, replacementFound, replacedNode, false, false); + } else { + int ret = typeParamMatches == TYPE_PARAMS_COUNT_MATCH ? ERASURE_MATCH : IMPOSSIBLE_MATCH; + boolean isErasurePattern = isPatternErasureMatch(); + boolean isEquivPattern = isPatternEquivalentMatch(); + if( ret == ERASURE_MATCH && !isErasurePattern && !isEquivPattern) + ret = IMPOSSIBLE_MATCH; + return new LocatorResponse(ret, replacementFound, replacedNode, false, false); + } + } + return null; + } + + @Override + public LocatorResponse match(Type node, NodeSetWrapper nodeSet, MatchLocator locator) { + this.matchLocator = locator; + if (!matchFineGrain(node, this.locator.fineGrain())) { + return toResponse(IMPOSSIBLE_MATCH); + } + int defaultLevel = this.locator.pattern.mustResolve ? POSSIBLE_MATCH : ACCURATE_MATCH; + if (this.locator.pattern.simpleName == null) { + int v = nodeSet.addMatch(node, defaultLevel); + return toResponse(v, true); + } + String qualifiedNameFromNode = getQualifiedNameFromType(node); + String simpleNameFromNode = getNameStringFromType(node); + String patternQualifiedString = null; + if( qualifiedNameFromNode != null && this.locator.pattern.qualification != null) { + // we have a qualified name in the node, and our pattern is searching for a qualified name + String q1 = new String(this.locator.pattern.qualification); + String sn1 = new String(this.locator.pattern.simpleName); + patternQualifiedString = !q1.isEmpty() ? (q1 + "." + sn1) : sn1; + LocatorResponse r1 = matchTypeNodeReturnComponent(node, patternQualifiedString, qualifiedNameFromNode, defaultLevel); + if( r1 != null ) return r1; + + // Not an exact match. We might need to check for more qualifications + if( qualifiedNameFromNode.endsWith(patternQualifiedString)) { + String[] patternQualifiedStringSegments = patternQualifiedString.split("\\."); + String firstSegment = patternQualifiedStringSegments == null || patternQualifiedStringSegments.length == 0 ? null : patternQualifiedStringSegments[0]; + String fqqnImport = fqqnFromImport(firstSegment); + if( fqqnImport != null ) { + String fqqn = fqqnImport + patternQualifiedString.substring(firstSegment.length()); + r1 = matchTypeNodeReturnComponent(node, qualifiedNameFromNode, fqqn, defaultLevel); + if( r1 != null ) return r1; + } + String[] nodeQualifiedStringSegments = qualifiedNameFromNode.split("\\."); + int patternLen = patternQualifiedStringSegments == null ? 0 : patternQualifiedStringSegments.length; + for( int i = 1; i <= patternLen; i++ ) { + String patternSegmentFromEnd = patternQualifiedStringSegments[patternLen - i]; + String matchingNodeSegment = nodeQualifiedStringSegments[nodeQualifiedStringSegments.length - i]; + if( !patternSegmentFromEnd.equals(matchingNodeSegment)) { + return toResponse(IMPOSSIBLE_MATCH); + } + } + } else if( patternQualifiedString.equals(qualifiedNameFromNode) || patternQualifiedString.endsWith("." + qualifiedNameFromNode)) { + String[] qualifiedNameFromNodeStringSegments = qualifiedNameFromNode.split("\\."); + String firstSegment = qualifiedNameFromNodeStringSegments == null || qualifiedNameFromNodeStringSegments.length == 0 ? null : qualifiedNameFromNodeStringSegments[0]; + String fqqnImport = fqqnFromImport(firstSegment); + if( fqqnImport != null ) { + String fqqn = fqqnImport + qualifiedNameFromNode.substring(firstSegment.length()); + r1 = matchTypeNodeReturnComponent(node, patternQualifiedString, fqqn, defaultLevel); + if( r1 != null ) return r1; + } + if( this.packageName != null ) { + String fqqn = this.packageName + "." + qualifiedNameFromNode; + r1 = matchTypeNodeReturnComponent(node, patternQualifiedString, fqqn, defaultLevel); + if( r1 != null ) return r1; + } + } else { + String[] qualifiedNameFromNodeSegments = qualifiedNameFromNode.split("\\."); + String[] qualifiedNamePatternSegments = patternQualifiedString.split("\\."); + String firstNodeSegment = qualifiedNameFromNodeSegments == null ? null : qualifiedNameFromNodeSegments.length == 0 ? null : qualifiedNameFromNodeSegments[0]; + String firstPatternSegment = qualifiedNamePatternSegments == null ? null : qualifiedNamePatternSegments.length == 0 ? null : qualifiedNamePatternSegments[0]; + String fqqnImportFromNode = fqqnFromImport(firstNodeSegment); + if( fqqnImportFromNode != null ) { + String fqqn = fqqnImportFromNode + qualifiedNameFromNode.substring(firstNodeSegment.length()); + r1 = matchTypeNodeReturnComponent(node, qualifiedNameFromNode, fqqn, defaultLevel); + if( r1 != null ) + return r1; + } + String fqqnImportFromPattern = fqqnFromImport(firstPatternSegment); + if( fqqnImportFromPattern != null ) { + String fqqn = fqqnImportFromPattern + patternQualifiedString.substring(firstPatternSegment.length()); + r1 = matchTypeNodeReturnComponent(node, qualifiedNameFromNode, fqqn, defaultLevel); + if( r1 != null ) + return r1; + } + + return toResponse(IMPOSSIBLE_MATCH); + } + } + if (simpleNameFromNode != null ) { + if( this.locator.matchesName(this.locator.pattern.simpleName, simpleNameFromNode.toCharArray()) ) { + int level = this.locator.pattern.mustResolve || this.locator.pattern.qualification == null ? POSSIBLE_MATCH : ACCURATE_MATCH; + int typeParamMatches = validateTypeParameters(node); + if( typeParamMatches == TYPE_PARAMS_NO_MATCH) level = IMPOSSIBLE_MATCH; + if( typeParamMatches == TYPE_PARAMS_COUNT_MATCH) level = ERASURE_MATCH; + + if( isPatternExactMatch()) { + if( typeParamMatches == TYPE_PARAMS_NO_MATCH) { + boolean patternHasTypeArgs = this.locator.pattern.hasTypeArguments(); + List nodeTypeArgs = node instanceof ParameterizedType pt ? pt.typeArguments() : null; + boolean nodeHasTypeArgs = nodeTypeArgs != null && nodeTypeArgs.size() > 0; + if( !patternHasTypeArgs && !nodeHasTypeArgs) { + return toResponse(level); + } + return new LocatorResponse(IMPOSSIBLE_MATCH, false, null, false, false); + } + } + + boolean isErasurePattern = isPatternErasureMatch(); + boolean isEquivPattern = isPatternEquivalentMatch(); + if( level == ERASURE_MATCH && !isErasurePattern && !isEquivPattern) + level = IMPOSSIBLE_MATCH; + + if( level != IMPOSSIBLE_MATCH ) { + if( !preferParamaterizedNode() || patternPrefersSimpleName()) { + Name n = getSimpleNameNodeFromType(node); + if( n != null ) { + nodeSet.addMatch(n, level); + return new LocatorResponse(level, true, n, true, true); + } + } + int v = nodeSet.addMatch(node, level); + return toResponse(v, true); + } + } + } + return toResponse(IMPOSSIBLE_MATCH); + } + + private boolean patternPrefersSimpleName() { + return false; +// char[] qual = this.locator.pattern.qualification; +// return qual == null; + } + private boolean isPatternErasureMatch() { + int r = this.locator.pattern.getMatchRule(); + return (r & SearchPattern.R_ERASURE_MATCH) == SearchPattern.R_ERASURE_MATCH; + } + private boolean isPatternEquivalentMatch() { + int r = this.locator.pattern.getMatchRule(); + return (r & SearchPattern.R_EQUIVALENT_MATCH) == SearchPattern.R_EQUIVALENT_MATCH; + } + private boolean isPatternExactMatch() { + int r = this.locator.pattern.getMatchRule(); + return (r & SearchPattern.R_FULL_MATCH) == SearchPattern.R_FULL_MATCH; + } + + + public static final int TYPE_PARAMS_MATCH = 1; + public static final int TYPE_PARAMS_COUNT_MATCH = 2; + public static final int TYPE_PARAMS_NO_MATCH = 3; + + private int validateTypeParameters(Type node) { + // SimpleType with typeName=QualifiedName + boolean patternHasTypeArgs = this.locator.pattern.hasTypeArguments(); + //boolean patternHasTypeParameters = this.locator.pattern.hasTypeParameters(); + boolean erasureMatch = isPatternErasureMatch(); + boolean equivMatch = isPatternEquivalentMatch(); + boolean exactMatch = isPatternExactMatch(); + if( patternHasTypeArgs && !(erasureMatch || equivMatch || exactMatch )) { + return TYPE_PARAMS_NO_MATCH; + } + + char[][][] fromPattern = this.locator.pattern.getTypeArguments(); + if( fromPattern == null ) { + return TYPE_PARAMS_MATCH; + } + if( node instanceof SimpleType st && (st.getName() instanceof QualifiedName || st.getName() instanceof SimpleName)) { + if( !erasureMatch && !equivMatch) { + for( int i = 0; i < fromPattern.length; i++ ) { + if( fromPattern[i] == null || fromPattern[i].length != 0 ) { + return TYPE_PARAMS_NO_MATCH; + } + } + } + return TYPE_PARAMS_MATCH; + } + + Type working = node; + boolean done = false; + int i = 0; + for( i = 0; i < fromPattern.length && !done; i++ ) { + char[][] thisLevelTypeParams = fromPattern[i]; + List typeArgs = working instanceof ParameterizedType pt ? pt.typeArguments() : null; + boolean emptyPatternParams = thisLevelTypeParams == null || thisLevelTypeParams.length == 0; + if( emptyPatternParams) { + if( exactMatch && emptyPatternParams && (typeArgs != null && typeArgs.size() > 0) ) { + return TYPE_PARAMS_NO_MATCH; + } + } else { + if( typeArgs == null || typeArgs.size() != thisLevelTypeParams.length) { + return TYPE_PARAMS_NO_MATCH; + } + for( int j = 0; j < thisLevelTypeParams.length; j++ ) { + ASTNode argj = (ASTNode)typeArgs.get(j); + IBinding domBinding = DOMASTNodeUtils.getBinding(argj); + String patternSig = new String(thisLevelTypeParams[j]); + IBinding patternBinding = JdtCoreDomPackagePrivateUtility.findBindingForType(node, patternSig); + if( patternBinding == null ) { + boolean plusOrMinus = patternSig.startsWith("+") || patternSig.startsWith("-"); + String safePatternString = plusOrMinus ? patternSig.substring(1) : patternSig; + if( safePatternString.startsWith("Q")) { + patternBinding = JdtCoreDomPackagePrivateUtility.findUnresolvedBindingForType(node, patternSig); + } else { + patternBinding = JdtCoreDomPackagePrivateUtility.findBindingForType(node, safePatternString); + } + } + boolean singleTypeArgMatches = TypeArgumentMatchingUtility.validateSingleTypeArgMatches(exactMatch, patternSig, patternBinding, domBinding, this.locator); + if( !singleTypeArgMatches ) { + return TYPE_PARAMS_COUNT_MATCH; + } + } + } + if( working instanceof ParameterizedType ptt) + working = ptt.getType(); + if( working instanceof QualifiedType qtt) { + working = qtt.getQualifier(); + } + if( working instanceof SimpleType) + done = true; + } + for( int k = i; k < fromPattern.length; k++ ) { + if( fromPattern[k] != null && fromPattern[k].length != 0) { + // More typeargs required, but we don't have any more + return TYPE_PARAMS_NO_MATCH; + } + } + return TYPE_PARAMS_MATCH; + } + + + private String getQualifiedNameFromType(Type query) { + if( query instanceof QualifiedType qtt) { + String qualString = getQualifiedNameFromType(qtt.getQualifier()); + String nameString = getNameStringFromType(query); + return qualString == null || nameString == null ? null : + qualString + "." + nameString; + } + if( query instanceof ParameterizedType ptt) { + return getQualifiedNameFromType(ptt.getType()); + } + if( query instanceof SimpleType st) { + String fqqn = fqqnFromImport(st.getName().toString()); + return fqqn != null ? fqqn : st.getName().toString(); + } + return null; + } + + private String getNameStringFromType(Type node) { + Name nnode = getNameNodeFromType(node); + nnode = (nnode instanceof QualifiedName qn ? qn.getName() : nnode); + return getNameStringFromNameObject(nnode); + } + + private String getNameStringFromNameObject(Name nnode) { + if (nnode instanceof SimpleName name) { + return name.getIdentifier(); + } + return nnode == null ? null : nnode.toString(); + } + + private org.eclipse.jdt.core.dom.Name getSimpleNameNodeFromType(Type node) { + org.eclipse.jdt.core.dom.Name name = getNameNodeFromType(node); + if( name != null ) { + if( name instanceof QualifiedName qn ) { + return qn.getName(); + } + return name; + } + return null; + } + + private org.eclipse.jdt.core.dom.Name getNameNodeFromType(Type node) { + if (node instanceof SimpleType simple) { + return simple.getName(); + } else if (node instanceof QualifiedType qualified) { + return qualified.getName(); + } else if( node instanceof ParameterizedType ptt) { + return getNameNodeFromType(ptt.getType()); + } + return null; + } + + private org.eclipse.jdt.core.dom.Name getQualifierNameNodeFromType(Type node) { + if (node instanceof SimpleType simple) { + return simple.getName(); + } else if (node instanceof QualifiedType qualified) { + return getQualifierNameNodeFromType(qualified.getQualifier()); + } else if( node instanceof ParameterizedType ptt) { + return getQualifierNameNodeFromType(ptt.getType()); + } + return null; + } + + + private String fqqnFromImport(String firstSegment) { + if( firstSegment == null ) + return null; + + for( Name n : imports ) { + if( n.isSimpleName() && n.toString().equals(firstSegment)) { + return n.toString(); + } + if( n.isQualifiedName() && n.toString().endsWith("." + firstSegment)) { + return n.toString(); + } + } + return null; + } + @Override + public LocatorResponse resolveLevel(org.eclipse.jdt.core.dom.ASTNode node, IBinding binding, MatchLocator locator) { + if (binding == null) { + if( node instanceof SimpleName sn) { + int accuracy = resolveLevelForSimpleName(node, sn.getIdentifier()); + if( accuracy != -1 ) { + // Add directly + IResource r = null; + IJavaElement enclosing = DOMASTNodeUtils.getEnclosingJavaElement(node); + IJavaElement ancestor = enclosing == null ? null : enclosing.getAncestor(IJavaElement.COMPILATION_UNIT); + try { + r = ancestor == null ? null : ancestor.getCorrespondingResource(); + } catch(JavaModelException jme) { + // ignore + } + + TypeReferenceMatch typeMatch = new TypeReferenceMatch(enclosing, accuracy, node.getStartPosition(), node.getLength(), insideDocComment(node), locator.getParticipant(), r); + try { + reportSearchMatch(locator, node, typeMatch); + } catch(CoreException ce) { + // ignore + } + // Then return not possible so it doesn't get added again + return toResponse(IMPOSSIBLE_MATCH); + } + } + return toResponse(INACCURATE_MATCH); + } + if (binding instanceof ITypeBinding typeBinding) { + if (!DOMTypeDeclarationLocator.matchSearchForTypeSuffix(typeBinding, this.locator.pattern.typeSuffix)) { + return toResponse(IMPOSSIBLE_MATCH); + } + if( hasImportAncestor(node) && !this.locator.isDeclarationOfReferencedTypesPattern) { + return resolveLevelForImportBinding(node, typeBinding, locator); + } + boolean patternHasTypeArgs = this.locator.pattern.hasTypeArguments(); + boolean patternHasTypeParameters = this.locator.pattern.hasTypeParameters(); + boolean patternHasSignatures = this.locator.pattern.hasSignatures(); + boolean erasureMatch = isPatternErasureMatch(); + boolean equivMatch = isPatternEquivalentMatch(); + boolean exactMatch = isPatternExactMatch(); + if( (patternHasTypeArgs && !(erasureMatch || equivMatch || exactMatch))) { + return toResponse(IMPOSSIBLE_MATCH); + } + int v = resolveLevelForTypeBinding(node, typeBinding, locator); + boolean prefersParameterized = preferParamaterizedNode(); + if( node instanceof ParameterizedType pt) { + ASTNode n = prefersParameterized ? pt : pt.getType(); + return new LocatorResponse(v, n != pt, n, false, false); + } + if( patternHasTypeArgs && !patternHasSignatures && !erasureMatch) { + // the search doesn't have type args in it, but the type does + return toResponse(IMPOSSIBLE_MATCH); + } + return toResponse(v); + } + if( binding instanceof IPackageBinding && node instanceof SimpleName sn) { + // var x = (B36479.C)val; + // might interpret the B36479 to be a package and C a type, + // rather than B36479 to be a type and C to be an inner-type + if( this.locator.isDeclarationOfReferencedTypesPattern) { + return toResponse(IMPOSSIBLE_MATCH); + } + if( hasPackageDeclarationAncestor(node)) { + return toResponse(IMPOSSIBLE_MATCH); + } + String identifier = sn.getIdentifier(); + if( this.locator.matchesName(this.locator.pattern.simpleName, identifier.toCharArray())) { + return toResponse(INACCURATE_MATCH); + } + + } + return toResponse(IMPOSSIBLE_MATCH); + } + + private boolean preferParamaterizedNode() { + int patternRule = this.locator.pattern.getMatchRule(); + boolean patternIsErasureMatch = isPatternErasureMatch(); + boolean patternIsEquivMatch = isPatternEquivalentMatch(); + + boolean hasTypeArgs = this.locator.pattern.hasTypeArguments(); + boolean hasTypeParams = this.locator.pattern.hasTypeParameters(); + boolean emptyTypeArgsPattern = this.locator.pattern.getTypeArguments() == null || + this.locator.pattern.getTypeArguments().length == 0; + if( patternIsEquivMatch) + return hasTypeArgs; + if( patternIsErasureMatch) { + return false; + } + if (this.locator.pattern.fineGrain != 0) { + return false; + } +// if( emptyTypeArgsPattern || patternIsErasureMatch || patternIsEquivMatch ) { +// return false; +// } + return true; + } + + private LocatorResponse resolveLevelForImportBinding(ASTNode node, ITypeBinding typeBinding, + MatchLocator locator2) { + if (this.locator.pattern.hasTypeArguments() && !this.isEquivalentMatch &&!this.isErasureMatch) { + return toResponse(0); + } + + // Return if fine grain is on and does not concern import reference + if ((this.locator.pattern.fineGrain != 0 && (this.locator.pattern.fineGrain & IJavaSearchConstants.IMPORT_DECLARATION_TYPE_REFERENCE) == 0)) { + return toResponse(0); + } + int newLevel = this.resolveLevelForTypeFQN(this.locator.pattern.simpleName, + this.locator.pattern.qualification, typeBinding, null); + return toResponse(newLevel); + } + private static boolean matchFineGrain(ASTNode node, int fineGrain) { + if (fineGrain == 0) { + return true; + } + if ((fineGrain & IJavaSearchConstants.INSTANCEOF_TYPE_REFERENCE) != 0 + && node.getLocationInParent() == InstanceofExpression.RIGHT_OPERAND_PROPERTY) { + return true; + } + if ((fineGrain & IJavaSearchConstants.CLASS_INSTANCE_CREATION_TYPE_REFERENCE) != 0 + && node.getLocationInParent() == ClassInstanceCreation.TYPE_PROPERTY) { + return true; + } + if ((fineGrain & IJavaSearchConstants.FIELD_DECLARATION_TYPE_REFERENCE) != 0 + && node.getLocationInParent() == FieldDeclaration.TYPE_PROPERTY) { + return true; + } + if ((fineGrain & IJavaSearchConstants.LOCAL_VARIABLE_DECLARATION_TYPE_REFERENCE) != 0 + && node.getLocationInParent() == VariableDeclarationStatement.TYPE_PROPERTY) { + return true; + } + if ((fineGrain & IJavaSearchConstants.PARAMETER_DECLARATION_TYPE_REFERENCE) != 0 + && node.getLocationInParent() == SingleVariableDeclaration.TYPE_PROPERTY + && node.getParent().getLocationInParent() == MethodDeclaration.PARAMETERS_PROPERTY) { + return true; + } + if ((fineGrain & IJavaSearchConstants.SUPERTYPE_TYPE_REFERENCE) != 0 + && (node.getLocationInParent() == TypeDeclaration.SUPERCLASS_TYPE_PROPERTY + || node.getLocationInParent() == TypeDeclaration.SUPER_INTERFACE_TYPES_PROPERTY)) { + return true; + } + if ((fineGrain & IJavaSearchConstants.THROWS_CLAUSE_TYPE_REFERENCE) != 0 + && node.getLocationInParent() == MethodDeclaration.THROWN_EXCEPTION_TYPES_PROPERTY) { + return true; + } + if ((fineGrain & IJavaSearchConstants.CAST_TYPE_REFERENCE) != 0 + && node.getLocationInParent() == CastExpression.TYPE_PROPERTY) { + return true; + } + if ((fineGrain & IJavaSearchConstants.CATCH_TYPE_REFERENCE) != 0 + && node.getLocationInParent() == SingleVariableDeclaration.TYPE_PROPERTY + && node.getParent().getLocationInParent() == CatchClause.EXCEPTION_PROPERTY) { + return true; + } + if ((fineGrain & IJavaSearchConstants.RETURN_TYPE_REFERENCE) != 0 + && (node.getLocationInParent() == MethodDeclaration.RETURN_TYPE2_PROPERTY + || node.getLocationInParent() == AnnotationTypeMemberDeclaration.TYPE_PROPERTY)) { + return true; + } + if ((fineGrain & IJavaSearchConstants.IMPORT_DECLARATION_TYPE_REFERENCE) != 0 + && node.getLocationInParent() == ImportDeclaration.NAME_PROPERTY) { + return true; + } + if ((fineGrain & IJavaSearchConstants.TYPE_ARGUMENT_TYPE_REFERENCE) != 0 + && (node.getLocationInParent() == ParameterizedType.TYPE_ARGUMENTS_PROPERTY + || node.getLocationInParent() == MethodInvocation.TYPE_ARGUMENTS_PROPERTY)) { + return true; + } + if ((fineGrain & IJavaSearchConstants.TYPE_VARIABLE_BOUND_TYPE_REFERENCE) != 0 + && node.getLocationInParent() == TypeParameter.TYPE_BOUNDS_PROPERTY) { + return true; + } + if ((fineGrain & IJavaSearchConstants.WILDCARD_BOUND_TYPE_REFERENCE) != 0 + && (node.getLocationInParent() == WildcardType.BOUND_PROPERTY + || node.getLocationInParent() == WildcardType.UPPER_BOUND_PROPERTY)) { + return true; + } + if ((fineGrain & IJavaSearchConstants.ANNOTATION_TYPE_REFERENCE) != 0 + && Set.of(NormalAnnotation.TYPE_NAME_PROPERTY, MarkerAnnotation.TYPE_NAME_PROPERTY, SingleMemberAnnotation.TYPE_NAME_PROPERTY).contains(node.getLocationInParent())) { + return true; + } + return false; + } + + /* + * Returns a match flag OR -1 if it cannot determine at all. + */ + private int resolveLevelForSimpleName(org.eclipse.jdt.core.dom.ASTNode node, String simpleNameNeedle) { + if( !simpleNameNeedle.contains(".") && this.locator.pattern.qualification != null && this.locator.pattern.qualification.length > 0 ) { //$NON-NLS-1$ + // we need to find out if we import this thing at all + for( Name id : imports) { + if( id instanceof QualifiedName qn) { + if( qn.getName().toString().equals(simpleNameNeedle)) { + char[] qualifiedPattern = this.locator.getQualifiedPattern(this.locator.pattern.simpleName, this.locator.pattern.qualification); + // we were imported as qualified name... + int level3 = this.resolveLevelForTypeSourceName(qualifiedPattern, qn.toString().toCharArray(), null); + if( level3 == ACCURATE_MATCH ) { + return INACCURATE_MATCH; + } + return INACCURATE_MATCH; + } + } + } + } + return -1; + } + + private int resolveLevelForTypeBinding(org.eclipse.jdt.core.dom.ASTNode node, ITypeBinding typeBinding, + MatchLocator locator) { + TypeReferencePattern trp = (locator != null && locator.pattern instanceof TypeReferencePattern tp ? tp : null); + trp = (trp == null && this.locator != null ? this.locator.pattern : trp); + if( trp == null ) { + // We don't even have a pattern + return IMPOSSIBLE_MATCH; + } + + if (trp.focus != null) { + return Objects.equals(typeBinding.getJavaElement(), locator.pattern.focus) ? + ACCURATE_MATCH : IMPOSSIBLE_MATCH; + } + IImportDiscovery importDiscovery = new IImportDiscovery() { + @Override + public String findImportForString(String s) { + return fqqnFromImport(s); + } + }; + + int newLevel = this.resolveLevelForTypeFQN(trp.simpleName, + trp.qualification, typeBinding, importDiscovery); + if( this.locator != null && this.locator.isDeclarationOfReferencedTypesPattern) { + return resolveLevelForTypeBindingDeclarationOfReferencedTypes(typeBinding, node, newLevel, locator); + } + if( newLevel == IMPOSSIBLE_MATCH ) { + String qualNameFromBinding = typeBinding.getQualifiedName(); + int simpleNameMatch = resolveLevelForSimpleName(node, qualNameFromBinding); + if( simpleNameMatch != -1 ) { + return simpleNameMatch; + } + } + if( newLevel == ACCURATE_MATCH ) { + if( trp.hasTypeArguments() ) { + return resolveLevelForTypeBindingWithTypeArguments(typeBinding, node, locator); + } + } + return newLevel; + } + + private int resolveLevelForTypeBindingWithTypeArguments(ITypeBinding typeBinding, ASTNode node, + MatchLocator locator2) { + boolean patternHasTypeArgs = this.locator.pattern.hasTypeArguments(); + boolean patternHasTypeParams = this.locator.pattern.hasTypeParameters(); + boolean patternHasTypeSignatures = !(patternHasTypeArgs && patternHasTypeParams); + + char[][][] patternTypeArgArray = this.locator.pattern.getTypeArguments(); + int patternTypeArgsLength = patternTypeArgArray == null ? -1 : + patternTypeArgArray[0] == null ? -1 : + patternTypeArgArray[0].length; + boolean bindingIsRaw = typeBinding.isRawType(); + boolean bindingIsGeneric = typeBinding.isGenericType(); + boolean bindingIsParameterized = typeBinding.isParameterizedType(); + int patternRule = this.locator.pattern.getMatchRule(); + boolean patternIsErasureMatch = isPatternErasureMatch(); + boolean patternIsEquivMatch = isPatternEquivalentMatch(); + + ITypeBinding[] bindingArgs = typeBinding.getTypeArguments(); + ITypeBinding[] bindingParams = typeBinding.getTypeParameters(); + int bindingTypeArgsLength = bindingArgs == null ? -1 : bindingArgs.length; + // Compare arguments lengths + if (patternTypeArgsLength == bindingTypeArgsLength) { + Type t = node instanceof Type ? (Type)node : null; + if( t != null ) { + int typeArgsValidation = validateTypeParameters(t); + if( typeArgsValidation == TYPE_PARAMS_MATCH) { + if( !patternHasTypeSignatures) { + return ERASURE_MATCH; + } + return ACCURATE_MATCH; + } + + if( typeArgsValidation == TYPE_PARAMS_COUNT_MATCH || typeArgsValidation == TYPE_PARAMS_NO_MATCH) { + if( isPatternExactMatch()) { + return IMPOSSIBLE_MATCH; + } else { + return ERASURE_MATCH; + } + } + } + if (!bindingIsRaw && patternHasTypeArgs) { + // generic patterns are always not compatible match + return ERASURE_MATCH; + } + return ACCURATE_MATCH; + } else { + if (patternTypeArgsLength==0) { + return ACCURATE_MATCH; + } else if (bindingTypeArgsLength==0) { + // If this is an import, we have to treat it differently + + // pattern looking for args but binding has none. +// ITypeBinding decl = typeBinding.getTypeDeclaration(); +// ITypeBinding[] declArgs = decl.getTypeArguments(); +// ITypeBinding[] declParams = decl.getTypeParameters(); + // raw binding is always compatible + if( patternIsEquivMatch && bindingIsRaw) { + return ACCURATE_MATCH; + } + if( !bindingIsRaw && !(patternIsEquivMatch || patternIsErasureMatch)) { + return IMPOSSIBLE_MATCH; + } + if( !patternIsEquivMatch || bindingIsRaw) + return ACCURATE_MATCH; + } + } + return IMPOSSIBLE_MATCH; + } + private int resolveLevelForTypeBindingDeclarationOfReferencedTypes(ITypeBinding typeBinding, ASTNode node, int newLevel, MatchLocator locator) { + IJavaElement enclosing = ((DeclarationOfReferencedTypesPattern)this.locator.pattern).enclosingElement; + // We don't add this node. We manually add the declaration + ITypeBinding t2 = typeBinding.getTypeDeclaration(); + IJavaElement je = t2 == null ? null : t2.getJavaElement(); + if( je != null && !this.foundElements.contains(je) && DOMASTNodeUtils.isWithinRange(node, enclosing)) { + ISourceReference sr = je instanceof ISourceReference ? (ISourceReference)je : null; + IResource r = null; + ISourceRange srg = null; + ISourceRange nameRange = null; + try { + srg = sr.getSourceRange(); + nameRange = sr.getNameRange(); + IJavaElement ancestor = je.getAncestor(IJavaElement.COMPILATION_UNIT); + if( ancestor == null ) { + ancestor = je.getAncestor(IJavaElement.CLASS_FILE); + } + if( ancestor != null ) { + r = ancestor.getCorrespondingResource(); + } + } catch(JavaModelException jme) { + // ignore + } + if( r == null ) { + if( je instanceof BinaryType) { + r = je.getJavaProject().getProject(); + } + } + ISourceRange rangeToUse = (nameRange == null) ? srg : nameRange; + if( rangeToUse != null && r != null) { + TypeDeclarationMatch tdm = new TypeDeclarationMatch(je, newLevel, + rangeToUse.getOffset(), rangeToUse.getLength(), + locator.getParticipant(), r); + try { + SearchMatchingUtility.reportSearchMatch(locator, tdm); + this.foundElements.add(je); + } catch(CoreException ce) { + // ignore + } + } + } + return IMPOSSIBLE_MATCH; + } + private org.eclipse.jdt.core.dom.CompilationUnit findCU(org.eclipse.jdt.core.dom.ASTNode node) { + if( node == null ) + return null; + if( node instanceof org.eclipse.jdt.core.dom.CompilationUnit cu) { + return cu; + } + return findCU(node.getParent()); + } + + public int match(SimpleName name, NodeSetWrapper nodeSet) { + String simpleName = name.getIdentifier(); + return simpleName != null && this.locator.matchesName(this.locator.pattern.simpleName, simpleName.toCharArray()) ? + POSSIBLE_MATCH : IMPOSSIBLE_MATCH; + } + public int match(QualifiedName name, NodeSetWrapper nodeSet) { + String simpleName = name.getName().getIdentifier(); + String qualifier = name.getQualifier().toString(); + if( this.locator.pattern.qualification == null ) { + // Return an impossible match here, because we are not seeking a qualifier. + // The SimpleName node should be the one to respond. + return IMPOSSIBLE_MATCH; + } + if( qualifier != null) { + String desiredQualifier = new String(this.locator.pattern.qualification); + if( !qualifier.equals(desiredQualifier)) { + return IMPOSSIBLE_MATCH; + } + } + return simpleName != null && this.locator.matchesName(this.locator.pattern.simpleName, simpleName.toCharArray()) ? + POSSIBLE_MATCH : IMPOSSIBLE_MATCH; + } + protected int resolveLevelForType(ITypeBinding typeBinding) { + if (typeBinding == null) { + if (this.locator.pattern.typeSuffix != IIndexConstants.TYPE_SUFFIX) return INACCURATE_MATCH; + } else { + switch (this.locator.pattern.typeSuffix) { + case IIndexConstants.CLASS_SUFFIX: + if (!typeBinding.isClass()) return IMPOSSIBLE_MATCH; + break; + case IIndexConstants.CLASS_AND_INTERFACE_SUFFIX: + if (!(typeBinding.isClass() || (typeBinding.isInterface() && !typeBinding.isAnnotation()))) return IMPOSSIBLE_MATCH; + break; + case IIndexConstants.CLASS_AND_ENUM_SUFFIX: + if (!(typeBinding.isClass() || typeBinding.isEnum())) return IMPOSSIBLE_MATCH; + break; + case IIndexConstants.INTERFACE_SUFFIX: + if (!typeBinding.isInterface() || typeBinding.isAnnotation()) return IMPOSSIBLE_MATCH; + break; + case IIndexConstants.INTERFACE_AND_ANNOTATION_SUFFIX: + if (!(typeBinding.isInterface() || typeBinding.isAnnotation())) return IMPOSSIBLE_MATCH; + break; + case IIndexConstants.ENUM_SUFFIX: + if (!typeBinding.isEnum()) return IMPOSSIBLE_MATCH; + break; + case IIndexConstants.ANNOTATION_TYPE_SUFFIX: + if (!typeBinding.isAnnotation()) return IMPOSSIBLE_MATCH; + break; + case IIndexConstants.TYPE_SUFFIX : // nothing + } + } + return this.resolveLevelForType(this.locator.pattern.simpleName, + this.locator.pattern.qualification, + typeBinding); + } + + private boolean hasImportAncestor(ASTNode node) { + ASTNode working = node; + while(working != null) { + if( working instanceof ImportDeclaration ) + return true; + working = working.getParent(); + } + return false; + } + + @Override + public void reportSearchMatch(MatchLocator locator, ASTNode node, SearchMatch match) throws CoreException { + IResource resource = match.getResource(); + if( resource == null ) { + return; + } + +// boolean matchIsEr = match.isErasure(); +// boolean matchIsEq = match.isEquivalent(); +// boolean matchIsEx = match.isExact(); + boolean report = (this.isErasureMatch && match.isErasure()) || (this.isEquivalentMatch && match.isEquivalent()) || match.isExact(); + if (!report) + return; + + updateMatchPositions(node, match); + updateMatchRule(node, match); + + report = (this.isErasureMatch && match.isErasure()) || (this.isEquivalentMatch && match.isEquivalent()) || match.isExact(); + if (!report) + return; + // fix org.eclipse.jdt.core.tests.model.JavaSearchBugsTests.testBug83804_Type() + Object object = match.getElement(); + if ( (object instanceof org.eclipse.jdt.internal.core.CompilationUnit unit) && unit.getPackageName() != null) { + String pkgName = CharOperation.toString(unit.getPackageName()); + IJavaElement element = unit.getPackageDeclaration(pkgName); + match.setElement(element); + } + SearchMatchingUtility.reportSearchMatch(locator, match); + } + + + private void updateMatchRule(ASTNode node, SearchMatch match) { + if( hasImportAncestor(node)) + return; + + // Compare arguments lengthes + int matchRule = match.getRule(); + char[][][] fromPattern = this.locator.pattern.getTypeArguments(); + int patternTypeArgsLength = (fromPattern == null || fromPattern.length == 0 || fromPattern[0] == null ? 0 : fromPattern.length <= 0 ? 0 : fromPattern[0].length); + int typeArgumentsLength = node instanceof ParameterizedType ptt ? ptt.typeArguments().size() : 0; + boolean hasTypeParameters = this.locator.pattern.hasTypeParameters(); + + if (match.isRaw()) { + if (patternTypeArgsLength != 0) { + matchRule &= ~SearchPattern.R_FULL_MATCH; + } + } + if (hasTypeParameters) { + matchRule = SearchPattern.R_ERASURE_MATCH; + } + + + + if (patternTypeArgsLength == typeArgumentsLength) { + if (!match.isRaw() && hasTypeParameters) { + // generic patterns are always not compatible match + match.setRule(SearchPattern.R_ERASURE_MATCH); + } + } else { + if (patternTypeArgsLength==0) { + if (!match.isRaw() || hasTypeParameters) { + match.setRule(matchRule & ~SearchPattern.R_FULL_MATCH); + } + } else if (typeArgumentsLength==0) { + // raw binding is always compatible + match.setRule(matchRule & ~SearchPattern.R_FULL_MATCH); + } else { + match.setRule(0); // impossible match + return; + } + } + if (fromPattern == null || fromPattern.length == 0 || fromPattern[0] == null) { + match.setRule(matchRule); + } + } + private void updateMatchPositions(ASTNode node, SearchMatch match) { + ASTNode replacementNodeForStartPosition = null; + ASTNode toCheck = node; + if( node instanceof ParameterizedType ptt) { + toCheck = ptt.getType(); + } + if( toCheck instanceof QualifiedType qtt) { + replacementNodeForStartPosition = findNodeMatchingPatternQualifier(qtt.getQualifier()); + } else if( toCheck instanceof SimpleType st) { + replacementNodeForStartPosition = findNodeMatchingPatternQualifier(st); + } + + if( replacementNodeForStartPosition != null ) { + int matchStart = match.getOffset(); + int matchEnd = matchStart + match.getLength(); + int newStart = replacementNodeForStartPosition.getStartPosition(); + int newLength = matchEnd - newStart; + match.setOffset(newStart); + match.setLength(newLength); + } + if (this.locator.pattern.simpleName == null) { + return; + } + + ASTNode working = (replacementNodeForStartPosition != null ? replacementNodeForStartPosition : node); + int trimQualifierStart = findTrimQualifierStart(working); + if( trimQualifierStart != -1 ) { + int matchStart = match.getOffset(); + int matchEnd = matchStart + match.getLength(); + int newStart = trimQualifierStart; + int newLength = matchEnd - newStart; + match.setOffset(newStart); + match.setLength(newLength); + } + } + private int findTrimQualifierStart(ASTNode working) { + String needle = this.locator.pattern.qualification == null ? null : new String(this.locator.pattern.qualification); + if( needle == null && working instanceof Type workingType) { + ASTNode n1 = getSimpleNameNodeFromType(workingType); + if( n1 != null ) { + return n1.getStartPosition(); + } + } + if( needle != null && working instanceof Type workingType ) { + Name n = getQualifierNameNodeFromType(workingType); + n = (n instanceof QualifiedName qn ? qn.getName() : n); + String asString = getNameStringFromNameObject(n); + if( asString != null ) { + if (this.locator.matchesName(this.locator.pattern.qualification, asString.toCharArray())) { + return n.getStartPosition(); + } + } + } + return -1; + } + private ASTNode findNodeMatchingPatternQualifier(Type qualifier) { + if( qualifier instanceof ParameterizedType pt) { + return findNodeMatchingPatternQualifier(pt.getType()); + } + Name[] retNode = new Name[] {null}; + String needle = this.locator.pattern.qualification == null ? null : new String(this.locator.pattern.qualification); + if( needle != null ) { + qualifier.accept(new ASTVisitor() { + @Override + public boolean visit(QualifiedName node) { + if( node.getName().toString().equals(needle)) { + retNode[0] = node.getName(); + } + return retNode[0] == null; + } + @Override + public boolean visit(SimpleName node) { + if( node.toString().equals(needle)) { + retNode[0] = node; + } + return retNode[0] == null; + } + @Override + public boolean visit(ParameterizedType node) { + node.getType().accept(this); + return false; + } + }); + } + return retNode[0]; + } + + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/NodeSetWrapper.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/NodeSetWrapper.java new file mode 100644 index 00000000000..c9e1f0d80a3 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/NodeSetWrapper.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * 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.core.search.matching; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jdt.core.search.SearchMatch; +import org.eclipse.jdt.core.search.SearchPattern; + +public class NodeSetWrapper { + private Set possibleASTNodes = new LinkedHashSet<>(); + public final Map trustedASTNodeLevels = new LinkedHashMap<>(); + private boolean mustResolve; + public NodeSetWrapper(boolean mustResolve) { + this.setMustResolve(mustResolve); + } + + public boolean mustResolve() { + return mustResolve; + } + + public void setMustResolve(boolean mustResolve) { + this.mustResolve = mustResolve; + } + + public int addMatch(org.eclipse.jdt.core.dom.ASTNode node, int matchLevel) { + int maskedLevel = matchLevel & PatternLocator.MATCH_LEVEL_MASK; + switch (maskedLevel) { + case PatternLocator.INACCURATE_MATCH: + if (matchLevel != maskedLevel) { + addTrustedMatch(node, Integer.valueOf(SearchMatch.A_INACCURATE+(matchLevel & PatternLocator.FLAVORS_MASK))); + } else { + addTrustedMatch(node, MatchingNodeSet.POTENTIAL_MATCH); + } + break; + case PatternLocator.POSSIBLE_MATCH: + addPossibleMatch(node); + break; + case PatternLocator.ERASURE_MATCH: + if (matchLevel != maskedLevel) { + addTrustedMatch(node, Integer.valueOf(SearchPattern.R_ERASURE_MATCH+(matchLevel & PatternLocator.FLAVORS_MASK))); + } else { + addTrustedMatch(node, MatchingNodeSet.ERASURE_MATCH); + } + break; + case PatternLocator.ACCURATE_MATCH: + if (matchLevel != maskedLevel) { + addTrustedMatch(node, Integer.valueOf(SearchMatch.A_ACCURATE+(matchLevel & PatternLocator.FLAVORS_MASK))); + } else { + addTrustedMatch(node, MatchingNodeSet.EXACT_MATCH); + } + break; + } + return matchLevel; + } + public void addPossibleMatch(org.eclipse.jdt.core.dom.ASTNode node) { + this.possibleASTNodes.add(node); + } + public void addTrustedMatch(org.eclipse.jdt.core.dom.ASTNode node, boolean isExact) { + addTrustedMatch(node, isExact ? MatchingNodeSet.EXACT_MATCH : MatchingNodeSet.POTENTIAL_MATCH); + } + void addTrustedMatch(org.eclipse.jdt.core.dom.ASTNode node, Integer level) { + this.trustedASTNodeLevels.put(node, level); + } + int getTrustedMatch(org.eclipse.jdt.core.dom.ASTNode node) { + Integer i = this.trustedASTNodeLevels.get(node); + return i == null ? 0 : i.intValue(); + } + +} \ No newline at end of file diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/SearchMatchingUtility.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/SearchMatchingUtility.java new file mode 100644 index 00000000000..6aa870395fc --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/SearchMatchingUtility.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * 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.core.search.matching; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.search.SearchMatch; + +/* + * Hold Utility methods for package-private functionality + */ +public class SearchMatchingUtility { + public static void reportSearchMatch(MatchLocator locator, SearchMatch match) throws CoreException { + locator.report(match); + } +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/TypeArgumentMatchingUtility.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/TypeArgumentMatchingUtility.java new file mode 100644 index 00000000000..c21052c49f1 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/core/search/matching/TypeArgumentMatchingUtility.java @@ -0,0 +1,261 @@ +/******************************************************************************* + * 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.core.search.matching; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.eclipse.jdt.core.Signature; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.internal.core.util.KeyToSignature; +import org.eclipse.jdt.internal.javac.dom.JavacTypeBinding; + +public class TypeArgumentMatchingUtility { + + public static boolean validateSingleTypeArgMatches(boolean requiresExactMatch, String patternSig, IBinding patternBinding, IBinding domBinding) { + return validateSingleTypeArgMatches(requiresExactMatch, patternSig, patternBinding, domBinding, null); + } + + public static boolean validateSingleTypeArgMatches(boolean requiresExactMatch, String patternSig, IBinding patternBinding, IBinding domBinding, PatternLocator locator) { + ITypeBinding domTypeBinding = domBinding instanceof ITypeBinding ? (ITypeBinding)domBinding : null; + String domKey1 = domBinding == null ? null : domBinding.getKey(); + String domSig = null; + if( isQuestionMark(domKey1)) { + domSig = domKey1; + } else if( domKey1 != null ) { + try { + KeyToSignature ks = new KeyToSignature(domKey1, KeyToSignature.SIGNATURE); + ks.parse(); + domSig = ks.toString(); + } catch(RuntimeException re ) { + // TODO ignore + } + } + if( domSig == null && domBinding != null ) { + domSig = domBinding instanceof JavacTypeBinding jctb ? jctb.getGenericTypeSignature(false) : domBinding.getKey(); + } + + if( requiresExactMatch ) { + if( Objects.equals(patternSig, domSig)) { + return true; + } + if( patternSig.equals("*") && isQuestionMark(domSig)) { + return true; + } + if( patternSig.startsWith("Q") || patternSig.startsWith("+Q") || patternSig.startsWith("-Q")) { + String patternSimpleName = null; + try { + patternSimpleName = Signature.getSignatureSimpleName(patternSig); + } catch(IllegalArgumentException iae) { + // Ignore + } + String bindingSimpleName = null; + try { + bindingSimpleName = Signature.getSignatureSimpleName(domSig); + } catch(IllegalArgumentException iae) { + // Ignore + } + if( Objects.equals(patternSimpleName, bindingSimpleName)) { + return true; + } + char[] patternCharArr = patternSimpleName == null ? null : patternSimpleName.toCharArray(); + char[] bindingCharArr = bindingSimpleName == null ? null : bindingSimpleName.toCharArray(); + if(locator != null && locator.matchesName(patternCharArr, bindingCharArr)) { + return true; + } + if( patternSimpleName != null && patternSimpleName.startsWith(bindingSimpleName + "<")) { + return true; + } + } + if( domBinding instanceof ITypeBinding tb ) { + ITypeBinding bound = tb.getBound(); + if( bound != null && bound != domBinding) { + return validateSingleTypeArgMatches(requiresExactMatch, patternSig, patternBinding, bound, locator); + } + } + return false; + } + + if( patternSig.equals(("*"))) + return true; + if( patternSig.equals(domSig)) { + return true; + } + + String patternKeyFromBinding = patternBinding == null ? null : patternBinding instanceof JavacTypeBinding jctb ? jctb.getGenericTypeSignature(false) : patternBinding.getKey(); + List patternAncestors = new ArrayList<>(); + if( patternBinding instanceof ITypeBinding patternTypeBinding) { + if( patternSig.startsWith("+") || patternSig.startsWith("-")) { + patternAncestors = findAllSuperclassAndInterfaceBindingsForWildcard(patternTypeBinding); + } else { + patternAncestors = findAllSuperclassAndInterfaceBindings(patternTypeBinding); + } + } + + List patternAncestorKeys = patternAncestors.stream().map(x -> x instanceof JavacTypeBinding jctb ? jctb.getGenericTypeSignature(false) : x.getKey()).collect(Collectors.toList()); + if( patternSig.startsWith("-")) { + if( domSig.startsWith("-") || !domSig.startsWith("+")) { + String domKey = domBinding instanceof JavacTypeBinding jctb ? jctb.getGenericTypeSignature(false) : domBinding.getKey(); + if( !patternAncestorKeys.contains(domKey)) { + return false; + } + } else if( domSig.startsWith("+") && !isQuestionMark(domSig)) { + return false; + } + } else if( patternSig.startsWith("+")) { + if( domSig.startsWith("-")) { + // There's no way ALL ancestors of dom can be a subclass of pattern unless pattern is java.lang.Object + return false; + } else { + List domHeirarchy = findAllSuperclassAndInterfaceBindingsForWildcard(domTypeBinding); + List domHeirarchyStrings = domHeirarchy.stream().map(x -> x instanceof JavacTypeBinding jctb ? jctb.getGenericTypeSignature(false) : x.getKey()).collect(Collectors.toList()); + if( patternKeyFromBinding != null ) { + if( !resolvedPatternMatchesDom(patternSig.substring(1), patternKeyFromBinding, domTypeBinding, domHeirarchyStrings)) { + return false; + } + } else { + if( !unresolvedPatternMatchesDom(patternSig, domSig, domTypeBinding, domHeirarchyStrings)) { + return false; + } + } + } + } else { + // pattern is a normal defined type, ex: Exception + if( domSig.startsWith("-")) { + List domHeirarchy = findAllSuperclassAndInterfaceBindingsForWildcard(domTypeBinding); + List domHeirarchyStrings = domHeirarchy.stream().map(x -> x instanceof JavacTypeBinding jctb ? jctb.getGenericTypeSignature(false) : x.getKey()).collect(Collectors.toList()); + if( patternKeyFromBinding == null && !domHeirarchyStrings.contains(patternKeyFromBinding)) + return false; + } else if( domSig.startsWith("+")) { + if( domTypeBinding != null ) { + ITypeBinding bound = domTypeBinding.getBound(); + String boundKey = bound == null ? null : bound instanceof JavacTypeBinding jctb ? jctb.getGenericTypeSignature(false) : bound.getKey(); + if( !isQuestionMark(domSig) && (boundKey == null || !patternAncestorKeys.contains(boundKey))) { + return false; + } + } + } else { + // Just two normal param types, see if they match + if( !patternSig.equals(domSig)) { + boolean unresolvedMatch = unresolvedPatternMatchesDom(patternSig, domSig, domTypeBinding, new ArrayList<>()); + if( unresolvedMatch ) { + return true; + } + if( locator != null && patternSig.startsWith("Q") && domSig != null) { + String patSig1 = patternSig.substring(1); + String dSig = domSig.lastIndexOf(".") == -1 ? null : domSig.substring(domSig.lastIndexOf(".") + 1); + if( dSig != null ) { + boolean stringMatch = locator.matchesName(patSig1.toCharArray(), dSig.toCharArray()); + if( stringMatch ) { + return true; + } + } + } + + return false; + } + } + } + return true; + } + + private static boolean resolvedPatternMatchesDom(String patternSig, String patternKeyFromBinding, ITypeBinding domTypeBinding, + List domHeirarchyStrings) { + String k = domTypeBinding instanceof JavacTypeBinding jctb ? jctb.getGenericTypeSignature(false) : domTypeBinding.getKey(); + if( isQuestionMark(k)) + return true; + if( domHeirarchyStrings.contains(patternSig)) + return true; + if( patternKeyFromBinding != null) { + if( domHeirarchyStrings.contains(patternKeyFromBinding)) + return true; + if( patternKeyFromBinding.startsWith("+") && domHeirarchyStrings.contains(patternKeyFromBinding.substring(1))) { + return true; + } + } + return false; + } + + private static boolean unresolvedPatternMatchesDom(String patternSig, String domSig, ITypeBinding domTypeBinding, + List domHeirarchyStrings) { + boolean patternSigIsUnresolved = false; + String patternSigTrimmed = null; + if( patternSig.startsWith("Q")) { + patternSigTrimmed = patternSig.substring(1); + patternSigIsUnresolved = true; + } else if( patternSig.startsWith("+Q")) { + patternSigTrimmed = patternSig.substring(2); + patternSigIsUnresolved = true; + } + if( patternSigIsUnresolved) { + // TODO this is insufficient + if( domSig.endsWith("." + patternSigTrimmed)) + return true; + } + + String patternSigWithoutPrefix = patternSig.startsWith("+") || patternSig.startsWith("-") ? patternSig.substring(1) : patternSig; + String domSigWithoutPrefix = domSig.startsWith("+") || domSig.startsWith("-") ? domSig.substring(1) : domSig; + String patternSig2 = patternSigWithoutPrefix.substring(1); + if( patternSig2.equals(domSigWithoutPrefix.substring(1))) + return true; + if( domSig.endsWith("." + patternSig2) ) + return true; + + return false; + } + private static boolean isQuestionMark(String k) { + // wut ? + boolean isQuestionMark = "+Ljava/lang/Object;".equals(k) || "+Ljava.lang.Object;".equals(k) || "+Qjava.lang.Object;".equals(k); + return isQuestionMark; + } + private static List findAllSuperclassAndInterfaceBindingsForWildcard(ITypeBinding binding) { + List ret = new ArrayList<>(); + if( binding == null ) + return ret; + // Sometimes we have a discovered binding which is already the bound... + ITypeBinding param = binding.isWildcardType() ? binding.getBound() : binding; + if( param != null ) { + ret.add(param); + fillAllSuperclassAndInterfaceBindings(param, ret); + } else { + // for pure `?` + ret.add(binding); + } + return ret; + } + private static List findAllSuperclassAndInterfaceBindings(ITypeBinding binding) { + List ret = new ArrayList<>(); + if( binding == null ) + return ret; + fillAllSuperclassAndInterfaceBindings(binding, ret); + return ret; + } + private static void fillAllSuperclassAndInterfaceBindings(ITypeBinding binding, List list) { + if( binding == null ) + return; + ITypeBinding[] ifaces = binding.getInterfaces(); + for( int q = 0; q < ifaces.length; q++ ) { + ITypeBinding oneInterface = ifaces[q]; + if( oneInterface != null ) { + list.add(oneInterface); + fillAllSuperclassAndInterfaceBindings(oneInterface, list); + } + } + ITypeBinding superClaz = binding.getSuperclass(); + if( superClaz != null ) { + list.add(superClaz); + fillAllSuperclassAndInterfaceBindings(superClaz, list); + } + } +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/javac/CachingClassSymbolClassReader.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/javac/CachingClassSymbolClassReader.java new file mode 100644 index 00000000000..1d1a6985a26 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/javac/CachingClassSymbolClassReader.java @@ -0,0 +1,799 @@ +/******************************************************************************* +* 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 static com.sun.tools.javac.code.Flags.ABSTRACT; +import static com.sun.tools.javac.code.Flags.BRIDGE; +import static com.sun.tools.javac.code.Flags.PUBLIC; +import static com.sun.tools.javac.code.Flags.STATIC; +import static com.sun.tools.javac.code.Flags.SYNTHETIC; +import static com.sun.tools.javac.code.Kinds.Kind.MTH; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Map.Entry; +import java.util.stream.StreamSupport; + +import javax.lang.model.element.ElementKind; +import javax.tools.JavaFileObject; + +import org.eclipse.core.runtime.ILog; + +import com.sun.tools.javac.code.Attribute; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.SymbolMetadata; +import com.sun.tools.javac.code.Symtab; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.TypeTag; +import com.sun.tools.javac.code.Types; +import com.sun.tools.javac.code.Attribute.Array; +import com.sun.tools.javac.code.Attribute.Compound; +import com.sun.tools.javac.code.Attribute.Constant; +import com.sun.tools.javac.code.Scope.WriteableScope; +import com.sun.tools.javac.code.Symbol.ClassSymbol; +import com.sun.tools.javac.code.Symbol.CompletionFailure; +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.TypeSymbol; +import com.sun.tools.javac.code.Symbol.TypeVariableSymbol; +import com.sun.tools.javac.code.Symbol.VarSymbol; +import com.sun.tools.javac.code.Type.ClassType; +import com.sun.tools.javac.code.Type.MethodType; +import com.sun.tools.javac.code.Type.TypeVar; +import com.sun.tools.javac.comp.Annotate; +import com.sun.tools.javac.comp.Annotate.AnnotationTypeCompleter; +import com.sun.tools.javac.comp.Annotate.AnnotationTypeMetadata; +import com.sun.tools.javac.file.PathFileObject; +import com.sun.tools.javac.jvm.ClassReader; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Convert; +import com.sun.tools.javac.util.Name; +import com.sun.tools.javac.util.Names; +import com.sun.tools.javac.util.Pair; + +/// This particular ClassReader keeps a "template" copy of the created symbols. So when asked again for reading a class +/// it will read in its cache and populate the symbols from the data is has in store (preventing from re-reading jars). +/// {@link Type} is stored as a signature. +/// For signatures and annotations, this requires access to various internal content. +public class CachingClassSymbolClassReader extends ClassReader { + + private static Map CACHE = Collections.synchronizedMap(new HashMap<>()); + + /// Allows to replace default strategy of ClassReader by one based on local templates + private static class StoringQueriesAnnotate extends Annotate { + + private final CachingClassSymbolClassReader reader; + private final List requested = new ArrayList<>(); + + public static void preRegister(Context context) { + context.put(annotateKey, (Context.Factory)c -> new StoringQueriesAnnotate(c)); + } + + protected StoringQueriesAnnotate(Context context) { + super(context); + this.reader = (CachingClassSymbolClassReader)ClassReader.instance(context); + } + + @Override + public void normal(Runnable r) { + super.normal(r); + AnnotationCompleterWrapper annotationCompleter = new AnnotationCompleterWrapper(r); + Symbol sym = annotationCompleter.annotationFor(); + if (sym != null) { + requested.add(annotationCompleter); + } + } + + public List annotationsFor(Symbol sym) { + return new ArrayList<>(requested).stream() // needs a copy to avoid Concurrent access + .filter(annCompleter -> annCompleter.annotationFor() == sym) + .map(annCompleter -> annCompleter.value(this.reader)) + .flatMap(List::stream) + .toList(); + } + + public void normal(Symbol sym, List toAnnotate) { + if (!toAnnotate.isEmpty()) { + this.normal(() -> { + var previousModule = reader.currentModule; + try { + reader.currentModule = findModule(sym); + com.sun.tools.javac.util.List newList = com.sun.tools.javac.util.List.from(toAnnotate.stream().map(template -> template.create(reader)).toList()); + if (sym.annotationsPendingCompletion()) { + sym.setDeclarationAttributes(newList); + } else { + sym.appendAttributes(newList); + } + } finally { + reader.currentModule = previousModule; + } + }); + } + } + } + + public static void preRegister(Context context) { + // this classReader requires the custom annotate + StoringQueriesAnnotate.preRegister(context); + context.put(classReaderKey, (Context.Factory)c -> new CachingClassSymbolClassReader(c)); + } + + private final Types localTypes; + private final Names localNames; + private final Symtab localSyms; + private final StoringQueriesAnnotate localAnnotate; + private final byte[] signatureBuffer = new byte[2000]; // big enough to handle any signature + private Method superSigToTypeMethod = null; + + protected CachingClassSymbolClassReader(Context context) { + super(context); + this.localTypes = Types.instance(context); + this.localNames = Names.instance(context); + this.localSyms = Symtab.instance(context); + this.localAnnotate = (StoringQueriesAnnotate)Annotate.instance(context); + // + try { + Field utf8ValidationField = ClassReader.class.getDeclaredField("utf8validation"); + utf8ValidationField.setAccessible(true); + utf8ValidationField.set(this, Convert.Validation.STRICT); + Field field = ClassReader.class.getDeclaredField("signatureBuffer"); + field.setAccessible(true); + field.set(this, signatureBuffer); // long enough to tolerate any type + superSigToTypeMethod = ClassReader.class.getDeclaredMethod("sigToType", byte[].class, int.class, int.class); + superSigToTypeMethod.setAccessible(true); + } catch (Exception ex) { + ILog.get().error(ex.getMessage(), ex); + } + } + + private static class VarSymbolTemplate { + private final long flags; + private final Name name; + private final int pos; + private final String typeSignature; + private final Object constantValue; + private boolean isDataExceptionParameter; + private boolean isDataResourceVariable; + private final SymbolMetadataTemplate metadata; + private List toAnnotate; + // + other data ? + + public VarSymbolTemplate(VarSymbol sym, CachingClassSymbolClassReader reader) { + this.flags = sym.flags_field; + this.name = sym.name; + this.pos = sym.pos; + this.typeSignature = reader.typeToSig(sym.type); + this.constantValue = sym.getConstantValue(); + this.isDataExceptionParameter = sym.isExceptionParameter(); + this.isDataResourceVariable = sym.isResourceVariable(); + this.metadata = new SymbolMetadataTemplate(sym.getMetadata(), reader); + this.toAnnotate = reader.localAnnotate.annotationsFor(sym); + } + + public VarSymbol create(Symbol owner, CachingClassSymbolClassReader reader) { + VarSymbol res = new VarSymbol(flags, name, reader.sigToType(typeSignature), owner); + res.pos = this.pos; + if (isDataExceptionParameter) { + res.setData(ElementKind.EXCEPTION_PARAMETER); + } else if (isDataResourceVariable) { + res.setData(ElementKind.RESOURCE_VARIABLE); + } else { + res.setData(this.constantValue); + } + this.metadata.applyTo(res, reader); + reader.localAnnotate.normal(res, this.toAnnotate); + return res; + } + } + + private static class TypeVariableTemplate { + private final long flags; + private final Name name; + private final String lowerBound; + private final String upperBound; + private final SymbolMetadataTemplate metadata; + private final List toAnnotate; + + public TypeVariableTemplate(TypeVariableSymbol base, CachingClassSymbolClassReader reader) { + this.flags = base.flags_field; + this.name = base.name; + this.lowerBound = base.type instanceof TypeVar typeVar && typeVar.getLowerBound() != null && typeVar.getLowerBound() != reader.localSyms.botType ? reader.typeToSig(typeVar.getLowerBound()) : null; + this.upperBound = base.type instanceof TypeVar typeVar && typeVar.getUpperBound() != null && typeVar.getUpperBound() != reader.localSyms.botType ? reader.typeToSig(typeVar.getUpperBound()) : null; + this.metadata = new SymbolMetadataTemplate(base.getMetadata(), reader); + this.toAnnotate = reader.localAnnotate.annotationsFor(base); + } + public TypeVariableSymbol create(Symbol owner, CachingClassSymbolClassReader reader) { + TypeVar tvar = new TypeVar(name, owner, this.lowerBound != null ? reader.sigToType(this.lowerBound) : reader.localSyms.botType); + tvar.tsym.flags_field = this.flags; + // this line needs to be before resolving upper bound as upper bound can reference this type + reader.typevars.enter(tvar.tsym); + if (this.upperBound != null) { + tvar.setUpperBound(reader.sigToType(this.upperBound)); + } + this.metadata.applyTo(tvar.tsym, reader); + reader.localAnnotate.normal(tvar.tsym, this.toAnnotate); + return (TypeVariableSymbol)tvar.tsym; + } + } + + private static class MethodSymbolTemplate { + private final long flags; + private final String name; + private final String methodTypeSignature; + private final List params; + private final List typeVariables; + private final SymbolMetadataTemplate metadata; + private final AttributeTemplate defaultValue; + private final List toAnnotate; + // + other data ? + + public MethodSymbolTemplate(MethodSymbol sym, CachingClassSymbolClassReader reader) { + this.flags = sym.flags_field; + this.name = sym.name.toString(); + this.params = sym.params().map(p -> new VarSymbolTemplate(p, reader)); + this.methodTypeSignature = reader.typeToSig(sym.type); + this.typeVariables = sym.getTypeParameters().map(t -> new TypeVariableTemplate(t, reader)); + this.metadata = new SymbolMetadataTemplate(sym.getMetadata(), reader); + this.defaultValue = AttributeTemplate.of(sym.getDefaultValue(), reader); + this.toAnnotate = reader.localAnnotate.annotationsFor(sym); + } + + public MethodSymbol create(TypeSymbol owner, CachingClassSymbolClassReader reader) { + reader.typevars = reader.typevars.dup(owner); + this.typeVariables.stream() + .map(template -> template.create(owner, reader)) + .forEach(reader.typevars::enter); + Type type = reader.sigToType(methodTypeSignature); + var res = new MethodSymbol(this.flags, reader.localNames.fromString(this.name), type, owner); + res.params = com.sun.tools.javac.util.List.from(this.params.stream().map(param -> param.create(res, reader)).toList()); + if (this.defaultValue != null) { + res.defaultValue = this.defaultValue.create(reader); + } + this.metadata.applyTo(owner, reader); + reader.localAnnotate.normal(res, this.toAnnotate); + reader.typevars = reader.typevars.leave(); + return res; + } + } + + private static class SymbolMetadataTemplate { + public SymbolMetadataTemplate(SymbolMetadata base, CachingClassSymbolClassReader reader) { + } + + public void applyTo(Symbol owner, CachingClassSymbolClassReader reader) { + } + } + + private static abstract class AttributeTemplate { + protected final String type; + protected AttributeTemplate(Type type, CachingClassSymbolClassReader reader) { + this.type = type != null && reader != null ? reader.typeToSig(type) : null; + } + protected AttributeTemplate(Attribute source, CachingClassSymbolClassReader reader) { + this(source != null ? source.type : null, reader); + } + public abstract T create(CachingClassSymbolClassReader reader); + + public static AttributeTemplate of(Attribute attribute, CachingClassSymbolClassReader reader) { + if (attribute == null) { + return null; + } + if (attribute instanceof Compound compound) { + return new CompoundTemplate(compound, reader); + } + if (attribute instanceof Constant constant) { + return new ConstantAttrTemplate(constant, reader); + } + if (attribute instanceof com.sun.tools.javac.code.Attribute.Class classAttr) { + return new ClassAttrTemplate(classAttr, reader); + } + if (attribute instanceof com.sun.tools.javac.code.Attribute.Enum enumAttr) { + return new EnumAttrTemplate(enumAttr, reader); + } + if (attribute instanceof Array arrayAttr) { + return new ArrayAttrTemplate(arrayAttr, reader); + } + // Some *Proxy support, using reflection 😟 + Class clazz = attribute.getClass(); + if (clazz.getName().equals(ClassReader.class.getName() + "$CompoundAnnotationProxy")) { + try { + Field valuesField = clazz.getDeclaredField("values"); + valuesField.setAccessible(true); + return new CompoundTemplate(attribute, (List>)valuesField.get(attribute), reader); + } catch (NoSuchFieldException | IllegalAccessException ex) { + ILog.get().error(ex.getMessage(), ex); + } + } + if (clazz.getName().equals(ClassReader.class.getName() + "$EnumAttributeProxy")) { + try { + Field enumType = clazz.getDeclaredField("enumType"); + enumType.setAccessible(true); + Type type = (Type)enumType.get(attribute); + Field enumValue = clazz.getDeclaredField("enumerator"); + enumValue.setAccessible(true); + String value = enumValue.get(attribute).toString(); + return new EnumAttrTemplate(type, value, reader); + } catch (NoSuchFieldException | IllegalAccessException ex) { + ILog.get().error(ex.getMessage(), ex); + } + } + if (clazz.getName().equals(ClassReader.class.getName() + "$ArrayAttributeProxy")) { + try { + Field valuesField = clazz.getDeclaredField("values"); + valuesField.setAccessible(true); + List values = (List)valuesField.get(attribute); + return new ArrayAttrTemplate(null, values, reader); + } catch (NoSuchFieldException | IllegalAccessException ex) { + ILog.get().error(ex.getMessage(), ex); + } + } + if (clazz.getName().equals(ClassReader.class.getName() + "$ClassAttributeProxy")) { + try { + Field classField = clazz.getDeclaredField("classType"); + classField.setAccessible(true); + Type c = (Type)classField.get(attribute); + return new ClassAttrTemplate(c, reader); + } catch (NoSuchFieldException | IllegalAccessException ex) { + ILog.get().error(ex.getMessage(), ex); + } + } + // not supported so far, just create a dummy + return new DummyAttributeTemplate(); + } + } + + private static class CompoundTemplate extends AttributeTemplate { + private final boolean synthetized; + private Map> values = new HashMap<>(); + + public CompoundTemplate(Compound target, CachingClassSymbolClassReader reader) { + super(target, reader); + this.synthetized = target.isSynthesized(); + target.getElementValues().forEach((method, attr) -> { + this.values.put(method.getSimpleName().toString(), AttributeTemplate.of(attr, reader)); + }); + } + + public CompoundTemplate(Attribute attr, List> list, CachingClassSymbolClassReader reader) { + super(attr, reader); + this.synthetized = false; + list.forEach(pair -> values.put(pair.fst.toString(), AttributeTemplate.of(pair.snd, reader))); + } + + @Override + public Compound create(CachingClassSymbolClassReader reader) { + com.sun.tools.javac.util.List> values = com.sun.tools.javac.util.List.nil(); + Type type = reader.sigToType(this.type); + for (Entry> entry : this.values.entrySet()) { + MethodSymbol method = findAccessMethod(type, reader.localNames.fromString(entry.getKey()), reader); + values = values.append(new Pair<>(method, entry.getValue().create(reader))); + } + var res = new Compound(reader.sigToType(this.type), values, null); + res.setSynthesized(this.synthetized); + return res; + } + + // copied from super ClassReader + private MethodSymbol findAccessMethod(Type container, Name name, CachingClassSymbolClassReader reader) { + CompletionFailure failure = null; + ModuleSymbol previousModule = reader.currentModule; + // accessing members may cause class to be loaded, and currentModule to change + try { + for (Symbol sym : container.tsym.members().getSymbolsByName(name)) { + if (sym.kind == MTH && sym.type.getParameterTypes().length() == 0) + return (MethodSymbol) sym; + } + } catch (CompletionFailure ex) { + failure = ex; + } finally { + reader.currentModule = previousModule; + } + // The method wasn't found: emit a warning and recover +// JavaFileObject prevSource = log.useSource(requestingOwner.classfile); +// try { +// if (lintClassfile) { +// if (failure == null) { +// log.warning(Warnings.AnnotationMethodNotFound(container, name)); +// } else { +// log.warning(Warnings.AnnotationMethodNotFoundReason(container, +// } +// } +// } finally { +// log.useSource(prevSource); +// } + // Construct a new method type and symbol. Use bottom + // type (typeof null) as return type because this type is + // a subtype of all reference types and can be converted + // to primitive types by unboxing. + MethodType mt = new MethodType( + com.sun.tools.javac.util.List.nil(), + reader.localSyms.botType, + com.sun.tools.javac.util.List.nil(), + reader.localSyms.methodClass); + return new MethodSymbol(PUBLIC | ABSTRACT, name, mt, container.tsym); + } + } + + private static class ConstantAttrTemplate extends AttributeTemplate { + private final Object value; + public ConstantAttrTemplate(Constant target, CachingClassSymbolClassReader reader) { + super(target, reader); + this.value = target.getValue(); + } + + @Override + public Constant create(CachingClassSymbolClassReader reader) { + return new Constant(reader.sigToType(this.type), value); + } + } + + private static class ClassAttrTemplate extends AttributeTemplate { + private final String classType; + public ClassAttrTemplate(Type classType, CachingClassSymbolClassReader reader) { + super((Type)null, reader); + this.classType = reader.typeToSig(classType); + } + public ClassAttrTemplate(com.sun.tools.javac.code.Attribute.Class target, CachingClassSymbolClassReader reader) { + super(target, reader); + this.classType = reader.typeToSig(target.getValue()); + } + + @Override + public com.sun.tools.javac.code.Attribute.Class create(CachingClassSymbolClassReader reader) { + return new com.sun.tools.javac.code.Attribute.Class(reader.localTypes, reader.sigToType(this.classType)); + } + } + + private static class EnumAttrTemplate extends AttributeTemplate { + private final String name; + public EnumAttrTemplate(Type enumType, String value, CachingClassSymbolClassReader reader) { + super(enumType, reader); + name = value; + } + public EnumAttrTemplate(com.sun.tools.javac.code.Attribute.Enum target, CachingClassSymbolClassReader reader) { + super(target, reader); + name = target.getValue().getSimpleName().toString(); + } + + @Override + public com.sun.tools.javac.code.Attribute.Enum create(CachingClassSymbolClassReader reader) { + Type enumType = reader.sigToType(this.type); + ModuleSymbol previousModule = reader.currentModule; + VarSymbol sym = (VarSymbol)enumType.tsym.members().findFirst(reader.localNames.fromString(this.name), s -> s instanceof VarSymbol vSym && vSym.isEnum()); + reader.currentModule = previousModule; + return new com.sun.tools.javac.code.Attribute.Enum(enumType, sym); + } + } + + private static class ArrayAttrTemplate extends AttributeTemplate { + private List> elements; + public ArrayAttrTemplate(Type type, List values, CachingClassSymbolClassReader reader) { + super(type, reader); + this.elements = values.stream().map(elt -> AttributeTemplate.of(elt, reader)).toList(); + } + public ArrayAttrTemplate(Array array, CachingClassSymbolClassReader reader) { + super(array, reader); + this.elements = array.getValue().stream().map(elt -> AttributeTemplate.of(elt, reader)).toList(); + } + @Override + public Array create(CachingClassSymbolClassReader reader) { + return new Array(reader.sigToType(this.type), this.elements.stream().map(elt -> elt.create(reader)).toArray(Attribute[]::new)); + } + } + + + private static class DummyAttributeTemplate extends AttributeTemplate { + public DummyAttributeTemplate() { + super((Attribute)null, null); + } + @Override + public Attribute create(CachingClassSymbolClassReader reader) { + return new Attribute(null) { + @Override + public void accept(Visitor arg0) { + // do nothing + } + }; + } + } + + private static class AnnotationTypeMetadataTemplate { + private final CompoundTemplate target; + private final CompoundTemplate repeatable; + + public AnnotationTypeMetadataTemplate(AnnotationTypeMetadata annotationTypeMetadata, CachingClassSymbolClassReader reader) { + this.target = annotationTypeMetadata.getTarget() != null ? new CompoundTemplate(annotationTypeMetadata.getTarget(), reader) : null; + this.repeatable = annotationTypeMetadata.getRepeatable() != null ? new CompoundTemplate(annotationTypeMetadata.getRepeatable(), reader) : null; + } + + public AnnotationTypeMetadata create(ClassSymbol c, CachingClassSymbolClassReader reader) { + return new AnnotationTypeMetadata(c, new AnnotationTypeCompleter() { + @Override + public void complete(ClassSymbol sym) throws CompletionFailure { + if (target != null) { + sym.getAnnotationTypeMetadata().setTarget(target.create(reader)); + } + if (repeatable != null) { + sym.getAnnotationTypeMetadata().setRepeatable(repeatable.create(reader)); + } + } + }); + } + + } + + private static class ClassSymbolTemplate { + + private final Instant creationTime; + private final long flags; + private final String superSymbol; + private final List interfaces; + private final List permitted; + private final List typeParams; + private final boolean isPermittedExplicit; + private final List members; + private final List innerTypes; + private final JavaFileObject classFile; + private final SymbolMetadataTemplate metadata; + private final AnnotationTypeMetadataTemplate annotationTypeMetadataTemplate; + private List toAnnotate; + + public ClassSymbolTemplate(ClassSymbol base, Instant creationTime, CachingClassSymbolClassReader reader) { + this.creationTime = creationTime; + this.classFile = base.classfile; + this.flags = base.flags_field; + this.superSymbol = reader.typeToSig(base.getSuperclass()); + this.interfaces = base.getInterfaces().map(reader::typeToSig); + this.permitted = base.getPermittedSubclasses().map(reader.localTypes::erasure).map(reader::typeToSig); + this.typeParams = base.getTypeParameters().map(t -> new TypeVariableTemplate(t, reader)); + this.isPermittedExplicit = base.isPermittedExplicit; + this.members = StreamSupport.stream(base.members().getSymbols().spliterator(), false).map(member -> + member instanceof VarSymbol varSymbol ? new VarSymbolTemplate(varSymbol, reader) : + member instanceof MethodSymbol methodSymbol ? new MethodSymbolTemplate(methodSymbol, reader) : + null).toList(); + this.innerTypes = StreamSupport.stream(base.members().getSymbols(ClassSymbol.class::isInstance).spliterator(), false) + .map(ClassSymbol.class::cast) + .map(ClassSymbol::getSimpleName) + .map(Name::toString) + .toList(); + this.metadata = new SymbolMetadataTemplate(base.getMetadata(), reader); + this.annotationTypeMetadataTemplate = base.isAnnotationType() ? + new AnnotationTypeMetadataTemplate(base.getAnnotationTypeMetadata(), reader) : + null; + this.toAnnotate = reader.localAnnotate.annotationsFor(base); + } + + public boolean isObsolete() { + return this.classFile.getLastModified() > this.creationTime.toEpochMilli(); + } + + public void applyTo(ClassSymbol target, CachingClassSymbolClassReader reader) { + if (target.classfile == null) { + target.classfile = this.classFile; + } + reader.currentOwner = target; + reader.currentClassFile = target.classfile; + + target.flags_field = this.flags; + reader.currentModule = findModule(target); + + target.isPermittedExplicit = this.isPermittedExplicit; + + ClassType ct = (ClassType)target.type; + // allocate scope for members + target.members_field = WriteableScope.create(target); + + // prepare type variable table + reader.typevars = reader.typevars.dup(reader.currentOwner); + if (ct.getEnclosingType().hasTag(TypeTag.CLASS)) + reader.enterTypevars(target.owner, ct.getEnclosingType()); + this.typeParams.stream() + .map(template -> template.create(target, reader)) + .forEach(reader.typevars::enter); + + // + members.stream().map(template -> toSymbol(template, target, reader)) + .filter(Symbol.class::isInstance) + .map(Symbol.class::cast) + .forEach(target.members_field::enter); + + ct.supertype_field = reader.sigToType(this.superSymbol); + ct.interfaces_field = com.sun.tools.javac.util.List.from(this.interfaces.stream() + .map(reader::sigToType) + .toArray(Type[]::new)); + ct.typarams_field = com.sun.tools.javac.util.List.from(this.typeParams.stream() + .map(t -> t.name) + .map(reader.typevars::findFirst) + .filter(Objects::nonNull) + .map(s -> s.type) + .toArray(Type[]::new)); + target.isPermittedExplicit = this.isPermittedExplicit; + this.permitted.stream() + .map(reader::sigToType) + .map(type -> type.tsym) + .filter(ClassSymbol.class::isInstance) + .map(ClassSymbol.class::cast) + .forEach(tsym -> target.addPermittedSubclass(tsym, 0)); + this.metadata.applyTo(target, reader); + if (target.isAnnotationType() && this.annotationTypeMetadataTemplate != null) { + target.setAnnotationTypeMetadata(this.annotationTypeMetadataTemplate.create(target, reader)); + } + for (String innerClass : this.innerTypes) { + // from ClassFinder.readInnerClasses` + ClassSymbol member = reader.enterClass(reader.localNames.fromString(innerClass), target); + if ((flags & STATIC) == 0) { + ((ClassType)member.type).setEnclosingType(target.type); + if (member.erasure_field != null) { + ((ClassType)member.erasure_field).setEnclosingType(reader.localTypes.erasure(target.type)); + } + } + // from super enterMembers + if ((member.flags_field & (SYNTHETIC|BRIDGE)) != SYNTHETIC || member.name.startsWith(reader.localNames.lambda)) + target.members().enter(member); + } + + reader.localAnnotate.normal(target, this.toAnnotate); + + reader.typevars = reader.typevars.leave(); + } + + private Object toSymbol(Object o, Symbol owner, CachingClassSymbolClassReader reader) { + if (o instanceof VarSymbolTemplate varTemplate) { + return varTemplate.create(owner, reader); + } + if (o instanceof MethodSymbolTemplate methodTemplate && owner instanceof TypeSymbol typeOwner) { + return methodTemplate.create(typeOwner, reader); + } + // other + return null; + } + } + + @Override + public void readClassFile(ClassSymbol c) { + if (c.classfile == null || + // currently not cache content from system library as same path can have different content according to context + c.classfile.getClass().getSimpleName().equals("JRTFileObject") || + c.classfile.getClass().getSimpleName().endsWith("SigJavaFileObject") || + (c.classfile instanceof PathFileObject pathFileObject && "JrtPath".equals(pathFileObject.getPath().getClass().getSimpleName())) || + // TODO support caching module-info too + Objects.equals(localNames.module_info, c.getSimpleName())) { + super.readClassFile(c); + } else { + ClassSymbolTemplate template = CACHE.get(c.classfile); + if (template == null || template.isObsolete()) { + Instant now = Instant.now(); // before actually reading the class file + super.readClassFile(c); + CACHE.put(c.classfile, new ClassSymbolTemplate(c, now, this)); + } else { + template.applyTo(c, this); + } + } + } + + public static ModuleSymbol findModule(Symbol target) { + Symbol moduleSymbol = target; + while (!(moduleSymbol instanceof ModuleSymbol) && !(moduleSymbol instanceof PackageSymbol) && moduleSymbol != null) { + moduleSymbol = moduleSymbol.owner; + } + if (moduleSymbol instanceof ModuleSymbol theModuleSymbol) { + return theModuleSymbol; + } + if (moduleSymbol instanceof PackageSymbol packageSymbol) { + return packageSymbol.modle; + } + return null; + } + + Type sigToType(String typeSignature) { + if (typeSignature == null || superSigToTypeMethod == null) { + return Type.noType; + } + try { + byte[] bytes = typeSignature.getBytes(); + return (Type)superSigToTypeMethod.invoke(this, bytes, 0, bytes.length); + } catch (Exception ex) { + ILog.get().error(ex.getMessage(), ex); + return null; + } + } + + String typeToSig(Type type) { + if (type == Type.noType) { + return null; + } + StringBuilder res = new StringBuilder(); + var generator = localTypes.new SignatureGenerator() { + @Override + protected void append(char ch) { + res.append(ch); + } + + @Override + protected void append(byte[] ba) { + res.append(new String(ba)); + } + + @Override + protected void append(Name name) { + res.append(name.toString()); + } + + // workaround to ensure exceptions are added + @Override + public boolean hasTypeVar(com.sun.tools.javac.util.List l) { + return l != null && !l.isEmpty(); + } + }; + generator.assembleSig(type); + return res.toString(); + } + + /// Workaround the fact that ClassReader.AnnotationCompleter is not visible + private static class AnnotationCompleterWrapper { + private static final Class ANNOTATION_COMPLETER_CLASS; + private static final Field SYM_FIELD; + private static final Field VALUE_FIELD; + static { + Class c = null; + Field sym = null; + Field value = null; + try { + c = ClassReader.class.getClassLoader().loadClass(ClassReader.class.getName() + "$AnnotationCompleter"); + sym = c.getDeclaredField("sym"); + sym.setAccessible(true); + value = c.getDeclaredField("l"); + value.setAccessible(true); + } catch (ClassNotFoundException | NoClassDefFoundError | NoSuchFieldException err) { + ILog.get().error(err.getMessage(), err); + } + ANNOTATION_COMPLETER_CLASS = c; + SYM_FIELD = sym; + VALUE_FIELD = value; + } + private final Object annotationCompleter; + public AnnotationCompleterWrapper(Object o) { + annotationCompleter = ANNOTATION_COMPLETER_CLASS.isInstance(o) ? o : null; + } + public Symbol annotationFor() { + if (this.annotationCompleter == null) { + return null; + } + try { + return (Symbol)SYM_FIELD.get(annotationCompleter); + } catch (IllegalArgumentException | IllegalAccessException e) { + ILog.get().error(e.getMessage(), e); + } + return null; + } + public List value(CachingClassSymbolClassReader reader) { + if (this.annotationCompleter == null) { + return List.of(); + } + try { + Object o = VALUE_FIELD.get(annotationCompleter); + if (o instanceof List attributes) { + return attributes.stream().map(Attribute.class::cast).map(attr -> AttributeTemplate.of(attr, reader)).map(CompoundTemplate.class::cast).toList(); + } + } catch (IllegalArgumentException | IllegalAccessException e) { + ILog.get().error(e.getMessage(), e); + } + return List.of(); + } + } +} 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..f45ead470ac --- /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.Context.Factory; +import com.sun.tools.javac.util.Log; +import com.sun.tools.javac.util.Options; + +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