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..5af9224736d
--- /dev/null
+++ b/.github/workflows/ci-dom-javac.yml
@@ -0,0 +1,53 @@
+name: Continuous Integration with DOM/Javac
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}-dom
+ cancel-in-progress: true
+
+on:
+ push:
+ branches: [ 'dom-based-operations', 'dom-with-javac' ]
+ pull_request:
+ branches: [ 'dom-based-operations', 'dom-with-javac' ]
+
+jobs:
+ build-dom-javac:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Install xmllint
+ shell: bash
+ run: |
+ sudo apt update
+ sudo apt install -y libxml2-utils
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ fetch-depth: 0
+ - name: Enable DOM-first and Javac
+ run: sed -i 's$$ -DCompilationUnit.DOM_BASED_OPERATIONS=true -DSourceIndexer.DOM_BASED_INDEXER=false -DICompilationUnitResolver=org.eclipse.jdt.core.dom.JavacCompilationUnitResolver -DAbstractImageBuilder.compiler=org.eclipse.jdt.internal.javac.JavacCompiler_$g' */pom.xml
+ - name: Set up JDKs ☕
+ uses: actions/setup-java@v4
+ with:
+ java-version: |
+ 8
+ 17
+ 21
+ mvn-toolchain-id: |
+ JavaSE-1.8
+ JavaSE-17
+ JavaSE-21
+ distribution: 'temurin'
+ - name: Set up Maven
+ uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5
+ with:
+ maven-version: 3.9.6
+ - name: Build with Maven 🏗️
+ run: |
+ mvn clean install --batch-mode -f org.eclipse.jdt.core.compiler.batch -DlocalEcjVersion=99.99
+ mvn -U clean verify --batch-mode --fail-at-end -Ptest-on-javase-21 -Pbree-libs -Papi-check -Djava.io.tmpdir=$WORKSPACE/tmp -Dproject.build.sourceEncoding=UTF-8 -Dtycho.surefire.argLine="--add-modules ALL-SYSTEM -Dcompliance=1.8,11,20 -Djdt.performance.asserts=disabled" -Dcbi-ecj-version=99.99
+ - name: Test Report
+ if: success() || failure() # run this step even if previous step failed
+ run: |
+ echo ▶️ TESTS RUN: $(xmllint --xpath 'string(/testsuite/@tests)' */target/surefire-reports/TEST-*.xml | awk '{n += $1}; END{print n}' -)
+ echo ❌ FAILURES: $(xmllint --xpath 'string(/testsuite/@failures)' */target/surefire-reports/TEST-*.xml | awk '{n += $1}; END{print n}' -)
+ echo 💥 ERRORS: $(xmllint --xpath 'string(/testsuite/@errors)' */target/surefire-reports/TEST-*.xml | awk '{n += $1}; END{print n}' -)
+ echo 🛑 SKIPPED: $(xmllint --xpath 'string(/testsuite/@skipped)' */target/surefire-reports/TEST-*.xml | awk '{n += $1}; END{print n}' -)
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 80219216a11..d269ed930d5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,4 +17,6 @@ Snap.*
/target/
# pomless
-.polyglot.*
\ No newline at end of file
+.polyglot.*
+
+.DS_Store
\ No newline at end of file
diff --git a/Jenkinsfile b/Jenkinsfile
index cce7d22dd19..50d33534ea8 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -10,10 +10,10 @@ pipeline {
}
tools {
maven 'apache-maven-latest'
- jdk 'openjdk-jdk21-latest'
+ jdk 'openjdk-jdk23-latest'
}
stages {
- stage('Build') {
+ stage('Build and Test') {
steps {
sh """#!/bin/bash -x
@@ -29,7 +29,8 @@ pipeline {
# export MAVEN_OPTS="-Xmx2G"
mvn clean install -f org.eclipse.jdt.core.compiler.batch -DlocalEcjVersion=99.99 -Dmaven.repo.local=$WORKSPACE/.m2/repository -DcompilerBaselineMode=disable -DcompilerBaselineReplace=none
-
+
+ # Build and test without DOM-first to ensure no regression takes place
mvn -U clean verify --batch-mode --fail-at-end -Dmaven.repo.local=$WORKSPACE/.m2/repository \
-Ptest-on-javase-23 -Pbree-libs -Papi-check -Pjavadoc -Pp2-repo \
-Dmaven.test.failure.ignore=true \
@@ -44,7 +45,6 @@ pipeline {
}
post {
always {
- archiveArtifacts artifacts: '*.log,*/target/work/data/.metadata/*.log,*/tests/target/work/data/.metadata/*.log,apiAnalyzer-workspace/.metadata/*.log,repository/target/repository/**,**/target/artifactcomparison/**', allowEmptyArchive: true
// The following lines use the newest build on master that did not fail a reference
// To not fail master build on failed test maven needs to be started with "-Dmaven.test.failure.ignore=true" it will then only marked unstable.
// To not fail the build also "unstable: true" is used to only mark the build unstable instead of failing when qualityGates are missed
@@ -54,10 +54,46 @@ pipeline {
// The eclipse compiler name is changed because the logfile not only contains ECJ but also API warnings.
// "pattern:" is used to collect warnings in dedicated files avoiding output of junit tests treated as warnings
junit '**/target/surefire-reports/*.xml'
- discoverGitReferenceBuild referenceJob: 'eclipse.jdt.core-github/master'
- recordIssues publishAllIssues:false, ignoreQualityGate:true, tool: eclipse(name: 'Compiler and API Tools', pattern: '**/target/compilelogs/*.xml'), qualityGates: [[threshold: 1, type: 'DELTA', unstable: true]]
+ //discoverGitReferenceBuild referenceJob: 'eclipse.jdt.core-github/master'
+ //recordIssues publishAllIssues:false, ignoreQualityGate:true, tool: eclipse(name: 'Compiler and API Tools', pattern: '**/target/compilelogs/*.xml'), qualityGates: [[threshold: 1, type: 'DELTA', unstable: true]]
+ }
+ }
+ }
+ stage('javac specific tests') {
+ steps {
+ sh """#!/bin/bash -x
+ mkdir -p $WORKSPACE/tmp
+
+ unset JAVA_TOOL_OPTIONS
+ unset _JAVA_OPTIONS
+ # force qualifier to start with `z` so we identify it more easily and it always seem more recent than upstrea
+ mvn install -DskipTests -Djava.io.tmpdir=$WORKSPACE/tmp \
+ -Dtycho.buildqualifier.format="'z'yyyyMMdd-HHmm" \
+ -Pp2-repo \
+ -pl org.eclipse.jdt.core.compiler.batch,org.eclipse.jdt.core,org.eclipse.jdt.core.javac,org.eclipse.jdt.core.tests.model,org.eclipse.jdt.core.tests.compiler,repository
+
+ mvn verify --batch-mode -f org.eclipse.jdt.core.tests.javac \
+ --fail-at-end -Ptest-on-javase-23 -Pbree-libs \
+ -DfailIfNoTests=false -DexcludedGroups=org.junit.Ignore -DproviderHint=junit47 \
+ -Papi-check -Djava.io.tmpdir=$WORKSPACE/tmp -Dproject.build.sourceEncoding=UTF-8 \
+ -Dmaven.test.failure.ignore=true -Dmaven.test.error.ignore=true
+"""
+ }
+ post {
+ always {
+ archiveArtifacts artifacts: '*.log,*/target/work/data/.metadata/*.log,*/tests/target/work/data/.metadata/*.log,apiAnalyzer-workspace/.metadata/*.log,repository/target/repository/**,**/target/artifactcomparison/**', allowEmptyArchive: true
+ junit 'org.eclipse.jdt.core.tests.javac/target/surefire-reports/*.xml'
}
}
}
+ stage('trigger JDT-LS with Javac build and tests') {
+ when {
+ branch 'dom-with-javac'
+ }
+ steps {
+ build(job: 'jdt-ls-javac', wait: false, propagate: false)
+ build(job: 'Build-JDT-with-Javac-p2-repo', wait: false, propagate: false)
+ }
+ }
}
}
diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/classfmt/ClassFileConstants.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/classfmt/ClassFileConstants.java
index 6f9495d5116..20bf090f052 100644
--- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/classfmt/ClassFileConstants.java
+++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/classfmt/ClassFileConstants.java
@@ -177,6 +177,10 @@ public interface ClassFileConstants {
long JDK22 = ((long)ClassFileConstants.MAJOR_VERSION_22 << 16) + ClassFileConstants.MINOR_VERSION_0;
long JDK23 = ((long)ClassFileConstants.MAJOR_VERSION_23 << 16) + ClassFileConstants.MINOR_VERSION_0;
+ /**
+ *
+ * @return The latest JDK level supported by ECJ (can be different from the latest known JDK level)
+ */
public static long getLatestJDKLevel() {
return ((long)ClassFileConstants.MAJOR_LATEST_VERSION << 16) + ClassFileConstants.MINOR_VERSION_0;
}
diff --git a/org.eclipse.jdt.core.javac/.classpath b/org.eclipse.jdt.core.javac/.classpath
new file mode 100644
index 00000000000..bdfae34cced
--- /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..9934205a305
--- /dev/null
+++ b/org.eclipse.jdt.core.javac/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,415 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=23
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=23
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.release=disabled
+org.eclipse.jdt.core.compiler.source=23
+org.eclipse.jdt.core.formatter.align_arrows_in_switch_on_columns=false
+org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false
+org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647
+org.eclipse.jdt.core.formatter.align_selector_in_method_invocation_on_expression_first_line=true
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns=false
+org.eclipse.jdt.core.formatter.align_with_spaces=false
+org.eclipse.jdt.core.formatter.alignment_for_additive_operator=16
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant=49
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field=49
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable=49
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method=49
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package=49
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter=0
+org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type=49
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assertion_message=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_loops=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain=0
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_arrow=16
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_colon=16
+org.eclipse.jdt.core.formatter.alignment_for_logical_operator=16
+org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
+org.eclipse.jdt.core.formatter.alignment_for_module_statements=16
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator=16
+org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_permitted_types_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_record_components=16
+org.eclipse.jdt.core.formatter.alignment_for_relational_operator=0
+org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_shift_operator=0
+org.eclipse.jdt.core.formatter.alignment_for_string_concatenation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_switch_case_with_arrow=20
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_type_annotations=0
+org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0
+org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0
+org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch=0
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case_after_arrow=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_record_constructor=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_record_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped=true
+org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=true
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=false
+org.eclipse.jdt.core.formatter.comment.indent_tag_description=false
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags=do not insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
+org.eclipse.jdt.core.formatter.comment.javadoc_do_not_separate_block_tags=false
+org.eclipse.jdt.core.formatter.comment.line_length=80
+org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
+org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
+org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
+org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=false
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_additive_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_permitted_types=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_after_logical_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_not_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_record_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_relational_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
+org.eclipse.jdt.core.formatter.insert_space_after_shift_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_additive_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case=insert
+org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_record_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_permitted_types=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_before_logical_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_constructor=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_record_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_relational_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_shift_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation=insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.join_line_comments=false
+org.eclipse.jdt.core.formatter.join_lines_in_comments=true
+org.eclipse.jdt.core.formatter.join_wrapped_lines=true
+org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_code_block_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_method_body_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_switch_body_block_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_switch_case_with_arrow_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line=one_line_never
+org.eclipse.jdt.core.formatter.lineSplit=120
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block=0
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block=0
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block=0
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.tabulation.char=tab
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.text_block_indentation=0
+org.eclipse.jdt.core.formatter.use_on_off_tags=true
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_additive_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false
+org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_logical_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
+org.eclipse.jdt.core.formatter.wrap_before_relational_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_shift_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_string_concatenation=true
+org.eclipse.jdt.core.formatter.wrap_before_switch_case_arrow_operator=false
+org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
+org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter
diff --git a/org.eclipse.jdt.core.javac/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jdt.core.javac/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 00000000000..cd01894ae74
--- /dev/null
+++ b/org.eclipse.jdt.core.javac/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,147 @@
+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=false
+sp_cleanup.one_if_rather_than_duplicate_blocks_that_fall_through=false
+sp_cleanup.operand_factorization=false
+sp_cleanup.organize_imports=true
+sp_cleanup.overridden_assignment=false
+sp_cleanup.overridden_assignment_move_decl=true
+sp_cleanup.plain_replacement=false
+sp_cleanup.precompile_regex=false
+sp_cleanup.primitive_comparison=false
+sp_cleanup.primitive_parsing=false
+sp_cleanup.primitive_rather_than_wrapper=false
+sp_cleanup.primitive_serialization=false
+sp_cleanup.pull_out_if_from_if_else=false
+sp_cleanup.pull_up_assignment=false
+sp_cleanup.push_down_negation=false
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.reduce_indentation=false
+sp_cleanup.redundant_comparator=false
+sp_cleanup.redundant_falling_through_block_end=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_modifiers=false
+sp_cleanup.remove_redundant_semicolons=false
+sp_cleanup.remove_redundant_type_arguments=false
+sp_cleanup.remove_trailing_whitespaces=false
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_array_creation=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unused_imports=false
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_method_parameters=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.replace_deprecated_calls=false
+sp_cleanup.return_expression=false
+sp_cleanup.simplify_lambda_expression_and_method_ref=false
+sp_cleanup.single_used_field=false
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.standard_comparison=false
+sp_cleanup.static_inner_class=false
+sp_cleanup.strictly_equal_or_different=false
+sp_cleanup.stringbuffer_to_stringbuilder=false
+sp_cleanup.stringbuilder=false
+sp_cleanup.stringbuilder_for_local_vars=true
+sp_cleanup.stringconcat_stringbuffer_stringbuilder=false
+sp_cleanup.stringconcat_to_textblock=false
+sp_cleanup.substring=false
+sp_cleanup.switch=false
+sp_cleanup.system_property=false
+sp_cleanup.system_property_boolean=false
+sp_cleanup.system_property_file_encoding=false
+sp_cleanup.system_property_file_separator=false
+sp_cleanup.system_property_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=true
+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..0745d90c2f9
--- /dev/null
+++ b/org.eclipse.jdt.core.javac/META-INF/MANIFEST.MF
@@ -0,0 +1,11 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Javac
+Bundle-SymbolicName: org.eclipse.jdt.core.javac;singleton:=true
+Bundle-Version: 1.0.0.qualifier
+Fragment-Host: org.eclipse.jdt.core
+Automatic-Module-Name: org.eclipse.jdt.core.javac
+Require-Capability: osgi.ee; filter:="(&(osgi.ee=JavaSE)(version=23))"
+Import-Package: org.eclipse.jdt.core.dom
+Export-Package: org.eclipse.jdt.internal.javac;x-friends:="org.eclipse.jdt.core.tests.javac",
+ org.eclipse.jdt.internal.javac.dom;x-friends:="org.eclipse.jdt.core.tests.javac"
diff --git a/org.eclipse.jdt.core.javac/META-INF/p2.inf b/org.eclipse.jdt.core.javac/META-INF/p2.inf
new file mode 100644
index 00000000000..83eda3a114a
--- /dev/null
+++ b/org.eclipse.jdt.core.javac/META-INF/p2.inf
@@ -0,0 +1,66 @@
+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\
+-DICompilationUnitResolver=org.eclipse.jdt.core.dom.JavacCompilationUnitResolver\n\
+-DAbstractImageBuilder.compilerFactory=org.eclipse.jdt.internal.javac.JavacCompilerFactory\n\
+-DCompilationUnit.DOM_BASED_OPERATIONS=true\n\
+-DCompilationUnit.codeComplete.DOM_BASED_OPERATIONS=true_\n\
+-DSourceIndexer.DOM_BASED_INDEXER=true);\
+
+instructions.unconfigure=\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(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\
+-DICompilationUnitResolver=org.eclipse.jdt.core.dom.JavacCompilationUnitResolver\n\
+-DAbstractImageBuilder.compilerFactory=org.eclipse.jdt.internal.javac.JavacCompilerFactory\n\
+-DCompilationUnit.DOM_BASED_OPERATIONS=true\n\
+-DCompilationUnit.codeComplete.DOM_BASED_OPERATIONS=true_\n\
+-DSourceIndexer.DOM_BASED_INDEXER=true);\
\ No newline at end of file
diff --git a/org.eclipse.jdt.core.javac/README.md b/org.eclipse.jdt.core.javac/README.md
new file mode 100644
index 00000000000..a4556d683f9
--- /dev/null
+++ b/org.eclipse.jdt.core.javac/README.md
@@ -0,0 +1,65 @@
+# JDT over Javac
+
+This branch is a work in progress experiment to leverage all higher-level JDT IDE features (DOM, IJavaElement, refactorings...) relying on Javac as underlying compiler/parser instead of ECJ.
+
+Why? Some background...
+* These days, with more frequent and more features Java releases, it's becoming hard for JDT to **cope with new Java features on time** and **facilitate support for upcoming/preview features before Java is released so JDT can participate to consolidation of the spec**. Over recent releases, JDT has failed at providing the features on time. This is mostly because of the difficulty of maintaining the Eclipse compiler: compilers are difficult bits of code to maintain and it takes a lot of time to implement things well in them. There is no clear sign the situation can improve here.
+* The Eclipse compiler has always suffered from occasional **inconsistencies with Javac** which end-users fail at understanding. Sometimes, ECJ is right, sometimes Javac is; but for end-users and for the ecosystem, Javac is the reference implementation and it's behavior is what they perceive as the actual specification
+* JDT has a very strong ecosystem (JDT-LS, plugins) a tons of nice features, so it seems profitable to **keep relying higher-level JDT APIs, such as model or DOM** to remain compatible with the ecosystem
+
+
+🎯 The technical proposal here mostly to **allow Javac to be used at the lowest-level of JDT**, under the hood, to populate higher-level models that are used in many operations; named the JDT DOM and IJavaElement models. It is expected that if we can create a good DOM and IJavaElement structure with another strategy (eg using Javac API), then all higher level operations will remain working as well without modification.
+
+▶️ **To test this**, you'll need to import the code of `org.eclipse.jdt.core` and `org.eclipse.jdt.core.javac` from this branch in your Eclipse workspace; and create a Launch Configuration of type "Eclipse Application" which does include the `org.eclipse.jdt.core` bundle. Go to _Arguments_ tab of this launch configuration, and add the following content to the _VM arguments_ list:
+
+> `-DCompilationUnit.DOM_BASED_OPERATIONS=true -DCompilationUnit.codeComplete.DOM_BASED_OPERATIONS=true -DSourceIndexer.DOM_BASED_INDEXER=true --add-opens jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED --add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED -DICompilationUnitResolver=org.eclipse.jdt.core.dom.JavacCompilationUnitResolver -DAbstractImageBuilder.compilerFactory=org.eclipse.jdt.internal.javac.JavacCompilerFactory`
+
+* `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 ...` 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.
+
+Note that 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.
+
+
+
+🥼 **This experiment** here currently mostly involves/support some IDE features thanks for the following design:
+* Refactoring ASTParser to allow delegating parsing/resolution to Javac instead of ECJ (system property `ICompilationUnitResolver` defines which parser to use). The Javac-based implementation is defined in the separate `org.eclipse.jdt.core.javac` fragment (so `org.eclipse.jdt.core` has no particular extra dependency on Javac by default) and consists mainly of
+ * orchestrating Javac via its API and use its output in...
+ * ...a converter from Javac diagnostics to JDT problems (then attached to the compilation unit)
+ * ...a converter from Javac to JDT DOM (partial) and
+ * ...a JavacBindingResolver relying on Javac "Symbol" to resolve types and references (partial)
+* Refactoring the Java Builder to allow using another compiler than ECJ, and provide a Javac-based implementation
+* Some methods of the higher-level JDT "IDE" model such as reconciling model with source, or `codeSelect` or populating the index can now process on top of a built DOM directly, without invoking ECJ to re-parse the source (`CompilationUnit.DOM_BASED_OPERATIONS` system property controls whether to parse with ECJ, or use DOM; `CompilationUnit.codeComplete.DOM_BASED_OPERATIONS` specifically controls the Code Completion strategy). It doesn't matter whether the DOM originates from Javac or ECJ conversion, both should lead to same output from those higher-level methods. So these changes are independent of Javac experiment, they're just providing an alternative "DOM-first" strategy for usual operations (where the only available strategy before was re-parsing/resolving with ECJ).
+
+
+🏗️ What works as a **proof of concept** with no strong design issue known/left, but still requires work to be generally usable:
+* about DOM consumption (plain JDT)
+ * Replace ECJ parser by DOM -> JDT model conversion (with binding) (estimated effort 💪💪💪)
+ * Complete DOM -> Index population (estimated effort 💪)
+ * More support completion based on DOM: filtering, priority, missing constructs (estimated effort 💪💪💪💪)
+ * Search (estimated effort 💪💪💪)
+* about DOM production (use Javac APIs to generate DOM)
+ * Complete Javac AST -> JDT DOM converter (estimated effort 💪💪)
+ * Complete Javac AST/Symbols -> IBinding resolver (estimated effort 💪💪💪)
+ * Map all Javac diagnostic types to JDT's IProblem (estimated effort 💪)
+ * Forward all JDT compilerOptions/project configuration to configure Javac execution -currently only source path/class path configured (estimated effort 💪💪)
+* .class generation with Javac instead of JDT during project build (estimated effort 💪💪)
+
+
+❓ What is known to be **not yet tried** to consider this experiment capable of getting on par with ECJ-based IDE:
+* Support for **annotation processing**, which hopefully will be mostly a matter of looping the `parse` and `attr` steps of compilation with annotation processors, before running (binding) resolver
+* Some **search** is still implemented using ECJ parser.
+* Consider using JavacTask like NetBeans or existing javac-ls to get more consistency and more benefits from using javac (need to ensure this doesn't create a new process each time)
+
+
+🤔 What are the potential concerns:
+* Currently, the AST is built more times than necessary, when we could just reuse the latest version.
+* **Memory cost** of retaining Javac contexts 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..1a5206be73a
--- /dev/null
+++ b/org.eclipse.jdt.core.javac/fragment.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
diff --git a/org.eclipse.jdt.core.javac/pom.xml b/org.eclipse.jdt.core.javac/pom.xml
new file mode 100644
index 00000000000..5f93050c744
--- /dev/null
+++ b/org.eclipse.jdt.core.javac/pom.xml
@@ -0,0 +1,56 @@
+
+
+
+ 4.0.0
+
+ eclipse.jdt.core
+ org.eclipse.jdt
+ 4.34.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.javadoc/jdk.javadoc.internal.doclets.formats.html.taglets.snippet=ALL-UNNAMED
+ --add-exports
+ jdk.javadoc/jdk.javadoc.internal.doclets.formats.html.taglets=ALL-UNNAMED
+
+
+
+
+
+
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..e1470d503b0
--- /dev/null
+++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacBindingResolver.java
@@ -0,0 +1,1628 @@
+/*******************************************************************************
+ * Copyright (c) 2023, Red Hat, Inc. and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.jdt.core.dom;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.type.TypeKind;
+
+import org.eclipse.core.runtime.ILog;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.WorkingCopyOwner;
+import org.eclipse.jdt.internal.javac.dom.JavacAnnotationBinding;
+import org.eclipse.jdt.internal.javac.dom.JavacErrorMethodBinding;
+import org.eclipse.jdt.internal.javac.dom.JavacLambdaBinding;
+import org.eclipse.jdt.internal.javac.dom.JavacMemberValuePairBinding;
+import org.eclipse.jdt.internal.javac.dom.JavacMethodBinding;
+import org.eclipse.jdt.internal.javac.dom.JavacModuleBinding;
+import org.eclipse.jdt.internal.javac.dom.JavacPackageBinding;
+import org.eclipse.jdt.internal.javac.dom.JavacTypeBinding;
+import org.eclipse.jdt.internal.javac.dom.JavacTypeVariableBinding;
+import org.eclipse.jdt.internal.javac.dom.JavacVariableBinding;
+
+import com.sun.source.tree.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.JavacTrees;
+import com.sun.tools.javac.code.Attribute;
+import com.sun.tools.javac.code.Attribute.Compound;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import com.sun.tools.javac.code.Symbol.ModuleSymbol;
+import com.sun.tools.javac.code.Symbol.PackageSymbol;
+import com.sun.tools.javac.code.Symbol.TypeSymbol;
+import com.sun.tools.javac.code.Symbol.TypeVariableSymbol;
+import com.sun.tools.javac.code.Symbol.VarSymbol;
+import com.sun.tools.javac.code.Symtab;
+import com.sun.tools.javac.code.Type.ArrayType;
+import com.sun.tools.javac.code.Type.ClassType;
+import com.sun.tools.javac.code.Type.ErrorType;
+import com.sun.tools.javac.code.Type.ForAll;
+import com.sun.tools.javac.code.Type.JCNoType;
+import com.sun.tools.javac.code.Type.JCPrimitiveType;
+import com.sun.tools.javac.code.Type.JCVoidType;
+import com.sun.tools.javac.code.Type.MethodType;
+import com.sun.tools.javac.code.Type.ModuleType;
+import com.sun.tools.javac.code.Type.PackageType;
+import com.sun.tools.javac.code.Type.TypeVar;
+import com.sun.tools.javac.code.TypeTag;
+import com.sun.tools.javac.code.Types;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.JCTree.JCAnnotatedType;
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree;
+import com.sun.tools.javac.tree.JCTree.JCClassDecl;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
+import com.sun.tools.javac.tree.JCTree.JCIdent;
+import com.sun.tools.javac.tree.JCTree.JCLambda;
+import com.sun.tools.javac.tree.JCTree.JCLiteral;
+import com.sun.tools.javac.tree.JCTree.JCMemberReference;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
+import com.sun.tools.javac.tree.JCTree.JCModuleDecl;
+import com.sun.tools.javac.tree.JCTree.JCNewArray;
+import com.sun.tools.javac.tree.JCTree.JCNewClass;
+import com.sun.tools.javac.tree.JCTree.JCPackageDecl;
+import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree;
+import com.sun.tools.javac.tree.JCTree.JCTypeApply;
+import com.sun.tools.javac.tree.JCTree.JCTypeCast;
+import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.tree.JCTree.JCWildcard;
+import com.sun.tools.javac.tree.TreeInfo;
+import com.sun.tools.javac.util.Context;
+
+/**
+ * Deals with creation of binding model, using the Symbol
s from Javac.
+ * @implNote Cannot move to another package because parent class is package visible only
+ */
+public class JavacBindingResolver extends BindingResolver {
+
+ private final JavacTask javac; // TODO evaluate memory cost of storing the instance
+ // it will probably be better to run the `Enter` and then only extract interesting
+ // date from it.
+ public final Context context;
+ public Map symbolToDeclaration;
+ public final IJavaProject javaProject;
+ private JavacConverter converter;
+ boolean isRecoveringBindings = false;
+
+ public static class BindingKeyException extends Exception {
+ private static final long serialVersionUID = -4468681148041117634L;
+ public BindingKeyException(Throwable t) {
+ super(t);
+ }
+ public BindingKeyException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
+ public class Bindings {
+ private Map annotationBindings = new HashMap<>();
+ public JavacAnnotationBinding getAnnotationBinding(Compound ann, IBinding recipient) {
+ JavacAnnotationBinding newInstance = new JavacAnnotationBinding(ann, JavacBindingResolver.this, recipient) { };
+ String k = newInstance.getKey();
+ if( k != null ) {
+ annotationBindings.putIfAbsent(k, newInstance);
+ return annotationBindings.get(k);
+ }
+ return null;
+ }
+ //
+ private Map memberValuePairBindings = new HashMap<>();
+ public JavacMemberValuePairBinding getMemberValuePairBinding(MethodSymbol key, Attribute value) {
+ JavacMemberValuePairBinding newInstance = new JavacMemberValuePairBinding(key, value, JavacBindingResolver.this) { };
+ String k = newInstance.getKey();
+ if( k != null ) {
+ memberValuePairBindings.putIfAbsent(k, newInstance);
+ return memberValuePairBindings.get(k);
+ }
+ return null;
+ }
+ //
+ private Map methodBindings = new HashMap<>();
+ public JavacMethodBinding getMethodBinding(MethodType methodType, MethodSymbol sym, com.sun.tools.javac.code.Type type,
+ boolean isSynthetic, boolean isDeclaration) {
+ if( isSynthetic ) {
+ return getSyntheticMethodBinding(methodType, sym, type);
+ } else {
+ return getMethodBinding(methodType, sym, type, isDeclaration);
+ }
+ }
+
+ public JavacMethodBinding getMethodBinding(MethodType methodType, MethodSymbol methodSymbol, com.sun.tools.javac.code.Type parentType, boolean isDeclaration) {
+ JavacMethodBinding newInstance = new JavacMethodBinding(methodType, methodSymbol, parentType, JavacBindingResolver.this, false, isDeclaration) { };
+ return insertAndReturn(newInstance);
+ }
+ public JavacMethodBinding getSyntheticMethodBinding(MethodType methodType, MethodSymbol methodSymbol, com.sun.tools.javac.code.Type parentType) {
+ JavacMethodBinding newInstance = new JavacMethodBinding(methodType, methodSymbol, parentType, JavacBindingResolver.this, true, false) { };
+ return insertAndReturn(newInstance);
+ }
+ public JavacMethodBinding getErrorMethodBinding(MethodType methodType, Symbol originatingSymbol) {
+ JavacMethodBinding newInstance = new JavacErrorMethodBinding(originatingSymbol, methodType, JavacBindingResolver.this) { };
+ return insertAndReturn(newInstance);
+ }
+ private JavacMethodBinding insertAndReturn(JavacMethodBinding newInstance) {
+ methodBindings.putIfAbsent(newInstance, newInstance);
+ return methodBindings.get(newInstance);
+ }
+ //
+ private Map moduleBindings = new HashMap<>();
+ public JavacModuleBinding getModuleBinding(ModuleType moduleType) {
+ JavacModuleBinding newInstance = new JavacModuleBinding(moduleType, JavacBindingResolver.this) { };
+ String k = newInstance.getKey();
+ if( k != null ) {
+ moduleBindings.putIfAbsent(k, newInstance);
+ return moduleBindings.get(k);
+ }
+ return null;
+ }
+ public JavacModuleBinding getModuleBinding(ModuleSymbol moduleSymbol) {
+ JavacModuleBinding newInstance = new JavacModuleBinding(moduleSymbol, JavacBindingResolver.this) { };
+ String k = newInstance.getKey();
+ if( k != null ) {
+ moduleBindings.putIfAbsent(k, newInstance);
+ return moduleBindings.get(k);
+ }
+ return null;
+ }
+ public JavacModuleBinding getModuleBinding(JCModuleDecl moduleDecl) {
+ JavacModuleBinding newInstance = new JavacModuleBinding(moduleDecl, JavacBindingResolver.this) { };
+ // Overwrite existing
+ String k = newInstance.getKey();
+ if( k != null ) {
+ moduleBindings.put(k, newInstance);
+ return moduleBindings.get(k);
+ }
+ return null;
+ }
+
+ //
+ private Map packageBindings = new HashMap<>();
+ public JavacPackageBinding getPackageBinding(PackageSymbol packageSymbol) {
+ JavacPackageBinding newInstance = new JavacPackageBinding(packageSymbol, JavacBindingResolver.this) { };
+ return preferentiallyInsertPackageBinding(newInstance);
+ }
+ public JavacPackageBinding getPackageBinding(Name name) {
+ String n = null;
+ if( name instanceof QualifiedName )
+ n = name.toString();
+ else if( name instanceof SimpleName snn) {
+ if( name.getParent() instanceof QualifiedName qn) {
+ if( qn.getName() == name ) {
+ n = qn.toString();
+ } else if( qn.getQualifier() == name) {
+ n = name.toString();
+ }
+ }
+ }
+ if( n == null )
+ return null;
+ JavacPackageBinding newInstance = new JavacPackageBinding(n, JavacBindingResolver.this) {};
+ return preferentiallyInsertPackageBinding(newInstance);
+ }
+ private JavacPackageBinding preferentiallyInsertPackageBinding(JavacPackageBinding newest) {
+ // A package binding may be created while traversing something as simple as a name.
+ // The binding using name-only logic should be instantiated, but
+ // when a proper symbol is found, it should be added to that object.
+ String k = newest == null ? null : newest.getKey();
+ if( k != null ) {
+ JavacPackageBinding current = packageBindings.get(k);
+ if( current == null ) {
+ packageBindings.putIfAbsent(k, newest);
+ } else if( current.getPackageSymbol() == null && newest.getPackageSymbol() != null) {
+ current.setPackageSymbol(newest.getPackageSymbol());
+ }
+ return packageBindings.get(k);
+ }
+ return null;
+ }
+ //
+ private Map typeBinding = new HashMap<>();
+ public JavacTypeBinding getTypeBinding(JCTree tree, com.sun.tools.javac.code.Type type) {
+ return getTypeBinding(type, tree instanceof JCClassDecl);
+ }
+ public JavacTypeBinding getTypeBinding(com.sun.tools.javac.code.Type type) {
+ if (type == null) {
+ return null;
+ }
+ return getTypeBinding(type.baseType() /* remove metadata for constant values */, false);
+ }
+ public JavacTypeBinding getTypeBinding(com.sun.tools.javac.code.Type type, boolean isDeclaration) {
+ if (type instanceof com.sun.tools.javac.code.Type.TypeVar typeVar) {
+ return getTypeVariableBinding(typeVar);
+ }
+ if (type == null || type == com.sun.tools.javac.code.Type.noType) {
+ return null;
+ }
+ if (type instanceof ErrorType errorType) {
+ var originalType = errorType.getOriginalType();
+ if (originalType != com.sun.tools.javac.code.Type.noType
+ && !(originalType instanceof com.sun.tools.javac.code.Type.MethodType)
+ && !(originalType instanceof com.sun.tools.javac.code.Type.ForAll)
+ && !(originalType instanceof com.sun.tools.javac.code.Type.ErrorType)) {
+ JavacTypeBinding newInstance = new JavacTypeBinding(originalType, type.tsym, isDeclaration, JavacBindingResolver.this) { };
+ typeBinding.putIfAbsent(newInstance, newInstance);
+ JavacTypeBinding jcb = typeBinding.get(newInstance);
+ jcb.setRecovered(true);
+ return jcb;
+ } else if (errorType.tsym instanceof ClassSymbol classErrorSymbol &&
+ Character.isJavaIdentifierStart(classErrorSymbol.getSimpleName().charAt(0))) {
+ // non usable original type: try symbol
+ JavacTypeBinding newInstance = new JavacTypeBinding(classErrorSymbol.type, classErrorSymbol, isDeclaration, JavacBindingResolver.this) { };
+ typeBinding.putIfAbsent(newInstance, newInstance);
+ JavacTypeBinding jcb = typeBinding.get(newInstance);
+ jcb.setRecovered(true);
+ return jcb;
+ }
+ // no type information we could recover from
+ return null;
+ }
+ if (!type.isParameterized() && !type.isRaw() && type instanceof ClassType classType
+ && classType.interfaces_field == null) {
+ // workaround faulty case of TypeMismatchQuickfixText.testMismatchingReturnTypeOnGenericMethod
+ // interfaces/supertypes are not set which seem to imply that the compiler generated
+ // a dummy type object that's not suitable for a binding.
+ // Fail back to an hopefully better type
+ type = type.tsym.type;
+ }
+ JavacTypeBinding newInstance = new JavacTypeBinding(type, type.tsym, isDeclaration, JavacBindingResolver.this) { };
+ typeBinding.putIfAbsent(newInstance, newInstance);
+ return typeBinding.get(newInstance);
+ }
+ //
+ private Map typeVariableBindings = new HashMap<>();
+ public JavacTypeVariableBinding getTypeVariableBinding(TypeVar typeVar) {
+ JavacTypeVariableBinding newInstance = new JavacTypeVariableBinding(typeVar, (TypeVariableSymbol)typeVar.tsym, JavacBindingResolver.this) { };
+ typeVariableBindings.putIfAbsent(newInstance, newInstance);
+ return typeVariableBindings.get(newInstance);
+ }
+ //
+ private Map variableBindings = new HashMap<>();
+ public JavacVariableBinding getVariableBinding(VarSymbol varSymbol) {
+ if (varSymbol == null) {
+ return null;
+ }
+ JavacVariableBinding newInstance = new JavacVariableBinding(varSymbol, JavacBindingResolver.this) { };
+ String k = newInstance.getKey();
+ if( k != null ) {
+ variableBindings.putIfAbsent(k, newInstance);
+ return variableBindings.get(k);
+ }
+ return null;
+ }
+ //
+ private Map lambdaBindings = new HashMap<>();
+ public JavacLambdaBinding getLambdaBinding(JavacMethodBinding javacMethodBinding, LambdaExpression lambda) {
+ JavacLambdaBinding newInstance = new JavacLambdaBinding(javacMethodBinding, lambda);
+ String k = newInstance.getKey();
+ if( k != null ) {
+ lambdaBindings.putIfAbsent(k, newInstance);
+ return lambdaBindings.get(k);
+ }
+ return null;
+ }
+
+ public IBinding getBinding(final Symbol owner, final com.sun.tools.javac.code.Type type) {
+ Symbol recoveredSymbol = getRecoveredSymbol(type);
+ if (recoveredSymbol != null) {
+ return getBinding(recoveredSymbol, recoveredSymbol.type);
+ }
+ if (type instanceof ErrorType) {
+ if (type.getOriginalType() instanceof MethodType missingMethodType) {
+ return getErrorMethodBinding(missingMethodType, owner);
+ }
+ }
+ if (owner instanceof final PackageSymbol other) {
+ return getPackageBinding(other);
+ } else if (owner instanceof ModuleSymbol typeSymbol) {
+ return getModuleBinding(typeSymbol);
+ } else if (owner instanceof Symbol.TypeVariableSymbol typeVariableSymbol) {
+ if (type instanceof TypeVar typeVar) {
+ return getTypeVariableBinding(typeVar);
+ } else if (typeVariableSymbol.type instanceof TypeVar typeVar) {
+ return getTypeVariableBinding(typeVar);
+ }
+ // without the type there is not much we can do; fallthrough to null
+ } else if (owner instanceof TypeSymbol typeSymbol) {
+ return getTypeBinding(isTypeOfType(type) ? type : typeSymbol.type);
+ } else if (owner instanceof final MethodSymbol other) {
+ return getMethodBinding(type instanceof com.sun.tools.javac.code.Type.MethodType methodType ? methodType : owner.type.asMethodType(), other, null, false);
+ } else if (owner instanceof final VarSymbol other) {
+ return getVariableBinding(other);
+ }
+ return null;
+ }
+ public IBinding getBinding(String key) {
+ IBinding binding;
+ binding = this.annotationBindings.get(key);
+ if (binding != null) {
+ return binding;
+ }
+ binding = this.memberValuePairBindings.get(key);
+ if (binding != null) {
+ return binding;
+ }
+ binding = this.methodBindings.values()
+ .stream()
+ .filter(methodBindings -> key.equals(methodBindings.getKey()))
+ .findAny()
+ .orElse(null);
+ if (binding != null) {
+ return binding;
+ }
+ binding = this.moduleBindings.get(key);
+ if (binding != null) {
+ return binding;
+ }
+ binding = this.packageBindings.get(key);
+ if (binding != null) {
+ return binding;
+ }
+ binding = this.typeBinding.values()
+ .stream()
+ .filter(typeBinding -> key.equals(typeBinding.getKey()))
+ .findAny()
+ .orElse(null);
+ if (binding != null) {
+ return binding;
+ }
+ return this.variableBindings.get(key);
+ }
+ }
+ public final Bindings bindings = new Bindings();
+ private WorkingCopyOwner owner;
+ private HashMap resolvedBindingsCache = new HashMap<>();
+
+ public JavacBindingResolver(IJavaProject javaProject, JavacTask javacTask, Context context, JavacConverter converter, WorkingCopyOwner owner) {
+ this.javac = javacTask;
+ this.context = context;
+ this.javaProject = javaProject;
+ this.converter = converter;
+ this.owner = owner;
+ }
+
+ private void resolve() {
+ if (this.symbolToDeclaration != null) {
+ // already done and ready
+ return;
+ }
+ synchronized (this.javac) { // prevents from multiple `analyze` for the same task
+ boolean alreadyAnalyzed = this.converter.domToJavac.values().stream().map(TreeInfo::symbolFor).anyMatch(Objects::nonNull);
+ if (!alreadyAnalyzed) {
+ // symbols not already present: analyze
+ try {
+ this.javac.analyze();
+ } catch (IOException e) {
+ ILog.get().error(e.getMessage(), e);
+ }
+ }
+ }
+ synchronized (this) {
+ if (this.symbolToDeclaration == null) {
+ Map wipSymbolToDeclaration = new HashMap<>();
+ this.converter.domToJavac.forEach((jdt, javac) -> {
+ // We don't want FieldDeclaration (ref ASTConverterTest2.test0433)
+ if (jdt instanceof MethodDeclaration ||
+ jdt instanceof VariableDeclaration ||
+ jdt instanceof EnumConstantDeclaration ||
+ jdt instanceof AnnotationTypeMemberDeclaration ||
+ jdt instanceof AbstractTypeDeclaration ||
+ jdt instanceof AnonymousClassDeclaration ||
+ jdt instanceof TypeParameter) {
+ var symbol = TreeInfo.symbolFor(javac);
+ if (symbol != null) {
+ wipSymbolToDeclaration.put(symbol, jdt);
+ }
+ }
+ });
+ // prefill the binding so that they're already searchable by key
+ wipSymbolToDeclaration.keySet().forEach(sym -> this.bindings.getBinding(sym, null));
+ this.symbolToDeclaration = wipSymbolToDeclaration;
+ }
+ }
+ }
+
+ @Override
+ public ASTNode findDeclaringNode(IBinding binding) {
+ return findNode(getJavacSymbol(binding));
+ }
+
+ @Override
+ public ASTNode findDeclaringNode(String bindingKey) {
+ resolve();
+ IBinding binding = this.bindings.getBinding(bindingKey);
+ if (binding == null) {
+ return null;
+ }
+ return findDeclaringNode(binding);
+ }
+
+ private Symbol getJavacSymbol(IBinding binding) {
+ if (binding instanceof JavacMemberValuePairBinding valuePair) {
+ return getJavacSymbol(valuePair.method);
+ }
+ if (binding instanceof JavacAnnotationBinding annotation) {
+ return getJavacSymbol(annotation.getAnnotationType());
+ }
+ if (binding instanceof JavacMethodBinding method) {
+ return method.methodSymbol;
+ }
+ if (binding instanceof JavacPackageBinding packageBinding) {
+ return packageBinding.getPackageSymbol();
+ }
+ if (binding instanceof JavacTypeBinding type) {
+ return type.typeSymbol;
+ }
+ if (binding instanceof JavacVariableBinding variable) {
+ return variable.variableSymbol;
+ }
+ return null;
+ }
+
+ public ASTNode findNode(Symbol symbol) {
+ if (this.symbolToDeclaration != null) {
+ return this.symbolToDeclaration.get(symbol);
+ }
+ return null;
+ }
+
+ @Override
+ public ITypeBinding resolveType(Type type) {
+ if (type.getParent() instanceof ParameterizedType parameterized
+ && type.getLocationInParent() == ParameterizedType.TYPE_PROPERTY) {
+ // use parent type for this as it keeps generics info
+ return resolveType(parameterized);
+ }
+ resolve();
+ if (type.getParent() instanceof ArrayCreation arrayCreation) {
+ JCTree jcArrayCreation = this.converter.domToJavac.get(arrayCreation);
+ return this.bindings.getTypeBinding(((JCNewArray)jcArrayCreation).type);
+ }
+ JCTree jcTree = this.converter.domToJavac.get(type);
+ if (jcTree instanceof JCIdent ident && ident.type != null) {
+ if (ident.type instanceof PackageType) {
+ return null;
+ }
+ return this.bindings.getTypeBinding(ident.type);
+ }
+ if (jcTree instanceof JCFieldAccess access) {
+ return this.bindings.getTypeBinding(access.type);
+ }
+ if (jcTree instanceof JCPrimitiveTypeTree primitive && primitive.type != null) {
+ return this.bindings.getTypeBinding(primitive.type);
+ }
+ if (jcTree instanceof JCArrayTypeTree arrayType && arrayType.type != null) {
+ return this.bindings.getTypeBinding(arrayType.type);
+ }
+ if (jcTree instanceof JCWildcard wcType && wcType.type != null) {
+ return this.bindings.getTypeBinding(wcType.type);
+ }
+ if (jcTree instanceof JCTypeApply jcta && jcta.type != null) {
+ var res = this.bindings.getTypeBinding(jcta.type);
+ if (res != null) {
+ return res;
+ }
+ if (jcta.getType().type instanceof ErrorType errorType) {
+ res = this.bindings.getTypeBinding(errorType.getOriginalType(), true);
+ if (res != null) {
+ return res;
+ }
+ }
+ if (jcta.getType().type != null) {
+ res = this.bindings.getTypeBinding(jcta.getType().type);
+ if (res != null) {
+ return res;
+ }
+ }
+ }
+ if (jcTree instanceof JCAnnotatedType annotated && annotated.type != null) {
+ return this.bindings.getTypeBinding(annotated.type);
+ }
+
+// return this.flowResult.stream().map(env -> env.enclClass)
+// .filter(Objects::nonNull)
+// .map(decl -> decl.type)
+// .map(javacType -> javacType.tsym)
+// .filter(sym -> Objects.equals(type.toString(), sym.name.toString()))
+// .findFirst()
+// .map(symbol -> new JavacTypeBinding(symbol, this))
+// .orElse(null);
+// }
+// if (type instanceof QualifiedType qualifiedType) {
+// JCTree jcTree = this.converter.domToJavac.get(qualifiedType);
+// }
+ if (type instanceof PrimitiveType primitive) { // a type can be requested even if there is no token for it in JCTree
+ return resolveWellKnownType(primitive.getPrimitiveTypeCode().toString());
+ }
+ if (type.getAST().apiLevel() >= AST.JLS10 && type.isVar()) {
+ if (type.getParent() instanceof VariableDeclaration varDecl) {
+ IVariableBinding varBinding = resolveVariable(varDecl);
+ if (varBinding != null) {
+ return varBinding.getType();
+ }
+ }
+ if (type.getParent() instanceof VariableDeclarationStatement statement &&
+ this.converter.domToJavac.get(statement) instanceof JCVariableDecl jcDecl &&
+ jcDecl.type != null) {
+ return this.bindings.getTypeBinding(jcDecl.type);
+ }
+ }
+ // Recovery: sometime with Javac, there is no suitable type/symbol
+ // Workaround: use a RecoveredTypeBinding
+ // Caveats: cascade to other workarounds
+ return createRecoveredTypeBinding(type);
+ }
+
+ private RecoveredTypeBinding createRecoveredTypeBinding(Type type) {
+ return new RecoveredTypeBinding(this, type) {
+ @Override
+ public ITypeBinding getTypeDeclaration() {
+ if (isParameterizedType()) {
+ return new GenericRecoveredTypeBinding(JavacBindingResolver.this, type, this);
+ }
+ return super.getTypeDeclaration();
+ }
+ @Override
+ public IPackageBinding getPackage() {
+ if (type instanceof SimpleType simpleType && simpleType.getName() instanceof SimpleName) {
+ return JavacBindingResolver.this.converter.domToJavac
+ .values()
+ .stream()
+ .filter(CompilationUnit.class::isInstance)
+ .map(CompilationUnit.class::cast)
+ .map(CompilationUnit::getPackage)
+ .map(PackageDeclaration::resolveBinding)
+ .findAny()
+ .orElse(super.getPackage());
+ }
+ return super.getPackage();
+ }
+ };
+ }
+
+ @Override
+ ITypeBinding resolveType(AnnotationTypeDeclaration type) {
+ resolve();
+ JCTree javacNode = this.converter.domToJavac.get(type);
+ if (javacNode instanceof JCClassDecl jcClassDecl && jcClassDecl.type != null) {
+ return this.bindings.getTypeBinding(jcClassDecl.type);
+ }
+ return null;
+ }
+
+ @Override
+ ITypeBinding resolveType(RecordDeclaration type) {
+ resolve();
+ JCTree javacNode = this.converter.domToJavac.get(type);
+ if (javacNode instanceof JCClassDecl jcClassDecl && jcClassDecl.type != null) {
+ return this.bindings.getTypeBinding(jcClassDecl.type);
+ }
+ return null;
+ }
+
+
+ @Override
+ ITypeBinding resolveType(TypeDeclaration type) {
+ resolve();
+ JCTree javacNode = this.converter.domToJavac.get(type);
+ if (javacNode instanceof JCClassDecl jcClassDecl && jcClassDecl.type != null) {
+ return this.bindings.getTypeBinding(jcClassDecl.type, true);
+ }
+ 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;
+ }
+ ITypeBinding resolveTypeParameter(TypeParameter typeParameter) {
+ resolve();
+ JCTree javacNode = this.converter.domToJavac.get(typeParameter);
+ if (javacNode instanceof JCTypeParameter jcClassDecl) {
+ return this.bindings.getTypeBinding(jcClassDecl.type);
+ }
+ return null;
+ }
+
+ @Override
+ IVariableBinding resolveField(FieldAccess fieldAccess) {
+ resolve();
+ JCTree javacElement = this.converter.domToJavac.get(fieldAccess);
+ if (javacElement instanceof JCFieldAccess javacFieldAccess && javacFieldAccess.sym instanceof VarSymbol varSymbol) {
+ return this.bindings.getVariableBinding(varSymbol);
+ }
+ return null;
+ }
+
+ @Override
+ IVariableBinding resolveField(SuperFieldAccess fieldAccess) {
+ resolve();
+ JCTree javacElement = this.converter.domToJavac.get(fieldAccess);
+ if (javacElement instanceof JCFieldAccess javacFieldAccess && javacFieldAccess.sym instanceof VarSymbol varSymbol) {
+ return this.bindings.getVariableBinding(varSymbol);
+ }
+ return null;
+ }
+
+ @Override
+ IMethodBinding resolveMethod(MethodInvocation method) {
+ resolve();
+ JCTree javacElement = this.converter.domToJavac.get(method);
+ List typeArgs = List.of();
+ if (javacElement instanceof JCMethodInvocation javacMethodInvocation) {
+ javacElement = javacMethodInvocation.getMethodSelect();
+ typeArgs = javacMethodInvocation.getTypeArguments().stream().map(jcExpr -> jcExpr.type).toList();
+ }
+ var type = javacElement.type;
+ // next condition matches `localMethod(this::missingMethod)`
+ if (javacElement instanceof JCIdent ident && type == null) {
+ ASTNode node = method;
+ while (node != null && !(node instanceof AbstractTypeDeclaration)) {
+ node = node.getParent();
+ }
+ if (node instanceof AbstractTypeDeclaration decl &&
+ this.converter.domToJavac.get(decl) instanceof JCClassDecl javacClassDecl &&
+ javacClassDecl.type instanceof ClassType classType &&
+ !classType.isErroneous()) {
+ type = classType;
+ }
+ if (type != null &&
+ type.tsym.members().findFirst(ident.getName(), MethodSymbol.class::isInstance) instanceof MethodSymbol methodSymbol &&
+ methodSymbol.type instanceof MethodType methodType) {
+ var res = this.bindings.getMethodBinding(methodType, methodSymbol, null, false);
+ if (res != null) {
+ return res;
+ }
+ }
+ }
+ var sym = javacElement instanceof JCIdent ident ? ident.sym :
+ javacElement instanceof JCFieldAccess fieldAccess ? fieldAccess.sym :
+ null;
+ if (type instanceof MethodType methodType && sym instanceof MethodSymbol methodSymbol) {
+ com.sun.tools.javac.code.Type parentType = null;
+ if (methodSymbol.owner instanceof ClassSymbol ownerClass && isTypeOfType(ownerClass.type)) {
+ if (ownerClass.type.isParameterized()
+ && method.getExpression() != null
+ && resolveExpressionType(method.getExpression()) instanceof JavacTypeBinding exprType) {
+ parentType = exprType.type;
+ } else {
+ parentType = ownerClass.type;
+ }
+ }
+ return this.bindings.getMethodBinding(methodType, methodSymbol, parentType, false);
+ }
+ if (type instanceof ErrorType errorType && errorType.getOriginalType() instanceof MethodType methodType) {
+ if (sym.owner instanceof TypeSymbol typeSymbol) {
+ Iterator methods = typeSymbol.members().getSymbolsByName(sym.getSimpleName(), m -> m instanceof MethodSymbol && methodType.equals(m.type)).iterator();
+ if (methods.hasNext()) {
+ return this.bindings.getMethodBinding(methodType, (MethodSymbol)methods.next(), null, false);
+ }
+ }
+ return this.bindings.getErrorMethodBinding(methodType, sym);
+ }
+ if (type == null && sym instanceof MethodSymbol methodSym && methodSym.type instanceof ForAll methodTemplateType) {
+ // build type from template
+ Map resolutionMapping = new HashMap<>();
+ var templateParameters = methodTemplateType.getTypeVariables();
+ for (int i = 0; i < typeArgs.size() && i < templateParameters.size(); i++) {
+ resolutionMapping.put(templateParameters.get(i), typeArgs.get(i));
+ }
+ MethodType methodType = new MethodType(
+ methodTemplateType.asMethodType().getParameterTypes().map(t -> applyType(t, resolutionMapping)),
+ applyType(methodTemplateType.asMethodType().getReturnType(), resolutionMapping),
+ methodTemplateType.asMethodType().getThrownTypes().map(t -> applyType(t, resolutionMapping)),
+ methodTemplateType.tsym);
+ return this.bindings.getMethodBinding(methodType, methodSym, methodSym.owner.type, false);
+ }
+ if (type == null && sym != null && sym.type.isErroneous()
+ && sym.owner.type instanceof ClassType classType) {
+ var parentTypeBinding = this.bindings.getTypeBinding(classType);
+ return Arrays.stream(parentTypeBinding.getDeclaredMethods())
+ .filter(binding -> binding.getName().equals(sym.getSimpleName().toString()))
+ .findAny()
+ .orElse(null);
+ }
+ if (type == null && sym instanceof MethodSymbol methodSymbol && methodSymbol.type instanceof MethodType
+ && javacElement instanceof JCFieldAccess selectedMethod
+ && selectedMethod.getExpression() != null
+ && selectedMethod.getExpression().type instanceof ClassType classType) {
+ // method is resolved, but type is not, probably because of invalid param
+ // workaround: check compatible method in selector
+ var parentTypeBinding = this.bindings.getTypeBinding(classType);
+ var res = Arrays.stream(parentTypeBinding.getDeclaredMethods())
+ .filter(binding -> binding instanceof JavacMethodBinding javacMethodBinding && javacMethodBinding.methodSymbol == methodSymbol)
+ .findAny()
+ .orElse(null);
+ if (res != null) {
+ return res;
+ }
+ }
+ 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) {
+ if (methodDecl.type != null) {
+ return this.bindings.getMethodBinding(methodDecl.type.asMethodType(), methodDecl.sym, null, true);
+ }
+ if (methodDecl.sym instanceof MethodSymbol methodSymbol && methodSymbol.type != null) {
+ return this.bindings.getMethodBinding(methodSymbol.type.asMethodType(), methodSymbol, null, true);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ IMethodBinding resolveMethod(LambdaExpression lambda) {
+ resolve();
+ JCTree javacElement = this.converter.domToJavac.get(lambda);
+ if (javacElement instanceof JCLambda jcLambda) {
+ JavacTypeBinding typeBinding = this.bindings.getTypeBinding(jcLambda.type);
+ if (typeBinding != null && typeBinding.getFunctionalInterfaceMethod() instanceof JavacMethodBinding methodBinding) {
+ return this.bindings.getLambdaBinding(methodBinding, lambda);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ IMethodBinding resolveMethod(MethodReference methodReference) {
+ resolve();
+ JCTree javacElement = this.converter.domToJavac.get(methodReference);
+ if (javacElement instanceof JCMemberReference memberRef && memberRef.sym instanceof MethodSymbol methodSymbol) {
+ return this.bindings.getMethodBinding(memberRef.referentType.asMethodType(), methodSymbol, null, false);
+ }
+ return null;
+ }
+
+ @Override
+ IMethodBinding resolveMember(AnnotationTypeMemberDeclaration member) {
+ resolve();
+ JCTree javacElement = this.converter.domToJavac.get(member);
+ if (javacElement instanceof JCMethodDecl methodDecl) {
+ return this.bindings.getMethodBinding(methodDecl.type.asMethodType(), methodDecl.sym, null, true);
+ }
+ return null;
+ }
+
+ @Override
+ IMethodBinding resolveConstructor(EnumConstantDeclaration enumConstantDeclaration) {
+ resolve();
+ JCTree javacElement = this.converter.domToJavac.get(enumConstantDeclaration);
+ if( javacElement instanceof JCVariableDecl jcvd ) {
+ javacElement = jcvd.init;
+ }
+ return javacElement instanceof JCNewClass jcExpr
+ && !jcExpr.constructor.type.isErroneous()?
+ this.bindings.getMethodBinding(jcExpr.constructor.type.asMethodType(), (MethodSymbol)jcExpr.constructor, null, true) :
+ null;
+ }
+
+ @Override
+ IMethodBinding resolveConstructor(SuperConstructorInvocation expression) {
+ resolve();
+ JCTree javacElement = this.converter.domToJavac.get(expression);
+ if (javacElement instanceof JCMethodInvocation javacMethodInvocation) {
+ javacElement = javacMethodInvocation.getMethodSelect();
+ }
+ if (javacElement instanceof JCIdent ident && ident.sym instanceof MethodSymbol methodSymbol) {
+ return this.bindings.getMethodBinding(ident.type != null ? ident.type.asMethodType() : methodSymbol.asType().asMethodType(), methodSymbol, null, false);
+ }
+ if (javacElement instanceof JCFieldAccess fieldAccess && fieldAccess.sym instanceof MethodSymbol methodSymbol) {
+ return this.bindings.getMethodBinding(fieldAccess.type.asMethodType(), methodSymbol, null, false);
+ }
+ return null;
+ }
+
+ @Override
+ IMethodBinding resolveMethod(SuperMethodInvocation method) {
+ resolve();
+ JCTree javacElement = this.converter.domToJavac.get(method);
+ if (javacElement instanceof JCMethodInvocation javacMethodInvocation) {
+ javacElement = javacMethodInvocation.getMethodSelect();
+ }
+ if (javacElement instanceof JCIdent ident && ident.sym instanceof MethodSymbol methodSymbol) {
+ return this.bindings.getMethodBinding(ident.type.asMethodType(), methodSymbol, null, false);
+ }
+ if (javacElement instanceof JCFieldAccess fieldAccess && fieldAccess.sym instanceof MethodSymbol methodSymbol
+ && fieldAccess.type != null /* when there are syntax errors */) {
+ return this.bindings.getMethodBinding(fieldAccess.type.asMethodType(), methodSymbol, null, false);
+ }
+ return null;
+ }
+
+ IBinding resolveCached(ASTNode node, Function l) {
+ // Avoid using `computeIfAbsent` because it throws
+ // ConcurrentModificationException when nesting calls
+ var res = resolvedBindingsCache.get(node);
+ if (res == null) {
+ res = l.apply(node);
+ resolvedBindingsCache.put(node, res);
+ }
+ return res;
+ }
+
+ @Override
+ IBinding resolveName(Name name) {
+ return resolveCached(name, (n) -> resolveNameImpl((Name)n));
+ }
+
+ private IBinding resolveNameImpl(Name name) {
+ resolve();
+ JCTree tree = this.converter.domToJavac.get(name);
+ if( tree != null ) {
+ var res = resolveNameToJavac(name, tree);
+ if (res != null) {
+ return res;
+ }
+ }
+ DocTreePath path = this.converter.findDocTreePath(name);
+ if (path != null) {
+ if (JavacTrees.instance(this.context).getElement(path) instanceof Symbol symbol) {
+ return this.bindings.getBinding(symbol, null);
+ }
+ }
+
+ PackageSymbol ps = findPackageSymbol(name);
+ if( ps != null ) {
+ return this.bindings.getPackageBinding(ps);
+ }
+ if( isPackageName(name)) {
+ return this.bindings.getPackageBinding(name);
+ }
+ 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 (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 ) {
+ IBinding ret = resolveNameToJavac(name, tree);
+ if (ret != null) {
+ return ret;
+ }
+ }
+ if (parent instanceof ImportDeclaration importDecl && importDecl.getName() == name) {
+ return resolveImport(importDecl);
+ }
+ if (parent instanceof QualifiedName parentName && parentName.getName() == name) {
+ return resolveNameImpl(parentName);
+ }
+ if( parent instanceof MethodRef mref && mref.getName() == name) {
+ return resolveReference(mref);
+ }
+ if( parent instanceof MemberRef mref && mref.getName() == name) {
+ return resolveReference(mref);
+ }
+ if (parent instanceof MethodInvocation methodInvocation && methodInvocation.getName() == name) {
+ return resolveMethod(methodInvocation);
+ }
+ if (parent instanceof MethodDeclaration methodDeclaration && methodDeclaration.getName() == name) {
+ return resolveMethod(methodDeclaration);
+ }
+ if (parent instanceof ExpressionMethodReference methodRef && methodRef.getName() == name) {
+ return resolveMethod(methodRef);
+ }
+ if (parent instanceof TypeMethodReference methodRef && methodRef.getName() == name) {
+ return resolveMethod(methodRef);
+ }
+ if (parent instanceof SuperMethodReference methodRef && methodRef.getName() == name) {
+ return resolveMethod(methodRef);
+ }
+ if (parent instanceof VariableDeclaration decl && decl.getName() == name) {
+ return resolveVariable(decl);
+ }
+ return null;
+ }
+
+ private boolean isPackageName(Name name) {
+ ASTNode working = name;
+ boolean insideQualifier = false;
+ while( working instanceof Name ) {
+ JCTree tree = this.converter.domToJavac.get(working);
+ if( tree instanceof JCFieldAccess jcfa) {
+ return jcfa.sym instanceof PackageSymbol;
+ }
+ if( working instanceof QualifiedName qnn) {
+ if( qnn.getQualifier() == working) {
+ insideQualifier = true;
+ }
+ }
+ working = working.getParent();
+ }
+ return insideQualifier;
+ }
+
+ private PackageSymbol findPackageSymbol(Name name) {
+ if( name instanceof SimpleName sn) {
+ ASTNode parent = sn.getParent();
+ if( parent instanceof QualifiedName qn) {
+ JCTree tree = this.converter.domToJavac.get(parent);
+ if( tree instanceof JCFieldAccess jcfa) {
+ if( qn.getQualifier().equals(name)) {
+ if( jcfa.selected instanceof JCIdent jcid && jcid.sym instanceof PackageSymbol pss)
+ return pss;
+ } else if( qn.getName().equals(name)) {
+ return jcfa.sym instanceof PackageSymbol pss ? pss : null;
+ }
+ }
+ }
+ }
+ if( name instanceof QualifiedName qn ) {
+ JCTree tree = this.converter.domToJavac.get(qn);
+ if( tree instanceof JCFieldAccess jcfa) {
+ return jcfa.sym instanceof PackageSymbol pss ? pss : null;
+ }
+ }
+ return null;
+ }
+
+ IBinding resolveNameToJavac(Name name, JCTree tree) {
+ boolean isTypeDeclaration = (name.getParent() instanceof AbstractTypeDeclaration typeDeclaration && typeDeclaration.getName() == name)
+ || (name.getParent() instanceof SimpleType type && type.getName() == name);
+ if( name.getParent() instanceof AnnotatableType st && st.getParent() instanceof ParameterizedType pt) {
+ if( st == pt.getType()) {
+ tree = this.converter.domToJavac.get(pt);
+ if (tree.type != null && !tree.type.isErroneous()) {
+ IBinding b = this.bindings.getTypeBinding(tree.type, isTypeDeclaration);
+ if( b != null ) {
+ return b;
+ }
+ }
+ }
+ }
+
+ if (tree instanceof JCIdent ident && ident.sym != null) {
+ if (ident.type instanceof ErrorType errorType
+ && errorType.getOriginalType() instanceof ErrorType) {
+ return null;
+ }
+ if (isTypeDeclaration) {
+ return this.bindings.getTypeBinding(ident.type != null ? ident.type : ident.sym.type, true);
+ }
+ return this.bindings.getBinding(ident.sym, ident.type != null ? ident.type : ident.sym.type);
+ }
+ if (tree instanceof JCTypeApply variableDecl && variableDecl.type != null) {
+ return this.bindings.getTypeBinding(variableDecl.type);
+ }
+ if (tree instanceof JCFieldAccess fieldAccess && fieldAccess.sym != null) {
+ com.sun.tools.javac.code.Type typeToUse = fieldAccess.type;
+ if(fieldAccess.selected instanceof JCTypeApply) {
+ typeToUse = fieldAccess.sym.type;
+ }
+ return this.bindings.getBinding(fieldAccess.sym, typeToUse);
+ }
+ 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 mtt) {
+ 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) {
+ return this.bindings.getVariableBinding(decl.sym);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public IPackageBinding resolvePackage(PackageDeclaration decl) {
+ resolve();
+ if (this.converter.domToJavac.get(decl) instanceof JCPackageDecl jcPackageDecl) {
+ return this.bindings.getPackageBinding(jcPackageDecl.packge);
+ }
+ return null;
+ }
+
+ @Override
+ public ITypeBinding resolveExpressionType(Expression expr) {
+ resolve();
+ if (expr instanceof SimpleName name) {
+ IBinding binding = resolveName(name);
+ // binding can be null when the code has syntax errors
+ if (binding == null || (binding.isRecovered() && !this.isRecoveringBindings)) {
+ return null;
+ }
+ switch (binding) {
+ case IVariableBinding variableBinding: return variableBinding.getType();
+ case ITypeBinding typeBinding: return typeBinding;
+ case IMethodBinding methodBinding: return methodBinding.getReturnType();
+ default:
+ return null;
+ }
+ }
+ var jcTree = this.converter.domToJavac.get(expr);
+ if (jcTree instanceof JCExpression expression
+ && isTypeOfType(expression.type)
+ && !expression.type.isErroneous()) {
+ var res = this.bindings.getTypeBinding(expression.type);
+ if (res != null) {
+ return res;
+ }
+ }
+ if (jcTree instanceof JCMethodInvocation javacMethodInvocation) {
+ if (javacMethodInvocation.meth.type instanceof MethodType methodType) {
+ return this.bindings.getTypeBinding(methodType.getReturnType());
+ } else if (javacMethodInvocation.meth.type instanceof ErrorType errorType) {
+ if (errorType.getOriginalType() instanceof MethodType methodType) {
+ return this.bindings.getTypeBinding(methodType.getReturnType());
+ }
+ }
+ return null;
+ }
+ if (jcTree instanceof JCNewClass newClass
+ && newClass.type != null
+ && Symtab.instance(this.context).errSymbol == newClass.type.tsym) {
+ jcTree = newClass.getIdentifier();
+ }
+ if (jcTree instanceof JCFieldAccess jcFieldAccess) {
+ if (jcFieldAccess.type instanceof PackageType) {
+ return null;
+ }
+ return this.bindings.getTypeBinding(jcFieldAccess.type.isErroneous() ? jcFieldAccess.sym.type : jcFieldAccess.type);
+ }
+ if (jcTree instanceof JCVariableDecl jcVariableDecl) {
+ if (jcVariableDecl.type != null) {
+ return this.bindings.getTypeBinding(jcVariableDecl.type);
+ } else {
+ return null;
+ }
+ }
+ if (jcTree instanceof JCTypeCast jcCast && jcCast.getType() != null) {
+ return this.bindings.getTypeBinding(jcCast.getType().type);
+ }
+ if (jcTree instanceof JCLiteral jcLiteral && jcLiteral.type.isErroneous()) {
+ if (jcLiteral.typetag == TypeTag.CLASS) {
+ return resolveWellKnownType("java.lang.String");
+ } else if (jcLiteral.typetag == TypeTag.BOT) {
+ return this.bindings.getTypeBinding(com.sun.tools.javac.code.Symtab.instance(this.context).botType);
+ }
+ return resolveWellKnownType(jcLiteral.typetag.name().toLowerCase());
+ }
+ if (jcTree instanceof JCExpression jcExpr) {
+ if (jcExpr.type instanceof PackageType) {
+ return null;
+ }
+ Symbol recoveredSymbol = getRecoveredSymbol(jcExpr.type);
+ if (recoveredSymbol != null) {
+ IBinding recoveredBinding = this.bindings.getBinding(recoveredSymbol, recoveredSymbol.type);
+ return switch (recoveredBinding) {
+ case IVariableBinding variableBinding -> variableBinding.getType();
+ case ITypeBinding typeBinding -> typeBinding;
+ case IMethodBinding methodBinding -> methodBinding.getReturnType();
+ default -> null;
+ };
+ }
+ if (jcExpr.type != null) {
+ var res = this.bindings.getTypeBinding(jcExpr.type);
+ if (res != null) {
+ return res;
+ }
+ }
+ // workaround Javac missing bindings in some cases
+ if (expr instanceof ClassInstanceCreation classInstanceCreation) {
+ return createRecoveredTypeBinding(classInstanceCreation.getType());
+ }
+ }
+ return null;
+ }
+
+ @Override
+ IMethodBinding resolveConstructor(ClassInstanceCreation expression) {
+ return (IMethodBinding)resolveCached(expression, (n) -> resolveConstructorImpl((ClassInstanceCreation)n));
+ }
+
+ /**
+ *
+ * @param t the type to check
+ * @return whether this is actually a type (returns
+ * {@code false} for things like {@link PackageType},
+ * {@link MethodType}...
+ */
+ public static boolean isTypeOfType(com.sun.tools.javac.code.Type t) {
+ return t == null ? false :
+ switch (t.getKind()) {
+ case PACKAGE, MODULE, EXECUTABLE, OTHER -> false;
+ default -> true;
+ };
+ }
+
+ private IMethodBinding resolveConstructorImpl(ClassInstanceCreation expression) {
+ resolve();
+ if (this.converter.domToJavac.get(expression) instanceof JCNewClass jcExpr) {
+ if (jcExpr.constructor != null && !jcExpr.constructor.type.isErroneous()) {
+ return this.bindings.getMethodBinding(jcExpr.constructor.type.asMethodType(), (MethodSymbol)jcExpr.constructor, jcExpr.type, false);
+ }
+ }
+ ITypeBinding type = resolveType(expression.getType());
+ if (type != null) {
+ List givenTypes = ((List)expression.arguments()).stream()
+ .map(this::resolveExpressionType)
+ .toList();
+ boolean hasTrailingNull;
+ boolean matchExactParamCount = false;
+ do {
+ hasTrailingNull = !givenTypes.isEmpty() && givenTypes.getLast() == null;
+ // try just checking by known args
+ // first filter by args count
+ var matchExactParamCountFinal = matchExactParamCount;
+ var finalGivenTypes = givenTypes;
+ var candidates = Arrays.stream(type.getDeclaredMethods())
+ .filter(IMethodBinding::isConstructor)
+ .filter(other -> matchExactParamCountFinal ? other.getParameterTypes().length == finalGivenTypes.size() : other.getParameterTypes().length >= finalGivenTypes.size())
+ .toList();
+ if (candidates.size() == 1) {
+ return candidates.get(0);
+ }
+ if (candidates.size() > 1 && expression.arguments().size() > 0) {
+ // then try filtering by arg types
+ var typeFilteredCandidates = candidates.stream()
+ .filter(other -> matchTypes(finalGivenTypes, other.getParameterTypes()))
+ .toList();
+ if (typeFilteredCandidates.size() == 1) {
+ return typeFilteredCandidates.get(0);
+ }
+ }
+ if (hasTrailingNull) {
+ givenTypes = givenTypes.subList(0, givenTypes.size() - 1);
+ matchExactParamCount = true;
+ }
+ } while (hasTrailingNull);
+ }
+ return null;
+ }
+
+ private boolean matchTypes(List givenTypes, ITypeBinding[] expectedTypes) {
+ for (int i = 0; i < Math.min(givenTypes.size(), expectedTypes.length); i++) {
+ ITypeBinding givenType = givenTypes.get(i);
+ ITypeBinding expectedType = expectedTypes[i];
+ if (givenType != null) {
+ if (!givenType.isAssignmentCompatible(expectedType)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ @Override
+ IMethodBinding resolveConstructor(ConstructorInvocation invocation) {
+ return (IMethodBinding)resolveCached(invocation, (n) -> resolveConstructorImpl((ConstructorInvocation)n));
+ }
+
+ private IMethodBinding resolveConstructorImpl(ConstructorInvocation invocation) {
+ resolve();
+ JCTree javacElement = this.converter.domToJavac.get(invocation);
+ if (javacElement instanceof JCMethodInvocation javacMethodInvocation) {
+ javacElement = javacMethodInvocation.getMethodSelect();
+ }
+ if (javacElement instanceof JCIdent ident && ident.sym instanceof MethodSymbol methodSymbol) {
+ return this.bindings.getMethodBinding(ident.type != null && ident.type.getKind() == TypeKind.EXECUTABLE ? ident.type.asMethodType() : methodSymbol.type.asMethodType(), methodSymbol, null, false);
+ }
+ if (javacElement instanceof JCFieldAccess fieldAccess && fieldAccess.sym instanceof MethodSymbol methodSymbol) {
+ return this.bindings.getMethodBinding(fieldAccess.type.asMethodType(), methodSymbol, null, false);
+ }
+ return null;
+ }
+
+ public Types getTypes() {
+ return Types.instance(this.context);
+ }
+
+ private java.util.List getTypeArguments(final Name name) {
+ if (name.getParent() instanceof SimpleType simpleType) {
+ return getTypeArguments(simpleType);
+ }
+ if (name.getParent() instanceof MethodInvocation methodInvocation && name == methodInvocation.getName()) {
+ return getTypeArguments(methodInvocation);
+ }
+ return null;
+ }
+
+ private java.util.List getTypeArguments(final Type type) {
+ if (type instanceof SimpleType simpleType
+ && simpleType.getParent() instanceof ParameterizedType paramType
+ && paramType.getType() == simpleType) {
+ java.util.List typeArguments = paramType.typeArguments();
+
+ if (typeArguments == null) {
+ return null;
+ }
+ return typeArguments.stream() //
+ .map(a -> {
+ JCTree tree = this.converter.domToJavac.get(a);
+ if (tree == null) {
+ return null;
+ }
+ if (tree instanceof JCIdent ident && ident.sym instanceof TypeSymbol typeSymbol) {
+ return typeSymbol;
+ }
+ if (tree instanceof JCFieldAccess access && access.sym instanceof TypeSymbol typeSymbol) {
+ return typeSymbol;
+ }
+ return null;
+ }) //
+ .collect(Collectors.toList());
+
+ }
+ return null;
+ }
+
+ private java.util.List getTypeArguments(final MethodInvocation methodInvocation) {
+ java.util.List typeArguments = methodInvocation.typeArguments();
+ if (typeArguments == null) {
+ return null;
+ }
+ return typeArguments.stream() //
+ .map(a -> {
+ JCTree tree = this.converter.domToJavac.get(a);
+ if (tree == null) {
+ return null;
+ }
+ if (tree instanceof JCIdent ident && ident.sym instanceof TypeSymbol typeSymbol) {
+ return typeSymbol;
+ }
+ if (tree instanceof JCFieldAccess access && access.sym instanceof TypeSymbol typeSymbol) {
+ return typeSymbol;
+ }
+ return null;
+ }) //
+ .collect(Collectors.toList());
+ }
+
+ IModuleBinding resolveModule(ModuleDeclaration module) {
+ return (IModuleBinding)resolveCached(module, (n) -> resolveModuleImpl((ModuleDeclaration)n));
+ }
+
+ private IBinding resolveModuleImpl(ModuleDeclaration module) {
+ resolve();
+ JCTree javacElement = this.converter.domToJavac.get(module);
+ if( javacElement instanceof JCModuleDecl jcmd) {
+ Object o = jcmd.sym.type;
+ if( o instanceof ModuleType mt ) {
+ return this.bindings.getModuleBinding(mt);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the constant value or the binding that a Javac attribute represents.
+ *
+ * See a detailed explanation of the returned value: {@link org.eclipse.jdt.core.dom.IMethodBinding#getDefaultValue()}
+ *
+ * @param attribute the javac attribute
+ * @return the constant value or the binding that a Javac attribute represents
+ */
+ public Object getValueFromAttribute(Attribute attribute) {
+ if (attribute == null) {
+ return null;
+ }
+ if (attribute instanceof Attribute.Constant constant) {
+ return constant.value;
+ } else if (attribute instanceof Attribute.Class clazz) {
+ return this.bindings.getTypeBinding(clazz.classType);
+ } else if (attribute instanceof Attribute.Enum enumm) {
+ return this.bindings.getVariableBinding(enumm.value);
+ } else if (attribute instanceof Attribute.Array array) {
+ return Stream.of(array.values) //
+ .map(nestedAttr -> {
+ if (nestedAttr instanceof Attribute.Constant constant) {
+ return constant.value;
+ } else if (nestedAttr instanceof Attribute.Class clazz) {
+ return this.bindings.getTypeBinding(clazz.classType);
+ } else if (nestedAttr instanceof Attribute.Enum enumerable) {
+ return this.bindings.getVariableBinding(enumerable.value);
+ }
+ throw new IllegalArgumentException("Unexpected attribute type: " + nestedAttr.getClass().getCanonicalName());
+ }) //
+ .toArray(Object[]::new);
+ }
+ throw new IllegalArgumentException("Unexpected attribute type: " + attribute.getClass().getCanonicalName());
+ }
+
+ @Override
+ IBinding resolveImport(ImportDeclaration importDeclaration) {
+ return resolveCached(importDeclaration, (n) -> resolveImportImpl((ImportDeclaration)n));
+ }
+
+ private IBinding resolveImportImpl(ImportDeclaration importDeclaration) {
+ var javac = this.converter.domToJavac.get(importDeclaration.getName());
+ if (javac instanceof JCFieldAccess fieldAccess) {
+ if (fieldAccess.sym != null) {
+ return this.bindings.getBinding(fieldAccess.sym, null);
+ }
+ if (importDeclaration.isStatic()) {
+ com.sun.tools.javac.code.Type type = fieldAccess.getExpression().type;
+ if (type != null) {
+ IBinding binding = Arrays.stream(this.bindings.getTypeBinding(type).getDeclaredMethods())
+ .filter(method -> Objects.equals(fieldAccess.getIdentifier().toString(), method.getName()))
+ .findAny()
+ .orElse(null);
+ if (binding == null) {
+ binding = Arrays.stream(this.bindings.getTypeBinding(type).getDeclaredFields()).filter(
+ field -> Objects.equals(fieldAccess.getIdentifier().toString(), field.getName()))
+ .findAny().orElse(null);
+ }
+ return binding;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public ITypeBinding resolveWellKnownType(String typeName) {
+ resolve(); // could be skipped, but this method is used by ReconcileWorkingCopyOperation to generate errors
+ com.sun.tools.javac.code.Symtab symtab = com.sun.tools.javac.code.Symtab.instance(this.context);
+ com.sun.tools.javac.code.Type type = switch (typeName) {
+ case "byte", "java.lang.Byte" -> symtab.byteType;
+ case "char", "java.lang.Character" -> symtab.charType;
+ case "double", "java.lang.Double" -> symtab.doubleType;
+ case "float", "java.lang.Float" -> symtab.floatType;
+ case "int", "java.lang.Integer" -> symtab.intType;
+ case "long", "java.lang.Long" -> symtab.longType;
+ case "short", "java.lang.Short" -> symtab.shortType;
+ case "boolean", "java.lang.Boolean" -> symtab.booleanType;
+ case "void", "java.lang.Void" -> symtab.voidType;
+ case "java.lang.Object" -> symtab.objectType;
+ case "java.lang.String" -> symtab.stringType;
+ case "java.lang.StringBuffer" -> symtab.stringBufferType;
+ case "java.lang.Throwable" -> symtab.throwableType;
+ case "java.lang.Exception" -> symtab.exceptionType;
+ case "java.lang.RuntimeException" -> symtab.runtimeExceptionType;
+ case "java.lang.Error" -> symtab.errorType;
+ case "java.lang.Class" -> symtab.classType;
+ case "java.lang.Cloneable" -> symtab.cloneableType;
+ case "java.io.Serializable" -> symtab.serializableType;
+ default -> null;
+ };
+ if (type == null) {
+ return null;
+ }
+ return this.bindings.getTypeBinding(type, true);
+ }
+
+ @Override
+ IAnnotationBinding resolveAnnotation(Annotation annotation) {
+ return (IAnnotationBinding)resolveCached(annotation, (n) -> resolveAnnotationImpl((Annotation)n));
+ }
+
+ IAnnotationBinding resolveAnnotationImpl(Annotation annotation) {
+ resolve();
+ IBinding recipient = null;
+ if (annotation.getParent() instanceof AnnotatableType annotatable) {
+ recipient = annotatable.resolveBinding();
+ } else if (annotation.getParent() instanceof FieldDeclaration fieldDeclaration) {
+ recipient = ((VariableDeclarationFragment)fieldDeclaration.fragments().get(0)).resolveBinding();
+ }
+ var javac = this.converter.domToJavac.get(annotation);
+ if (javac instanceof JCAnnotation jcAnnotation) {
+ return this.bindings.getAnnotationBinding(jcAnnotation.attribute, recipient);
+ }
+ 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;
+ }
+
+ @Override
+ IBinding resolveReference(MemberRef ref) {
+ return resolveCached(ref, (n) -> resolveReferenceImpl((MemberRef)n));
+ }
+
+ private IBinding resolveReferenceImpl(MemberRef ref) {
+ resolve();
+ DocTreePath path = this.converter.findDocTreePath(ref);
+ if (path != null && JavacTrees.instance(this.context).getElement(path) instanceof Symbol symbol) {
+ return this.bindings.getBinding(symbol, null);
+ }
+ return null;
+ }
+ private static Symbol getRecoveredSymbol(com.sun.tools.javac.code.Type type) {
+ if (type instanceof ErrorType) {
+ try {
+ Field candidateSymbolField = type.getClass().getField("candidateSymbol");
+ candidateSymbolField.setAccessible(true);
+
+ Object symbolFieldValue = candidateSymbolField.get(type);
+ if (symbolFieldValue instanceof Symbol symbol) {
+ return symbol;
+ }
+ } catch (NoSuchFieldException | IllegalAccessException unused) {
+ // fall through to null
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public WorkingCopyOwner getWorkingCopyOwner() {
+ return this.owner;
+ }
+
+ @Override
+ Object resolveConstantExpressionValue(Expression expression) {
+ JCTree jcTree = this.converter.domToJavac.get(expression);
+ if (jcTree instanceof JCLiteral literal) {
+ return literal.getValue();
+ }
+ return TreeInfo.symbolFor(jcTree) instanceof VarSymbol varSymbol ? varSymbol.getConstantValue() : null;
+ }
+}
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..719d3c3b22a
--- /dev/null
+++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacCompilationUnitResolver.java
@@ -0,0 +1,1018 @@
+/*******************************************************************************
+ * 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.Diagnostic.Kind;
+import javax.tools.DiagnosticListener;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.ILog;
+import org.eclipse.core.runtime.IProduct;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.Signature;
+import org.eclipse.jdt.core.WorkingCopyOwner;
+import org.eclipse.jdt.core.compiler.CategorizedProblem;
+import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.core.compiler.IProblem;
+import org.eclipse.jdt.core.compiler.InvalidInputException;
+import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.env.AccessRestriction;
+import org.eclipse.jdt.internal.compiler.env.IBinaryType;
+import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
+import org.eclipse.jdt.internal.compiler.env.ISourceType;
+import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
+import org.eclipse.jdt.internal.compiler.impl.ITypeRequestor;
+import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
+import org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
+import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
+import org.eclipse.jdt.internal.compiler.util.Util;
+import org.eclipse.jdt.internal.core.CancelableNameEnvironment;
+import org.eclipse.jdt.internal.core.JavaModelManager;
+import org.eclipse.jdt.internal.core.JavaProject;
+import org.eclipse.jdt.internal.core.dom.ICompilationUnitResolver;
+import org.eclipse.jdt.internal.core.util.BindingKeyParser;
+import org.eclipse.jdt.internal.javac.JavacProblemConverter;
+import org.eclipse.jdt.internal.javac.JavacUtils;
+import org.eclipse.jdt.internal.javac.UnusedProblemFactory;
+import org.eclipse.jdt.internal.javac.UnusedTreeScanner;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.JavacTask;
+import com.sun.source.util.TaskEvent;
+import com.sun.source.util.TaskListener;
+import com.sun.tools.javac.api.JavacTool;
+import com.sun.tools.javac.api.MultiTaskListener;
+import com.sun.tools.javac.code.Symbol.PackageSymbol;
+import com.sun.tools.javac.comp.CompileStates.CompileState;
+import com.sun.tools.javac.file.JavacFileManager;
+import com.sun.tools.javac.main.Option;
+import com.sun.tools.javac.parser.JavadocTokenizer;
+import com.sun.tools.javac.parser.Scanner;
+import com.sun.tools.javac.parser.ScannerFactory;
+import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle;
+import com.sun.tools.javac.parser.Tokens.TokenKind;
+import com.sun.tools.javac.tree.JCTree.JCClassDecl;
+import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.DiagnosticSource;
+import com.sun.tools.javac.util.Options;
+
+/**
+ * Allows to create and resolve DOM ASTs using Javac
+ * @implNote Cannot move to another package because parent class is package visible only
+ */
+public class JavacCompilationUnitResolver implements ICompilationUnitResolver {
+ public JavacCompilationUnitResolver() {
+ // 0-arg constructor
+ }
+ private interface GenericRequestor {
+ public void acceptBinding(String bindingKey, IBinding binding);
+ }
+ private List createSourceUnitList(String[] sourceFilePaths, String[] encodings) {
+ // make list of source unit
+ int length = sourceFilePaths.length;
+ List sourceUnitList = new ArrayList<>(length);
+ for (int i = 0; i < length; i++) {
+ org.eclipse.jdt.internal.compiler.env.ICompilationUnit obj = createSourceUnit(sourceFilePaths[i], encodings[i]);
+ if( obj != null )
+ sourceUnitList.add(obj);
+ }
+ return sourceUnitList;
+ }
+
+ private org.eclipse.jdt.internal.compiler.env.ICompilationUnit createSourceUnit(String sourceFilePath, String encoding) {
+ char[] contents = null;
+ try {
+ contents = Util.getFileCharContent(new File(sourceFilePath), encoding);
+ } catch(IOException e) {
+ return null;
+ }
+ if (contents == null) {
+ return null;
+ }
+ return new org.eclipse.jdt.internal.compiler.batch.CompilationUnit(contents, sourceFilePath, encoding);
+ }
+
+
+ @Override
+ public void resolve(String[] sourceFilePaths, String[] encodings, String[] bindingKeys, FileASTRequestor requestor,
+ int apiLevel, Map compilerOptions, List classpaths, int flags,
+ IProgressMonitor monitor) {
+ List sourceUnitList = createSourceUnitList(sourceFilePaths, encodings);
+ JavacBindingResolver bindingResolver = null;
+
+ // parse source units
+ Map res =
+ parse(sourceUnitList.toArray(org.eclipse.jdt.internal.compiler.env.ICompilationUnit[]::new), apiLevel, compilerOptions, true, flags, (IJavaProject)null, null, -1, monitor);
+
+ for (var entry : res.entrySet()) {
+ CompilationUnit cu = entry.getValue();
+ requestor.acceptAST(new String(entry.getKey().getFileName()), cu);
+ if (bindingResolver == null && (JavacBindingResolver)cu.ast.getBindingResolver() != null) {
+ bindingResolver = (JavacBindingResolver)cu.ast.getBindingResolver();
+ }
+ }
+
+ resolveRequestedBindingKeys(bindingResolver, bindingKeys,
+ (a,b) -> requestor.acceptBinding(a,b),
+ classpaths.stream().toArray(Classpath[]::new),
+ new CompilerOptions(compilerOptions),
+ res.values(), null, new HashMap<>(), monitor);
+ }
+
+ @Override
+ public void resolve(ICompilationUnit[] compilationUnits, String[] bindingKeys, ASTRequestor requestor, int apiLevel,
+ Map compilerOptions, IJavaProject project, WorkingCopyOwner workingCopyOwner, int flags,
+ IProgressMonitor monitor) {
+ Map units = parse(compilationUnits, apiLevel, compilerOptions, true, flags, workingCopyOwner, monitor);
+ if (requestor != null) {
+ final JavacBindingResolver[] bindingResolver = new JavacBindingResolver[1];
+ bindingResolver[0] = null;
+
+ final Map bindingMap = new HashMap<>();
+ {
+ INameEnvironment environment = null;
+ if (project instanceof JavaProject javaProject) {
+ try {
+ environment = new CancelableNameEnvironment(javaProject, workingCopyOwner, monitor);
+ } catch (JavaModelException e) {
+ // fall through
+ }
+ }
+ if (environment == null) {
+ environment = new NameEnvironmentWithProgress(new Classpath[0], null, monitor);
+ }
+ LookupEnvironment lu = new LookupEnvironment(new ITypeRequestor() {
+
+ @Override
+ public void accept(IBinaryType binaryType, PackageBinding packageBinding,
+ AccessRestriction accessRestriction) {
+ // do nothing
+ }
+
+ @Override
+ public void accept(org.eclipse.jdt.internal.compiler.env.ICompilationUnit unit,
+ AccessRestriction accessRestriction) {
+ // do nothing
+ }
+
+ @Override
+ public void accept(ISourceType[] sourceType, PackageBinding packageBinding,
+ AccessRestriction accessRestriction) {
+ // do nothing
+ }
+
+ }, new CompilerOptions(compilerOptions), null, environment);
+ requestor.additionalBindingResolver = javacAdditionalBindingCreator(bindingMap, environment, lu, bindingResolver);
+ }
+
+ units.forEach((a,b) -> {
+ if (bindingResolver[0] == null && (JavacBindingResolver)b.ast.getBindingResolver() != null) {
+ bindingResolver[0] = (JavacBindingResolver)b.ast.getBindingResolver();
+ }
+ requestor.acceptAST(a,b);
+ resolveBindings(b, bindingMap, apiLevel);
+ });
+
+ resolveRequestedBindingKeys(bindingResolver[0], bindingKeys,
+ (a,b) -> requestor.acceptBinding(a,b),
+ new Classpath[0], // TODO need some classpaths
+ new CompilerOptions(compilerOptions),
+ units.values(), project, bindingMap, monitor);
+ } else {
+ Iterator it = units.values().iterator();
+ while(it.hasNext()) {
+ resolveBindings(it.next(), apiLevel);
+ }
+ }
+ }
+
+ private void resolveRequestedBindingKeys(JavacBindingResolver bindingResolver, String[] bindingKeys, GenericRequestor requestor,
+ Classpath[] cp,CompilerOptions opts,
+ Collection units,
+ IJavaProject project,
+ Map bindingMap,
+ IProgressMonitor monitor) {
+ if (bindingResolver == null) {
+ var compiler = ToolProvider.getSystemJavaCompiler();
+ var context = new Context();
+ JavacTask task = (JavacTask) compiler.getTask(null, null, null, List.of(), List.of(), List.of());
+ bindingResolver = new JavacBindingResolver(null, task, context, new JavacConverter(null, null, context, null, true, -1), null);
+ }
+
+ for (CompilationUnit cu : units) {
+ cu.accept(new BindingBuilder(bindingMap));
+ }
+
+ INameEnvironment environment = null;
+ if (project instanceof JavaProject javaProject) {
+ try {
+ environment = new CancelableNameEnvironment(javaProject, null, monitor);
+ } catch (JavaModelException e) {
+ // do nothing
+ }
+ }
+ if (environment == null) {
+ environment = new NameEnvironmentWithProgress(cp, null, monitor);
+ }
+
+ LookupEnvironment lu = new LookupEnvironment(new ITypeRequestor() {
+
+ @Override
+ public void accept(IBinaryType binaryType, PackageBinding packageBinding,
+ AccessRestriction accessRestriction) {
+ // do nothing
+ }
+
+ @Override
+ public void accept(org.eclipse.jdt.internal.compiler.env.ICompilationUnit unit,
+ AccessRestriction accessRestriction) {
+ // do nothing
+ }
+
+ @Override
+ public void accept(ISourceType[] sourceType, PackageBinding packageBinding,
+ AccessRestriction accessRestriction) {
+ // do nothing
+ }
+
+ }, opts, null, environment);
+
+ // resolve the requested bindings
+ for (String bindingKey : bindingKeys) {
+
+ int arrayCount = Signature.getArrayCount(bindingKey);
+ IBinding bindingFromMap = bindingMap.get(bindingKey);
+ if (bindingFromMap != null) {
+ // from parsed files
+ requestor.acceptBinding(bindingKey, bindingFromMap);
+ } else {
+
+ if (arrayCount > 0) {
+ String elementKey = Signature.getElementType(bindingKey);
+ IBinding elementBinding = bindingMap.get(elementKey);
+ if (elementBinding instanceof ITypeBinding elementTypeBinding) {
+ requestor.acceptBinding(bindingKey, elementTypeBinding.createArrayType(arrayCount));
+ continue;
+ }
+ }
+
+ CustomBindingKeyParser bkp = new CustomBindingKeyParser(bindingKey);
+ bkp.parse(true);
+ char[][] name = bkp.compoundName;
+
+// // from ECJ
+// char[] charArrayFQN = Signature.toCharArray(bindingKey.toCharArray());
+// char[][] twoDimensionalCharArrayFQN = Stream.of(new String(charArrayFQN).split("/")) //
+// .map(myString -> myString.toCharArray()) //
+// .toArray(char[][]::new);
+// char[][] twoDimensionalCharArrayFQN = new char[][] {};
+ NameEnvironmentAnswer answer = environment.findType(name);
+ if( answer != null ) {
+ IBinaryType binaryType = answer.getBinaryType();
+ if (binaryType != null) {
+ BinaryTypeBinding binding = lu.cacheBinaryType(binaryType, null);
+ 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) {
+ res.get(in).setTypeRoot(in);
+ }
+ return res;
+ }
+ // build individually
+ Map res = new HashMap<>(compilationUnits.length, 1.f);
+ for (ICompilationUnit in : compilationUnits) {
+ if (in instanceof org.eclipse.jdt.internal.compiler.env.ICompilationUnit compilerUnit) {
+ res.put(in, parse(new org.eclipse.jdt.internal.compiler.env.ICompilationUnit[] { compilerUnit },
+ apiLevel, compilerOptions, resolveBindings, flags, in.getJavaProject(), workingCopyOwner, -1, monitor).get(compilerUnit));
+ res.get(in).setTypeRoot(in);
+ }
+ }
+ return res;
+ }
+
+ @Override
+ public void parse(String[] sourceFilePaths, String[] encodings, FileASTRequestor requestor, int apiLevel,
+ Map compilerOptions, int flags, IProgressMonitor monitor) {
+
+ for( int i = 0; i < sourceFilePaths.length; i++ ) {
+ org.eclipse.jdt.internal.compiler.env.ICompilationUnit ast = createSourceUnit(sourceFilePaths[i], encodings[i]);
+ Map res =
+ parse(new org.eclipse.jdt.internal.compiler.env.ICompilationUnit[] {ast}, apiLevel, compilerOptions, false, flags, (IJavaProject)null, null, -1, monitor);
+ CompilationUnit result = res.get(ast);
+ requestor.acceptAST(sourceFilePaths[i], result);
+ }
+ }
+
+
+ private void resolveBindings(CompilationUnit unit, int apiLevel) {
+ resolveBindings(unit, new HashMap<>(), apiLevel);
+ }
+
+ private void resolveBindings(CompilationUnit unit, Map bindingMap, int apiLevel) {
+ try {
+ if (unit.getPackage() != null) {
+ IPackageBinding pb = unit.getPackage().resolveBinding();
+ if (pb != null) {
+ bindingMap.put(pb.getKey(), pb);
+ }
+ }
+ if (!unit.types().isEmpty()) {
+ List types = unit.types();
+ for( int i = 0; i < types.size(); i++ ) {
+ ITypeBinding tb = types.get(i).resolveBinding();
+ if (tb != null) {
+ bindingMap.put(tb.getKey(), tb);
+ }
+ }
+ }
+ if( apiLevel >= AST.JLS9_INTERNAL) {
+ if (unit.getModule() != null) {
+ IModuleBinding mb = unit.getModule().resolveBinding();
+ if (mb != null) {
+ bindingMap.put(mb.getKey(), mb);
+ }
+ }
+ }
+ } catch (Exception e) {
+ ILog.get().warn("Failed to resolve binding", e);
+ }
+ }
+
+ @Override
+ public CompilationUnit toCompilationUnit(org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit,
+ boolean resolveBindings, IJavaProject project, List classpaths,
+ int focalPoint, int apiLevel, Map compilerOptions,
+ WorkingCopyOwner workingCopyOwner, WorkingCopyOwner typeRootWorkingCopyOwner, int flags, IProgressMonitor monitor) {
+
+ // collect working copies
+ var workingCopies = JavaModelManager.getJavaModelManager().getWorkingCopies(workingCopyOwner, true);
+ if (workingCopies == null) {
+ workingCopies = new ICompilationUnit[0];
+ }
+ var pathToUnit = new HashMap();
+ Arrays.stream(workingCopies) //
+ .map(org.eclipse.jdt.internal.compiler.env.ICompilationUnit.class::cast) //
+ .forEach(inMemoryCu -> {
+ pathToUnit.put(new String(inMemoryCu.getFileName()), inMemoryCu);
+ });
+
+ // note that this intentionally overwrites an existing working copy entry for the same file
+ pathToUnit.put(new String(sourceUnit.getFileName()), sourceUnit);
+
+ // TODO currently only parse
+ CompilationUnit res = parse(pathToUnit.values().toArray(org.eclipse.jdt.internal.compiler.env.ICompilationUnit[]::new),
+ apiLevel, compilerOptions, resolveBindings, flags, project, workingCopyOwner, focalPoint, monitor).get(sourceUnit);
+ if (resolveBindings) {
+ resolveBindings(res, apiLevel);
+ }
+ // For comparison
+// CompilationUnit res2 = CompilationUnitResolver.FACADE.toCompilationUnit(sourceUnit, initialNeedsToResolveBinding, project, classpaths, nodeSearcher, apiLevel, compilerOptions, typeRootWorkingCopyOwner, typeRootWorkingCopyOwner, flags, monitor);
+// //res.typeAndFlags=res2.typeAndFlags;
+// String res1a = res.toString();
+// String res2a = res2.toString();
+//
+// AnnotationTypeDeclaration l1 = (AnnotationTypeDeclaration)res.types().get(0);
+// AnnotationTypeDeclaration l2 = (AnnotationTypeDeclaration)res2.types().get(0);
+// Object o1 = l1.bodyDeclarations().get(0);
+// Object o2 = l2.bodyDeclarations().get(0);
+ return res;
+ }
+
+ private Map parse(org.eclipse.jdt.internal.compiler.env.ICompilationUnit[] sourceUnits, int apiLevel,
+ Map compilerOptions, boolean resolveBindings, int flags, IJavaProject javaProject, WorkingCopyOwner workingCopyOwner,
+ int focalPoint, IProgressMonitor monitor) {
+ if (sourceUnits.length == 0) {
+ return Collections.emptyMap();
+ }
+ var compiler = ToolProvider.getSystemJavaCompiler();
+ Context context = new Context();
+ 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);
+ boolean[] hasParseError = new boolean[] { false };
+ DiagnosticListener diagnosticListener = diagnostic -> {
+ findTargetDOM(filesToUnits, diagnostic).ifPresent(dom -> {
+ hasParseError[0] |= diagnostic.getKind() == Kind.ERROR;
+ 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);
+ }
+ });
+ };
+ 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 extends Tree> topTypes = unit.getTypeDecls();
+ int typeCount = topTypes.size();
+ // Once all top level types of this Java file have been resolved,
+ // we can report the unused import to the DOM.
+ if (typeCount <= 1) {
+ addProblemsToDOM(dom, unusedImports);
+ } else if (typeCount > 1 && topTypes.get(typeCount - 1) instanceof JCClassDecl lastType) {
+ if (Objects.equals(currentTopLevelType, lastType.sym)) {
+ addProblemsToDOM(dom, unusedImports);
+ }
+ }
+ }
+ }
+ });
+ // must be 1st thing added to context
+ context.put(DiagnosticListener.class, diagnosticListener);
+ boolean docEnabled = JavaCore.ENABLED.equals(compilerOptions.get(JavaCore.COMPILER_DOC_COMMENT_SUPPORT));
+ JavacUtils.configureJavacContext(context, compilerOptions, javaProject, JavacUtils.isTest(javaProject, sourceUnits));
+ Options.instance(context).put(Option.PROC, "only");
+ Optional.ofNullable(Platform.getProduct())
+ .map(IProduct::getApplication)
+ // if application is not a test runner (so we don't have regressions with JDT test suite because of too many problems
+ .or(() -> Optional.ofNullable(System.getProperty("eclipse.application")))
+ .filter(name -> !name.contains("test") && !name.contains("junit"))
+ // continue as far as possible to get extra warnings about unused
+ .ifPresent(id -> Options.instance(context).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;
+ if (!unitFile.getName().endsWith(".java") || sourceUnit.getFileName() == null || sourceUnit.getFileName().length == 0) {
+ sourceUnitPath = Path.of(new File("whatever.java").toURI());
+ } else {
+ sourceUnitPath = Path.of(unitFile.toURI());
+ }
+ var fileObject = fileManager.getJavaFileObject(sourceUnitPath);
+ fileManager.cache(fileObject, CharBuffer.wrap(sourceUnit.getContents()));
+ AST ast = createAST(compilerOptions, apiLevel, context, flags);
+ CompilationUnit res = ast.newCompilationUnit();
+ result.put(sourceUnit, res);
+ filesToUnits.put(fileObject, res);
+ fileObjects.add(fileObject);
+ }
+
+
+ JCCompilationUnit javacCompilationUnit = null;
+ Iterable options = configureAPIfNecessary(fileManager) ? null : Arrays.asList("-proc:none");
+ JavacTask task = ((JavacTool)compiler).getTask(null, fileManager, null /* already added to context */, options, List.of() /* already set */, fileObjects, context);
+ {
+ // don't know yet a better way to ensure those necessary flags get configured
+ var javac = com.sun.tools.javac.main.JavaCompiler.instance(context);
+ javac.keepComments = true;
+ javac.genEndPos = true;
+ javac.lineDebugInfo = true;
+ }
+
+ try {
+ var elements = task.parse().iterator();
+ var aptPath = fileManager.getLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH);
+ if ((flags & ICompilationUnit.FORCE_PROBLEM_DETECTION) != 0
+ || (aptPath != null && aptPath.iterator().hasNext())) {
+ task.analyze();
+ }
+
+ Throwable cachedThrown = null;
+
+ for (int i = 0 ; i < sourceUnits.length; i++) {
+ if (elements.hasNext() && elements.next() instanceof JCCompilationUnit u) {
+ javacCompilationUnit = u;
+ } else {
+ return Map.of();
+ }
+ try {
+ String rawText = null;
+ try {
+ rawText = fileObjects.get(i).getCharContent(true).toString();
+ } catch( IOException ioe) {
+ // ignore
+ }
+ CompilationUnit res = result.get(sourceUnits[i]);
+ AST ast = res.ast;
+ JavacConverter converter = new JavacConverter(ast, javacCompilationUnit, context, rawText, docEnabled, focalPoint);
+ converter.populateCompilationUnit(res, javacCompilationUnit);
+ // javadoc problems explicitly set as they're not sent to DiagnosticListener (maybe find a flag to do it?)
+ var javadocProblems = converter.javadocDiagnostics.stream()
+ .map(problemConverter::createJavacProblem)
+ .filter(Objects::nonNull)
+ .toArray(IProblem[]::new);
+ if (javadocProblems.length > 0) {
+ int initialSize = res.getProblems().length;
+ var newProblems = Arrays.copyOf(res.getProblems(), initialSize + javadocProblems.length);
+ System.arraycopy(javadocProblems, 0, newProblems, initialSize, javadocProblems.length);
+ res.setProblems(newProblems);
+ }
+ List javadocComments = new ArrayList<>();
+ res.accept(new ASTVisitor(true) {
+ @Override
+ public void postVisit(ASTNode node) { // fix some positions
+ if( node.getParent() != null ) {
+ if( node.getStartPosition() < node.getParent().getStartPosition()) {
+ int parentEnd = node.getParent().getStartPosition() + node.getParent().getLength();
+ if( node.getStartPosition() >= 0 ) {
+ node.getParent().setSourceRange(node.getStartPosition(), parentEnd - node.getStartPosition());
+ }
+ }
+ }
+ }
+ @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);
+ resolver.isRecoveringBindings = (flags & ICompilationUnit.ENABLE_BINDINGS_RECOVERY) != 0;
+ ast.setBindingResolver(resolver);
+ }
+ //
+ ast.setOriginalModificationCount(ast.modificationCount()); // "un-dirty" AST so Rewrite can process it
+ ast.setDefaultNodeFlag(ast.getDefaultNodeFlag() & ~ASTNode.ORIGINAL);
+ } catch (Throwable thrown) {
+ if (cachedThrown == null) {
+ cachedThrown = thrown;
+ }
+ ILog.get().error("Internal failure while parsing or converting AST for unit " + new String(sourceUnits[i].getFileName()));
+ ILog.get().error(thrown.getMessage(), thrown);
+ }
+ }
+ if (cachedThrown != null) {
+ throw new RuntimeException(cachedThrown);
+ }
+ } catch (IOException ex) {
+ ILog.get().error(ex.getMessage(), ex);
+ }
+
+ return result;
+ }
+
+ 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 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 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) {
+ JavaCore.getPlugin().getLog().log(org.eclipse.core.runtime.Status.error(ex.getMessage(), ex));
+ }
+
+ // need to scan with ecjScanner first to populate some line indexes used by the CommentMapper
+ // on longer-term, implementing an alternative comment mapper based on javac scanner might be best
+ addCommentsToUnit(missingComments, unit);
+ unit.initCommentMapper(ecjScanner);
+ }
+
+ static void addCommentsToUnit(Collection comments, CompilationUnit res) {
+ List before = res.getCommentList() == null ? new ArrayList<>() : new ArrayList<>(res.getCommentList());
+ comments.stream().filter(comment -> comment.getStartPosition() >= 0 && JavacCompilationUnitResolver.noCommentAt(res, comment.getStartPosition()))
+ .forEach(before::add);
+ before.sort(Comparator.comparingInt(Comment::getStartPosition));
+ res.setCommentTable(before.toArray(Comment[]::new));
+ }
+
+ private static boolean noCommentAt(CompilationUnit unit, int pos) {
+ if (unit.getCommentList() == null) {
+ return true;
+ }
+ return ((List)unit.getCommentList()).stream()
+ .allMatch(other -> pos < other.getStartPosition() || pos >= other.getStartPosition() + other.getLength());
+ }
+
+ private static class BindingBuilder extends ASTVisitor {
+ public Map bindingMap = new HashMap<>();
+
+ public BindingBuilder(Map bindingMap) {
+ this.bindingMap = bindingMap;
+ }
+
+ @Override
+ public boolean visit(TypeDeclaration node) {
+ IBinding binding = node.resolveBinding();
+ if (binding != null) {
+ bindingMap.putIfAbsent(binding.getKey(), binding);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean visit(MethodDeclaration node) {
+ IBinding binding = node.resolveBinding();
+ if (binding != null) {
+ bindingMap.putIfAbsent(binding.getKey(), binding);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean visit(EnumDeclaration node) {
+ IBinding binding = node.resolveBinding();
+ if (binding != null) {
+ bindingMap.putIfAbsent(binding.getKey(), binding);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean visit(RecordDeclaration node) {
+ IBinding binding = node.resolveBinding();
+ if (binding != null) {
+ bindingMap.putIfAbsent(binding.getKey(), binding);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean visit(SingleVariableDeclaration node) {
+ IBinding binding = node.resolveBinding();
+ if (binding != null) {
+ bindingMap.putIfAbsent(binding.getKey(), binding);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean visit(VariableDeclarationFragment node) {
+ IBinding binding = node.resolveBinding();
+ if (binding != null) {
+ bindingMap.putIfAbsent(binding.getKey(), binding);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean visit(AnnotationTypeDeclaration node) {
+ IBinding binding = node.resolveBinding();
+ if (binding != null) {
+ bindingMap.putIfAbsent(binding.getKey(), binding);
+ }
+ return true;
+ }
+ }
+
+ private static Function javacAdditionalBindingCreator(Map bindingMap, INameEnvironment environment, LookupEnvironment lu, BindingResolver[] bindingResolverPointer) {
+
+ return key -> {
+
+ {
+ // check parsed files
+ IBinding binding = bindingMap.get(key);
+ if (binding != null) {
+ return binding;
+ }
+ }
+
+ // if the requested type is an array type,
+ // check the parsed files for element type and create the array variant
+ int arrayCount = Signature.getArrayCount(key);
+ if (arrayCount > 0) {
+ String elementKey = Signature.getElementType(key);
+ IBinding elementBinding = bindingMap.get(elementKey);
+ if (elementBinding instanceof ITypeBinding elementTypeBinding) {
+ return elementTypeBinding.createArrayType(arrayCount);
+ }
+ }
+
+ // check name environment
+ CustomBindingKeyParser bkp = new CustomBindingKeyParser(key);
+ bkp.parse(true);
+ char[][] name = bkp.compoundName;
+ NameEnvironmentAnswer answer = environment.findType(name);
+ if (answer != null) {
+ IBinaryType binaryType = answer.getBinaryType();
+ if (binaryType != null) {
+ BinaryTypeBinding binding = lu.cacheBinaryType(binaryType, null);
+ return new TypeBinding(bindingResolverPointer[0], binding);
+ }
+ }
+
+ return null;
+ };
+ }
+
+ private boolean configureAPIfNecessary(JavacFileManager fileManager) {
+ Iterable extends File> apPaths = fileManager.getLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH);
+ if (apPaths != null) {
+ return true;
+ }
+
+ Iterable extends File> apModulePaths = fileManager.getLocation(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH);
+ if (apModulePaths != null) {
+ return true;
+ }
+
+ Iterable extends File> 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 extends File> 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..85fd9bc7ec9
--- /dev/null
+++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacConverter.java
@@ -0,0 +1,3635 @@
+/*******************************************************************************
+ * 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.core.dom;
+
+import static com.sun.tools.javac.code.Flags.VARARGS;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.OptionalInt;
+import java.util.PriorityQueue;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Predicate;
+
+import javax.lang.model.type.TypeKind;
+
+import org.eclipse.core.runtime.ILog;
+import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword;
+import org.eclipse.jdt.core.dom.ModuleModifier.ModuleModifierKeyword;
+import org.eclipse.jdt.core.dom.PrefixExpression.Operator;
+import org.eclipse.jdt.core.dom.PrimitiveType.Code;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.parser.RecoveryScanner;
+
+import com.sun.source.tree.CaseTree.CaseKind;
+import com.sun.source.tree.ModuleTree.ModuleKind;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.util.DocTreePath;
+import com.sun.source.util.TreePath;
+import com.sun.tools.javac.code.BoundKind;
+import com.sun.tools.javac.code.Flags;
+import com.sun.tools.javac.parser.ParserFactory;
+import com.sun.tools.javac.parser.Tokens.Comment;
+import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle;
+import com.sun.tools.javac.tree.DCTree.DCDocComment;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.JCTree.JCAnnotatedType;
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+import com.sun.tools.javac.tree.JCTree.JCAnyPattern;
+import com.sun.tools.javac.tree.JCTree.JCArrayAccess;
+import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree;
+import com.sun.tools.javac.tree.JCTree.JCAssert;
+import com.sun.tools.javac.tree.JCTree.JCAssign;
+import com.sun.tools.javac.tree.JCTree.JCAssignOp;
+import com.sun.tools.javac.tree.JCTree.JCBinary;
+import com.sun.tools.javac.tree.JCTree.JCBindingPattern;
+import com.sun.tools.javac.tree.JCTree.JCBlock;
+import com.sun.tools.javac.tree.JCTree.JCBreak;
+import com.sun.tools.javac.tree.JCTree.JCCase;
+import com.sun.tools.javac.tree.JCTree.JCCaseLabel;
+import com.sun.tools.javac.tree.JCTree.JCCatch;
+import com.sun.tools.javac.tree.JCTree.JCClassDecl;
+import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
+import com.sun.tools.javac.tree.JCTree.JCConditional;
+import com.sun.tools.javac.tree.JCTree.JCConstantCaseLabel;
+import com.sun.tools.javac.tree.JCTree.JCContinue;
+import com.sun.tools.javac.tree.JCTree.JCDefaultCaseLabel;
+import com.sun.tools.javac.tree.JCTree.JCDirective;
+import com.sun.tools.javac.tree.JCTree.JCDoWhileLoop;
+import com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop;
+import com.sun.tools.javac.tree.JCTree.JCErroneous;
+import com.sun.tools.javac.tree.JCTree.JCExports;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
+import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
+import com.sun.tools.javac.tree.JCTree.JCForLoop;
+import com.sun.tools.javac.tree.JCTree.JCIdent;
+import com.sun.tools.javac.tree.JCTree.JCIf;
+import com.sun.tools.javac.tree.JCTree.JCImport;
+import com.sun.tools.javac.tree.JCTree.JCInstanceOf;
+import com.sun.tools.javac.tree.JCTree.JCLabeledStatement;
+import com.sun.tools.javac.tree.JCTree.JCLambda;
+import com.sun.tools.javac.tree.JCTree.JCLiteral;
+import com.sun.tools.javac.tree.JCTree.JCMemberReference;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
+import com.sun.tools.javac.tree.JCTree.JCModifiers;
+import com.sun.tools.javac.tree.JCTree.JCModuleDecl;
+import com.sun.tools.javac.tree.JCTree.JCNewArray;
+import com.sun.tools.javac.tree.JCTree.JCNewClass;
+import com.sun.tools.javac.tree.JCTree.JCOpens;
+import com.sun.tools.javac.tree.JCTree.JCPackageDecl;
+import com.sun.tools.javac.tree.JCTree.JCParens;
+import com.sun.tools.javac.tree.JCTree.JCPattern;
+import com.sun.tools.javac.tree.JCTree.JCPatternCaseLabel;
+import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree;
+import com.sun.tools.javac.tree.JCTree.JCProvides;
+import com.sun.tools.javac.tree.JCTree.JCRecordPattern;
+import com.sun.tools.javac.tree.JCTree.JCRequires;
+import com.sun.tools.javac.tree.JCTree.JCReturn;
+import com.sun.tools.javac.tree.JCTree.JCSkip;
+import com.sun.tools.javac.tree.JCTree.JCStatement;
+import com.sun.tools.javac.tree.JCTree.JCSwitch;
+import com.sun.tools.javac.tree.JCTree.JCSwitchExpression;
+import com.sun.tools.javac.tree.JCTree.JCSynchronized;
+import com.sun.tools.javac.tree.JCTree.JCThrow;
+import com.sun.tools.javac.tree.JCTree.JCTry;
+import com.sun.tools.javac.tree.JCTree.JCTypeApply;
+import com.sun.tools.javac.tree.JCTree.JCTypeCast;
+import com.sun.tools.javac.tree.JCTree.JCTypeIntersection;
+import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
+import com.sun.tools.javac.tree.JCTree.JCTypeUnion;
+import com.sun.tools.javac.tree.JCTree.JCUnary;
+import com.sun.tools.javac.tree.JCTree.JCUses;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.tree.JCTree.JCWhileLoop;
+import com.sun.tools.javac.tree.JCTree.JCWildcard;
+import com.sun.tools.javac.tree.JCTree.JCYield;
+import com.sun.tools.javac.tree.JCTree.Tag;
+import com.sun.tools.javac.tree.TreeInfo;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.JCDiagnostic;
+import com.sun.tools.javac.util.Log;
+import com.sun.tools.javac.util.Names;
+import com.sun.tools.javac.util.Position;
+import com.sun.tools.javac.util.Position.LineMap;
+
+/**
+ * Deals with conversion of Javac domain into JDT DOM domain
+ * @implNote Cannot move to another package as it uses some package protected methods
+ */
+@SuppressWarnings("unchecked")
+class JavacConverter {
+
+ private static final String ERROR = "";
+ private static final String FAKE_IDENTIFIER = new String(RecoveryScanner.FAKE_IDENTIFIER);
+ public final AST ast;
+ private 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);
+ javacCompilationUnit.getTypeDecls().stream()
+ .map(n -> convertBodyDeclaration(n, res))
+ .filter(Objects::nonNull)
+ .forEach(res.types()::add);
+ res.accept(new FixPositions());
+ }
+
+ private PackageDeclaration convertMalformedPackageDeclaration(JCErroneous jcer) {
+ if( jcer.errs != null && jcer.errs.size() > 0 && jcer.errs.get(0) instanceof JCModifiers) {
+ // Legitimate chance this is a misplaced modifier, private package, etc
+ int errEndPos = jcer.getEndPosition(this.javacCompilationUnit.endPositions);
+ String possiblePackageDecl = this.rawText.length() > (errEndPos + 7) ? this.rawText.substring(errEndPos, errEndPos + 7) : null;
+ if( "package".equals(possiblePackageDecl)) {
+ int newLine = this.rawText.indexOf("\n", errEndPos);
+ String decl = null;
+ if( newLine != -1 ) {
+ decl = this.rawText.substring(errEndPos, newLine).trim();
+ } else {
+ decl = this.rawText.substring(errEndPos);
+ }
+ String pkgName = decl.substring(7).trim();
+ if( pkgName.endsWith(";")) {
+ pkgName = pkgName.substring(0,pkgName.length()-1);
+ }
+ PackageDeclaration res = this.ast.newPackageDeclaration();
+ res.setName(toName(pkgName, 0, this.ast));
+ setJavadocForNode(jcer, res);
+ res.setSourceRange(errEndPos, Math.max(0, pkgName.length()));
+ res.setFlags(res.getFlags() | ASTNode.MALFORMED);
+ return res;
+ }
+ }
+ return null;
+ }
+
+ private int[] toLineEndPosTable(LineMap lineMap, int fileLength) {
+ List lineEnds = new ArrayList<>();
+ int line = 1;
+ try {
+ do {
+ lineEnds.add(lineMap.getStartPosition(line + 1) - 1);
+ line++;
+ } while (true);
+ } catch (ArrayIndexOutOfBoundsException ex) {
+ // expected
+ }
+ lineEnds.add(fileLength - 1);
+ return lineEnds.stream().mapToInt(Integer::intValue).toArray();
+ }
+
+ private PackageDeclaration convert(JCPackageDecl javac) {
+ PackageDeclaration res = this.ast.newPackageDeclaration();
+ res.setName(toName(javac.getPackageName()));
+ commonSettings(res, javac);
+ Iterator it = javac.annotations.iterator();
+ while(it.hasNext()) {
+ res.annotations().add(convert(it.next()));
+ }
+ String raw = this.rawText.substring(res.getStartPosition(), res.getStartPosition() + res.getLength());
+ if( !raw.trim().endsWith(";")) {
+ res.setFlags(res.getFlags() | ASTNode.MALFORMED);
+ }
+ return res;
+ }
+
+ private ModuleDeclaration convert(JCModuleDecl javac) {
+ ModuleDeclaration res = this.ast.newModuleDeclaration();
+ res.setName(toName(javac.getName()));
+ this.domToJavac.put(res.getName(), javac);
+ boolean isOpen = javac.getModuleType() == ModuleKind.OPEN;
+ res.setOpen(isOpen);
+ if (javac.getDirectives() != null) {
+ List directives = javac.getDirectives();
+ for (int i = 0; i < directives.size(); i++) {
+ JCDirective jcDirective = directives.get(i);
+ res.moduleStatements().add(convert(jcDirective));
+ }
+ }
+ commonSettings(res, javac);
+ if( isOpen ) {
+ int start = res.getStartPosition();
+ if( !this.rawText.substring(start).trim().startsWith("open")) {
+ // we are open but we don't start with open... so... gotta look backwards
+ String prefix = this.rawText.substring(0,start);
+ if( prefix.trim().endsWith("open")) {
+ // previous token is open
+ int ind = new StringBuffer().append(prefix).reverse().toString().indexOf("nepo");
+ if( ind != -1 ) {
+ int gap = ind + 4;
+ res.setSourceRange(res.getStartPosition() - gap, res.getLength() + gap);
+ }
+ }
+ }
+ }
+ List l = convertModifierAnnotations(javac.mods, res);
+ res.annotations().addAll(l);
+ return res;
+ }
+
+ private ModuleDirective convert(JCDirective javac) {
+ return switch (javac.getKind()) {
+ case EXPORTS -> convert((JCExports)javac);
+ case OPENS -> convert((JCOpens)javac);
+ case PROVIDES -> convert((JCProvides)javac);
+ case REQUIRES -> convert((JCRequires)javac);
+ case USES -> convert((JCUses)javac);
+ default -> throw new IllegalStateException();
+ };
+ }
+
+ private ExportsDirective convert(JCExports javac) {
+ ExportsDirective res = this.ast.newExportsStatement();
+ res.setName(toName(javac.getPackageName()));
+ commonSettings(res, javac);
+ List mods = javac.getModuleNames();
+ if (mods != null) {
+ Iterator it = mods.iterator();
+ while(it.hasNext()) {
+ JCExpression jcpe = it.next();
+ Expression e = convertExpression(jcpe);
+ if( e != null )
+ res.modules().add(e);
+ }
+ }
+ return res;
+ }
+
+ private OpensDirective convert(JCOpens javac) {
+ OpensDirective res = this.ast.newOpensDirective();
+ res.setName(toName(javac.getPackageName()));
+ commonSettings(res, javac);
+ List mods = javac.getModuleNames();
+ if (mods != null) {
+ Iterator it = mods.iterator();
+ while (it.hasNext()) {
+ JCExpression jcpe = it.next();
+ Expression e = convertExpression(jcpe);
+ if (e != null)
+ res.modules().add(e);
+ }
+ }
+ return res;
+ }
+
+ private ProvidesDirective convert(JCProvides javac) {
+ ProvidesDirective res = this.ast.newProvidesDirective();
+ res.setName(toName(javac.getServiceName()));
+ for (var jcName : javac.implNames) {
+ res.implementations().add(toName(jcName));
+ }
+ commonSettings(res, javac);
+ return res;
+ }
+
+ private RequiresDirective convert(JCRequires javac) {
+ RequiresDirective res = this.ast.newRequiresDirective();
+ res.setName(toName(javac.getModuleName()));
+ int javacStart = javac.getStartPosition();
+ List modifiersToAdd = new ArrayList<>();
+ if (javac.isTransitive()) {
+ ModuleModifier trans = this.ast.newModuleModifier(ModuleModifierKeyword.TRANSITIVE_KEYWORD);
+ int transStart = this.rawText.substring(javacStart).indexOf(ModuleModifierKeyword.TRANSITIVE_KEYWORD.toString());
+ if( transStart != -1 ) {
+ int trueStart = javacStart + transStart;
+ trans.setSourceRange(trueStart, ModuleModifierKeyword.TRANSITIVE_KEYWORD.toString().length());
+ }
+ modifiersToAdd.add(trans);
+ }
+ if (javac.isStatic()) {
+ ModuleModifier stat = this.ast.newModuleModifier(ModuleModifierKeyword.STATIC_KEYWORD);
+ int statStart = this.rawText.substring(javacStart).indexOf(ModuleModifierKeyword.STATIC_KEYWORD.toString());
+ if( statStart != -1 ) {
+ int trueStart = javacStart + statStart;
+ stat.setSourceRange(trueStart, ModuleModifierKeyword.STATIC_KEYWORD.toString().length());
+ }
+ modifiersToAdd.add(stat);
+ }
+ modifiersToAdd.sort((a, b) -> a.getStartPosition() - b.getStartPosition());
+ modifiersToAdd.stream().forEach(res.modifiers()::add);
+ commonSettings(res, javac);
+ return res;
+ }
+
+ private UsesDirective convert(JCUses javac) {
+ UsesDirective res = this.ast.newUsesDirective();
+ res.setName(toName(javac.getServiceName()));
+ commonSettings(res, javac);
+ return res;
+ }
+
+ private ImportDeclaration convert(JCImport javac) {
+ ImportDeclaration res = this.ast.newImportDeclaration();
+ commonSettings(res, javac);
+ if (javac.isStatic()) {
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ res.setStatic(true);
+ }
+ }
+ var select = javac.getQualifiedIdentifier();
+ if (select.getIdentifier().contentEquals("*")) {
+ res.setOnDemand(true);
+ res.setName(toName(select.getExpression()));
+ } else {
+ 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 JCIdent include trailing semicolon, eg in try-resources
+ if (res instanceof Name || res instanceof FieldAccess || res instanceof SuperFieldAccess) {
+ while (endPos > start && 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) {
+ res.setSourceRange(javac.getStartPosition(), Math.max(0, length));
+ if( removeWhitespace ) {
+ removeSurroundingWhitespaceFromRange(res);
+ }
+ this.domToJavac.put(res, javac);
+ setJavadocForNode(javac, res);
+ }
+ }
+
+ private void nameSettings(SimpleName name, JCMethodDecl javac, String selector, boolean isConstructor) {
+ if ((selector.equals(ERROR) || selector.equals(FAKE_IDENTIFIER)))
+ return;
+ var start = javac.getPreferredPosition();
+ if (start > -1) {
+ // handle constructor length using type name instead of selector.
+ var length = isConstructor ? name.toString().length() : selector.length();
+ name.setSourceRange(start, length);
+ }
+ }
+
+ private void nameSettings(SimpleName name, JCVariableDecl javac, String varName) {
+ if (varName.equals(ERROR) || varName.equals(FAKE_IDENTIFIER))
+ return;
+ var start = javac.getPreferredPosition();
+ if (start > -1) {
+ name.setSourceRange(start, varName.length());
+ }
+ }
+
+ private Name toName(JCTree expression) {
+ return toName(expression, null);
+ }
+
+ Name toName(JCTree expression, BiConsumer extraSettings ) {
+ if (expression instanceof JCIdent ident) {
+ Name res = convertName(ident.getName());
+ commonSettings(res, expression);
+ if( extraSettings != null )
+ extraSettings.accept(res, ident);
+ return res;
+ }
+ if (expression instanceof JCFieldAccess fieldAccess) {
+ JCExpression faExpression = fieldAccess.getExpression();
+ SimpleName n = (SimpleName)convertName(fieldAccess.getIdentifier());
+ if (n == null) {
+ n = this.ast.newSimpleName(FAKE_IDENTIFIER);
+ n.setFlags(ASTNode.RECOVERED);
+ }
+ commonSettings(n, fieldAccess);
+
+ Name qualifier = toName(faExpression, extraSettings);
+ QualifiedName res = this.ast.newQualifiedName(qualifier, n);
+ commonSettings(res, fieldAccess);
+ if( extraSettings != null )
+ extraSettings.accept(res, fieldAccess);
+ // don't calculate source range if the identifier is not valid.
+ if (!fieldAccess.getIdentifier().contentEquals(FAKE_IDENTIFIER)
+ && !fieldAccess.getIdentifier().contentEquals(ERROR)) {
+ // fix name position according to qualifier position
+ int nameIndex = this.rawText.indexOf(fieldAccess.getIdentifier().toString(),
+ qualifier.getStartPosition() + qualifier.getLength());
+ if (nameIndex >= 0) {
+ n.setSourceRange(nameIndex, fieldAccess.getIdentifier().toString().length());
+ }
+ }
+ return res;
+ }
+ if (expression instanceof JCAnnotatedType jcat) {
+ Name n = toName(jcat.underlyingType, extraSettings);
+ commonSettings(n, jcat.underlyingType);
+ return n;
+ }
+ if (expression instanceof JCTypeApply jcta) {
+ Name n = toName(jcta.clazz, extraSettings);
+ commonSettings(n, jcta.clazz);
+ return n;
+ }
+ throw new UnsupportedOperationException("toName for " + expression + " (" + expression == null ? "null" : expression.getClass().getName() + ")");
+ }
+
+ private AbstractTypeDeclaration convertClassDecl(JCClassDecl javacClassDecl, ASTNode parent) {
+ if( javacClassDecl.getKind() == Kind.ANNOTATION_TYPE &&
+ (this.ast.apiLevel <= AST.JLS2_INTERNAL || this.ast.scanner.complianceLevel < ClassFileConstants.JDK1_5)) {
+ return null;
+ }
+ if( javacClassDecl.getKind() == Kind.ENUM &&
+ (this.ast.apiLevel <= AST.JLS2_INTERNAL || this.ast.scanner.complianceLevel < ClassFileConstants.JDK1_5)) {
+ return null;
+ }
+ if( javacClassDecl.getKind() == Kind.RECORD &&
+ (this.ast.apiLevel < AST.JLS16_INTERNAL || this.ast.scanner.complianceLevel < ClassFileConstants.JDK16)) {
+ return null;
+ }
+
+ AbstractTypeDeclaration res = switch (javacClassDecl.getKind()) {
+ case ANNOTATION_TYPE -> this.ast.newAnnotationTypeDeclaration();
+ case ENUM -> this.ast.newEnumDeclaration();
+ case RECORD -> this.ast.newRecordDeclaration();
+ case INTERFACE -> {
+ TypeDeclaration decl = this.ast.newTypeDeclaration();
+ decl.setInterface(true);
+ yield decl;
+ }
+ case CLASS -> javacClassDecl.getModifiers() != null && (javacClassDecl.getModifiers().flags & Flags.IMPLICIT_CLASS) != 0 ?
+ new ImplicitTypeDeclaration(this.ast) :
+ this.ast.newTypeDeclaration();
+ default -> throw new IllegalStateException();
+ };
+ return convertClassDecl(javacClassDecl, parent, res);
+ }
+
+ private AbstractTypeDeclaration convertClassDecl(JCClassDecl javacClassDecl, ASTNode parent, AbstractTypeDeclaration res) {
+ commonSettings(res, javacClassDecl);
+ SimpleName simpName = (SimpleName)convertName(javacClassDecl.getSimpleName());
+ if(!(res instanceof ImplicitTypeDeclaration) && simpName != null) {
+ res.setName(simpName);
+ int searchNameFrom = javacClassDecl.getPreferredPosition();
+ if (javacClassDecl.getModifiers() != null) {
+ searchNameFrom = Math.max(searchNameFrom, TreeInfo.getEndPos(javacClassDecl.getModifiers(), this.javacCompilationUnit.endPositions));
+ }
+ int namePosition = this.rawText.indexOf(simpName.getIdentifier(), searchNameFrom);
+ if (namePosition >= 0) {
+ simpName.setSourceRange(namePosition, simpName.getIdentifier().length());
+ }
+ }
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ res.modifiers().addAll(convert(javacClassDecl.mods, res));
+ } else {
+ int jls2Flags = getJLS2ModifiersFlags(javacClassDecl.mods);
+ jls2Flags &= ~Flags.INTERFACE; // remove AccInterface flags, see ASTConverter
+ res.internalSetModifiers(jls2Flags);
+ }
+ if (res instanceof TypeDeclaration typeDeclaration) {
+ if (javacClassDecl.getExtendsClause() != null) {
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ typeDeclaration.setSuperclassType(convertToType(javacClassDecl.getExtendsClause()));
+ } else {
+ JCExpression e = javacClassDecl.getExtendsClause();
+ Name m = toName(e);
+ if( m != null ) {
+ typeDeclaration.setSuperclass(m);
+ }
+ }
+ }
+ if (javacClassDecl.getImplementsClause() != null) {
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ javacClassDecl.getImplementsClause().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(typeDeclaration.superInterfaceTypes()::add);
+ } else {
+ Iterator it = javacClassDecl.getImplementsClause().iterator();
+ while(it.hasNext()) {
+ JCExpression next = it.next();
+ Name m = toName(next);
+ if( m != null ) {
+ typeDeclaration.superInterfaces().add(m);
+ }
+ }
+ }
+ }
+
+ if( javacClassDecl.getTypeParameters() != null ) {
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ Iterator i = javacClassDecl.getTypeParameters().iterator();
+ while(i.hasNext()) {
+ JCTypeParameter next = i.next();
+ typeDeclaration.typeParameters().add(convert(next));
+ }
+ }
+ }
+
+ if (javacClassDecl.getPermitsClause() != null) {
+ if( this.ast.apiLevel >= AST.JLS17_INTERNAL) {
+ javacClassDecl.getPermitsClause().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(typeDeclaration.permittedTypes()::add);
+ if (!javacClassDecl.getPermitsClause().isEmpty()) {
+ int permitsOffset = this.rawText.substring(javacClassDecl.pos).indexOf("permits") + javacClassDecl.pos;
+ typeDeclaration.setRestrictedIdentifierStartPosition(permitsOffset);
+ }
+ }
+ }
+ if (javacClassDecl.getMembers() != null) {
+ List members = javacClassDecl.getMembers();
+ ASTNode previous = null;
+ for( int i = 0; i < members.size(); i++ ) {
+ ASTNode decl = convertBodyDeclaration(members.get(i), res);
+ if( decl != null ) {
+ typeDeclaration.bodyDeclarations().add(decl);
+ if (previous != null) {
+ int istart = decl.getStartPosition();
+ int siblingEnds = previous.getStartPosition() + previous.getLength();
+ if(previous.getStartPosition() >= 0 && siblingEnds > istart && istart > previous.getStartPosition()) {
+ previous.setSourceRange(previous.getStartPosition(), istart - previous.getStartPosition()-1);
+ }
+ }
+ previous = decl;
+ }
+ }
+ }
+ } else if (res instanceof EnumDeclaration enumDecl) {
+ List enumStatements= enumDecl.enumConstants();
+ if (javacClassDecl.getMembers() != null) {
+ for(JCTree member : javacClassDecl.getMembers()) {
+ EnumConstantDeclaration dec1 = convertEnumConstantDeclaration(member, parent, enumDecl);
+ if( dec1 != null ) {
+ enumStatements.add(dec1);
+ } else {
+ // body declaration
+ ASTNode bodyDecl = convertBodyDeclaration(member, res);
+ if( bodyDecl != null ) {
+ res.bodyDeclarations().add(bodyDecl);
+ }
+ }
+ }
+ }
+ } else if (res instanceof AnnotationTypeDeclaration annotDecl) {
+ //setModifiers(annotationTypeMemberDeclaration2, annotationTypeMemberDeclaration);
+ final SimpleName name = new SimpleName(this.ast);
+ name.internalSetIdentifier(new String(annotDecl.typeName.toString()));
+ res.setName(name);
+ if( javacClassDecl.defs != null ) {
+ for( Iterator i = javacClassDecl.defs.iterator(); i.hasNext(); ) {
+ ASTNode converted = convertBodyDeclaration(i.next(), res);
+ if( converted != null ) {
+ res.bodyDeclarations.add(converted);
+ }
+ }
+ }
+
+// org.eclipse.jdt.internal.compiler.ast.TypeReference typeReference = annotDecl.get
+// if (typeReference != null) {
+// Type returnType = convertType(typeReference);
+// setTypeForMethodDeclaration(annotationTypeMemberDeclaration2, returnType, 0);
+// }
+// int declarationSourceStart = annotationTypeMemberDeclaration.declarationSourceStart;
+// int declarationSourceEnd = annotationTypeMemberDeclaration.bodyEnd;
+// annotationTypeMemberDeclaration2.setSourceRange(declarationSourceStart, declarationSourceEnd - declarationSourceStart + 1);
+// // The javadoc comment is now got from list store in compilation unit declaration
+// convert(annotationTypeMemberDeclaration.javadoc, annotationTypeMemberDeclaration2);
+// org.eclipse.jdt.internal.compiler.ast.Expression memberValue = annotationTypeMemberDeclaration.defaultValue;
+// if (memberValue != null) {
+// annotationTypeMemberDeclaration2.setDefault(convert(memberValue));
+// }
+
+ } else if (res instanceof RecordDeclaration recordDecl) {
+ int start = javacClassDecl.getPreferredPosition();
+ if( start != -1 ) {
+ recordDecl.setRestrictedIdentifierStartPosition(start);
+ }
+ for (JCTree node : javacClassDecl.getMembers()) {
+ if (node instanceof JCVariableDecl vd && !vd.getModifiers().getFlags().contains(javax.lang.model.element.Modifier.STATIC)) {
+ SingleVariableDeclaration vdd = (SingleVariableDeclaration)convertVariableDeclaration(vd);
+ // Records cannot have modifiers
+ vdd.modifiers().clear();
+ // Add only annotation modifiers
+ vdd.modifiers().addAll(convertModifierAnnotations(vd.getModifiers(), vdd));
+ recordDecl.recordComponents().add(vdd);
+ } else {
+ ASTNode converted = convertBodyDeclaration(node, res);
+ if( converted != null ) {
+ res.bodyDeclarations.add(converted);
+ }
+ }
+ }
+ } else if (res instanceof ImplicitTypeDeclaration) {
+ javacClassDecl.getMembers().stream()
+ .map(member -> convertBodyDeclaration(member, res))
+ .filter(Objects::nonNull)
+ .forEach(res.bodyDeclarations()::add);
+ }
+ return res;
+ }
+
+ private TypeParameter convert(JCTypeParameter typeParameter) {
+ final TypeParameter ret = new TypeParameter(this.ast);
+ commonSettings(ret, typeParameter);
+ final SimpleName simpleName = new SimpleName(this.ast);
+ simpleName.internalSetIdentifier(typeParameter.getName().toString());
+ int start = typeParameter.pos;
+ int end = typeParameter.pos + typeParameter.getName().length();
+ simpleName.setSourceRange(start, end - start);
+ ret.setName(simpleName);
+ List bounds = typeParameter.getBounds();
+ Iterator i = bounds.iterator();
+ while(i.hasNext()) {
+ JCTree t = i.next();
+ Type type = convertToType(t);
+ ret.typeBounds().add(type);
+ end = typeParameter.getEndPosition(this.javacCompilationUnit.endPositions);
+ }
+ if (typeParameter.getAnnotations() != null && this.ast.apiLevel() >= AST.JLS8_INTERNAL) {
+ typeParameter.getAnnotations().stream()
+ .map(this::convert)
+ .forEach(ret.modifiers()::add);
+ }
+// org.eclipse.jdt.internal.compiler.ast.Annotation[] annotations = typeParameter.annotations;
+// if (annotations != null) {
+// if (annotations[0] != null)
+// annotationsStart = annotations[0].sourceStart;
+// annotateTypeParameter(typeParameter2, typeParameter.annotations);
+// }
+// final TypeReference superType = typeParameter.type;
+// end = typeParameter.declarationSourceEnd;
+// if (superType != null) {
+// Type type = convertType(superType);
+// typeParameter2.typeBounds().add(type);
+// end = type.getStartPosition() + type.getLength() - 1;
+// }
+// TypeReference[] bounds = typeParameter.bounds;
+// if (bounds != null) {
+// Type type = null;
+// for (int index = 0, length = bounds.length; index < length; index++) {
+// type = convertType(bounds[index]);
+// typeParameter2.typeBounds().add(type);
+// end = type.getStartPosition() + type.getLength() - 1;
+// }
+// }
+// start = annotationsStart < typeParameter.declarationSourceStart ? annotationsStart : typeParameter.declarationSourceStart;
+// end = retrieveClosingAngleBracketPosition(end);
+// if (this.resolveBindings) {
+// recordName(simpleName, typeParameter);
+// recordNodes(typeParameter2, typeParameter);
+// typeParameter2.resolveBinding();
+// }
+ ret.setSourceRange(start, end - start);
+ return ret;
+ }
+
+ private ASTNode convertBodyDeclaration(JCTree tree, ASTNode parent) {
+ if( parent instanceof AnnotationTypeDeclaration && tree instanceof JCMethodDecl methodDecl) {
+ return convertMethodInAnnotationTypeDecl(methodDecl, parent);
+ }
+ if (tree instanceof JCMethodDecl methodDecl) {
+ return convertMethodDecl(methodDecl, parent);
+ }
+ if (tree instanceof JCClassDecl jcClassDecl) {
+ return convertClassDecl(jcClassDecl, parent);
+ }
+ if (tree instanceof JCVariableDecl jcVariableDecl) {
+ return convertFieldDeclaration(jcVariableDecl, parent);
+ }
+ if (tree instanceof JCBlock block) {
+ Initializer res = this.ast.newInitializer();
+ commonSettings(res, tree);
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ // Now we have the tough task of going from a flags number to actual modifiers with source ranges
+ res.modifiers().addAll(convertModifiersFromFlags(block.getStartPosition(), block.endpos, block.flags));
+ } else {
+ res.internalSetModifiers(getJLS2ModifiersFlags(block.flags));
+ }
+ boolean fillBlock = shouldFillBlock(block, this.focalPoint);
+ if( fillBlock ) {
+ res.setBody(convertBlock(block));
+ } else {
+ Block b = this.ast.newBlock();
+ commonSettings(res, block);
+ res.setBody(b);
+ }
+ return res;
+ }
+ if (tree instanceof JCErroneous || tree instanceof JCSkip) {
+ return null;
+ }
+ ILog.get().error("Unsupported " + tree + " of type" + tree.getClass());
+ Block substitute = this.ast.newBlock();
+ commonSettings(substitute, tree);
+ return substitute;
+ }
+
+ private ASTNode convertMethodInAnnotationTypeDecl(JCMethodDecl javac, ASTNode parent) {
+ AnnotationTypeMemberDeclaration res = new AnnotationTypeMemberDeclaration(this.ast);
+ commonSettings(res, javac);
+ res.modifiers().addAll(convert(javac.getModifiers(), res));
+ res.setType(convertToType(javac.getReturnType()));
+ if( javac.defaultValue != null) {
+ res.setDefault(convertExpression(javac.defaultValue));
+ }
+ if (convertName(javac.getName()) instanceof SimpleName simpleName) {
+ res.setName(simpleName);
+ int start = javac.getPreferredPosition();
+ if (start > -1) {
+ simpleName.setSourceRange(start, javac.getName().toString().length());
+ }
+ }
+ return res;
+ }
+
+ private String getNodeName(ASTNode node) {
+ if( node instanceof AbstractTypeDeclaration atd) {
+ return atd.getName().toString();
+ }
+ if( node instanceof EnumDeclaration ed) {
+ return ed.getName().toString();
+ }
+ return null;
+ }
+
+ private String getMethodDeclName(JCMethodDecl javac, ASTNode parent, boolean records) {
+ String name = javac.getName().toString();
+ boolean javacIsConstructor = Objects.equals(javac.getName(), Names.instance(this.context).init);
+ if( javacIsConstructor) {
+ // sometimes javac mistakes a method with no return type as a constructor
+ String parentName = getNodeName(parent);
+ String tmpString1 = this.rawText.substring(javac.pos);
+ int openParen = tmpString1.indexOf("(");
+ int openBrack = tmpString1.indexOf("{");
+ int endPos = -1;
+ if( openParen != -1 ) {
+ endPos = openParen;
+ }
+ if( records && openBrack != -1 ) {
+ endPos = endPos == -1 ? openBrack : Math.min(openBrack, endPos);
+ }
+ if( endPos != -1 ) {
+ String methodName = tmpString1.substring(0, endPos).trim();
+ if (!methodName.isEmpty() &&
+ Character.isJavaIdentifierStart(methodName.charAt(0)) &&
+ methodName.substring(1).chars().allMatch(Character::isJavaIdentifierPart) &&
+ !methodName.equals(parentName)) {
+ return methodName;
+ }
+ }
+ return parentName;
+ }
+ return name;
+ }
+
+ private MethodDeclaration convertMethodDecl(JCMethodDecl javac, ASTNode parent) {
+ if (TreeInfo.getEndPos(javac, this.javacCompilationUnit.endPositions) <= javac.getStartPosition()) {
+ // not really existing, analysis sugar; let's skip
+ return null;
+ }
+ MethodDeclaration res = this.ast.newMethodDeclaration();
+ commonSettings(res, javac);
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ res.modifiers().addAll(convert(javac.getModifiers(), res));
+ } else {
+ res.internalSetModifiers(getJLS2ModifiersFlags(javac.mods));
+ }
+ String javacName = javac.getName().toString();
+ String methodDeclName = getMethodDeclName(javac, parent, parent instanceof RecordDeclaration);
+ boolean methodDeclNameMatchesInit = Objects.equals(methodDeclName, Names.instance(this.context).init.toString());
+ boolean javacNameMatchesInit = javacName.equals("");
+ boolean javacNameMatchesError = javacName.equals(ERROR);
+ boolean javacNameMatchesInitAndMethodNameMatchesTypeName = javacNameMatchesInit && methodDeclName.equals(getNodeName(parent));
+ boolean isConstructor = methodDeclNameMatchesInit || javacNameMatchesInitAndMethodNameMatchesTypeName;
+ res.setConstructor(isConstructor);
+ if (isConstructor && javac.getParameters().isEmpty()
+ && javac.getBody().endpos == Position.NOPOS) { // probably generated
+ return null;
+ }
+ boolean isCompactConstructor = false;
+ if(isConstructor && parent instanceof RecordDeclaration) {
+ String postName = this.rawText.substring(javac.pos + methodDeclName.length()).trim();
+ String firstChar = postName != null && postName.length() > 0 ? postName.substring(0,1) : null;
+ isCompactConstructor = ("{".equals(firstChar));
+ if( this.ast.apiLevel >= AST.JLS16_INTERNAL) {
+ res.setCompactConstructor(isCompactConstructor);
+ }
+ }
+ boolean malformed = false;
+ if(isConstructor && !javacNameMatchesInitAndMethodNameMatchesTypeName) {
+ malformed = true;
+ }
+ if( javacNameMatchesError || (javacNameMatchesInit && !isConstructor )) {
+ malformed = true;
+ }
+
+ JCTree retTypeTree = javac.getReturnType();
+ Type retType = null;
+ if( !javacNameMatchesError) {
+ var name = this.ast.newSimpleName(methodDeclName);
+ nameSettings(name, javac, methodDeclName, isConstructor);
+ res.setName(name);
+ } else {
+ // javac name is an error, so let's treat the return type as the name
+ if (retTypeTree instanceof JCIdent jcid) {
+ var name = this.ast.newSimpleName(jcid.getName().toString());
+ nameSettings(name, javac, javacName, isConstructor);
+ res.setName(name);
+ retTypeTree = null;
+ if (jcid.toString().equals(getNodeName(parent))) {
+ res.setConstructor(true);
+ isConstructor = true;
+ }
+ }
+ }
+
+ if( retTypeTree == null ) {
+ if( isConstructor && this.ast.apiLevel == AST.JLS2_INTERNAL ) {
+ retType = this.ast.newPrimitiveType(convert(TypeKind.VOID));
+ // // TODO need to find the right range
+ retType.setSourceRange(javac.mods.pos + getJLS2ModifiersFlagsAsStringLength(javac.mods.flags), 0);
+ }
+ } else {
+ retType = convertToType(retTypeTree);
+ }
+ var dims = convertDimensionsAfterPosition(retTypeTree, javac.pos);
+ if (!dims.isEmpty() && retTypeTree.pos > javac.pos ) {
+ // The array dimensions are part of the variable name
+ if( this.ast.apiLevel < AST.JLS8_INTERNAL) {
+ res.setExtraDimensions(dims.size());
+ } else {
+ res.extraDimensions().addAll(dims);
+ }
+ retType = convertToType(unwrapDimensions(retTypeTree, dims.size()));
+ }
+
+ if( retType != null || isConstructor) {
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ res.setReturnType2(retType);
+ } else {
+ res.internalSetReturnType(retType);
+ }
+ } else {
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ res.setReturnType2(null);
+ }
+ }
+
+ if( !isCompactConstructor) {
+ // Compact constructor does not show the parameters even though javac finds them
+ javac.getParameters().stream().map(this::convertVariableDeclaration).forEach(res.parameters()::add);
+ }
+ if (javac.getReceiverParameter() != null) {
+ Type receiverType = convertToType(javac.getReceiverParameter().getType());
+ if (receiverType instanceof AnnotatableType annotable) {
+ javac.getReceiverParameter().getModifiers().getAnnotations().stream() //
+ .map(this::convert)
+ .forEach(annotable.annotations()::add);
+ }
+ if (receiverType != null) {
+ res.setReceiverType(receiverType);
+ }
+ if (javac.getReceiverParameter().getNameExpression() instanceof JCFieldAccess qualifiedName) {
+ res.setReceiverQualifier((SimpleName)toName(qualifiedName.getExpression()));
+ }
+ }
+
+ if( javac.getTypeParameters() != null ) {
+ Iterator i = javac.getTypeParameters().iterator();
+ while(i.hasNext()) {
+ JCTypeParameter next = i.next();
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ res.typeParameters().add(convert(next));
+ } else {
+ // TODO
+ }
+ }
+ }
+
+ if (javac.getBody() != null
+ && javac.getBody().endpos > javac.getBody().getStartPosition()) { // otherwise, it's probably generated by lombok
+ boolean fillBlock = shouldFillBlock(javac, this.focalPoint);
+ if( fillBlock ) {
+ Block b = convertBlock(javac.getBody());
+ if (b != null) {
+ AbstractTypeDeclaration td = findSurroundingTypeDeclaration(parent);
+ boolean isInterface = td instanceof TypeDeclaration td1 && td1.isInterface();
+ long modFlags = javac.getModifiers() == null ? 0 : javac.getModifiers().flags;
+ boolean isAbstractOrNative = (modFlags & (Flags.ABSTRACT | Flags.NATIVE)) != 0;
+ boolean isJlsBelow8 = this.ast.apiLevel < AST.JLS8_INTERNAL;
+ boolean isJlsAbove8 = this.ast.apiLevel > AST.JLS8_INTERNAL;
+ long flagsToCheckForAboveJLS8 = Flags.STATIC | Flags.DEFAULT | (isJlsAbove8 ? Flags.PRIVATE : 0);
+ boolean notAllowed = (isAbstractOrNative || (isInterface && (isJlsBelow8 || (modFlags & flagsToCheckForAboveJLS8) == 0)));
+ if (notAllowed) {
+ res.setFlags(res.getFlags() | ASTNode.MALFORMED);
+ }
+ res.setBody(b);
+ if( (b.getFlags() & ASTNode.MALFORMED) > 0 ) {
+ malformed = true;
+ }
+ }
+ } else {
+ Block b = this.ast.newBlock();
+ commonSettings(res, javac);
+ res.setBody(b);
+ }
+ }
+
+ for (JCExpression thrown : javac.getThrows()) {
+ if (this.ast.apiLevel < AST.JLS8_INTERNAL) {
+ res.thrownExceptions().add(toName(thrown));
+ } else {
+ Type type = convertToType(thrown);
+ if (type != null) {
+ res.thrownExceptionTypes().add(type);
+ }
+ }
+ }
+ if( malformed ) {
+ res.setFlags(res.getFlags() | ASTNode.MALFORMED);
+ }
+ return res;
+ }
+
+ private boolean shouldFillBlock(JCTree tree, int focalPoint2) {
+ int start = tree.getStartPosition();
+ int endPos = tree.getEndPosition(this.javacCompilationUnit.endPositions);
+ if( focalPoint == -1 || (focalPoint >= start && focalPoint <= endPos)) {
+ return true;
+ }
+ return false;
+ }
+ private AbstractTypeDeclaration findSurroundingTypeDeclaration(ASTNode parent) {
+ if( parent == null )
+ return null;
+ if( parent instanceof AbstractTypeDeclaration t) {
+ return t;
+ }
+ return findSurroundingTypeDeclaration(parent.getParent());
+ }
+
+ private VariableDeclaration convertVariableDeclarationForLambda(JCVariableDecl javac) {
+ if(javac.getType() == null && javac.getStartPosition() == javac.getPreferredPosition() /* check no "var" */) {
+ return createVariableDeclarationFragment(javac);
+ } else if (javac.getType() != null && javac.getType().getPreferredPosition() == Position.NOPOS) { // "virtual" node added for analysis, not part of AST
+ return createVariableDeclarationFragment(javac);
+ } else {
+ return convertVariableDeclaration(javac);
+ }
+ }
+ private VariableDeclaration convertVariableDeclaration(JCVariableDecl javac) {
+ // if (singleDecl) {
+ SingleVariableDeclaration res = this.ast.newSingleVariableDeclaration();
+ commonSettings(res, javac);
+ if (convertName(javac.getName()) instanceof SimpleName simpleName) {
+ nameSettings(simpleName, javac, simpleName.toString());
+ res.setName(simpleName);
+ }
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ res.modifiers().addAll(convert(javac.getModifiers(), res));
+ } else {
+ res.internalSetModifiers(getJLS2ModifiersFlags(javac.mods));
+ }
+ var dims = convertDimensionsAfterPosition(javac.getType(), javac.getPreferredPosition()); // +1 to exclude part of the type declared before name
+ if(!dims.isEmpty() && (javac.mods.flags & VARARGS) == 0) {
+ // The array dimensions are part of the variable name
+ if( this.ast.apiLevel < AST.JLS8_INTERNAL) {
+ res.setExtraDimensions(dims.size()); // the type is 1-dim array
+ } else {
+ res.extraDimensions().addAll(dims);
+ }
+ res.setType(convertToType(unwrapDimensions(javac.getType(), dims.size())));
+ } else if ( (javac.mods.flags & VARARGS) != 0) {
+ JCTree type = javac.getType();
+ if (type instanceof JCAnnotatedType annotatedType) {
+ annotatedType.getAnnotations().stream()
+ .map(this::convert)
+ .forEach(res.varargsAnnotations()::add);
+ type = annotatedType.getUnderlyingType();
+ }
+ // We have varity
+ if(type instanceof JCArrayTypeTree arr) {
+ res.setType(convertToType(arr.elemtype));
+ }
+ if( this.ast.apiLevel > AST.JLS2_INTERNAL) {
+ res.setVarargs(true);
+ }
+ } else {
+ // the array dimensions are part of the type
+ if (javac.getType() != null) {
+ if( !(javac.getType() instanceof JCErroneous)) {
+ Type type = convertToType(javac.getType());
+ if (type != null) {
+ res.setType(type);
+ }
+ }
+ } else if (javac.getStartPosition() != javac.getPreferredPosition()
+ && this.rawText.substring(javac.getStartPosition(), javac.getPreferredPosition()).matches("var(\\s)+")) {
+ SimpleName varName = this.ast.newSimpleName("var");
+ varName.setSourceRange(javac.getStartPosition(), varName.getIdentifier().length());
+ Type varType = this.ast.newSimpleType(varName);
+ varType.setSourceRange(varName.getStartPosition(), varName.getLength());
+ res.setType(varType);
+ }
+ }
+ if (javac.getInitializer() != null) {
+ res.setInitializer(convertExpression(javac.getInitializer()));
+ }
+ return res;
+ }
+
+ private int getJLS2ModifiersFlags(JCModifiers mods) {
+ return getJLS2ModifiersFlags(mods.flags);
+ }
+
+ private VariableDeclarationFragment createVariableDeclarationFragment(JCVariableDecl javac) {
+ VariableDeclarationFragment fragment = this.ast.newVariableDeclarationFragment();
+ commonSettings(fragment, javac);
+ int fragmentEnd = javac.getEndPosition(this.javacCompilationUnit.endPositions);
+ int fragmentStart = javac.pos;
+ int fragmentLength = fragmentEnd - fragmentStart; // ???? - 1;
+ fragment.setSourceRange(fragmentStart, Math.max(0, fragmentLength));
+ removeSurroundingWhitespaceFromRange(fragment);
+ removeTrailingCharFromRange(fragment, new char[] {';', ','});
+ removeSurroundingWhitespaceFromRange(fragment);
+ if (convertName(javac.getName()) instanceof SimpleName simpleName) {
+ fragment.setName(simpleName);
+ }
+ var dims = convertDimensionsAfterPosition(javac.getType(), fragmentStart);
+ if( this.ast.apiLevel < AST.JLS8_INTERNAL) {
+ fragment.setExtraDimensions(dims.size());
+ } else {
+ fragment.extraDimensions().addAll(dims);
+ }
+ if (javac.getInitializer() != null) {
+ Expression initializer = convertExpression(javac.getInitializer());
+ fragment.setInitializer(initializer);
+ // we may receive range for `int i = 0;` (with semicolon and newline). If we
+ // have an initializer, use it's endPos instead for the fragment
+ int length = initializer.getStartPosition() + initializer.getLength() - fragment.getStartPosition();
+ if (length >= 0) {
+ fragment.setSourceRange(fragment.getStartPosition(), length);
+ }
+ }
+ return fragment;
+ }
+
+ private FieldDeclaration convertFieldDeclaration(JCVariableDecl javac, ASTNode parent) {
+ VariableDeclarationFragment fragment = createVariableDeclarationFragment(javac);
+ List sameStartPosition = new ArrayList<>();
+ if( parent instanceof AbstractTypeDeclaration decl) {
+ decl.bodyDeclarations().stream().filter(x -> x instanceof FieldDeclaration)
+ .filter(x -> ((FieldDeclaration)x).getType().getStartPosition() == javac.vartype.getStartPosition())
+ .forEach(x -> sameStartPosition.add((ASTNode)x));
+ }
+ if( parent instanceof AnonymousClassDeclaration decl) {
+ decl.bodyDeclarations().stream().filter(x -> x instanceof FieldDeclaration)
+ .filter(x -> ((FieldDeclaration)x).getType().getStartPosition() == javac.vartype.getStartPosition())
+ .forEach(x -> sameStartPosition.add((ASTNode)x));
+ }
+ if( sameStartPosition.size() >= 1 ) {
+ FieldDeclaration fd = (FieldDeclaration)sameStartPosition.get(0);
+ if( fd != null ) {
+ fd.fragments().add(fragment);
+ int newParentEnd = fragment.getStartPosition() + fragment.getLength();
+ fd.setSourceRange(fd.getStartPosition(), newParentEnd - fd.getStartPosition() + 1);
+ }
+ return null;
+ } else {
+ FieldDeclaration res = this.ast.newFieldDeclaration(fragment);
+ commonSettings(res, javac);
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ res.modifiers().addAll(convert(javac.getModifiers(), res));
+ } else {
+ res.internalSetModifiers(getJLS2ModifiersFlags(javac.mods));
+ }
+
+ Type resType = null;
+ int count = fragment.getExtraDimensions();
+ if( count > 0 ) {
+ // must do simple type here
+ JCTree t = javac.getType();
+ if( t instanceof JCArrayTypeTree jcatt) {
+ // unwrap the jcatt count times?
+ JCTree working = jcatt;
+ for( int i = 0; i < count; i++ ) {
+ if( working instanceof JCArrayTypeTree work2) {
+ working = work2.getType();
+ }
+ }
+ resType = convertToType(working);
+ } else {
+ resType = convertToType(javac.getType());
+ }
+ } else {
+ resType = convertToType(javac.getType());
+ }
+ if (resType != null) {
+ res.setType(resType);
+ }
+ if( javac.getType() instanceof JCErroneous && resType instanceof SimpleType st && st.getName() instanceof SimpleName sn && sn.toString().equals(FAKE_IDENTIFIER)) {
+ if( fragment.getName() instanceof SimpleName fragName && fragName.toString().equals(FAKE_IDENTIFIER)) {
+ return null;
+ }
+ }
+
+ return res;
+ }
+ }
+
+
+ private void setJavadocForNode(JCTree javac, ASTNode node) {
+ Comment c = this.javacCompilationUnit.docComments.getComment(javac);
+ if(c != null && (c.getStyle() == Comment.CommentStyle.JAVADOC_BLOCK || c.getStyle() == CommentStyle.JAVADOC_LINE)) {
+ Javadoc javadoc = (Javadoc)convert(c, javac);
+ if (node instanceof BodyDeclaration bodyDeclaration) {
+ bodyDeclaration.setJavadoc(javadoc);
+ bodyDeclaration.setSourceRange(javadoc.getStartPosition(), bodyDeclaration.getStartPosition() + bodyDeclaration.getLength() - javadoc.getStartPosition());
+ } else if (node instanceof ModuleDeclaration moduleDeclaration) {
+ moduleDeclaration.setJavadoc(javadoc);
+ moduleDeclaration.setSourceRange(javadoc.getStartPosition(), moduleDeclaration.getStartPosition() + moduleDeclaration.getLength() - javadoc.getStartPosition());
+ } else if (node instanceof PackageDeclaration packageDeclaration) {
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ packageDeclaration.setJavadoc(javadoc);
+ } else {
+ this.notAttachedComments.add(javadoc);
+ }
+ packageDeclaration.setSourceRange(javadoc.getStartPosition(), packageDeclaration.getStartPosition() + packageDeclaration.getLength() - javadoc.getStartPosition());
+ } else {
+ this.notAttachedComments.add(javadoc);
+ }
+ }
+ }
+
+ private Expression convertExpressionImpl(JCExpression javac) {
+ if (javac instanceof JCIdent ident) {
+ if (Objects.equals(ident.name, Names.instance(this.context)._this)) {
+ ThisExpression res = this.ast.newThisExpression();
+ commonSettings(res, javac);
+ return res;
+ }
+ return toName(ident);
+ }
+ if (javac instanceof JCLiteral literal) {
+ return convertLiteral(literal);
+ }
+ if (javac instanceof JCFieldAccess fieldAccess) {
+ if (Objects.equals(Names.instance(this.context)._class, fieldAccess.getIdentifier())) {
+ TypeLiteral res = this.ast.newTypeLiteral();
+ commonSettings(res, javac);
+ res.setType(convertToType(fieldAccess.getExpression()));
+ return res;
+ }
+ if (Objects.equals(Names.instance(this.context)._this, fieldAccess.getIdentifier())) {
+ ThisExpression res = this.ast.newThisExpression();
+ commonSettings(res, javac);
+ res.setQualifier(toName(fieldAccess.getExpression()));
+ return res;
+ }
+ if (fieldAccess.getExpression() instanceof JCFieldAccess parentFieldAccess && Objects.equals(Names.instance(this.context)._super, parentFieldAccess.getIdentifier())) {
+ SuperFieldAccess res = this.ast.newSuperFieldAccess();
+ commonSettings(res, javac);
+ res.setQualifier(toName(parentFieldAccess.getExpression()));
+ res.setName((SimpleName)convertName(fieldAccess.getIdentifier()));
+ return res;
+ }
+ if (fieldAccess.getExpression() instanceof JCIdent parentFieldAccess && Objects.equals(Names.instance(this.context)._super, parentFieldAccess.getName())) {
+ SuperFieldAccess res = this.ast.newSuperFieldAccess();
+ commonSettings(res, javac);
+ res.setName((SimpleName)convertName(fieldAccess.getIdentifier()));
+ return res;
+ }
+ if (fieldAccess.getExpression() instanceof JCIdent parentFieldAccess && Objects.equals(Names.instance(this.context)._this, parentFieldAccess.getName())) {
+ FieldAccess res = this.ast.newFieldAccess();
+ commonSettings(res, javac);
+ res.setExpression(convertExpression(parentFieldAccess));
+ if (convertName(fieldAccess.getIdentifier()) instanceof SimpleName name) {
+ res.setName(name);
+ }
+ return res;
+ }
+ if (fieldAccess.getExpression() instanceof JCIdent qualifier) {
+ Name qualifierName = convertName(qualifier.getName());
+ SimpleName qualifiedName = (SimpleName)convertName(fieldAccess.getIdentifier());
+ if (qualifiedName == null) {
+ // when there are syntax errors where the statement is not completed.
+ qualifiedName = this.ast.newSimpleName(FAKE_IDENTIFIER);
+ qualifiedName.setFlags(ASTNode.RECOVERED);
+ }
+ QualifiedName res = this.ast.newQualifiedName(qualifierName, qualifiedName);
+ commonSettings(res, javac);
+ return res;
+ }
+ useQualifiedName: if (fieldAccess.getExpression() instanceof JCFieldAccess parentFieldAccess) {
+ JCFieldAccess cursor = parentFieldAccess;
+ if (Objects.equals(Names.instance(this.context)._class, cursor.getIdentifier())
+ || Objects.equals(Names.instance(this.context)._this, cursor.getIdentifier())
+ || Objects.equals(Names.instance(this.context)._super, cursor.getIdentifier())) {
+ break useQualifiedName;
+ }
+ while (cursor.getExpression() instanceof JCFieldAccess newParent) {
+ cursor = newParent;
+ if (Objects.equals(Names.instance(this.context)._class, cursor.getIdentifier())
+ || Objects.equals(Names.instance(this.context)._this, cursor.getIdentifier())
+ || Objects.equals(Names.instance(this.context)._super, cursor.getIdentifier())) {
+ break useQualifiedName;
+ }
+ }
+
+ if (cursor.getExpression() instanceof JCIdent oldestIdentifier
+ && !Objects.equals(Names.instance(this.context)._class, oldestIdentifier.getName())
+ && !Objects.equals(Names.instance(this.context)._this, oldestIdentifier.getName())
+ && !Objects.equals(Names.instance(this.context)._super, oldestIdentifier.getName())) {
+ // all segments are simple names
+ return convertQualifiedName(fieldAccess);
+ }
+ }
+ FieldAccess res = this.ast.newFieldAccess();
+ commonSettings(res, javac);
+ res.setExpression(convertExpression(fieldAccess.getExpression()));
+ if (convertName(fieldAccess.getIdentifier()) instanceof SimpleName name) {
+ res.setName(name);
+ }
+ return res;
+ }
+ if (javac instanceof JCMethodInvocation methodInvocation) {
+ JCExpression nameExpr = methodInvocation.getMethodSelect();
+ if (nameExpr instanceof JCFieldAccess access) {
+ // Handle super method calls first
+ boolean superCall1 = access.getExpression() instanceof JCFieldAccess && Objects.equals(Names.instance(this.context)._super, ((JCFieldAccess)access.getExpression()).getIdentifier());
+ boolean superCall2 = Objects.equals(Names.instance(this.context)._super.toString(), access.getExpression().toString());
+ if (superCall1 || superCall2) {
+ JCFieldAccess fa = superCall1 ? ((JCFieldAccess)access.getExpression()) : access;
+ SuperMethodInvocation res2 = this.ast.newSuperMethodInvocation();
+ commonSettings(res2, javac);
+ methodInvocation.getArguments().stream().map(this::convertExpression).forEach(res2.arguments()::add);
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ methodInvocation.getTypeArguments().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(res2.typeArguments()::add);
+ }
+ if( superCall1 ) {
+ res2.setQualifier(toName(fa.getExpression()));
+ }
+ res2.setName((SimpleName)convertName(access.getIdentifier()));
+ return res2;
+ }
+ }
+
+ MethodInvocation res = this.ast.newMethodInvocation();
+ commonSettings(res, methodInvocation);
+ if (nameExpr instanceof JCIdent ident) {
+ if (Objects.equals(ident.getName(), Names.instance(this.context)._super)) {
+ return convertSuperMethodInvocation(methodInvocation);
+ }
+ SimpleName name = (SimpleName)convertName(ident.getName());
+ commonSettings(name, ident);
+ res.setName(name);
+ } else if (nameExpr instanceof JCFieldAccess access) {
+ boolean superCall1 = access.getExpression() instanceof JCFieldAccess && Objects.equals(Names.instance(this.context)._super, ((JCFieldAccess)access.getExpression()).getIdentifier());
+ boolean superCall2 = Objects.equals(Names.instance(this.context)._super.toString(), access.getExpression().toString());
+ if (superCall1 || superCall2) {
+ JCFieldAccess fa = superCall1 ? ((JCFieldAccess)access.getExpression()) : access;
+ SuperMethodInvocation res2 = this.ast.newSuperMethodInvocation();
+ commonSettings(res2, javac);
+ methodInvocation.getArguments().stream().map(this::convertExpression).forEach(res.arguments()::add);
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ methodInvocation.getTypeArguments().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(res.typeArguments()::add);
+ }
+ if( superCall1 ) {
+ res2.setQualifier(toName(fa.getExpression()));
+ }
+ res2.setName((SimpleName)convertName(access.getIdentifier()));
+ return res2;
+ }
+ if (convertName(access.getIdentifier()) instanceof SimpleName simpleName) {
+ res.setName(simpleName);
+ String asString = access.getIdentifier().toString();
+ commonSettings(simpleName, access);
+ int foundOffset = this.rawText.indexOf(asString, access.getPreferredPosition());
+ if (foundOffset > 0) {
+ simpleName.setSourceRange(foundOffset, asString.length());
+ }
+ }
+ res.setExpression(convertExpression(access.getExpression()));
+ }
+ if (methodInvocation.getArguments() != null) {
+ methodInvocation.getArguments().stream()
+ .map(this::convertExpression)
+ .forEach(res.arguments()::add);
+ }
+ if (methodInvocation.getTypeArguments() != null) {
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ methodInvocation.getTypeArguments().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(res.typeArguments()::add);
+ }
+ }
+ return res;
+ }
+ if (javac instanceof JCNewClass newClass) {
+ ClassInstanceCreation res = this.ast.newClassInstanceCreation();
+ commonSettings(res, javac);
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ res.setType(convertToType(newClass.getIdentifier()));
+ } else {
+ Name n = toName(newClass.clazz);
+ if( n != null )
+ res.setName(n);
+ }
+ if (newClass.getClassBody() != null && newClass.getClassBody() instanceof JCClassDecl javacAnon) {
+ AnonymousClassDeclaration anon = createAnonymousClassDeclaration(javacAnon, res);
+ res.setAnonymousClassDeclaration(anon);
+ }
+ if (newClass.getArguments() != null) {
+ newClass.getArguments().stream()
+ .map(this::convertExpression)
+ .forEach(res.arguments()::add);
+ }
+ if (newClass.encl != null) {
+ res.setExpression(convertExpression(newClass.encl));
+ }
+ if( newClass.getTypeArguments() != null && this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ Iterator it = newClass.getTypeArguments().iterator();
+ while(it.hasNext()) {
+ Type e = convertToType(it.next());
+ if( e != null ) {
+ res.typeArguments().add(e);
+ }
+ }
+ }
+ return res;
+ }
+ if (javac instanceof JCBinary binary) {
+ return handleInfixExpression(binary, javac);
+
+ }
+ if (javac instanceof JCUnary unary) {
+ if (unary.getTag() != Tag.POSTINC && unary.getTag() != Tag.POSTDEC) {
+ PrefixExpression res = this.ast.newPrefixExpression();
+ commonSettings(res, javac);
+ res.setOperand(convertExpression(unary.getExpression()));
+ res.setOperator(switch (unary.getTag()) {
+ case POS -> PrefixExpression.Operator.PLUS;
+ case NEG -> PrefixExpression.Operator.MINUS;
+ case NOT -> PrefixExpression.Operator.NOT;
+ case COMPL -> PrefixExpression.Operator.COMPLEMENT;
+ case PREINC -> PrefixExpression.Operator.INCREMENT;
+ case PREDEC -> PrefixExpression.Operator.DECREMENT;
+ default -> null;
+ });
+ return res;
+ } else {
+ PostfixExpression res = this.ast.newPostfixExpression();
+ commonSettings(res, javac);
+ res.setOperand(convertExpression(unary.getExpression()));
+ res.setOperator(switch (unary.getTag()) {
+ case POSTINC -> PostfixExpression.Operator.INCREMENT;
+ case POSTDEC -> PostfixExpression.Operator.DECREMENT;
+ default -> null;
+ });
+ return res;
+ }
+ }
+ if (javac instanceof JCParens parens) {
+ ParenthesizedExpression res = this.ast.newParenthesizedExpression();
+ commonSettings(res, javac);
+ res.setExpression(convertExpression(parens.getExpression()));
+ return res;
+ }
+ if (javac instanceof JCAssign assign) {
+ Assignment res = this.ast.newAssignment();
+ commonSettings(res, javac);
+ res.setLeftHandSide(convertExpression(assign.getVariable()));
+ res.setRightHandSide(convertExpression(assign.getExpression()));
+ return res;
+ }
+ if (javac instanceof JCAssignOp assignOp) {
+ Assignment res = this.ast.newAssignment();
+ commonSettings(res, javac);
+ res.setLeftHandSide(convertExpression(assignOp.getVariable()));
+ res.setRightHandSide(convertExpression(assignOp.getExpression()));
+ res.setOperator(switch (assignOp.getTag()) {
+ case PLUS_ASG -> Assignment.Operator.PLUS_ASSIGN;
+ case BITOR_ASG -> Assignment.Operator.BIT_OR_ASSIGN;
+ case BITXOR_ASG-> Assignment.Operator.BIT_XOR_ASSIGN;
+ case BITAND_ASG-> Assignment.Operator.BIT_AND_ASSIGN;
+ case SL_ASG-> Assignment.Operator.LEFT_SHIFT_ASSIGN;
+ case SR_ASG-> Assignment.Operator.RIGHT_SHIFT_SIGNED_ASSIGN;
+ case USR_ASG-> Assignment.Operator.RIGHT_SHIFT_UNSIGNED_ASSIGN;
+ case MINUS_ASG-> Assignment.Operator.MINUS_ASSIGN;
+ case MUL_ASG-> Assignment.Operator.TIMES_ASSIGN;
+ case DIV_ASG-> Assignment.Operator.DIVIDE_ASSIGN;
+ case MOD_ASG-> Assignment.Operator.REMAINDER_ASSIGN;
+ default -> null;
+ });
+ return res;
+ }
+ if (javac instanceof JCInstanceOf jcInstanceOf) {
+ JCPattern jcPattern = jcInstanceOf.getPattern();
+ if (jcInstanceOf.getType() != null && jcPattern == null) {
+ InstanceofExpression res = this.ast.newInstanceofExpression();
+ commonSettings(res, javac);
+ res.setLeftOperand(convertExpression(jcInstanceOf.getExpression()));
+ res.setRightOperand(convertToType(jcInstanceOf.getType()));
+ return res;
+ }
+ if (jcPattern instanceof JCAnyPattern) {
+ InstanceofExpression res = this.ast.newInstanceofExpression();
+ commonSettings(res, javac);
+ res.setLeftOperand(convertExpression(jcInstanceOf.getExpression()));
+ throw new UnsupportedOperationException("Right operand not supported yet");
+// return res;
+ }
+ PatternInstanceofExpression res = this.ast.newPatternInstanceofExpression();
+ commonSettings(res, javac);
+ res.setLeftOperand(convertExpression(jcInstanceOf.getExpression()));
+ Pattern p = convert(jcPattern);
+ if( p != null && this.ast.apiLevel >= AST.JLS20_INTERNAL)
+ res.setPattern(convert(jcPattern));
+ else {
+ res.setRightOperand(convertToSingleVarDecl(jcPattern));
+ }
+ return res;
+ }
+ if (javac instanceof JCArrayAccess jcArrayAccess) {
+ ArrayAccess res = this.ast.newArrayAccess();
+ commonSettings(res, javac);
+ res.setArray(convertExpression(jcArrayAccess.getExpression()));
+ res.setIndex(convertExpression(jcArrayAccess.getIndex()));
+ return res;
+ }
+ if (javac instanceof JCTypeCast jcCast) {
+ CastExpression res = this.ast.newCastExpression();
+ commonSettings(res, javac);
+ res.setExpression(convertExpression(jcCast.getExpression()));
+ res.setType(convertToType(jcCast.getType()));
+ return res;
+ }
+ if (javac instanceof JCMemberReference jcMemberReference) {
+ JCExpression qualifierExpression = jcMemberReference.getQualifierExpression();
+ if (Objects.equals(Names.instance(this.context).init, jcMemberReference.getName())) {
+ CreationReference res = this.ast.newCreationReference();
+ commonSettings(res, javac);
+ res.setType(convertToType(qualifierExpression));
+ if (jcMemberReference.getTypeArguments() != null) {
+ jcMemberReference.getTypeArguments().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(res.typeArguments()::add);
+ }
+ return res;
+ } else if (qualifierExpression.getKind() == Kind.PARAMETERIZED_TYPE || qualifierExpression.getKind() == Kind.ARRAY_TYPE) {
+ TypeMethodReference res = this.ast.newTypeMethodReference();
+ commonSettings(res, javac);
+ res.setType(convertToType(qualifierExpression));
+ res.setName((SimpleName)convertName(jcMemberReference.getName()));
+ if (jcMemberReference.getTypeArguments() != null) {
+ jcMemberReference.getTypeArguments().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(res.typeArguments()::add);
+ }
+ return res;
+ } else if (qualifierExpression instanceof JCIdent ident
+ && Names.instance(this.context)._super.equals(ident.getName())) {
+ SuperMethodReference res = this.ast.newSuperMethodReference();
+ commonSettings(res, javac);
+ res.setName((SimpleName)convertName(jcMemberReference.getName()));
+ if (jcMemberReference.getTypeArguments() != null) {
+ jcMemberReference.getTypeArguments().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(res.typeArguments()::add);
+ }
+ return res;
+ } else if (qualifierExpression instanceof JCFieldAccess fieldAccess
+ && Names.instance(this.context)._super.equals(fieldAccess.getIdentifier())) {
+ SuperMethodReference res = this.ast.newSuperMethodReference();
+ commonSettings(res, javac);
+ res.setName((SimpleName)convertName(jcMemberReference.getName()));
+ res.setQualifier(toName(fieldAccess.getExpression()));
+ if (jcMemberReference.getTypeArguments() != null) {
+ jcMemberReference.getTypeArguments().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(res.typeArguments()::add);
+ }
+ return res;
+ } else {
+ ExpressionMethodReference res = this.ast.newExpressionMethodReference();
+ commonSettings(res, javac);
+ res.setExpression(convertExpression(jcMemberReference.getQualifierExpression()));
+ res.setName((SimpleName)convertName(jcMemberReference.getName()));
+ if (jcMemberReference.getTypeArguments() != null) {
+ jcMemberReference.getTypeArguments().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(res.typeArguments()::add);
+ }
+ return res;
+ }
+ }
+ if (javac instanceof JCConditional jcCondition) {
+ ConditionalExpression res = this.ast.newConditionalExpression();
+ commonSettings(res, javac);
+ res.setExpression(convertExpression(jcCondition.getCondition()));
+ res.setThenExpression(convertExpression(jcCondition.getTrueExpression()));
+ res.setElseExpression(convertExpression(jcCondition.getFalseExpression()));
+ return res;
+ }
+ if (javac instanceof JCLambda jcLambda) {
+ LambdaExpression res = this.ast.newLambdaExpression();
+ commonSettings(res, javac);
+ jcLambda.getParameters().stream()
+ .filter(JCVariableDecl.class::isInstance)
+ .map(JCVariableDecl.class::cast)
+ .map(this::convertVariableDeclarationForLambda)
+ .forEach(res.parameters()::add);
+ int arrowIndex = this.rawText.indexOf("->", jcLambda.getStartPosition());
+ int parenthesisIndex = this.rawText.indexOf(")", jcLambda.getStartPosition());
+ res.setParentheses(parenthesisIndex >= 0 && parenthesisIndex < arrowIndex);
+ ASTNode body = jcLambda.getBody() instanceof JCExpression expr ? convertExpression(expr) :
+ jcLambda.getBody() instanceof JCStatement stmt ? convertStatement(stmt, res) :
+ null;
+ if( body != null )
+ res.setBody(body);
+ // TODO set parenthesis looking at the next non-whitespace char after the last parameter
+ int endPos = jcLambda.getEndPosition(this.javacCompilationUnit.endPositions);
+ res.setSourceRange(jcLambda.pos, endPos - jcLambda.pos);
+ return res;
+ }
+ if (javac instanceof JCNewArray jcNewArray) {
+ ArrayCreation res = this.ast.newArrayCreation();
+ commonSettings(res, javac);
+ if (jcNewArray.getType() == null) {
+ // we have no type, we should return an initializer directly
+ ArrayInitializer ret = createArrayInitializerFromJCNewArray(jcNewArray);
+ return ret;
+ }
+
+ if (jcNewArray.getType() != null) {
+ Type type = convertToType(jcNewArray.getType());
+ ArrayType arrayType;
+ if (type instanceof ArrayType childArrayType) {
+ arrayType = childArrayType;
+ if( this.ast.apiLevel >= AST.JLS8_INTERNAL) {
+ var extraDimensions = jcNewArray.getDimAnnotations().stream()
+ .map(annotations -> annotations.stream().map(this::convert).toList())
+ .map(annotations -> {
+ Dimension dim = this.ast.newDimension();
+ dim.annotations().addAll(annotations);
+ int startOffset = annotations.stream().mapToInt(Annotation::getStartPosition).min().orElse(-1);
+ int endOffset = annotations.stream().mapToInt(ann -> ann.getStartPosition() + ann.getLength()).max().orElse(-1);
+ dim.setSourceRange(startOffset, endOffset - startOffset);
+ return dim;
+ })
+ .toList();
+ if (arrayType.dimensions().isEmpty()) {
+ arrayType.dimensions().addAll(extraDimensions);
+ } else {
+ var lastDimension = arrayType.dimensions().removeFirst();
+ arrayType.dimensions().addAll(extraDimensions);
+ arrayType.dimensions().add(lastDimension);
+ }
+ int totalRequiredDims = countDimensions(jcNewArray.getType()) + 1;
+ int totalCreated = arrayType.dimensions().size();
+ if( totalCreated < totalRequiredDims) {
+ int endPos = jcNewArray.getEndPosition(this.javacCompilationUnit.endPositions);
+ int startPos = jcNewArray.getStartPosition();
+ String raw = this.rawText.substring(startPos, endPos);
+ for( int i = 0; i < totalRequiredDims; i++ ) {
+ int absoluteEndChar = startPos + ordinalIndexOf(raw, "]", i+1);
+ int absoluteEnd = absoluteEndChar + 1;
+ int absoluteStart = startPos + ordinalIndexOf(raw, "[", i+1);
+ boolean found = false;
+ if( absoluteEnd != -1 && absoluteStart != -1 ) {
+ for( int j = 0; i < totalCreated && !found; j++ ) {
+ Dimension d = (Dimension)arrayType.dimensions().get(j);
+ if( d.getStartPosition() == absoluteStart && (d.getStartPosition() + d.getLength()) == absoluteEnd) {
+ found = true;
+ }
+ }
+ if( !found ) {
+ // Need to make a new one
+ Dimension d = this.ast.newDimension();
+ d.setSourceRange(absoluteStart, absoluteEnd - absoluteStart);
+ arrayType.dimensions().add(i, d);
+ totalCreated++;
+ }
+ }
+ }
+ }
+ } else {
+ // JLS < 8, just wrap underlying type
+ arrayType = this.ast.newArrayType(childArrayType);
+ }
+ } else if(jcNewArray.dims != null && jcNewArray.dims.size() > 0 ){
+ // Child is not array type
+ arrayType = this.ast.newArrayType(type);
+ int dims = jcNewArray.dims.size();
+ for( int i = 0; i < dims - 1; i++ ) {
+ if( this.ast.apiLevel >= AST.JLS8_INTERNAL) {
+ // TODO, this dimension needs source range
+ arrayType.dimensions().addFirst(this.ast.newDimension());
+ } else {
+ // JLS < 8, wrap underlying
+ arrayType = this.ast.newArrayType(arrayType);
+ }
+ }
+ } else {
+ // Child is not array type, and 0 dims for underlying
+ arrayType = this.ast.newArrayType(type);
+ }
+ commonSettings(arrayType, jcNewArray.getType());
+ res.setType(arrayType);
+ }
+ jcNewArray.getDimensions().map(this::convertExpression).forEach(res.dimensions()::add);
+ if (jcNewArray.getInitializers() != null) {
+ res.setInitializer(createArrayInitializerFromJCNewArray(jcNewArray));
+ }
+ return res;
+ }
+ if (javac instanceof JCAnnotation jcAnnot) {
+ return convert(jcAnnot);
+ }
+ if (javac instanceof JCPrimitiveTypeTree primitiveTree) {
+ SimpleName res = this.ast.newSimpleName(primitiveTree.getPrimitiveTypeKind().name());
+ commonSettings(res, javac);
+ return res;
+ }
+ if (javac instanceof JCSwitchExpression jcSwitch) {
+ SwitchExpression res = this.ast.newSwitchExpression();
+ commonSettings(res, javac);
+ JCExpression switchExpr = jcSwitch.getExpression();
+ if( switchExpr instanceof JCParens jcp) {
+ switchExpr = jcp.getExpression();
+ }
+ res.setExpression(convertExpression(switchExpr));
+
+ List cases = jcSwitch.getCases();
+ Iterator it = cases.iterator();
+ ArrayList bodyList = new ArrayList<>();
+ while(it.hasNext()) {
+ JCCase switchCase = it.next();
+ bodyList.add(switchCase);
+ if( switchCase.getCaseKind() == CaseKind.STATEMENT ) {
+ if( switchCase.getStatements() != null && switchCase.getStatements().size() > 0 ) {
+ bodyList.addAll(switchCase.getStatements());
+ }
+ } else {
+ bodyList.add(switchCase.getBody());
+ }
+ }
+
+ Iterator stmtIterator = bodyList.iterator();
+ while(stmtIterator.hasNext()) {
+ JCTree next = stmtIterator.next();
+ if( next instanceof JCStatement jcs) {
+ Statement s1 = convertStatement(jcs, res);
+ if( s1 != null ) {
+ res.statements().add(s1);
+ }
+ } else if( next instanceof JCExpression jce) {
+ Expression s1 = convertExpression(jce);
+ if( s1 != null ) {
+ // make a yield statement out of it??
+ YieldStatement r1 = this.ast.newYieldStatement();
+ commonSettings(r1, jce);
+ r1.setExpression(s1);
+ res.statements().add(r1);
+ }
+ }
+ }
+ return res;
+ }
+ if (javac instanceof JCTree.JCArrayTypeTree arrayTypeTree) {
+ Type type = convertToType(javac);
+ TypeLiteral res = this.ast.newTypeLiteral();
+ res.setType(type);
+ commonSettings(res, arrayTypeTree);
+ return res;
+ }
+ return null;
+ }
+
+ private SingleVariableDeclaration convertToSingleVarDecl(JCPattern jcPattern) {
+ if( jcPattern instanceof JCBindingPattern jcbp && jcbp.var instanceof JCVariableDecl decl) {
+ SingleVariableDeclaration vdd = (SingleVariableDeclaration)convertVariableDeclaration(decl);
+ return vdd;
+ }
+ return null;
+ }
+
+ private List consecutiveInfixExpressionsWithEqualOps(JCBinary binary, Tag opcode) {
+ return consecutiveInfixExpressionsWithEqualOps(binary, opcode, new ArrayList());
+ }
+ private List consecutiveInfixExpressionsWithEqualOps(
+ JCBinary binary, Tag opcode, List consecutive) {
+
+ if( opcode.equals(binary.getTag())) {
+ if( consecutive != null ) {
+ JCExpression left = binary.getLeftOperand();
+ if( left instanceof JCBinary jcb) {
+ consecutive = consecutiveInfixExpressionsWithEqualOps(jcb, opcode, consecutive);
+ } else {
+ consecutive.add(left);
+ }
+ }
+ if( consecutive != null ) {
+ JCExpression right = binary.getRightOperand();
+ if( right instanceof JCBinary jcb) {
+ consecutive = consecutiveInfixExpressionsWithEqualOps(jcb, opcode, consecutive);
+ } else {
+ consecutive.add(right);
+ }
+ }
+ return consecutive;
+ }
+ return null;
+ }
+
+ private Expression handleInfixExpression(JCBinary binary, JCExpression javac) {
+ List conseq = consecutiveInfixExpressionsWithEqualOps(binary, binary.getTag());
+ if( conseq != null && conseq.size() > 2 ) {
+ return handleConsecutiveInfixExpression(binary, javac, conseq);
+ }
+
+ InfixExpression res = this.ast.newInfixExpression();
+ commonSettings(res, javac);
+
+ Expression left = convertExpression(binary.getLeftOperand());
+ if (left != null) {
+ res.setLeftOperand(left);
+ }
+ Expression right = convertExpression(binary.getRightOperand());
+ if (right != null) {
+ res.setRightOperand(right);
+ }
+ res.setOperator(binaryTagToInfixOperator(binary.getTag()));
+ return res;
+ }
+
+ private Expression handleConsecutiveInfixExpression(JCBinary binary, JCExpression javac,
+ List conseq) {
+
+ InfixExpression res = this.ast.newInfixExpression();
+ commonSettings(res, javac);
+
+ Expression left = convertExpression(conseq.get(0));
+ if (left != null) {
+ res.setLeftOperand(left);
+ }
+ Expression right = convertExpression(conseq.get(1));
+ if (right != null) {
+ res.setRightOperand(right);
+ }
+ for( int i = 2; i < conseq.size(); i++ ) {
+ res.extendedOperands().add(convertExpression(conseq.get(i)));
+ }
+
+ res.setOperator(binaryTagToInfixOperator(binary.getTag()));
+ return res;
+ }
+
+ private InfixExpression.Operator binaryTagToInfixOperator(Tag t) {
+ return switch (t) {
+ case OR -> InfixExpression.Operator.CONDITIONAL_OR;
+ case AND -> InfixExpression.Operator.CONDITIONAL_AND;
+ case BITOR -> InfixExpression.Operator.OR;
+ case BITXOR -> InfixExpression.Operator.XOR;
+ case BITAND -> InfixExpression.Operator.AND;
+ case EQ -> InfixExpression.Operator.EQUALS;
+ case NE -> InfixExpression.Operator.NOT_EQUALS;
+ case LT -> InfixExpression.Operator.LESS;
+ case GT -> InfixExpression.Operator.GREATER;
+ case LE -> InfixExpression.Operator.LESS_EQUALS;
+ case GE -> InfixExpression.Operator.GREATER_EQUALS;
+ case SL -> InfixExpression.Operator.LEFT_SHIFT;
+ case SR -> InfixExpression.Operator.RIGHT_SHIFT_SIGNED;
+ case USR -> InfixExpression.Operator.RIGHT_SHIFT_UNSIGNED;
+ case PLUS -> InfixExpression.Operator.PLUS;
+ case MINUS -> InfixExpression.Operator.MINUS;
+ case MUL -> InfixExpression.Operator.TIMES;
+ case DIV -> InfixExpression.Operator.DIVIDE;
+ case MOD -> InfixExpression.Operator.REMAINDER;
+ default -> null;
+ };
+ }
+
+
+ /**
+ * precondition: you've checked all the segments are identifier that can be used in a qualified name
+ */
+ private Name convertQualifiedName(JCFieldAccess fieldAccess) {
+ JCExpression parent = fieldAccess.getExpression();
+ Name parentName;
+ if (parent instanceof JCFieldAccess parentFieldAccess) {
+ parentName = convertQualifiedName(parentFieldAccess);
+ } else if (parent instanceof JCIdent parentIdent) {
+ parentName = convertName(parentIdent.getName());
+ } else {
+ throw new IllegalArgumentException("Unrecognized javac AST node type: " + parent.getClass().getCanonicalName());
+ }
+ commonSettings(parentName, parent);
+ SimpleName segmentName = (SimpleName)convertName(fieldAccess.getIdentifier());
+ int endPos = fieldAccess.getEndPosition(this.javacCompilationUnit.endPositions);
+ int startPos = endPos - fieldAccess.getIdentifier().length();
+ segmentName.setSourceRange(startPos, fieldAccess.getIdentifier().length());
+ QualifiedName res = this.ast.newQualifiedName(parentName, segmentName);
+ commonSettings(res, fieldAccess);
+ return res;
+ }
+
+ private Expression convertExpression(JCExpression javac) {
+ Expression ret = convertExpressionImpl(javac);
+ if( ret != null )
+ return ret;
+
+ // Handle errors or default situation
+ if (javac instanceof JCErroneous error) {
+ if (error.getErrorTrees().size() == 1) {
+ JCTree tree = error.getErrorTrees().get(0);
+ if (tree instanceof JCExpression nestedExpr) {
+ try {
+ return convertExpression(nestedExpr);
+ } catch (Exception ex) {
+ // pass-through: do not break when attempting such reconcile
+ }
+ }
+ }
+ var res = this.ast.newSimpleName(FAKE_IDENTIFIER);
+ res.setFlags(ASTNode.RECOVERED);
+ commonSettings(res, javac);
+ return res;
+ }
+ ILog.get().error("Unsupported " + javac + " of type" + (javac == null ? "null" : javac.getClass()));
+ var res = this.ast.newSimpleName(FAKE_IDENTIFIER);
+ res.setFlags(ASTNode.RECOVERED);
+ commonSettings(res, javac);
+ return res;
+ }
+
+ private Pattern convert(JCPattern jcPattern) {
+ if (this.ast.apiLevel >= AST.JLS21_INTERNAL) {
+ if (jcPattern instanceof JCBindingPattern jcBindingPattern) {
+ TypePattern jdtPattern = this.ast.newTypePattern();
+ commonSettings(jdtPattern, jcBindingPattern);
+ jdtPattern.setPatternVariable((SingleVariableDeclaration)convertVariableDeclaration(jcBindingPattern.var));
+ return jdtPattern;
+ } else if (jcPattern instanceof JCRecordPattern jcRecordPattern) {
+ RecordPattern jdtPattern = this.ast.newRecordPattern();
+ commonSettings(jdtPattern, jcRecordPattern);
+ jdtPattern.setPatternType(convertToType(jcRecordPattern.deconstructor));
+ for (JCPattern nestedJcPattern : jcRecordPattern.nested) {
+ jdtPattern.patterns().add(convert(nestedJcPattern));
+ }
+ return jdtPattern;
+ } else if (jcPattern instanceof JCAnyPattern jcAnyPattern) {
+ TypePattern jdtPattern = this.ast.newTypePattern();
+ commonSettings(jdtPattern, jcAnyPattern);
+ VariableDeclarationFragment variable = this.ast.newVariableDeclarationFragment();
+ commonSettings(variable, jcAnyPattern);
+ variable.setName(this.ast.newSimpleName("_"));
+ jdtPattern.setPatternVariable(variable);
+ return jdtPattern;
+ }
+ }
+ return null;
+ }
+
+ private ArrayInitializer createArrayInitializerFromJCNewArray(JCNewArray jcNewArray) {
+ ArrayInitializer initializer = this.ast.newArrayInitializer();
+ commonSettings(initializer, jcNewArray);
+ if (!jcNewArray.getInitializers().isEmpty()) {
+ jcNewArray.getInitializers().stream().map(this::convertExpression).forEach(initializer.expressions()::add);
+ this.rawText.charAt(0);
+ 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() + 1;
+ while (end < this.rawText.length() && this.rawText.charAt(end) != '}') {
+ end++;
+ }
+ initializer.setSourceRange(start, end - start);
+ }
+ return initializer;
+ }
+
+ private AnonymousClassDeclaration createAnonymousClassDeclaration(JCClassDecl javacAnon, ASTNode parent) {
+ AnonymousClassDeclaration anon = this.ast.newAnonymousClassDeclaration();
+ commonSettings(anon, javacAnon);
+ if (javacAnon.getMembers() != null) {
+ List members = javacAnon.getMembers();
+ for( int i = 0; i < members.size(); i++ ) {
+ ASTNode decl = convertBodyDeclaration(members.get(i), anon);
+ if( decl != null ) {
+ anon.bodyDeclarations().add(decl);
+ }
+ }
+ }
+ return anon;
+ }
+
+ /**
+ *
+ * @param tree
+ * @param pos
+ * @return a list of dimensions for the given type. If target < JLS8, then
+ * it returns a list of null objects, of the right size for the dimensions
+ */
+ private List convertDimensionsAfterPosition(JCTree tree, int pos) {
+ if (tree == null) {
+ return List.of();
+ }
+ List res = new ArrayList<>();
+ JCTree elem = tree;
+ do {
+ if( elem.pos >= pos) {
+ if (elem instanceof JCArrayTypeTree arrayType) {
+ if (this.ast.apiLevel < AST.JLS8_INTERNAL) {
+ res.add(null);
+ } else {
+ Dimension dimension = this.ast.newDimension();
+ res.add(dimension);
+ // Would be better to use a Tokenizer here that is capable of skipping comments
+ int startPosition = this.rawText.indexOf('[', arrayType.pos);
+ int endPosition = this.rawText.indexOf(']', startPosition);
+ dimension.setSourceRange(startPosition, endPosition - startPosition + 1);
+ }
+ elem = arrayType.getType();
+ } else if (elem instanceof JCAnnotatedType annotated && annotated.getUnderlyingType() instanceof JCArrayTypeTree arrayType) {
+ if (this.ast.apiLevel < AST.JLS8_INTERNAL) {
+ res.add(null);
+ } else {
+ Dimension dimension = this.ast.newDimension();
+ annotated.getAnnotations().stream()
+ .map(this::convert)
+ .forEach(dimension.annotations()::add);
+ // Would be better to use a Tokenizer here that is capable of skipping comments
+ int startPosition = this.rawText.indexOf('[', arrayType.pos);
+ int endPosition = this.rawText.indexOf(']', startPosition);
+ dimension.setSourceRange(startPosition, endPosition - startPosition + 1);
+ res.add(dimension);
+ }
+ elem = arrayType.getType();
+ } else {
+ elem = null;
+ }
+ } else {
+ elem = null;
+ }
+ } while (elem != null);
+ return res;
+ }
+
+ private JCTree unwrapDimensions(JCTree tree, int count) {
+ JCTree elem = tree;
+ while (count > 0) {
+ if (elem instanceof JCArrayTypeTree arrayTree) {
+ elem = arrayTree.getType();
+ count--;
+ } else if (elem instanceof JCAnnotatedType annotated && annotated.getUnderlyingType() instanceof JCArrayTypeTree arrayType) {
+ elem = arrayType.getType();
+ count--;
+ } else {
+ count = 0;
+ }
+
+ }
+ return elem;
+ }
+
+ private int countDimensions(JCTree tree) {
+ JCTree elem = tree;
+ int count = 0;
+ boolean done = false;
+ while (!done) {
+ if (elem instanceof JCArrayTypeTree arrayTree) {
+ elem = arrayTree.getType();
+ count++;
+ } else if (elem instanceof JCAnnotatedType annotated && annotated.getUnderlyingType() instanceof JCArrayTypeTree arrayType) {
+ elem = arrayType.getType();
+ count++;
+ } else {
+ done = true;
+ }
+ }
+ return count;
+ }
+
+
+ private SuperMethodInvocation convertSuperMethodInvocation(JCMethodInvocation javac) {
+ SuperMethodInvocation res = this.ast.newSuperMethodInvocation();
+ commonSettings(res, javac);
+ javac.getArguments().stream().map(this::convertExpression).forEach(res.arguments()::add);
+
+ //res.setFlags(javac.getFlags() | ASTNode.MALFORMED);
+ if( this.ast.apiLevel > AST.JLS2_INTERNAL) {
+ javac.getTypeArguments().stream().map(this::convertToType).forEach(res.typeArguments()::add);
+ }
+ return res;
+ }
+
+ private SuperConstructorInvocation convertSuperConstructorInvocation(JCMethodInvocation javac) {
+ SuperConstructorInvocation res = this.ast.newSuperConstructorInvocation();
+ commonSettings(res, javac);
+ ensureTrailingSemicolonInRange(res);
+ javac.getArguments().stream().map(this::convertExpression).forEach(res.arguments()::add);
+
+ //res.setFlags(javac.getFlags() | ASTNode.MALFORMED);
+ if( this.ast.apiLevel > AST.JLS2_INTERNAL) {
+ javac.getTypeArguments().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(res.typeArguments()::add);
+ }
+ if( javac.getMethodSelect() instanceof JCFieldAccess jcfa && jcfa.selected != null ) {
+ res.setExpression(convertExpression(jcfa.selected));
+ }
+ return res;
+ }
+
+
+ private ConstructorInvocation convertThisConstructorInvocation(JCMethodInvocation javac) {
+ ConstructorInvocation res = this.ast.newConstructorInvocation();
+ commonSettings(res, javac);
+ // add the trailing `;`
+ // it's always there, since this is always a statement, since this is always `this();` or `super();`
+ // (or equivalent with type parameters)
+ res.setSourceRange(res.getStartPosition(), res.getLength() + 1);
+ javac.getArguments().stream().map(this::convertExpression).forEach(res.arguments()::add);
+ if( this.ast.apiLevel > AST.JLS2_INTERNAL) {
+ javac.getTypeArguments().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(res.typeArguments()::add);
+ }
+ return res;
+ }
+
+ private Expression convertLiteral(JCLiteral literal) {
+ Object value = literal.getValue();
+ if (value instanceof Number) {
+ // to check if the literal is actually a prefix expression of it is a hex
+ // negative value we need to check the source char value.
+ char firstChar = this.rawText.substring(literal.getStartPosition(), literal.getStartPosition() + 1)
+ .charAt(0);
+
+ if( firstChar != '-' ) {
+ NumberLiteral res = this.ast.newNumberLiteral();
+ commonSettings(res, literal);
+ String fromSrc = this.rawText.substring(res.getStartPosition(), res.getStartPosition() + res.getLength());
+ try {
+ res.setToken(fromSrc);
+ } catch (IllegalArgumentException ex) {
+ // probably some lombok oddity, let's ignore
+ }
+ return res;
+ } else {
+ PrefixExpression res = this.ast.newPrefixExpression();
+ commonSettings(res, literal);
+
+ String fromSrc = this.rawText.substring(res.getStartPosition()+1, res.getStartPosition() + res.getLength());
+ NumberLiteral operand = this.ast.newNumberLiteral();
+ commonSettings(operand, literal);
+ operand.setToken(fromSrc);
+
+ res.setOperand(operand);
+ res.setOperator(Operator.MINUS);
+ return res;
+ }
+ }
+ if (value instanceof String string) {
+ boolean malformed = false;
+ if (this.rawText.charAt(literal.pos) == '"'
+ && this.rawText.charAt(literal.pos + 1) == '"'
+ && this.rawText.charAt(literal.pos + 2) == '"') {
+ if (this.ast.apiLevel() > AST.JLS14) {
+ TextBlock res = this.ast.newTextBlock();
+ commonSettings(res, literal);
+ String rawValue = this.rawText.substring(literal.pos, literal.getEndPosition(this.javacCompilationUnit.endPositions));
+ res.internalSetEscapedValue(rawValue, string);
+ return res;
+ }
+ malformed = true;
+ }
+ StringLiteral res = this.ast.newStringLiteral();
+ commonSettings(res, literal);
+ int startPos = res.getStartPosition();
+ int len = res.getLength();
+ if( string.length() != len && len > 2) {
+ try {
+ string = this.rawText.substring(startPos, startPos + len);
+ if (!string.startsWith("\"")) {
+ string = '"' + string;
+ }
+ if (!string.endsWith("\"")) {
+ string = string + '"';
+ }
+ res.internalSetEscapedValue(string);
+ } catch(IndexOutOfBoundsException ignore) {
+ res.setLiteralValue(string); // TODO: we want the token here
+ }
+ } else {
+ res.setLiteralValue(string); // TODO: we want the token here
+ }
+ if (malformed) {
+ res.setFlags(res.getFlags() | ASTNode.MALFORMED);
+ }
+ return res;
+ }
+ if (value instanceof Boolean string) {
+ BooleanLiteral res = this.ast.newBooleanLiteral(string.booleanValue());
+ commonSettings(res, literal);
+ return res;
+ }
+ if (value == null) {
+ NullLiteral res = this.ast.newNullLiteral();
+ commonSettings(res, literal);
+ return res;
+ }
+ if (value instanceof Character v) {
+ CharacterLiteral res = this.ast.newCharacterLiteral();
+ commonSettings(res, literal);
+ res.setCharValue(v.charValue());
+ return res;
+ }
+ throw new UnsupportedOperationException("Not supported yet " + literal + "\n of type" + literal.getClass().getName());
+ }
+
+ private Statement convertStatement(JCStatement javac, ASTNode parent) {
+ if (TreeInfo.getEndPos(javac, this.javacCompilationUnit.endPositions) <= javac.getPreferredPosition()) {
+ return null;
+ }
+ if (javac instanceof JCReturn returnStatement) {
+ ReturnStatement res = this.ast.newReturnStatement();
+ commonSettings(res, javac);
+ if (returnStatement.getExpression() != null) {
+ res.setExpression(convertExpression(returnStatement.getExpression()));
+ }
+ return res;
+ }
+ if (javac instanceof JCBlock block) {
+ return convertBlock(block);
+ }
+ if (javac instanceof JCExpressionStatement jcExpressionStatement) {
+ JCExpression jcExpression = jcExpressionStatement.getExpression();
+ if (jcExpression instanceof JCMethodInvocation jcMethodInvocation
+ && jcMethodInvocation.getMethodSelect() instanceof JCIdent methodName
+ && Objects.equals(methodName.getName(), Names.instance(this.context)._this)) {
+ return convertThisConstructorInvocation(jcMethodInvocation);
+ }
+ if (jcExpressionStatement.getExpression() == null) {
+ return null;
+ }
+ if (jcExpressionStatement.getExpression() instanceof JCErroneous jcError) {
+ if (jcError.getErrorTrees().size() == 1) {
+ JCTree tree = jcError.getErrorTrees().get(0);
+ if (tree instanceof JCStatement nestedStmt) {
+ try {
+ Statement stmt = convertStatement(nestedStmt, parent);
+ if( stmt != null )
+ stmt.setFlags(stmt.getFlags() | ASTNode.RECOVERED);
+ return stmt;
+ } catch (Exception ex) {
+ // pass-through: do not break when attempting such reconcile
+ }
+ }
+ if (tree instanceof JCExpression expr) {
+ Expression expression = convertExpression(expr);
+ ExpressionStatement res = this.ast.newExpressionStatement(expression);
+ commonSettings(res, javac);
+ return res;
+ }
+ }
+ parent.setFlags(parent.getFlags() | ASTNode.MALFORMED);
+ return null;
+ }
+ boolean uniqueCaseFound = false;
+ if (jcExpressionStatement.getExpression() instanceof JCMethodInvocation methodInvocation) {
+ JCExpression nameExpr = methodInvocation.getMethodSelect();
+ if (nameExpr instanceof JCIdent ident) {
+ if (Objects.equals(ident.getName(), Names.instance(this.context)._super)) {
+ uniqueCaseFound = true;
+ }
+ }
+ if (nameExpr instanceof JCFieldAccess jcfa) {
+ if (Objects.equals(jcfa.getIdentifier(), Names.instance(this.context)._super)) {
+ uniqueCaseFound = true;
+ }
+ }
+ }
+ if( uniqueCaseFound ) {
+ return convertSuperConstructorInvocation((JCMethodInvocation)jcExpressionStatement.getExpression());
+ }
+ ExpressionStatement res = this.ast.newExpressionStatement(convertExpression(jcExpressionStatement.getExpression()));
+ commonSettings(res, javac);
+ return res;
+ }
+ if (javac instanceof JCVariableDecl jcVariableDecl) {
+ VariableDeclarationFragment fragment = createVariableDeclarationFragment(jcVariableDecl);
+ List sameStartPosition = new ArrayList<>();
+ if (parent instanceof Block decl && jcVariableDecl.vartype != null) {
+ decl.statements().stream().filter(x -> x instanceof VariableDeclarationStatement)
+ .filter(x -> ((VariableDeclarationStatement)x).getType().getStartPosition() == jcVariableDecl.vartype.getStartPosition())
+ .forEach(x -> sameStartPosition.add((ASTNode)x));
+ } else if( parent instanceof ForStatement decl && jcVariableDecl.vartype != null) {
+ // TODO somehow doubt this will work as expected
+ decl.initializers().stream().filter(x -> x instanceof VariableDeclarationExpression)
+ .filter(x -> ((VariableDeclarationExpression)x).getType().getStartPosition() == jcVariableDecl.vartype.getStartPosition())
+ .forEach(x -> sameStartPosition.add((ASTNode)x));
+ }
+ if( sameStartPosition.size() >= 1 ) {
+ Object obj0 = sameStartPosition.get(0);
+ if( obj0 instanceof VariableDeclarationStatement fd ) {
+ fd.fragments().add(fragment);
+ int newParentEnd = fragment.getStartPosition() + fragment.getLength();
+ fd.setSourceRange(fd.getStartPosition(), newParentEnd - fd.getStartPosition() + 1);
+ removeSurroundingWhitespaceFromRange(fd);
+ } else if( obj0 instanceof VariableDeclarationExpression fd ) {
+ fd.fragments().add(fragment);
+ int newParentEnd = fragment.getStartPosition() + fragment.getLength();
+ fd.setSourceRange(fd.getStartPosition(), newParentEnd - fd.getStartPosition() + 1);
+ removeTrailingSemicolonFromRange(fd);
+ removeSurroundingWhitespaceFromRange(fd);
+ }
+ return null;
+ }
+ VariableDeclarationStatement res = this.ast.newVariableDeclarationStatement(fragment);
+ commonSettings(res, javac);
+
+ if (jcVariableDecl.vartype != null) {
+ if( jcVariableDecl.vartype instanceof JCArrayTypeTree jcatt) {
+ int extraDims = 0;
+ if( fragment.extraArrayDimensions > 0 ) {
+ extraDims = fragment.extraArrayDimensions;
+ } else if( this.ast.apiLevel > AST.JLS4_INTERNAL && fragment.extraDimensions() != null && fragment.extraDimensions().size() > 0 ) {
+ extraDims = fragment.extraDimensions().size();
+ }
+ res.setType(convertToType(unwrapDimensions(jcatt, extraDims)));
+ } else {
+ res.setType(convertToType(findBaseType(jcVariableDecl.vartype)));
+ }
+ } else if( jcVariableDecl.declaredUsingVar() ) {
+ SimpleType st = this.ast.newSimpleType(this.ast.newSimpleName("var"));
+ st.setSourceRange(javac.getStartPosition(), 3);
+ res.setType(st);
+ }
+ if( this.ast.apiLevel > AST.JLS2_INTERNAL) {
+ res.modifiers().addAll(convert(jcVariableDecl.getModifiers(), res));
+ } else {
+ JCModifiers mods = jcVariableDecl.getModifiers();
+ int[] total = new int[] {0};
+ mods.getFlags().forEach(x -> {total[0] += modifierToFlagVal(x);});
+ res.internalSetModifiers(total[0]);
+ }
+ return res;
+ }
+ if (javac instanceof JCIf ifStatement) {
+ return convertIfStatement(ifStatement);
+ }
+ if (javac instanceof JCThrow throwStatement) {
+ ThrowStatement res = this.ast.newThrowStatement();
+ commonSettings(res, javac);
+ res.setExpression(convertExpression(throwStatement.getExpression()));
+ return res;
+ }
+ if (javac instanceof JCTry tryStatement) {
+ return convertTryStatement(tryStatement, parent);
+ }
+ if (javac instanceof JCSynchronized jcSynchronized) {
+ SynchronizedStatement res = this.ast.newSynchronizedStatement();
+ commonSettings(res, javac);
+ JCExpression syncExpr = jcSynchronized.getExpression();
+ if( syncExpr instanceof JCParens jcp) {
+ syncExpr = jcp.getExpression();
+ }
+ res.setExpression(convertExpression(syncExpr));
+ res.setBody(convertBlock(jcSynchronized.getBlock()));
+ return res;
+ }
+ if (javac instanceof JCForLoop jcForLoop) {
+ ForStatement res = this.ast.newForStatement();
+ commonSettings(res, javac);
+ Statement stmt = convertStatement(jcForLoop.getStatement(), res);
+ if( stmt != null )
+ res.setBody(stmt);
+ var initializerIt = jcForLoop.getInitializer().iterator();
+ while(initializerIt.hasNext()) {
+ Expression expr = convertStatementToExpression(initializerIt.next(), res);
+ if( expr != null )
+ res.initializers().add(expr);
+ }
+ if (jcForLoop.getCondition() != null) {
+ Expression expr = convertExpression(jcForLoop.getCondition());
+ if( expr != null )
+ res.setExpression(expr);
+ }
+
+ Iterator updateIt = jcForLoop.getUpdate().iterator();
+ while(updateIt.hasNext()) {
+ Expression expr = convertStatementToExpression(updateIt.next(), res);
+ if( expr != null )
+ res.updaters().add(expr);
+ }
+ return res;
+ }
+ if (javac instanceof JCEnhancedForLoop jcEnhancedForLoop) {
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ EnhancedForStatement res = this.ast.newEnhancedForStatement();
+ commonSettings(res, javac);
+ res.setParameter((SingleVariableDeclaration)convertVariableDeclaration(jcEnhancedForLoop.getVariable()));
+ Expression expr = convertExpression(jcEnhancedForLoop.getExpression());
+ if( expr != null )
+ res.setExpression(expr);
+ Statement stmt = convertStatement(jcEnhancedForLoop.getStatement(), res);
+ if( stmt != null )
+ res.setBody(stmt);
+ return res;
+ } else {
+ EmptyStatement res = this.ast.newEmptyStatement();
+ commonSettings(res, javac);
+ return res;
+ }
+ }
+ if (javac instanceof JCBreak jcBreak) {
+ BreakStatement res = this.ast.newBreakStatement();
+ commonSettings(res, javac);
+ if (jcBreak.getLabel() != null) {
+ res.setLabel((SimpleName)convertName(jcBreak.getLabel()));
+ }
+ return res;
+ }
+ if (javac instanceof JCSwitch jcSwitch) {
+ SwitchStatement res = this.ast.newSwitchStatement();
+ commonSettings(res, javac);
+ JCExpression switchExpr = jcSwitch.getExpression();
+ if( switchExpr instanceof JCParens jcp) {
+ switchExpr = jcp.getExpression();
+ }
+ res.setExpression(convertExpression(switchExpr));
+ jcSwitch.getCases().stream()
+ .flatMap(switchCase -> {
+ List stmts = new ArrayList<>();
+ switch(switchCase.getCaseKind()) {
+ case CaseKind.STATEMENT: {
+ int numStatements = switchCase.getStatements() != null ? switchCase.getStatements().size()
+ : 0;
+ stmts.add(switchCase);
+ if (numStatements > 0) {
+ stmts.addAll(switchCase.getStatements());
+ }
+ return stmts.stream();
+ }
+ case CaseKind.RULE: {
+ stmts.add(switchCase);
+ JCTree body = switchCase.getBody();
+ if (body instanceof JCExpressionStatement stmt) {
+ stmts.add(stmt);
+ }
+ }
+ }
+ return stmts.stream();
+ }).map(x -> convertStatement(x, res))
+ .filter(x -> x != null)
+ .forEach(res.statements()::add);
+ return res;
+ }
+ if (javac instanceof JCCase jcCase) {
+ return convertSwitchCase(jcCase);
+ }
+ if (javac instanceof JCWhileLoop jcWhile) {
+ WhileStatement res = this.ast.newWhileStatement();
+ commonSettings(res, javac);
+ JCExpression expr = jcWhile.getCondition();
+ if( expr instanceof JCParens jcp) {
+ expr = jcp.getExpression();
+ }
+ res.setExpression(convertExpression(expr));
+ Statement body = convertStatement(jcWhile.getStatement(), res);
+ if( body != null )
+ res.setBody(body);
+ return res;
+ }
+ if (javac instanceof JCDoWhileLoop jcDoWhile) {
+ DoStatement res = this.ast.newDoStatement();
+ commonSettings(res, javac);
+ JCExpression expr = jcDoWhile.getCondition();
+ if( expr instanceof JCParens jcp) {
+ expr = jcp.getExpression();
+ }
+ Expression expr1 = convertExpression(expr);
+ if( expr != null )
+ res.setExpression(expr1);
+
+ Statement body = convertStatement(jcDoWhile.getStatement(), res);
+ if( body != null )
+ res.setBody(body);
+ return res;
+ }
+ if (javac instanceof JCYield jcYield) {
+ YieldStatement res = this.ast.newYieldStatement();
+ commonSettings(res, javac);
+ res.setExpression(convertExpression(jcYield.getValue()));
+ return res;
+ }
+ if (javac instanceof JCContinue jcContinue) {
+ ContinueStatement res = this.ast.newContinueStatement();
+ commonSettings(res, javac);
+ if (jcContinue.getLabel() != null) {
+ res.setLabel((SimpleName)convertName(jcContinue.getLabel()));
+ }
+ return res;
+ }
+ if (javac instanceof JCLabeledStatement jcLabel) {
+ LabeledStatement res = this.ast.newLabeledStatement();
+ commonSettings(res, javac);
+ res.setLabel((SimpleName)convertName(jcLabel.getLabel()));
+ Statement stmt = convertStatement(jcLabel.getStatement(), res);
+ if( stmt != null )
+ res.setBody(stmt);
+ return res;
+ }
+ if (javac instanceof JCAssert jcAssert) {
+ AssertStatement res =this.ast.newAssertStatement();
+ commonSettings(res, javac);
+ Expression expr = convertExpression(jcAssert.getCondition());
+ if( expr != null )
+ res.setExpression(expr);
+ if( jcAssert.getDetail() != null ) {
+ Expression det = convertExpression(jcAssert.getDetail());
+ if( det != null )
+ res.setMessage(det);
+ }
+ return res;
+ }
+ if (javac instanceof JCClassDecl jcclass) {
+ TypeDeclarationStatement res = this.ast.newTypeDeclarationStatement(convertClassDecl(jcclass, parent));
+ commonSettings(res, javac);
+ return res;
+ }
+ if (javac instanceof JCSkip) {
+ EmptyStatement res = this.ast.newEmptyStatement();
+ commonSettings(res, javac);
+ return res;
+ }
+ throw new UnsupportedOperationException("Missing support to convert " + javac + "of type " + javac.getClass().getName());
+ }
+
+ private Statement convertSwitchCase(JCCase jcCase) {
+ SwitchCase res = this.ast.newSwitchCase();
+ commonSettings(res, jcCase);
+ if( this.ast.apiLevel >= AST.JLS14_INTERNAL) {
+ if (jcCase.getGuard() != null && (jcCase.getLabels().size() > 1 || jcCase.getLabels().get(0) instanceof JCPatternCaseLabel)) {
+ GuardedPattern guardedPattern = this.ast.newGuardedPattern();
+ guardedPattern.setExpression(convertExpression(jcCase.getGuard()));
+ guardedPattern.setRestrictedIdentifierStartPosition(jcCase.guard.getStartPosition() - 5); // javac gives start position without "when " while jdt expects it with
+ if (jcCase.getLabels().length() > 1) {
+ int start = Integer.MAX_VALUE;
+ int end = Integer.MIN_VALUE;
+ EitherOrMultiPattern eitherOrMultiPattern = this.ast.newEitherOrMultiPattern();
+ for (JCCaseLabel label : jcCase.getLabels()) {
+ if (label.pos < start) {
+ start = label.pos;
+ }
+ if (end < label.getEndPosition(this.javacCompilationUnit.endPositions)) {
+ end = label.getEndPosition(this.javacCompilationUnit.endPositions);
+ }
+ if (label instanceof JCPatternCaseLabel jcPattern) {
+ eitherOrMultiPattern.patterns().add(convert(jcPattern.getPattern()));
+ }
+ // skip over any constants, they are not valid anyways
+ }
+ eitherOrMultiPattern.setSourceRange(start, end - start);
+ guardedPattern.setPattern(eitherOrMultiPattern);
+ } else if (jcCase.getLabels().length() == 1) {
+ if (jcCase.getLabels().get(0) instanceof JCPatternCaseLabel jcPattern) {
+ guardedPattern.setPattern(convert(jcPattern.getPattern()));
+ } else {
+ // see same above note regarding guarded case labels using constants
+ throw new UnsupportedOperationException("cannot convert case label: " + jcCase.getLabels().get(0));
+ }
+ }
+ int start = guardedPattern.getPattern().getStartPosition();
+ int end = guardedPattern.getExpression().getStartPosition() + guardedPattern.getExpression().getLength();
+ guardedPattern.setSourceRange(start, end - start);
+ res.expressions().add(guardedPattern);
+ } else {
+ if (jcCase.getLabels().length() == 1 && jcCase.getLabels().get(0) instanceof JCPatternCaseLabel jcPattern) {
+ Pattern p = convert(jcPattern.getPattern());
+ if( p != null ) {
+ int start = jcPattern.getStartPosition();
+ p.setSourceRange(start, jcPattern.getEndPosition(this.javacCompilationUnit.endPositions)-start);
+ res.expressions().add(p);
+ }
+ } else {
+ // Override length to just be `case blah:`
+ for (JCCaseLabel jcLabel : jcCase.getLabels()) {
+ switch (jcLabel) {
+ case JCConstantCaseLabel constantLabel: {
+ if (constantLabel.expr.toString().equals("null")) {
+ res.expressions().add(this.ast.newNullLiteral());
+ }
+ break;
+ }
+ case JCDefaultCaseLabel defaultCase: {
+ if (jcCase.getLabels().size() != 1) {
+ res.expressions().add(this.ast.newCaseDefaultExpression());
+ }
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ }
+ int start1 = res.getStartPosition();
+ int colon = this.rawText.indexOf(":", start1);
+ if( colon != -1 ) {
+ res.setSourceRange(start1, colon - start1 + 1);
+ }
+ }
+ jcCase.getExpressions().stream().map(this::convertExpression).forEach(res.expressions()::add);
+ }
+ res.setSwitchLabeledRule(jcCase.getCaseKind() == CaseKind.RULE);
+ } else {
+ // Override length to just be `case blah:`
+ int start1 = res.getStartPosition();
+ int colon = this.rawText.indexOf(":", start1);
+ if( colon != -1 ) {
+ res.setSourceRange(start1, colon - start1 + 1);
+ }
+ List l = jcCase.getExpressions();
+ if( l.size() == 1 ) {
+ res.setExpression(convertExpression(l.get(0)));
+ } else if( l.size() == 0 ) {
+ res.setExpression(null);
+ }
+ }
+ // jcCase.getStatements is processed as part of JCSwitch conversion
+ return res;
+ }
+
+ private Expression convertStatementToExpression(JCStatement javac, ASTNode parent) {
+ if (javac instanceof JCExpressionStatement jcExpressionStatement) {
+ return convertExpression(jcExpressionStatement.getExpression());
+ }
+ Statement javacStatement = convertStatement(javac, parent);
+ if (javacStatement instanceof VariableDeclarationStatement decl && decl.fragments().size() == 1) {
+ javacStatement.delete();
+ VariableDeclarationFragment fragment = (VariableDeclarationFragment)decl.fragments().get(0);
+ fragment.delete();
+ VariableDeclarationExpression jdtVariableDeclarationExpression = this.ast.newVariableDeclarationExpression(fragment);
+ commonSettings(jdtVariableDeclarationExpression, javac);
+ if (javac instanceof JCVariableDecl jcvd && jcvd.vartype != null) {
+ if( jcvd.vartype instanceof JCArrayTypeTree jcatt) {
+ int extraDims = 0;
+ if( fragment.extraArrayDimensions > 0 ) {
+ extraDims = fragment.extraArrayDimensions;
+ } else if( this.ast.apiLevel > AST.JLS4_INTERNAL && fragment.extraDimensions() != null && fragment.extraDimensions().size() > 0 ) {
+ extraDims = fragment.extraDimensions().size();
+ }
+ jdtVariableDeclarationExpression.setType(convertToType(unwrapDimensions(jcatt, extraDims)));
+ } else {
+ jdtVariableDeclarationExpression.setType(convertToType(findBaseType(jcvd.vartype)));
+ }
+ }
+ return jdtVariableDeclarationExpression;
+ }
+ return null;
+ }
+
+ private JCTree findBaseType(JCExpression vartype) {
+ if( vartype instanceof JCArrayTypeTree jcatt) {
+ return findBaseType(jcatt.elemtype);
+ }
+ return vartype;
+ }
+
+ private Block convertBlock(JCBlock javac) {
+ Block res = this.ast.newBlock();
+ commonSettings(res, javac);
+ if (javac.getStatements() != null) {
+ for (JCStatement next : javac.getStatements()) {
+ Statement s = convertStatement(next, res);
+ if( s != null ) {
+ res.statements().add(s);
+ }
+ }
+ }
+ return res;
+ }
+
+ private TryStatement convertTryStatement(JCTry javac, ASTNode parent) {
+ TryStatement res = this.ast.newTryStatement();
+ commonSettings(res, javac);
+ res.setBody(convertBlock(javac.getBlock()));
+ if (javac.finalizer != null) {
+ res.setFinally(convertBlock(javac.getFinallyBlock()));
+ }
+
+ if( this.ast.apiLevel >= AST.JLS8_INTERNAL) {
+ if( javac.getResources().size() > 0) {
+ Iterator it = javac.getResources().iterator();
+ while(it.hasNext()) {
+ ASTNode working = convertTryResource(it.next(), parent);
+ if( working instanceof VariableDeclarationExpression) {
+ res.resources().add(working);
+ } else if( this.ast.apiLevel >= AST.JLS9_INTERNAL && working instanceof Name){
+ res.resources().add(working);
+ } else {
+ res.setFlags(res.getFlags() | ASTNode.MALFORMED);
+ }
+ }
+ } else {
+ res.setFlags(res.getFlags() | ASTNode.MALFORMED);
+ }
+ }
+ javac.getCatches().stream().map(this::convertCatcher).forEach(res.catchClauses()::add);
+ return res;
+ }
+
+ private Expression convertTryResource(JCTree javac, ASTNode parent) {
+ if (javac instanceof JCVariableDecl decl) {
+ var converted = convertVariableDeclaration(decl);
+ final VariableDeclarationFragment fragment;
+ if (converted instanceof VariableDeclarationFragment f) {
+ fragment = f;
+ } else if (converted instanceof SingleVariableDeclaration single) {
+ single.delete();
+ this.domToJavac.remove(single);
+ fragment = this.ast.newVariableDeclarationFragment();
+ commonSettings(fragment, javac);
+ fragment.setFlags(single.getFlags());
+ SimpleName name = (SimpleName)single.getName().clone(this.ast);
+ fragment.setName(name);
+ Expression initializer = single.getInitializer();
+ if (initializer != null) {
+ initializer.delete();
+ fragment.setInitializer(initializer);
+ }
+ if (parent.getAST().apiLevel() > AST.JLS4) {
+ for (Dimension extraDimension : (List)single.extraDimensions()) {
+ extraDimension.delete();
+ fragment.extraDimensions().add(extraDimension);
+ }
+ }
+ } else {
+ fragment = this.ast.newVariableDeclarationFragment();
+ }
+ VariableDeclarationExpression res = this.ast.newVariableDeclarationExpression(fragment);
+ commonSettings(res, javac);
+ removeTrailingSemicolonFromRange(res);
+ res.setType(convertToType(decl.getType()));
+ if( this.ast.apiLevel > AST.JLS2_INTERNAL) {
+ res.modifiers().addAll(convert(decl.getModifiers(), res));
+ } else {
+ JCModifiers mods = decl.getModifiers();
+ int[] total = new int[] {0};
+ mods.getFlags().forEach(x -> {total[0] += modifierToFlagVal(x);});
+ res.internalSetModifiers(total[0]);
+ }
+ return res;
+ }
+ if (javac instanceof JCExpression jcExpression) {
+ return convertExpression(jcExpression);
+ }
+ return null;
+ }
+
+ private void removeTrailingSemicolonFromRange(ASTNode res) {
+ removeTrailingCharFromRange(res, new char[] {';'});
+ }
+ private void ensureTrailingSemicolonInRange(ASTNode res) {
+ int end = res.getStartPosition() + res.getLength();
+ if( end < this.rawText.length() && this.rawText.charAt(end-1) != ';' && this.rawText.charAt(end) == ';') {
+ // jdt expects semicolon to be part of the range
+ res.setSourceRange(res.getStartPosition(), res.getLength() + 1);
+ }
+ }
+
+ private void removeSurroundingWhitespaceFromRange(ASTNode res) {
+ int start = res.getStartPosition();
+ if (start >= 0 && start < this.rawText.length()) {
+ String rawSource = this.rawText.substring(start, start + res.getLength());
+ int trimLeading = rawSource.length() - rawSource.stripLeading().length();
+ int trimTrailing = rawSource.length() - rawSource.stripTrailing().length();
+ if( (trimLeading != 0 || trimTrailing != 0) && res.getLength() > trimLeading + trimTrailing ) {
+ //String newContent = this.rawText.substring(start+trimLeading, start+trimLeading+res.getLength()-trimLeading-trimTrailing);
+ res.setSourceRange(start+trimLeading, res.getLength() - trimLeading - trimTrailing);
+ }
+ }
+ }
+
+ private void removeTrailingCharFromRange(ASTNode res, char[] possible) {
+ int endPos = res.getStartPosition() + res.getLength();
+ char lastChar = this.rawText.charAt(endPos-1);
+ boolean found = false;
+ for( int i = 0; i < possible.length; i++ ) {
+ if( lastChar == possible[i]) {
+ found = true;
+ }
+ }
+ if( found ) {
+ res.setSourceRange(res.getStartPosition(), res.getLength() - 1);
+ }
+ }
+
+ private CatchClause convertCatcher(JCCatch javac) {
+ CatchClause res = this.ast.newCatchClause();
+ commonSettings(res, javac);
+ res.setBody(convertBlock(javac.getBlock()));
+ res.setException((SingleVariableDeclaration)convertVariableDeclaration(javac.getParameter()));
+ return res;
+ }
+
+ private IfStatement convertIfStatement(JCIf javac) {
+ IfStatement res = this.ast.newIfStatement();
+ commonSettings(res, javac);
+ if (javac.getCondition() != null) {
+ JCExpression expr = javac.getCondition();
+ if( expr instanceof JCParens jpc) {
+ res.setExpression(convertExpression(jpc.getExpression()));
+ } else {
+ res.setExpression(convertExpression(expr));
+ }
+ }
+ if (javac.getThenStatement() != null) {
+ Statement stmt = convertStatement(javac.getThenStatement(), res);
+ if( stmt != null )
+ res.setThenStatement(stmt);
+ }
+ if (javac.getElseStatement() != null) {
+ Statement stmt = convertStatement(javac.getElseStatement(), res);
+ if( stmt != null )
+ res.setElseStatement(stmt);
+ }
+ return res;
+ }
+
+ /**
+ * ⚠️ node position in JCTree must be absolute
+ * @param javac
+ * @return
+ */
+ Type convertToType(JCTree javac) {
+ if (javac instanceof JCIdent ident) {
+ Name name = convertName(ident.name);
+ name.setSourceRange(ident.getStartPosition(), ident.name.length());
+ SimpleType res = this.ast.newSimpleType(name);
+ commonSettings(res, ident);
+ return res;
+ }
+ if (javac instanceof JCFieldAccess qualified) {
+ try {
+ if( qualified.getExpression() == null ) {
+ Name qn = toName(qualified);
+ commonSettings(qn, javac);
+ SimpleType res = this.ast.newSimpleType(qn);
+ commonSettings(res, qualified);
+ return res;
+ }
+ } catch (Exception ex) {
+ }
+ // case of not translatable name, eg because of generics
+ // TODO find a better check instead of relying on exception
+ Type qualifierType = convertToType(qualified.getExpression());
+ SimpleName simpleName = (SimpleName)convertName(qualified.getIdentifier());
+ int simpleNameStart = this.rawText.indexOf(simpleName.getIdentifier(), qualifierType.getStartPosition() + qualifierType.getLength());
+ if (simpleNameStart > 0) {
+ simpleName.setSourceRange(simpleNameStart, simpleName.getIdentifier().length());
+ } else if (simpleName.getIdentifier().isEmpty()){
+ // the name second segment is invalid
+ simpleName.delete();
+ return qualifierType;
+ } else { // lombok case
+ simpleName.setSourceRange(qualifierType.getStartPosition(), 0);
+ }
+ if(qualifierType instanceof SimpleType simpleType && (ast.apiLevel() < AST.JLS8 || simpleType.annotations().isEmpty())) {
+ simpleType.delete();
+ Name parentName = simpleType.getName();
+ parentName.setParent(null, null);
+ QualifiedName name = this.ast.newQualifiedName(simpleType.getName(), simpleName);
+ commonSettings(name, javac);
+ int length = name.getName().getStartPosition() + name.getName().getLength() - name.getStartPosition();
+ if (name.getStartPosition() >= 0) {
+ name.setSourceRange(name.getStartPosition(), Math.max(0, length));
+ }
+ SimpleType res = this.ast.newSimpleType(name);
+ commonSettings(res, javac);
+ if (name.getStartPosition() >= 0) {
+ res.setSourceRange(name.getStartPosition(), Math.max(0, length));
+ }
+ return res;
+ } else {
+ QualifiedType res = this.ast.newQualifiedType(qualifierType, simpleName);
+ commonSettings(res, qualified);
+ return res;
+ }
+ }
+ if (javac instanceof JCPrimitiveTypeTree primitiveTypeTree) {
+ PrimitiveType res = this.ast.newPrimitiveType(convert(primitiveTypeTree.getPrimitiveTypeKind()));
+ commonSettings(res, primitiveTypeTree);
+ return res;
+ }
+ if (javac instanceof JCTypeUnion union) {
+ if (this.ast.apiLevel() > AST.JLS3) {
+ UnionType res = this.ast.newUnionType();
+ commonSettings(res, javac);
+ union.getTypeAlternatives().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(res.types()::add);
+ return res;
+ } else {
+ Optional lastType = union.getTypeAlternatives().reverse().stream().map(this::convertToType).filter(Objects::nonNull).findFirst();
+ lastType.ifPresent(a -> a.setFlags(a.getFlags() | ASTNode.MALFORMED));
+ return lastType.get();
+ }
+ }
+ if (javac instanceof JCArrayTypeTree jcArrayType) {
+ Type t = convertToType(jcArrayType.getType());
+ if (t == null) {
+ return null;
+ }
+ ArrayType res;
+ if (t instanceof ArrayType childArrayType && this.ast.apiLevel > AST.JLS4_INTERNAL) {
+ res = childArrayType;
+ res.dimensions().addFirst(this.ast.newDimension());
+ commonSettings(res, jcArrayType.getType());
+ } else {
+ int dims = countDimensions(jcArrayType);
+ res = this.ast.newArrayType(t);
+ if( dims == 0 ) {
+ commonSettings(res, jcArrayType);
+ } else {
+ int endPos = jcArrayType.getEndPosition(this.javacCompilationUnit.endPositions);
+ int startPos = jcArrayType.getStartPosition();
+ try {
+ String raw = this.rawText.substring(startPos, endPos);
+ int ordinalEnd = ordinalIndexOf(raw, "]", dims);
+ int ordinalStart = ordinalIndexOf(raw, "[", dims);
+ if( ordinalEnd != -1 ) {
+ commonSettings(res, jcArrayType, ordinalEnd + 1, true);
+ if( this.ast.apiLevel >= AST.JLS8_INTERNAL ) {
+ if( res.dimensions().size() > 0 ) {
+ ((Dimension)res.dimensions().get(0)).setSourceRange(startPos + ordinalStart, ordinalEnd - ordinalStart + 1);
+ }
+ }
+ return res;
+ }
+ } catch( Throwable tErr) {
+ }
+ commonSettings(res, jcArrayType);
+ }
+ }
+ return res;
+ }
+ if (javac instanceof JCTypeApply jcTypeApply) {
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ ParameterizedType res = this.ast.newParameterizedType(convertToType(jcTypeApply.getType()));
+ commonSettings(res, javac);
+ jcTypeApply.getTypeArguments().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(res.typeArguments()::add);
+ return res;
+ } else {
+ return convertToType(jcTypeApply.clazz);
+ }
+ }
+ if (javac instanceof JCWildcard wc) {
+ WildcardType res = this.ast.newWildcardType();
+ if( wc.kind.kind == BoundKind.SUPER) {
+ final Type bound = convertToType(wc.inner);
+ res.setBound(bound, false);
+ } else if( wc.kind.kind == BoundKind.EXTENDS) {
+ final Type bound = convertToType(wc.inner);
+ res.setBound(bound, true);
+ }
+ commonSettings(res, javac);
+ return res;
+ }
+ if (javac instanceof JCTypeIntersection jcTypeIntersection) {
+ IntersectionType res = this.ast.newIntersectionType();
+ commonSettings(res, javac);
+ jcTypeIntersection.getBounds().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(res.types()::add);
+ return res;
+ }
+ if (javac instanceof JCAnnotatedType jcAnnotatedType) {
+ Type res = null;
+ JCExpression jcpe = jcAnnotatedType.getUnderlyingType();
+ if( jcAnnotatedType.getAnnotations() != null //
+ && !jcAnnotatedType.getAnnotations().isEmpty() //
+ && this.ast.apiLevel >= AST.JLS8_INTERNAL
+ && !(jcpe instanceof JCWildcard)) {
+ if( jcpe instanceof JCFieldAccess jcfa2) {
+ if( jcfa2.selected instanceof JCAnnotatedType || jcfa2.selected instanceof JCTypeApply) {
+ QualifiedType nameQualifiedType = new QualifiedType(this.ast);
+ commonSettings(nameQualifiedType, javac);
+ nameQualifiedType.setQualifier(convertToType(jcfa2.selected));
+ nameQualifiedType.setName(this.ast.newSimpleName(jcfa2.name.toString()));
+ res = nameQualifiedType;
+ } else {
+ NameQualifiedType nameQualifiedType = new NameQualifiedType(this.ast);
+ commonSettings(nameQualifiedType, javac);
+ nameQualifiedType.setQualifier(toName(jcfa2.selected));
+ nameQualifiedType.setName(this.ast.newSimpleName(jcfa2.name.toString()));
+ res = nameQualifiedType;
+ }
+ } else if (jcpe instanceof JCIdent simpleType) {
+ res = this.ast.newSimpleType(convertName(simpleType.getName()));
+ commonSettings(res, javac);
+ }
+ }
+ if (res == null) { // nothing specific
+ res = convertToType(jcAnnotatedType.getUnderlyingType());
+ }
+ if (res instanceof AnnotatableType annotatableType && this.ast.apiLevel() >= AST.JLS8_INTERNAL) {
+ for (JCAnnotation annotation : jcAnnotatedType.getAnnotations()) {
+ annotatableType.annotations().add(convert(annotation));
+ }
+ } else if (res instanceof ArrayType arrayType) {
+ if (this.ast.apiLevel() >= AST.JLS8 && !arrayType.dimensions().isEmpty()) {
+ for (JCAnnotation annotation : jcAnnotatedType.getAnnotations()) {
+ ((Dimension)arrayType.dimensions().get(0)).annotations().add(convert(annotation));
+ }
+ }
+ }
+ return res;
+ }
+ if (javac instanceof JCErroneous || javac == null /* when there are syntax errors */) {
+ // returning null could result in upstream errors, so return a fake type
+ var res = this.ast.newSimpleType(this.ast.newSimpleName(FAKE_IDENTIFIER));
+ res.setFlags(ASTNode.RECOVERED);
+ return res;
+ }
+ throw new UnsupportedOperationException("Not supported yet, type " + javac + " of class" + javac.getClass());
+ }
+ 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);
+ final SimpleName simpleName = new SimpleName(this.ast);
+ commonSettings(simpleName, jcid);
+ simpleName.internalSetIdentifier(new String(jcid.getName().toString()));
+ int start = jcid.pos;
+ int end = start + jcid.getName().toString().length();
+ simpleName.setSourceRange(start, end - start );
+ pair.setName(simpleName);
+ Expression value = null;
+ if (jcass.rhs instanceof JCNewArray jcNewArray) {
+ ArrayInitializer initializer = this.ast.newArrayInitializer();
+ commonSettings(initializer, javac);
+ jcNewArray.getInitializers().stream().map(this::convertExpression).forEach(initializer.expressions()::add);
+ value = initializer;
+ } else {
+ value = convertExpression(jcass.rhs);
+ }
+ commonSettings(value, jcass.rhs);
+ pair.setValue(value);
+ start = value.getStartPosition();
+ end = value.getStartPosition() + value.getLength() - 1;
+ pair.setSourceRange(start, end - start + 1);
+ res.values().add(pair);
+ }
+ }
+ }
+ return res;
+ }
+ }
+//
+// public Annotation addAnnotation(IAnnotationBinding annotation, AST ast, ImportRewriteContext context) {
+// Type type = addImport(annotation.getAnnotationType(), ast, context, TypeLocation.OTHER);
+// Name name;
+// if (type instanceof SimpleType) {
+// SimpleType simpleType = (SimpleType) type;
+// name = simpleType.getName();
+// // cut 'name' loose from its parent, so that it can be reused
+// simpleType.setName(ast.newName("a")); //$NON-NLS-1$
+// } else {
+// name = ast.newName("invalid"); //$NON-NLS-1$
+// }
+//
+// IMemberValuePairBinding[] mvps= annotation.getDeclaredMemberValuePairs();
+// if (mvps.length == 0) {
+// MarkerAnnotation result = ast.newMarkerAnnotation();
+// result.setTypeName(name);
+// return result;
+// } else if (mvps.length == 1 && "value".equals(mvps[0].getName())) { //$NON-NLS-1$
+// SingleMemberAnnotation result= ast.newSingleMemberAnnotation();
+// result.setTypeName(name);
+// Object value = mvps[0].getValue();
+// if (value != null)
+// result.setValue(addAnnotation(ast, value, context));
+// return result;
+// } else {
+// NormalAnnotation result = ast.newNormalAnnotation();
+// result.setTypeName(name);
+// for (int i= 0; i < mvps.length; i++) {
+// IMemberValuePairBinding mvp = mvps[i];
+// MemberValuePair mvpNode = ast.newMemberValuePair();
+// mvpNode.setName(ast.newSimpleName(mvp.getName()));
+// Object value = mvp.getValue();
+// if (value != null)
+// mvpNode.setValue(addAnnotation(ast, value, context));
+// result.values().add(mvpNode);
+// }
+// return result;
+// }
+// }
+
+ private List convert(JCModifiers modifiers, ASTNode parent) {
+ List res = new ArrayList<>();
+ convertModifiers(modifiers, parent, res);
+ convertModifierAnnotations(modifiers, parent, res);
+ sortModifierNodesByPosition(res);
+ return res;
+ }
+
+ private void sortModifierNodesByPosition(List l) {
+ l.sort((o1, o2) -> {
+ ASTNode a1 = (ASTNode)o1;
+ ASTNode a2 = (ASTNode)o2;
+ return a1.getStartPosition() - a2.getStartPosition();
+ });
+ }
+
+ private void convertModifiers(JCModifiers modifiers, ASTNode parent, List res) {
+ Iterator mods = modifiers.getFlags().iterator();
+ while(mods.hasNext()) {
+ Modifier converted = convert(mods.next(), modifiers.pos, modifiers.getEndPosition(this.javacCompilationUnit.endPositions) + 1);
+ if (converted.getStartPosition() >= 0) {
+ // some modifiers are added to the list without being really part of
+ // the text/DOM. JDT doesn't like it, so we filter out the "implicit"
+ // modifiers
+ res.add(converted);
+ }
+ }
+ }
+
+
+ private List convertModifierAnnotations(JCModifiers modifiers, ASTNode parent ) {
+ List res = new ArrayList<>();
+ convertModifierAnnotations(modifiers, parent, res);
+ sortModifierNodesByPosition(res);
+ return res;
+ }
+
+ private void convertModifierAnnotations(JCModifiers modifiers, ASTNode parent, List res) {
+ modifiers.getAnnotations().stream().map(this::convert).forEach(res::add);
+ }
+
+ private List convertModifiersFromFlags(int startPos, int endPos, long oflags) {
+ String rawTextSub = this.rawText.substring(startPos, endPos);
+ List res = new ArrayList<>();
+ ModifierKeyword[] ops = {
+ org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.PUBLIC_KEYWORD,
+ org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.PROTECTED_KEYWORD,
+ org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.PRIVATE_KEYWORD,
+ org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.STATIC_KEYWORD,
+ org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.ABSTRACT_KEYWORD,
+ org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.FINAL_KEYWORD,
+ org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.NATIVE_KEYWORD,
+ org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.SYNCHRONIZED_KEYWORD,
+ org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.TRANSIENT_KEYWORD,
+ org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.VOLATILE_KEYWORD,
+ org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.STRICTFP_KEYWORD,
+ org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.DEFAULT_KEYWORD,
+ org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.SEALED_KEYWORD,
+ org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.NON_SEALED_KEYWORD
+ };
+ for( int i = 0; i < ops.length; i++ ) {
+ ModifierKeyword k = ops[i];
+ int flagVal = k.toFlagValue();
+ if( (oflags & flagVal) > 0 ) {
+ Modifier m = this.ast.newModifier(k);
+ String asStr = k.toString();
+ int foundLoc = rawTextSub.indexOf(asStr);
+ if( foundLoc != -1 ) {
+ m.setSourceRange(startPos + foundLoc, asStr.length());
+ }
+ res.add(m);
+ }
+ }
+ return res;
+ }
+
+ private int getJLS2ModifiersFlags(long oflags) {
+ int flags = 0;
+ if( (oflags & Flags.PUBLIC) > 0) flags += Flags.PUBLIC;
+ if( (oflags & Flags.PRIVATE) > 0) flags += Flags.PRIVATE;
+ if( (oflags & Flags.PROTECTED) > 0) flags += Flags.PROTECTED;
+ if( (oflags & Flags.STATIC) > 0) flags += Flags.STATIC;
+ if( (oflags & Flags.FINAL) > 0) flags += Flags.FINAL;
+ if( (oflags & Flags.SYNCHRONIZED) > 0) flags += Flags.SYNCHRONIZED;
+ if( (oflags & Flags.VOLATILE) > 0) flags += Flags.VOLATILE;
+ if( (oflags & Flags.TRANSIENT) > 0) flags += Flags.TRANSIENT;
+ if( (oflags & Flags.NATIVE) > 0) flags += Flags.NATIVE;
+ if( (oflags & Flags.INTERFACE) > 0) flags += Flags.INTERFACE;
+ if( (oflags & Flags.ABSTRACT) > 0) flags += Flags.ABSTRACT;
+ if( (oflags & Flags.STRICTFP) > 0) flags += Flags.STRICTFP;
+ return flags;
+ }
+
+ private int getJLS2ModifiersFlagsAsStringLength(long flags) {
+ int len = 0;
+ if( (flags & Flags.PUBLIC) > 0) len += 6 + 1;
+ if( (flags & Flags.PRIVATE) > 0) len += 7 + 1;
+ if( (flags & Flags.PROTECTED) > 0) len += 9 + 1;
+ if( (flags & Flags.STATIC) > 0) len += 5 + 1;
+ if( (flags & Flags.FINAL) > 0) len += 5 + 1;
+ if( (flags & Flags.SYNCHRONIZED) > 0) len += 12 + 1;
+ if( (flags & Flags.VOLATILE) > 0) len += 8 + 1;
+ if( (flags & Flags.TRANSIENT) > 0) len += 9 + 1;
+ if( (flags & Flags.NATIVE) > 0) len += 6 + 1;
+ if( (flags & Flags.INTERFACE) > 0) len += 9 + 1;
+ if( (flags & Flags.ABSTRACT) > 0) len += 8 + 1;
+ if( (flags & Flags.STRICTFP) > 0) len += 8 + 1;
+ return len;
+ }
+
+
+ private ModifierKeyword modifierToKeyword(javax.lang.model.element.Modifier javac) {
+ return switch (javac) {
+ case PUBLIC -> ModifierKeyword.PUBLIC_KEYWORD;
+ case PROTECTED -> ModifierKeyword.PROTECTED_KEYWORD;
+ case PRIVATE -> ModifierKeyword.PRIVATE_KEYWORD;
+ case ABSTRACT -> ModifierKeyword.ABSTRACT_KEYWORD;
+ case DEFAULT -> ModifierKeyword.DEFAULT_KEYWORD;
+ case STATIC -> ModifierKeyword.STATIC_KEYWORD;
+ case SEALED -> ModifierKeyword.SEALED_KEYWORD;
+ case NON_SEALED -> ModifierKeyword.NON_SEALED_KEYWORD;
+ case FINAL -> ModifierKeyword.FINAL_KEYWORD;
+ case TRANSIENT -> ModifierKeyword.TRANSIENT_KEYWORD;
+ case VOLATILE -> ModifierKeyword.VOLATILE_KEYWORD;
+ case SYNCHRONIZED -> ModifierKeyword.SYNCHRONIZED_KEYWORD;
+ case NATIVE -> ModifierKeyword.NATIVE_KEYWORD;
+ case STRICTFP -> ModifierKeyword.STRICTFP_KEYWORD;
+ };
+ }
+ private Modifier modifierToDom(javax.lang.model.element.Modifier javac) {
+ return this.ast.newModifier(modifierToKeyword(javac));
+ }
+ private int modifierToFlagVal(javax.lang.model.element.Modifier javac) {
+ ModifierKeyword m = modifierToKeyword(javac);
+ if( m != null ) {
+ return m.toFlagValue();
+ }
+ return 0;
+ }
+
+
+ private Modifier convert(javax.lang.model.element.Modifier javac, int startPos, int endPos) {
+ Modifier res = modifierToDom(javac);
+ if (startPos >= 0 && endPos >= startPos && endPos <= this.rawText.length()) {
+ int indOf = this.rawText.indexOf(res.getKeyword().toString(), startPos, endPos);
+ if( indOf != -1 ) {
+ res.setSourceRange(indOf, res.getKeyword().toString().length());
+ }
+ }
+ return res;
+ }
+
+
+ private Name convertName(com.sun.tools.javac.util.Name javac) {
+ if (javac == null || Objects.equals(javac, Names.instance(this.context).error)) {
+ var res = this.ast.newSimpleName(FAKE_IDENTIFIER);
+ res.setFlags(ASTNode.RECOVERED);
+ return res;
+ }
+ if (Objects.equals(javac, Names.instance(this.context).empty)) {
+ return this.ast.newSimpleName("_");
+ }
+ String nameString = javac.toString();
+ int lastDot = nameString.lastIndexOf(".");
+ if (lastDot < 0) {
+ try {
+ return this.ast.newSimpleName(nameString);
+ } catch (IllegalArgumentException ex) { // invalid name: super, this...
+ var res = this.ast.newSimpleName(FAKE_IDENTIFIER);
+ res.setFlags(ASTNode.RECOVERED);
+ return res;
+ }
+ } else {
+ return this.ast.newQualifiedName(convertName(javac.subName(0, lastDot)), (SimpleName)convertName(javac.subName(lastDot + 1, javac.length() - 1)));
+ }
+ // position is set later, in FixPositions, as computing them depends on the sibling
+ }
+
+
+ public org.eclipse.jdt.core.dom.Comment convert(Comment javac, JCTree context) {
+ if ((javac.getStyle() == CommentStyle.JAVADOC_BLOCK || javac.getStyle() == 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);
+ 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.newJavadoc();
+ };
+ javac.isDeprecated(); javac.getText(); // initialize docComment
+ jdt.setSourceRange(javac.getSourcePos(0), javac.getText().length());
+ return jdt;
+ }
+
+ public org.eclipse.jdt.core.dom.Comment convert(Comment javac, int pos, int endPos) {
+ if (javac.getStyle() == CommentStyle.JAVADOC_BLOCK || javac.getStyle() == CommentStyle.JAVADOC_LINE) {
+ var parser = new com.sun.tools.javac.parser.DocCommentParser(ParserFactory.instance(this.context), Log.instance(this.context).currentSource(), javac);
+ JavadocConverter javadocConverter = new JavadocConverter(this, parser.parse(), pos, endPos, this.buildJavadoc);
+ this.javadocConverters.add(javadocConverter);
+ Javadoc javadoc = javadocConverter.convertJavadoc();
+ if (this.ast.apiLevel() >= AST.JLS23) {
+ javadoc.setMarkdown(javac.getStyle() == CommentStyle.JAVADOC_LINE);
+ }
+ this.javadocDiagnostics.addAll(javadocConverter.getDiagnostics());
+ return javadoc;
+ }
+ org.eclipse.jdt.core.dom.Comment jdt = switch (javac.getStyle()) {
+ case LINE -> this.ast.newLineComment();
+ case BLOCK -> this.ast.newBlockComment();
+ case JAVADOC_BLOCK -> this.ast.newJavadoc();
+ case JAVADOC_LINE -> this.ast.newLineComment();
+ };
+ javac.isDeprecated(); javac.getText(); // initialize docComment
+ jdt.setSourceRange(pos, endPos - pos);
+ return jdt;
+ }
+
+ class FixPositions extends ASTVisitor {
+ private final String contents;
+
+ FixPositions() {
+ super(true);
+ String s = null;
+ try {
+ s = JavacConverter.this.javacCompilationUnit.getSourceFile().getCharContent(true).toString();
+ } catch (IOException ex) {
+ ILog.get().error(ex.getMessage(), ex);
+ }
+ this.contents = s;
+ }
+
+ @Override
+ public boolean visit(QualifiedName node) {
+ if (node.getStartPosition() < 0) {
+ int foundOffset = findPositionOfText(node.getFullyQualifiedName(), node.getParent(), siblingsOf(node));
+ if (foundOffset >= 0) {
+ node.setSourceRange(foundOffset, node.getFullyQualifiedName().length());
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void endVisit(QualifiedName node) {
+ if (node.getName().getStartPosition() >= 0) {
+ node.setSourceRange(node.getQualifier().getStartPosition(), node.getName().getStartPosition() + node.getName().getLength() - node.getQualifier().getStartPosition());
+ }
+ }
+
+ @Override
+ public boolean visit(SimpleName name) {
+ if (name.getStartPosition() < 0) {
+ 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())
+ .min();
+ if (start.isPresent() && end.isPresent()) {
+ if (JavadocConverter.isInline(tagElement)) {
+ // include some extra wrapping chars ( `{...}` or `[...]`)
+ tagElement.setSourceRange(start.getAsInt() - 1, end.getAsInt() + 1);
+ } else {
+ tagElement.setSourceRange(start.getAsInt(), end.getAsInt());
+ }
+ }
+ }
+ }
+
+ 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) {
+ AnonymousClassDeclaration e = createAnonymousClassDeclaration(jccd, enumConstantDeclaration);
+ if( e != null ) {
+ if( enumName != null ) {
+ String preTrim = this.rawText.substring(e.getStartPosition() + enumName.length());
+ String trimmed = preTrim.stripLeading();
+ int toSkip = preTrim.length() - trimmed.length();
+ e.setSourceRange(e.getStartPosition() + enumName.length() + toSkip, e.getLength() - enumName.length() - toSkip);
+ }
+ 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