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/Jenkinsfile b/Jenkinsfile
index d6309cba57f..50d33534ea8 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -13,7 +13,7 @@ pipeline {
jdk 'openjdk-jdk23-latest'
}
stages {
- stage('Build') {
+ stage('Build and Test') {
steps {
sh """#!/bin/bash -x
@@ -29,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/core/compiler/CharOperation.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/core/compiler/CharOperation.java
index 5933dbd1958..1b27107d4ec 100644
--- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/core/compiler/CharOperation.java
+++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/core/compiler/CharOperation.java
@@ -3738,55 +3738,12 @@ public static final char[] replace(
char[] toBeReplaced,
char[] replacementChars) {
- int max = array.length;
- int replacedLength = toBeReplaced.length;
- int replacementLength = replacementChars.length;
-
- int[] starts = new int[5];
- int occurrenceCount = 0;
-
- if (!equals(toBeReplaced, replacementChars)) {
-
- next : for (int i = 0; i < max;) {
- int index = indexOf(toBeReplaced, array, true, i);
- if (index == -1) {
- i++;
- continue next;
- }
- if (occurrenceCount == starts.length) {
- System.arraycopy(
- starts,
- 0,
- starts = new int[occurrenceCount * 2],
- 0,
- occurrenceCount);
- }
- starts[occurrenceCount++] = index;
- i = index + replacedLength;
- }
- }
- if (occurrenceCount == 0)
+ String s1 = new String(array);
+ String myRet = s1.replace(new String(toBeReplaced), new String(replacementChars));
+ if( myRet.equals(s1)) {
return array;
- char[] result =
- new char[max
- + occurrenceCount * (replacementLength - replacedLength)];
- int inStart = 0, outStart = 0;
- for (int i = 0; i < occurrenceCount; i++) {
- int offset = starts[i] - inStart;
- System.arraycopy(array, inStart, result, outStart, offset);
- inStart += offset;
- outStart += offset;
- System.arraycopy(
- replacementChars,
- 0,
- result,
- outStart,
- replacementLength);
- inStart += replacedLength;
- outStart += replacementLength;
}
- System.arraycopy(array, inStart, result, outStart, max - inStart);
- return result;
+ return myRet.toCharArray();
}
/**
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..79ba930895c
--- /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..2d7eb4e61bb
--- /dev/null
+++ b/org.eclipse.jdt.core.javac/META-INF/MANIFEST.MF
@@ -0,0 +1,12 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Javac backend for JDT Core
+Bundle-Vendor: Eclipse JDT-LS
+Bundle-SymbolicName: org.eclipse.jdt.core.javac;singleton:=true
+Bundle-Version: 1.0.0.qualifier
+Fragment-Host: org.eclipse.jdt.core
+Automatic-Module-Name: org.eclipse.jdt.core.javac
+Require-Capability: osgi.ee; filter:="(&(osgi.ee=JavaSE)(version=23))"
+Import-Package: org.eclipse.jdt.core.dom
+Export-Package: org.eclipse.jdt.internal.javac;x-friends:="org.eclipse.jdt.core.tests.javac",
+ org.eclipse.jdt.internal.javac.dom;x-friends:="org.eclipse.jdt.core.tests.javac"
diff --git a/org.eclipse.jdt.core.javac/META-INF/p2.inf b/org.eclipse.jdt.core.javac/META-INF/p2.inf
new file mode 100644
index 00000000000..43514b8480f
--- /dev/null
+++ b/org.eclipse.jdt.core.javac/META-INF/p2.inf
@@ -0,0 +1,75 @@
+instructions.configure=org.eclipse.equinox.p2.touchpoint.eclipse.addJvmArg(jvmArg:\
+--add-opens\n\
+jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED\n\
+--add-opens\n\
+jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED\n\
+--add-opens\n\
+jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED\n\
+--add-opens\n\
+jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED\n\
+--add-opens\n\
+jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED\n\
+--add-opens\n\
+jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED\n\
+--add-opens\n\
+jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED\n\
+--add-opens\n\
+jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED\n\
+--add-opens\n\
+jdk.javadoc/jdk.javadoc.internal.doclets.formats.html.taglets.snippet=ALL-UNNAMED\n\
+--add-opens\n\
+jdk.javadoc/jdk.javadoc.internal.doclets.formats.html.taglets=ALL-UNNAMED\n\
+--add-opens\n\
+jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED\n\
+--add-opens\n\
+jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED\n\
+--add-opens\n\
+jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED\n\
+--add-opens\n\
+jdk.compiler/com.sun.tools.javac.platform=ALL-UNNAMED\n\
+--add-opens\n\
+jdk.compiler/com.sun.tools.javac.resources=ALL-UNNAMED\n\
+-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\
+);
+
+# See https://github.com/eclipse-equinox/p2/issues/572 about requirement for multiple instructions
+instructions.unconfigure=\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.javadoc/jdk.javadoc.internal.doclets.formats.html.taglets.snippet=ALL-UNNAMED);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.javadoc/jdk.javadoc.internal.doclets.formats.html.taglets=ALL-UNNAMED);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.platform=ALL-UNNAMED);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:--add-opens);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:jdk.compiler/com.sun.tools.javac.resources=ALL-UNNAMED);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:-DICompilationUnitResolver=org.eclipse.jdt.core.dom.JavacCompilationUnitResolver);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:-DAbstractImageBuilder.compilerFactory=org.eclipse.jdt.internal.javac.JavacCompilerFactory);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:-DCompilationUnit.DOM_BASED_OPERATIONS=true);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:-DCompilationUnit.codeComplete.DOM_BASED_OPERATIONS=true_);\
+org.eclipse.equinox.p2.touchpoint.eclipse.removeJvmArg(jvmArg:-DSourceIndexer.DOM_BASED_INDEXER=true);
\ No newline at end of file
diff --git a/org.eclipse.jdt.core.javac/README.md b/org.eclipse.jdt.core.javac/README.md
new file mode 100644
index 00000000000..cfef1072d8d
--- /dev/null
+++ b/org.eclipse.jdt.core.javac/README.md
@@ -0,0 +1,69 @@
+# Eclipse JDT over Javac
+
+This fragment contains a Javac backend (instead of ECJ) for JDT features:
+* error reporting/reconciling
+* build/compilation
+* indexing
+* code selection (Ctrl + click / hover)
+* code completion (requires JDT fork bundle + opt-out flag)
+* search/match (requires JDT fork bundle + opt-out flag)
+
+## ❓ Why?
+
+Some background...
+* These days, with more frequent and more features Java releases, it's becoming hard for JDT to **cope with new Java features on time** and **facilitate support for upcoming/preview features before Java is released so JDT can participate to consolidation of the spec**. Over recent releases, JDT has failed at providing the features on time. This is mostly because of the difficulty of maintaining the Eclipse compiler: compilers are difficult bits of code to maintain and it takes a lot of time to implement things well in them. There is no clear sign the situation can improve here.
+* The Eclipse compiler has always suffered from occasional **inconsistencies with Javac** which end-users fail at understanding. Sometimes, ECJ is right, sometimes Javac is; but for end-users and for the ecosystem, Javac is the reference implementation and it's behavior is what they perceive as the actual specification
+* JDT has a very strong ecosystem (JDT-LS, plugins) a tons of nice features, so it seems profitable to **keep relying higher-level JDT APIs, such as model or DOM** to remain compatible with the ecosystem
+
+🎯 The technical proposal here mostly to **allow Javac to be used at the lowest-level of JDT**, under the hood, to populate higher-level models that are used in many operations; named the JDT DOM and IJavaElement models. It is expected that if we can create a good DOM and IJavaElement structure with another strategy (eg using Javac API), then all higher level operations will remain working as well without modification.
+
+## 📥 Install on top of existing JDT in your Eclipse IDE
+
+Using the _Install New Software_ dialog and pointing to https://ci.eclipse.org/ls/job/jdt-core-incubator/job/dom-with-javac/lastSuccessfulBuild/artifact/repository/target/repository/ p2 repository,
+install the _Javac backend for JDT_ artifact. Installing it will also tweak your `eclipse.ini` file to add the relevant options.
+
+Note that some required feature switches are not yet available in upstream JDT (see above) and thus will still default to ECJ.
+
+## ⌨️ Development
+
+### ⌨️ Contribute
+
+From a PDE-able IDE using a target platform that is suitable for JDT development (usually default case).
+
+You'll need to import the code of `org.eclipse.jdt.core` and `org.eclipse.jdt.core.javac` from this branch in your Eclipse workspace; and create a Launch Configuration of type "Eclipse Application" which does include the `org.eclipse.jdt.core` bundle. Go to _Arguments_ tab of this launch configuration, and add the following content to the _VM arguments_ list:
+
+* `-DCompilationUnit.DOM_BASED_OPERATIONS=true -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 ...` as visible in the `org.eclipse.jdt.core.javac/META-INF/p2.inf` file to allow to access internal API of the JVM, including Javac ones
+* `ICompilationUnitResolver=org.eclipse.jdt.core.dom.JavacCompilationUnitResolver` system property enables using Javac instead of ECJ to create JDT DOM AST.
+* `AbstractImageBuilder.compilerFactory=org.eclipse.jdt.internal.javac.JavacCompilerFactory` system property instruct the builder to use Javac instead of ECJ to generate the .class file during build.
+
+Those properties can be set separately, which can useful when developing one particular aspect of this proposal, which property to set depends on what you want to focus on.
+
+### 📈 Progress
+
+* Refactorings in upstream JDT got merged in order to allow alternative (non-ECJ strategies) for
+ * reconciling model/errors using DOM ✔DONE
+ * generating DOM from alternative parser ✔DONE
+ * codeSelect ✔DONE
+ * indexing ✔DONE
+ * completion ⌨️IN PROGRESS
+ * search/match ⌨️IN PROGRESS
+ * compiler ✔DONE
+* Javac backend for
+ * producing Javac AST & Symbols ✔DONE (incl. annotation processing)
+ * Javac->JDT AST conversion ✔DONE (incl. annotation processing)
+ * binding conversion ✔DONE
+ * JavacCompiler ✔DONE
+
+
+🤔 What are the potential concerns:
+* **Memory cost** of retaining Javac contexts needs to be evaluated (can we get rid of the context earlier? Can we share subparts of the concerns across multiple files in the project?...)
+* It seems hard to find reusable parts from the **CompletionEngine**, although many proposals shouldn't really depend on the parser (so should be reusable)
+
+
+😧 What are the confirmed concerns:
+* **Null analysis** and some other **static analysis** are coded deep in ECJ and cannot be used with Javac. A solution can be to leverage another analysis engine (eg SpotBugs, SonarQube) deal with those features.
+* At the moment, Javac cannot be configured to **generate .class despite CompilationError** in them like ECJ can do to allow updating the target application even when some code is not complete yet
+ * We may actually be capable of hacking something like this in Eclipse/Javac integration (although it would be best to provide this directly in Javac), but running a 1st attempt of compilation, collecting errors, and then alterating the working copy of the source passed to Javac in case of error. More or less `if (diagnostics.anyMatch(getKind() == "error") { removeBrokenAST(diagnostic); injectAST("throw new CompilationError(diagnostic.getMessage()")`.
diff --git a/org.eclipse.jdt.core.javac/build.properties b/org.eclipse.jdt.core.javac/build.properties
new file mode 100644
index 00000000000..e3023e14e99
--- /dev/null
+++ b/org.eclipse.jdt.core.javac/build.properties
@@ -0,0 +1,5 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ fragment.xml
diff --git a/org.eclipse.jdt.core.javac/fragment.xml b/org.eclipse.jdt.core.javac/fragment.xml
new file mode 100644
index 00000000000..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..9d83517d8d7
--- /dev/null
+++ b/org.eclipse.jdt.core.javac/pom.xml
@@ -0,0 +1,62 @@
+
+
+
+ 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.compiler/com.sun.tools.javac.platform=ALL-UNNAMED
+ --add-exports
+ jdk.compiler/com.sun.tools.javac.resources=ALL-UNNAMED
+ --add-exports
+ jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED
+ --add-exports
+ jdk.javadoc/jdk.javadoc.internal.doclets.formats.html.taglets.snippet=ALL-UNNAMED
+ --add-exports
+ jdk.javadoc/jdk.javadoc.internal.doclets.formats.html.taglets=ALL-UNNAMED
+
+
+
+
+
+
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..a0f730ab9df
--- /dev/null
+++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacBindingResolver.java
@@ -0,0 +1,1861 @@
+/*******************************************************************************
+ * 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.CompilationUnitTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.DocTreePath;
+import com.sun.source.util.JavacTask;
+import com.sun.source.util.TreePath;
+import com.sun.tools.javac.api.JavacTaskImpl;
+import com.sun.tools.javac.api.JavacTrees;
+import com.sun.tools.javac.code.Attribute;
+import com.sun.tools.javac.code.Attribute.Compound;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import com.sun.tools.javac.code.Symbol.ModuleSymbol;
+import com.sun.tools.javac.code.Symbol.PackageSymbol;
+import com.sun.tools.javac.code.Symbol.RootPackageSymbol;
+import com.sun.tools.javac.code.Symbol.TypeSymbol;
+import com.sun.tools.javac.code.Symbol.TypeVariableSymbol;
+import com.sun.tools.javac.code.Symbol.VarSymbol;
+import com.sun.tools.javac.code.Symtab;
+import com.sun.tools.javac.code.Type.ArrayType;
+import com.sun.tools.javac.code.Type.ClassType;
+import com.sun.tools.javac.code.Type.ErrorType;
+import com.sun.tools.javac.code.Type.ForAll;
+import com.sun.tools.javac.code.Type.JCNoType;
+import com.sun.tools.javac.code.Type.JCPrimitiveType;
+import com.sun.tools.javac.code.Type.JCVoidType;
+import com.sun.tools.javac.code.Type.MethodType;
+import com.sun.tools.javac.code.Type.ModuleType;
+import com.sun.tools.javac.code.Type.PackageType;
+import com.sun.tools.javac.code.Type.TypeVar;
+import com.sun.tools.javac.code.TypeTag;
+import com.sun.tools.javac.code.Types;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.JCTree.JCAnnotatedType;
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree;
+import com.sun.tools.javac.tree.JCTree.JCClassDecl;
+import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
+import com.sun.tools.javac.tree.JCTree.JCIdent;
+import com.sun.tools.javac.tree.JCTree.JCLambda;
+import com.sun.tools.javac.tree.JCTree.JCLiteral;
+import com.sun.tools.javac.tree.JCTree.JCMemberReference;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
+import com.sun.tools.javac.tree.JCTree.JCModuleDecl;
+import com.sun.tools.javac.tree.JCTree.JCNewArray;
+import com.sun.tools.javac.tree.JCTree.JCNewClass;
+import com.sun.tools.javac.tree.JCTree.JCPackageDecl;
+import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree;
+import com.sun.tools.javac.tree.JCTree.JCTypeApply;
+import com.sun.tools.javac.tree.JCTree.JCTypeCast;
+import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.tree.JCTree.JCWildcard;
+import com.sun.tools.javac.tree.TreeInfo;
+import com.sun.tools.javac.util.Context;
+
+/**
+ * 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 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) {
+ if( packageSymbol.owner instanceof PackageSymbol parentPack) {
+ if( !(parentPack instanceof RootPackageSymbol) )
+ getPackageBinding(parentPack);
+ }
+ JavacPackageBinding newInstance = new JavacPackageBinding(packageSymbol, JavacBindingResolver.this) { };
+ return preferentiallyInsertPackageBinding(newInstance);
+ }
+ public JavacPackageBinding getPackageBinding(Name name) {
+ String n = packageNameToString(name);
+ if( n == null )
+ return null;
+ JavacPackageBinding newInstance = new JavacPackageBinding(n, JavacBindingResolver.this) {};
+ return preferentiallyInsertPackageBinding(newInstance);
+ }
+
+ public JavacPackageBinding findExistingPackageBinding(Name name) {
+ String n = name == null ? null : name.toString();
+ if( n == null )
+ return null;
+ JavacPackageBinding newInstance = new JavacPackageBinding(n, JavacBindingResolver.this) {};
+ String k = newInstance == null ? null : newInstance.getKey();
+ if( k != null ) {
+ JavacPackageBinding current = packageBindings.get(k);
+ return current;
+ }
+ return null;
+ }
+
+ private String packageNameToString(Name name) {
+ String n = null;
+ if( name instanceof QualifiedName )
+ n = name.toString();
+ else if( name instanceof SimpleName snn) {
+ if( name.getParent() instanceof QualifiedName qn) {
+ if( qn.getName() == name ) {
+ n = qn.toString();
+ } else if( qn.getQualifier() == name) {
+ n = name.toString();
+ }
+ }
+ }
+ return n;
+ }
+
+ private JavacPackageBinding preferentiallyInsertPackageBinding(JavacPackageBinding newest) {
+ // A package binding may be created while traversing something as simple as a name.
+ // The binding using name-only logic should be instantiated, but
+ // when a proper symbol is found, it should be added to that object.
+ String k = newest == null ? null : newest.getKey();
+ if( k != null ) {
+ JavacPackageBinding current = packageBindings.get(k);
+ if( current == null ) {
+ packageBindings.putIfAbsent(k, newest);
+ } else if( current.getPackageSymbol() == null && newest.getPackageSymbol() != null) {
+ current.setPackageSymbol(newest.getPackageSymbol());
+ }
+ return packageBindings.get(k);
+ }
+ return null;
+ }
+ //
+ private Map typeBinding = new HashMap<>();
+ public JavacTypeBinding getTypeBinding(JCTree tree, com.sun.tools.javac.code.Type type) {
+ return getTypeBinding(type, tree instanceof JCClassDecl);
+ }
+
+
+ public JavacTypeBinding getTypeBinding(com.sun.tools.javac.code.Type type) {
+ if (type == null) {
+ return null;
+ }
+ return getTypeBinding(type.baseType() /* remove metadata for constant values */, false);
+ }
+ public JavacTypeBinding getTypeBinding(com.sun.tools.javac.code.Type type, boolean isDeclaration) {
+ if (type instanceof com.sun.tools.javac.code.Type.TypeVar typeVar) {
+ return getTypeVariableBinding(typeVar);
+ }
+ if (type == null || type == com.sun.tools.javac.code.Type.noType) {
+ return null;
+ }
+ if (type instanceof ErrorType errorType) {
+ var originalType = errorType.getOriginalType();
+ if (originalType != com.sun.tools.javac.code.Type.noType
+ && !(originalType instanceof com.sun.tools.javac.code.Type.MethodType)
+ && !(originalType instanceof com.sun.tools.javac.code.Type.ForAll)
+ && !(originalType instanceof com.sun.tools.javac.code.Type.ErrorType)) {
+ JavacTypeBinding newInstance = new JavacTypeBinding(originalType, type.tsym, isDeclaration, JavacBindingResolver.this) { };
+ typeBinding.putIfAbsent(newInstance, newInstance);
+ JavacTypeBinding jcb = typeBinding.get(newInstance);
+ jcb.setRecovered(true);
+ return jcb;
+ } else if (errorType.tsym instanceof ClassSymbol classErrorSymbol &&
+ Character.isJavaIdentifierStart(classErrorSymbol.getSimpleName().charAt(0))) {
+ // non usable original type: try symbol
+ JavacTypeBinding newInstance = new JavacTypeBinding(classErrorSymbol.type, classErrorSymbol, isDeclaration, JavacBindingResolver.this) { };
+ typeBinding.putIfAbsent(newInstance, newInstance);
+ JavacTypeBinding jcb = typeBinding.get(newInstance);
+ jcb.setRecovered(true);
+ return jcb;
+ }
+ // no type information we could recover from
+ return null;
+ }
+ if (!type.isParameterized() && !type.isRaw() && type instanceof ClassType classType
+ && classType.interfaces_field == null) {
+ // workaround faulty case of TypeMismatchQuickfixText.testMismatchingReturnTypeOnGenericMethod
+ // interfaces/supertypes are not set which seem to imply that the compiler generated
+ // a dummy type object that's not suitable for a binding.
+ // Fail back to an hopefully better type
+ type = type.tsym.type;
+ }
+ JavacTypeBinding newInstance = new JavacTypeBinding(type, type.tsym, isDeclaration, JavacBindingResolver.this) { };
+ typeBinding.putIfAbsent(newInstance, newInstance);
+ return typeBinding.get(newInstance);
+ }
+ //
+ private Map typeVariableBindings = new HashMap<>();
+ public JavacTypeVariableBinding getTypeVariableBinding(TypeVar typeVar) {
+ JavacTypeVariableBinding newInstance = new JavacTypeVariableBinding(typeVar, (TypeVariableSymbol)typeVar.tsym, JavacBindingResolver.this) { };
+ typeVariableBindings.putIfAbsent(newInstance, newInstance);
+ return typeVariableBindings.get(newInstance);
+ }
+ //
+ private Map variableBindings = new HashMap<>();
+ public JavacVariableBinding getVariableBinding(VarSymbol varSymbol) {
+ if (varSymbol == null) {
+ return null;
+ }
+ JavacVariableBinding newInstance = new JavacVariableBinding(varSymbol, JavacBindingResolver.this) { };
+ String k = newInstance.getKey();
+ if( k != null ) {
+ variableBindings.putIfAbsent(k, newInstance);
+ return variableBindings.get(k);
+ }
+ return null;
+ }
+ //
+ private Map lambdaBindings = new HashMap<>();
+ public JavacLambdaBinding getLambdaBinding(JavacMethodBinding javacMethodBinding, LambdaExpression lambda) {
+ JavacLambdaBinding newInstance = new JavacLambdaBinding(javacMethodBinding, lambda);
+ String k = newInstance.getKey();
+ if( k != null ) {
+ lambdaBindings.putIfAbsent(k, newInstance);
+ return lambdaBindings.get(k);
+ }
+ return null;
+ }
+
+ public IBinding getBinding(final Symbol owner, final com.sun.tools.javac.code.Type type) {
+ Symbol recoveredSymbol = getRecoveredSymbol(type);
+ if (recoveredSymbol != null) {
+ return getBinding(recoveredSymbol, recoveredSymbol.type);
+ }
+ if (type instanceof ErrorType || owner.owner == null || owner.owner.type == com.sun.tools.javac.code.Type.noType) {
+ if (type.getOriginalType() instanceof MethodType missingMethodType) {
+ return getErrorMethodBinding(missingMethodType, owner);
+ }
+ }
+ if (owner instanceof final PackageSymbol other) {
+ return getPackageBinding(other);
+ } else if (owner instanceof ModuleSymbol typeSymbol) {
+ return getModuleBinding(typeSymbol);
+ } else if (owner instanceof Symbol.TypeVariableSymbol typeVariableSymbol) {
+ if (type instanceof TypeVar typeVar) {
+ return getTypeVariableBinding(typeVar);
+ } else if (typeVariableSymbol.type instanceof TypeVar typeVar) {
+ return getTypeVariableBinding(typeVar);
+ }
+ // without the type there is not much we can do; fallthrough to null
+ } else if (owner instanceof TypeSymbol typeSymbol) {
+ return getTypeBinding(isTypeOfType(type) ? type : typeSymbol.type);
+ } else if (owner instanceof final MethodSymbol other) {
+ 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<>();
+ private List javacCompilationUnits;
+
+ public JavacBindingResolver(IJavaProject javaProject, JavacTask javacTask, Context context, JavacConverter converter, WorkingCopyOwner owner, List javacCompilationUnits) {
+ this.javac = javacTask;
+ this.context = context;
+ this.javaProject = javaProject;
+ this.converter = converter;
+ this.owner = owner;
+ this.javacCompilationUnits = javacCompilationUnits;
+ }
+
+ private void resolve() {
+ if (this.symbolToDeclaration != null) {
+ // already done and ready
+ return;
+ }
+ 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 {
+ Iterable extends Element> elements;
+ // long start = System.currentTimeMillis();
+ if (this.javac instanceof JavacTaskImpl javacTaskImpl) {
+ if (javacCompilationUnits != null && !javacCompilationUnits.isEmpty()) {
+ Iterable extends CompilationUnitTree> trees = javacCompilationUnits;
+ elements = javacTaskImpl.enter(trees);
+ } else {
+ elements = javacTaskImpl.enter();
+ }
+ elements = javacTaskImpl.analyze(elements);
+// long count = StreamSupport.stream(elements.spliterator(), false).count();
+// String name = elements.iterator().hasNext()
+// ? elements.iterator().next().getSimpleName().toString()
+// : "";
+// ILog.get().info("enter/analyze elements=" + count + ", took: "
+// + (System.currentTimeMillis() - start) + ", first=" + name);
+ } else {
+ elements = this.javac.analyze();
+// long count = StreamSupport.stream(elements.spliterator(), false).count();
+// String name = elements.iterator().hasNext()
+// ? elements.iterator().next().getSimpleName().toString()
+// : "";
+// ILog.get().info("analyze elements=" + count + ", took: " + (System.currentTimeMillis() - start)
+// + ", first=" + name);
+ }
+ } catch (IOException | Error | RuntimeException e) {
+ ILog.get().error(e.getMessage(), e);
+ }
+ }
+ // some cleanups to encourage garbage collection
+ JavacCompilationUnitResolver.cleanup(context);
+ }
+ this.javac = null;
+ synchronized (this) {
+ if (this.symbolToDeclaration == null) {
+ Map wipSymbolToDeclaration = new HashMap<>();
+ this.converter.domToJavac.forEach((jdt, javac) -> {
+ // We don't want FieldDeclaration (ref ASTConverterTest2.test0433)
+ if (jdt instanceof MethodDeclaration ||
+ jdt instanceof VariableDeclaration ||
+ jdt instanceof EnumConstantDeclaration ||
+ jdt instanceof AnnotationTypeMemberDeclaration ||
+ jdt instanceof AbstractTypeDeclaration ||
+ jdt instanceof AnonymousClassDeclaration ||
+ jdt instanceof TypeParameter) {
+ var symbol = TreeInfo.symbolFor(javac);
+ if (symbol != null) {
+ wipSymbolToDeclaration.put(symbol, jdt);
+ }
+ }
+ });
+ // prefill the binding so that they're already searchable by key
+ wipSymbolToDeclaration.keySet().forEach(sym -> this.bindings.getBinding(sym, null));
+ this.symbolToDeclaration = wipSymbolToDeclaration;
+ }
+ }
+ }
+
+ @Override
+ public ASTNode findDeclaringNode(IBinding binding) {
+ return findNode(getJavacSymbol(binding));
+ }
+
+ @Override
+ public ASTNode findDeclaringNode(String bindingKey) {
+ resolve();
+ IBinding binding = this.bindings.getBinding(bindingKey);
+ if (binding == null) {
+ return null;
+ }
+ return findDeclaringNode(binding);
+ }
+
+ private Symbol getJavacSymbol(IBinding binding) {
+ if (binding instanceof JavacMemberValuePairBinding valuePair) {
+ return getJavacSymbol(valuePair.method);
+ }
+ if (binding instanceof JavacAnnotationBinding annotation) {
+ return getJavacSymbol(annotation.getAnnotationType());
+ }
+ if (binding instanceof JavacMethodBinding method) {
+ return method.methodSymbol;
+ }
+ if (binding instanceof JavacPackageBinding packageBinding) {
+ return packageBinding.getPackageSymbol();
+ }
+ if (binding instanceof JavacTypeBinding type) {
+ return type.typeSymbol;
+ }
+ if (binding instanceof JavacVariableBinding variable) {
+ return variable.variableSymbol;
+ }
+ return null;
+ }
+
+ public ASTNode findNode(Symbol symbol) {
+ if (this.symbolToDeclaration != null) {
+ return this.symbolToDeclaration.get(symbol);
+ }
+ return null;
+ }
+
+ @Override
+ public ITypeBinding resolveType(Type type) {
+ if (type.getParent() instanceof ParameterizedType parameterized
+ && type.getLocationInParent() == ParameterizedType.TYPE_PROPERTY) {
+ // use parent type for this as it keeps generics info
+ return resolveType(parameterized);
+ }
+ resolve();
+ if (type.getParent() instanceof ArrayCreation arrayCreation) {
+ JCTree jcArrayCreation = this.converter.domToJavac.get(arrayCreation);
+ return this.bindings.getTypeBinding(((JCNewArray)jcArrayCreation).type);
+ }
+ JCTree jcTree = this.converter.domToJavac.get(type);
+ if (jcTree instanceof JCIdent ident && ident.type != null) {
+ if (ident.type instanceof PackageType) {
+ return null;
+ }
+ return this.bindings.getTypeBinding(ident.type);
+ }
+ if (jcTree instanceof JCFieldAccess access) {
+ IBinding b = this.getFieldAccessBinding(access);
+ return b instanceof ITypeBinding tb ? tb : null;
+ }
+ if (jcTree instanceof JCPrimitiveTypeTree primitive && primitive.type != null) {
+ return this.bindings.getTypeBinding(primitive.type);
+ }
+ if (jcTree instanceof JCArrayTypeTree arrayType && arrayType.type != null) {
+ return this.bindings.getTypeBinding(arrayType.type);
+ }
+ if (jcTree instanceof JCWildcard wcType && wcType.type != null) {
+ return this.bindings.getTypeBinding(wcType.type);
+ }
+ if (jcTree instanceof JCTypeApply jcta && jcta.type != null) {
+ var res = this.bindings.getTypeBinding(jcta.type);
+ if (res != null) {
+ return res;
+ }
+ if (jcta.getType().type instanceof ErrorType errorType) {
+ res = this.bindings.getTypeBinding(errorType.getOriginalType(), true);
+ if (res != null) {
+ return res;
+ }
+ }
+ if (jcta.getType().type != null) {
+ res = this.bindings.getTypeBinding(jcta.getType().type);
+ if (res != null) {
+ return res;
+ }
+ }
+ }
+ if (jcTree instanceof JCAnnotatedType annotated && annotated.type != null) {
+ return this.bindings.getTypeBinding(annotated.type);
+ }
+
+// return this.flowResult.stream().map(env -> env.enclClass)
+// .filter(Objects::nonNull)
+// .map(decl -> decl.type)
+// .map(javacType -> javacType.tsym)
+// .filter(sym -> Objects.equals(type.toString(), sym.name.toString()))
+// .findFirst()
+// .map(symbol -> new JavacTypeBinding(symbol, this))
+// .orElse(null);
+// }
+// if (type instanceof QualifiedType qualifiedType) {
+// JCTree jcTree = this.converter.domToJavac.get(qualifiedType);
+// }
+ if (type instanceof PrimitiveType primitive) { // a type can be requested even if there is no token for it in JCTree
+ return resolveWellKnownType(primitive.getPrimitiveTypeCode().toString());
+ }
+ if (type.getAST().apiLevel() >= AST.JLS10 && type.isVar()) {
+ if (type.getParent() instanceof VariableDeclaration varDecl) {
+ IVariableBinding varBinding = resolveVariable(varDecl);
+ if (varBinding != null) {
+ return varBinding.getType();
+ }
+ }
+ if (type.getParent() instanceof VariableDeclarationStatement statement &&
+ this.converter.domToJavac.get(statement) instanceof JCVariableDecl jcDecl &&
+ jcDecl.type != null) {
+ return this.bindings.getTypeBinding(jcDecl.type);
+ }
+ }
+ // Recovery: sometime with Javac, there is no suitable type/symbol
+ // Workaround: use a RecoveredTypeBinding
+ // Caveats: cascade to other workarounds
+ return createRecoveredTypeBinding(type);
+ }
+
+ private RecoveredTypeBinding createRecoveredTypeBinding(Type type) {
+ return new RecoveredTypeBinding(this, type) {
+ @Override
+ public ITypeBinding getTypeDeclaration() {
+ if (isParameterizedType()) {
+ return new GenericRecoveredTypeBinding(JavacBindingResolver.this, type, this);
+ }
+ return super.getTypeDeclaration();
+ }
+ @Override
+ public IPackageBinding getPackage() {
+ if (type instanceof SimpleType simpleType && simpleType.getName() instanceof SimpleName) {
+ return JavacBindingResolver.this.converter.domToJavac
+ .values()
+ .stream()
+ .filter(CompilationUnit.class::isInstance)
+ .map(CompilationUnit.class::cast)
+ .map(CompilationUnit::getPackage)
+ .map(PackageDeclaration::resolveBinding)
+ .findAny()
+ .orElse(super.getPackage());
+ }
+ return super.getPackage();
+ }
+ };
+ }
+
+ @Override
+ ITypeBinding resolveType(AnnotationTypeDeclaration type) {
+ resolve();
+ JCTree javacNode = this.converter.domToJavac.get(type);
+ if (javacNode instanceof JCClassDecl jcClassDecl && jcClassDecl.type != null) {
+ return this.bindings.getTypeBinding(jcClassDecl.type);
+ }
+ return null;
+ }
+
+ @Override
+ ITypeBinding resolveType(RecordDeclaration type) {
+ resolve();
+ JCTree javacNode = this.converter.domToJavac.get(type);
+ if (javacNode instanceof JCClassDecl jcClassDecl && jcClassDecl.type != null) {
+ return this.bindings.getTypeBinding(jcClassDecl.type);
+ }
+ return null;
+ }
+
+
+ @Override
+ ITypeBinding resolveType(TypeDeclaration type) {
+ resolve();
+ JCTree javacNode = this.converter.domToJavac.get(type);
+ if (javacNode instanceof JCClassDecl jcClassDecl && jcClassDecl.type != null) {
+ return this.bindings.getTypeBinding(jcClassDecl.type, true);
+ }
+ if (javacNode instanceof JCClassDecl jcClassDecl && jcClassDecl.sym != null && jcClassDecl.sym.type != null) {
+ return this.bindings.getTypeBinding(jcClassDecl.sym.type, true);
+ }
+ return null;
+ }
+
+ @Override
+ ITypeBinding resolveType(EnumDeclaration enumDecl) {
+ resolve();
+ JCTree javacNode = this.converter.domToJavac.get(enumDecl);
+ if (javacNode instanceof JCClassDecl jcClassDecl && jcClassDecl.type != null) {
+ return this.bindings.getTypeBinding(jcClassDecl.type, true);
+ }
+ return null;
+ }
+
+ @Override
+ ITypeBinding resolveType(AnonymousClassDeclaration anonymousClassDecl) {
+ resolve();
+ JCTree javacNode = this.converter.domToJavac.get(anonymousClassDecl);
+ if (javacNode instanceof JCClassDecl jcClassDecl && jcClassDecl.type != null) {
+ return this.bindings.getTypeBinding(jcClassDecl.type, true);
+ }
+ return null;
+ }
+ ITypeBinding resolveTypeParameter(TypeParameter typeParameter) {
+ resolve();
+ JCTree javacNode = this.converter.domToJavac.get(typeParameter);
+ if (javacNode instanceof JCTypeParameter jcClassDecl) {
+ return this.bindings.getTypeBinding(jcClassDecl.type);
+ }
+ return null;
+ }
+
+ @Override
+ IVariableBinding resolveField(FieldAccess fieldAccess) {
+ resolve();
+ JCTree javacElement = this.converter.domToJavac.get(fieldAccess);
+ if (javacElement instanceof JCFieldAccess javacFieldAccess && javacFieldAccess.sym instanceof VarSymbol varSymbol) {
+ return this.bindings.getVariableBinding(varSymbol);
+ }
+ return null;
+ }
+
+ @Override
+ IVariableBinding resolveField(SuperFieldAccess fieldAccess) {
+ resolve();
+ JCTree javacElement = this.converter.domToJavac.get(fieldAccess);
+ if (javacElement instanceof JCFieldAccess javacFieldAccess && javacFieldAccess.sym instanceof VarSymbol varSymbol) {
+ return this.bindings.getVariableBinding(varSymbol);
+ }
+ return null;
+ }
+
+ @Override
+ IMethodBinding resolveMethod(MethodInvocation method) {
+ resolve();
+ JCTree javacElement = this.converter.domToJavac.get(method);
+ List typeArgs = List.of();
+ if (javacElement instanceof JCMethodInvocation javacMethodInvocation) {
+ javacElement = javacMethodInvocation.getMethodSelect();
+ typeArgs = javacMethodInvocation.getTypeArguments().stream().map(jcExpr -> jcExpr.type).toList();
+ }
+ var type = javacElement.type;
+ // next condition matches `localMethod(this::missingMethod)`
+ if (javacElement instanceof JCIdent ident && type == null) {
+ ASTNode node = method;
+ while (node != null && !(node instanceof AbstractTypeDeclaration)) {
+ node = node.getParent();
+ }
+ if (node instanceof AbstractTypeDeclaration decl &&
+ this.converter.domToJavac.get(decl) instanceof JCClassDecl javacClassDecl &&
+ javacClassDecl.type instanceof ClassType classType &&
+ !classType.isErroneous()) {
+ type = classType;
+ }
+ if (type != null &&
+ type.tsym.members().findFirst(ident.getName(), MethodSymbol.class::isInstance) instanceof MethodSymbol methodSymbol &&
+ methodSymbol.type instanceof MethodType methodType) {
+ var res = this.bindings.getMethodBinding(methodType, methodSymbol, null, false);
+ if (res != null) {
+ return res;
+ }
+ }
+ }
+ var sym = javacElement instanceof JCIdent ident ? ident.sym :
+ javacElement instanceof JCFieldAccess fieldAccess ? fieldAccess.sym :
+ null;
+ if (type instanceof MethodType methodType && sym instanceof MethodSymbol methodSymbol) {
+ com.sun.tools.javac.code.Type parentType = null;
+ if (methodSymbol.owner instanceof ClassSymbol ownerClass && isTypeOfType(ownerClass.type)) {
+ if (ownerClass.type.isParameterized()
+ && method.getExpression() != null
+ && resolveExpressionType(method.getExpression()) instanceof JavacTypeBinding exprType) {
+ parentType = exprType.type;
+ } else {
+ parentType = ownerClass.type;
+ }
+ }
+ return this.bindings.getMethodBinding(methodType, methodSymbol, parentType, false);
+ }
+ if (type instanceof ErrorType errorType && errorType.getOriginalType() instanceof MethodType methodType) {
+ if (sym.owner instanceof TypeSymbol typeSymbol) {
+ Iterator methods = typeSymbol.members().getSymbolsByName(sym.getSimpleName(), m -> m instanceof MethodSymbol && methodType.equals(m.type)).iterator();
+ if (methods.hasNext()) {
+ return this.bindings.getMethodBinding(methodType, (MethodSymbol)methods.next(), null, false);
+ }
+ }
+ return this.bindings.getErrorMethodBinding(methodType, sym);
+ }
+ if (type == null && sym instanceof MethodSymbol methodSym && methodSym.type instanceof ForAll methodTemplateType) {
+ // build type from template
+ Map resolutionMapping = new HashMap<>();
+ var templateParameters = methodTemplateType.getTypeVariables();
+ for (int i = 0; i < typeArgs.size() && i < templateParameters.size(); i++) {
+ resolutionMapping.put(templateParameters.get(i), typeArgs.get(i));
+ }
+ MethodType methodType = new MethodType(
+ methodTemplateType.asMethodType().getParameterTypes().map(t -> applyType(t, resolutionMapping)),
+ applyType(methodTemplateType.asMethodType().getReturnType(), resolutionMapping),
+ methodTemplateType.asMethodType().getThrownTypes().map(t -> applyType(t, resolutionMapping)),
+ methodTemplateType.tsym);
+ return this.bindings.getMethodBinding(methodType, methodSym, methodSym.owner.type, false);
+ }
+ if (type == null && sym != null && sym.type.isErroneous()
+ && sym.owner.type instanceof ClassType classType) {
+ var parentTypeBinding = this.bindings.getTypeBinding(classType);
+ return Arrays.stream(parentTypeBinding.getDeclaredMethods())
+ .filter(binding -> binding.getName().equals(sym.getSimpleName().toString()))
+ .findAny()
+ .orElse(null);
+ }
+ if (type == null && sym instanceof MethodSymbol methodSymbol && methodSymbol.type instanceof MethodType
+ && javacElement instanceof JCFieldAccess selectedMethod
+ && selectedMethod.getExpression() != null
+ && selectedMethod.getExpression().type instanceof ClassType classType) {
+ // method is resolved, but type is not, probably because of invalid param
+ // workaround: check compatible method in selector
+ var parentTypeBinding = this.bindings.getTypeBinding(classType);
+ var res = Arrays.stream(parentTypeBinding.getDeclaredMethods())
+ .filter(binding -> binding instanceof JavacMethodBinding javacMethodBinding && javacMethodBinding.methodSymbol == methodSymbol)
+ .findAny()
+ .orElse(null);
+ if (res != null) {
+ return res;
+ }
+ }
+ if (sym instanceof MethodSymbol && sym.type instanceof MethodType) {
+ return (IMethodBinding)this.bindings.getBinding(sym, sym.type);
+ }
+ return null;
+ }
+
+ /**
+ * Derives an "applied" type replacing know TypeVar with their current value
+ * @param from The type to check for replacement of TypeVars
+ * @param resolutionMapping a dictionary defining which concrete type must replace TypeVar
+ * @return The derived "applied" type: recursively checks the type, replacing
+ * known {@link TypeVar} instances in those with their value defined in `resolutionMapping`
+ */
+ private static com.sun.tools.javac.code.Type applyType(com.sun.tools.javac.code.Type from, Map resolutionMapping) {
+ if (from instanceof TypeVar typeVar) {
+ var directMapping = resolutionMapping.get(from);
+ if (directMapping != null) {
+ return directMapping;
+ }
+ return typeVar;
+ }
+ if (from instanceof JCNoType || from instanceof JCVoidType ||
+ from instanceof JCPrimitiveType) {
+ return from;
+ }
+ if (from instanceof ClassType classType) {
+ var args = classType.getTypeArguments().map(typeArg -> applyType(typeArg, resolutionMapping));
+ if (Objects.equals(args, classType.getTypeArguments())) {
+ return classType;
+ }
+ return new ClassType(classType.getEnclosingType(), args, classType.tsym);
+ }
+ if (from instanceof ArrayType arrayType) {
+ var targetElemType = applyType(arrayType.elemtype, resolutionMapping);
+ if (Objects.equals(targetElemType, arrayType.elemtype)) {
+ return arrayType;
+ }
+ return new ArrayType(targetElemType, arrayType.tsym);
+ }
+ return from;
+ }
+
+ @Override
+ IMethodBinding resolveMethod(MethodDeclaration method) {
+ resolve();
+ JCTree javacElement = this.converter.domToJavac.get(method);
+ if (javacElement instanceof JCMethodDecl methodDecl) {
+ if (methodDecl.type != null) {
+ return this.bindings.getMethodBinding(methodDecl.type.asMethodType(), methodDecl.sym, null, true);
+ }
+ if (methodDecl.sym instanceof MethodSymbol methodSymbol && methodSymbol.type != null) {
+ return this.bindings.getMethodBinding(methodSymbol.type.asMethodType(), methodSymbol, null, true);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ IMethodBinding resolveMethod(LambdaExpression lambda) {
+ resolve();
+ JCTree javacElement = this.converter.domToJavac.get(lambda);
+ if (javacElement instanceof JCLambda jcLambda) {
+ JavacTypeBinding typeBinding = this.bindings.getTypeBinding(jcLambda.type);
+ if (typeBinding != null && typeBinding.getFunctionalInterfaceMethod() instanceof JavacMethodBinding methodBinding) {
+ return this.bindings.getLambdaBinding(methodBinding, lambda);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ IMethodBinding resolveMethod(MethodReference methodReference) {
+ resolve();
+ JCTree javacElement = this.converter.domToJavac.get(methodReference);
+ if (javacElement instanceof JCMemberReference memberRef && memberRef.sym instanceof MethodSymbol methodSymbol) {
+ return this.bindings.getMethodBinding(memberRef.referentType == null ? methodSymbol.type.asMethodType() : memberRef.referentType.asMethodType(), methodSymbol, null, false);
+ }
+ return null;
+ }
+
+ @Override
+ IMethodBinding resolveMember(AnnotationTypeMemberDeclaration member) {
+ resolve();
+ JCTree javacElement = this.converter.domToJavac.get(member);
+ if (javacElement instanceof JCMethodDecl methodDecl) {
+ return this.bindings.getMethodBinding(methodDecl.type.asMethodType(), methodDecl.sym, null, true);
+ }
+ return null;
+ }
+
+ @Override
+ IMethodBinding resolveConstructor(EnumConstantDeclaration enumConstantDeclaration) {
+ resolve();
+ JCTree javacElement = this.converter.domToJavac.get(enumConstantDeclaration);
+ if( javacElement instanceof JCVariableDecl jcvd ) {
+ javacElement = jcvd.init;
+ }
+ return javacElement instanceof JCNewClass jcExpr
+ && !jcExpr.constructor.type.isErroneous()?
+ this.bindings.getMethodBinding(jcExpr.constructor.type.asMethodType(), (MethodSymbol)jcExpr.constructor, null, true) :
+ null;
+ }
+
+ @Override
+ IMethodBinding resolveConstructor(SuperConstructorInvocation expression) {
+ resolve();
+ JCTree javacElement = this.converter.domToJavac.get(expression);
+ if (javacElement instanceof JCMethodInvocation javacMethodInvocation) {
+ javacElement = javacMethodInvocation.getMethodSelect();
+ }
+ if (javacElement instanceof JCIdent ident && ident.sym instanceof MethodSymbol methodSymbol) {
+ if (ident.type != null && (ident.type instanceof MethodType || ident.type instanceof ForAll)) {
+ return this.bindings.getMethodBinding(ident.type.asMethodType(), methodSymbol, null, false);
+ } else if (methodSymbol.asType() instanceof MethodType || methodSymbol.asType() instanceof ForAll) {
+ return this.bindings.getMethodBinding(methodSymbol.asType().asMethodType(), methodSymbol, null, false);
+ }
+ }
+ if (javacElement instanceof JCFieldAccess fieldAccess && fieldAccess.sym instanceof MethodSymbol methodSymbol) {
+ return this.bindings.getMethodBinding(fieldAccess.type.asMethodType(), methodSymbol, null, false);
+ }
+ return null;
+ }
+
+ @Override
+ IMethodBinding resolveMethod(SuperMethodInvocation method) {
+ resolve();
+ JCTree javacElement = this.converter.domToJavac.get(method);
+ if (javacElement instanceof JCMethodInvocation javacMethodInvocation) {
+ javacElement = javacMethodInvocation.getMethodSelect();
+ }
+ if (javacElement instanceof JCIdent ident && ident.sym instanceof MethodSymbol methodSymbol) {
+ return this.bindings.getMethodBinding(ident.type.asMethodType(), methodSymbol, null, false);
+ }
+ if (javacElement instanceof JCFieldAccess fieldAccess && fieldAccess.sym instanceof MethodSymbol methodSymbol
+ && fieldAccess.type != null /* when there are syntax errors */) {
+ return this.bindings.getMethodBinding(fieldAccess.type.asMethodType(), methodSymbol, null, false);
+ }
+ return null;
+ }
+
+ IBinding resolveCached(ASTNode node, Function l) {
+ // Avoid using `computeIfAbsent` because it throws
+ // ConcurrentModificationException when nesting calls
+ var res = resolvedBindingsCache.get(node);
+ if (res == null) {
+ res = l.apply(node);
+ resolvedBindingsCache.put(node, res);
+ }
+ return res;
+ }
+
+ @Override
+ IBinding resolveName(Name name) {
+ return resolveCached(name, (n) -> resolveNameImpl((Name)n));
+ }
+
+ private IBinding resolveNameImpl(Name name) {
+ resolve();
+ JCTree tree = this.converter.domToJavac.get(name);
+ if( tree != null ) {
+ var res = resolveNameToJavac(name, tree);
+ if (res != null) {
+ return res;
+ }
+ }
+ DocTreePath path = this.converter.findDocTreePath(name);
+ if (path != null) {
+ if (JavacTrees.instance(this.context).getElement(path) instanceof Symbol symbol) {
+ return this.bindings.getBinding(symbol, null);
+ }
+ }
+
+ PackageSymbol ps = findPackageSymbol(name);
+ if( ps != null ) {
+ return this.bindings.getPackageBinding(ps);
+ }
+ if( isPackageName(name)) {
+ return this.bindings.getPackageBinding(name);
+ }
+ if( tree instanceof JCIdent jcid && jcid.sym instanceof ClassSymbol && jcid.type instanceof ErrorType) {
+ IBinding b = this.bindings.findExistingPackageBinding(name);
+ if( b != null )
+ return b;
+ }
+
+
+ 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 ) {
+ // Looks duplicate to top of method, but is not. Must remain.
+ IBinding ret = resolveNameToJavac(name, tree);
+ if (ret != null) {
+ return ret;
+ }
+ }
+ if (parent instanceof ImportDeclaration importDecl && importDecl.getName() == name) {
+ return resolveImport(importDecl);
+ }
+ if (parent instanceof QualifiedName parentName && parentName.getName() == name) {
+ return resolveNameImpl(parentName);
+ }
+ if( parent instanceof MethodRef mref && mref.getName() == name) {
+ return resolveReference(mref);
+ }
+ if( parent instanceof MemberRef mref && mref.getName() == name) {
+ return resolveReference(mref);
+ }
+ if (parent instanceof MethodInvocation methodInvocation && methodInvocation.getName() == name) {
+ return resolveMethod(methodInvocation);
+ }
+ if (parent instanceof MethodDeclaration methodDeclaration && methodDeclaration.getName() == name) {
+ return resolveMethod(methodDeclaration);
+ }
+ if (parent instanceof ExpressionMethodReference methodRef && methodRef.getName() == name) {
+ return resolveMethod(methodRef);
+ }
+ if (parent instanceof TypeMethodReference methodRef && methodRef.getName() == name) {
+ return resolveMethod(methodRef);
+ }
+ if (parent instanceof SuperMethodReference methodRef && methodRef.getName() == name) {
+ return resolveMethod(methodRef);
+ }
+ if (parent instanceof VariableDeclaration decl && decl.getName() == name) {
+ return resolveVariable(decl);
+ }
+ return null;
+ }
+
+ private boolean isPackageName(Name name) {
+ ASTNode working = name;
+ boolean insideQualifier = false;
+ while( working instanceof Name ) {
+ JCTree tree = this.converter.domToJavac.get(working);
+ if( tree instanceof JCFieldAccess jcfa) {
+ return jcfa.sym instanceof PackageSymbol;
+ }
+ if( working instanceof QualifiedName qnn) {
+ if( qnn.getQualifier() == working) {
+ insideQualifier = true;
+ }
+ }
+ working = working.getParent();
+ }
+ return insideQualifier;
+ }
+
+ private PackageSymbol findPackageSymbol(Name name) {
+ if( name instanceof SimpleName sn) {
+ ASTNode parent = sn.getParent();
+ if( parent instanceof QualifiedName qn) {
+ JCTree tree = this.converter.domToJavac.get(parent);
+ if( tree instanceof JCFieldAccess jcfa) {
+ if( qn.getQualifier().equals(name)) {
+ if( jcfa.selected instanceof JCIdent jcid && jcid.sym instanceof PackageSymbol pss)
+ return pss;
+ } else if( qn.getName().equals(name)) {
+ return jcfa.sym instanceof PackageSymbol pss ? pss : null;
+ }
+ }
+ }
+ }
+ if( name instanceof QualifiedName qn ) {
+ JCTree tree = this.converter.domToJavac.get(qn);
+ if( tree instanceof JCFieldAccess jcfa) {
+ return jcfa.sym instanceof PackageSymbol pss ? pss : null;
+ }
+ }
+ return null;
+ }
+
+ IBinding resolveNameToJavac(Name name, JCTree tree) {
+ boolean isTypeDeclaration = (name.getParent() instanceof AbstractTypeDeclaration typeDeclaration && typeDeclaration.getName() == name)
+ || (name.getParent() instanceof SimpleType type && type.getName() == name);
+ if( name.getParent() instanceof AnnotatableType st && st.getParent() instanceof ParameterizedType pt) {
+ if( st == pt.getType()) {
+ tree = this.converter.domToJavac.get(pt);
+ if (tree.type != null && !tree.type.isErroneous()) {
+ IBinding b = this.bindings.getTypeBinding(tree.type, isTypeDeclaration);
+ if( b != null ) {
+ return b;
+ }
+ }
+ }
+ }
+
+ if (tree instanceof JCIdent ident && ident.sym != null) {
+ if (ident.type instanceof ErrorType errorType
+ && errorType.getOriginalType() instanceof ErrorType) {
+ return null;
+ }
+ if (isTypeDeclaration) {
+ return this.bindings.getTypeBinding(ident.type != null ? ident.type : ident.sym.type, true);
+ }
+ return this.bindings.getBinding(ident.sym, ident.type != null ? ident.type : ident.sym.type);
+ }
+ if (tree instanceof JCTypeApply variableDecl && variableDecl.type != null) {
+ return this.bindings.getTypeBinding(variableDecl.type);
+ }
+ if (tree instanceof JCFieldAccess fieldAccess) {
+ return this.getFieldAccessBinding(fieldAccess);
+ }
+ if (tree instanceof JCMethodInvocation methodInvocation && methodInvocation.meth.type != null) {
+ return this.bindings.getBinding(((JCFieldAccess)methodInvocation.meth).sym, methodInvocation.meth.type);
+ }
+ if (tree instanceof JCClassDecl classDecl && classDecl.sym != null) {
+ return this.bindings.getBinding(classDecl.sym, classDecl.type);
+ }
+ if (tree instanceof JCMethodDecl methodDecl && methodDecl.sym != null) {
+ return this.bindings.getBinding(methodDecl.sym, methodDecl.type);
+ }
+ if (tree instanceof JCVariableDecl variableDecl && variableDecl.sym != null) {
+ return this.bindings.getBinding(variableDecl.sym, variableDecl.type);
+ }
+ if (tree instanceof JCTypeParameter variableDecl && variableDecl.type != null && variableDecl.type.tsym != null) {
+ return this.bindings.getBinding(variableDecl.type.tsym, variableDecl.type);
+ }
+ if (tree instanceof JCModuleDecl variableDecl && variableDecl.sym != null && variableDecl.sym.type instanceof ModuleType 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;
+ }
+ if (expr instanceof SuperFieldAccess) {
+ return this.bindings.getTypeBinding(jcFieldAccess.selected.type);
+ }
+ 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();
+ } else if (annotation.getParent() instanceof TypeDeclaration td) {
+ recipient = td.resolveBinding();
+ }
+ var javac = this.converter.domToJavac.get(annotation);
+ if (javac instanceof JCAnnotation jcAnnotation) {
+ return this.bindings.getAnnotationBinding(jcAnnotation.attribute, recipient);
+ }
+ return null;
+ }
+
+ @Override
+ IBinding resolveReference(MethodRef ref) {
+ return resolveCached(ref, (n) -> resolveReferenceImpl((MethodRef)n));
+ }
+
+ private IBinding resolveReferenceImpl(MethodRef ref) {
+ resolve();
+ DocTreePath path = this.converter.findDocTreePath(ref);
+ if (path != null ) {
+ Element e = JavacTrees.instance(this.context).getElement(path);
+ if(e instanceof Symbol symbol) {
+ IBinding r1 = this.bindings.getBinding(symbol, null);
+ return r1;
+ }
+ TreePath dt = path.getTreePath();
+ if( dt != null) {
+ Tree t = dt.getLeaf();
+ if( t instanceof JCMethodDecl jcmd) {
+ MethodSymbol ms = jcmd.sym;
+ IBinding r1 = ms == null ? null : this.bindings.getBinding(ms, jcmd.type);
+ return r1;
+ }
+ }
+ }
+ if( ref.parameters() != null && ref.parameters().size() == 0) {
+ // exhaustively search for a similar method ref
+ DocTreePath[] possible = this.converter.searchRelatedDocTreePath(ref);
+ if( possible != null ) {
+ for( int i = 0; i < possible.length; i++ ) {
+ Element e = JavacTrees.instance(this.context).getElement(possible[i]);
+ if(e instanceof Symbol symbol) {
+ IBinding r1 = this.bindings.getBinding(symbol, null);
+ if( r1 != null )
+ return r1;
+ }
+ }
+ }
+ }
+ //
+ return null;
+ }
+
+ private IBinding getFieldAccessBinding(JCFieldAccess fieldAccess) {
+ JCFieldAccess jcfa2 = (fieldAccess.sym == null && fieldAccess.selected instanceof JCFieldAccess jcfa3) ? jcfa3 : fieldAccess;
+ if( jcfa2.sym != null ) {
+ com.sun.tools.javac.code.Type typeToUse = jcfa2.type;
+ if(jcfa2.selected instanceof JCTypeApply) {
+ typeToUse = jcfa2.sym.type;
+ }
+ IBinding bRet = this.bindings.getBinding(jcfa2.sym, typeToUse);
+ if( jcfa2 != fieldAccess && bRet instanceof ITypeBinding itb ) {
+ String fieldAccessIdentifier = fieldAccess.getIdentifier().toString();
+ // If we changed the field access, we need to go one generation lower
+ Function func = bindings -> {
+ for( int i = 0; i < bindings.length; i++ ) {
+ String childName = bindings[i].getName();
+ if( childName.equals(fieldAccessIdentifier)) {
+ return bindings[i];
+ }
+ }
+ return null;
+ };
+ IBinding ret = func.apply(itb.getDeclaredTypes());
+ if( ret != null )
+ return ret;
+ ret = func.apply(itb.getDeclaredFields());
+ if( ret != null )
+ return ret;
+ ret = func.apply(itb.getDeclaredMethods());
+ if( ret != null )
+ return ret;
+ }
+ return bRet;
+ }
+ return null;
+ }
+ @Override
+ IBinding resolveReference(MemberRef ref) {
+ return resolveCached(ref, (n) -> resolveReferenceImpl((MemberRef)n));
+ }
+
+ private IBinding resolveReferenceImpl(MemberRef ref) {
+ resolve();
+ DocTreePath path = this.converter.findDocTreePath(ref);
+ if (path != null && JavacTrees.instance(this.context).getElement(path) instanceof Symbol symbol) {
+ return this.bindings.getBinding(symbol, null);
+ }
+ return null;
+ }
+ private static Symbol getRecoveredSymbol(com.sun.tools.javac.code.Type type) {
+ if (type instanceof ErrorType) {
+ try {
+ Field candidateSymbolField = type.getClass().getField("candidateSymbol");
+ candidateSymbolField.setAccessible(true);
+
+ Object symbolFieldValue = candidateSymbolField.get(type);
+ if (symbolFieldValue instanceof Symbol symbol) {
+ return symbol;
+ }
+ } catch (NoSuchFieldException | IllegalAccessException unused) {
+ // fall through to null
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public WorkingCopyOwner getWorkingCopyOwner() {
+ return this.owner;
+ }
+
+ @Override
+ Object resolveConstantExpressionValue(Expression expression) {
+ JCTree jcTree = this.converter.domToJavac.get(expression);
+ if (jcTree instanceof JCLiteral literal) {
+ return literal.getValue();
+ }
+ return TreeInfo.symbolFor(jcTree) instanceof VarSymbol varSymbol ? varSymbol.getConstantValue() : null;
+ }
+ @Override
+ boolean resolveBoxing(Expression expression) {
+ // TODO need to handle many different things here, very rudimentary
+ if( expression.getParent() instanceof MethodInvocation mi) {
+ IMethodBinding mb = resolveMethod(mi);
+ int foundArg = -1;
+ if( mb != null ) {
+ for( int i = 0; i < mi.arguments().size() && foundArg == -1; i++ ) {
+ if( mi.arguments().get(i) == expression) {
+ foundArg = i;
+ }
+ }
+ if( foundArg != -1 ) {
+ ITypeBinding[] tbs = mb.getParameterTypes();
+ if( tbs.length > foundArg) {
+ ITypeBinding foundType = tbs[foundArg];
+ if( expression instanceof NumberLiteral nl) {
+ if( isBoxedVersion(nl, foundType)) {
+ return true;
+ }
+ } else {
+ if( expression instanceof MethodInvocation inner) {
+ JavacMethodBinding mbInner = (JavacMethodBinding)resolveMethod(inner);
+ ITypeBinding retTypeInner = mbInner == null ? null : mbInner.getReturnType();
+ if( isBoxedVersion(retTypeInner, foundType)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean isBoxedVersion(NumberLiteral unboxed, ITypeBinding boxed) {
+ if( boxed instanceof JavacTypeBinding boxedBind ) {
+ String boxedString = boxedBind.typeSymbol == null ? null : boxedBind.typeSymbol.toString();
+ if("java.lang.Integer".equals(boxedString)
+ || "java.lang.Float".equals(boxedString)
+ || "java.lang.Double".equals(boxedString)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private Map boxingMap = null;
+ private boolean isBoxedVersion(ITypeBinding unboxed, ITypeBinding boxed) {
+ if( boxingMap == null ) {
+ Map m = new HashMap();
+ m.put("java.lang.Boolean", "boolean");
+ m.put("java.lang.Byte", "byte");
+ m.put("java.lang.Character", "char");
+ m.put("java.lang.Float", "float");
+ m.put("java.lang.Integer", "int");
+ m.put("java.lang.Long", "long");
+ m.put("java.lang.Short", "short");
+ m.put("java.lang.Double", "double");
+ boxingMap = m;
+ }
+ if( boxed instanceof JavacTypeBinding boxedBind && unboxed instanceof JavacTypeBinding unboxedBind) {
+ String boxedString = boxedBind.typeSymbol == null ? null : boxedBind.typeSymbol.toString();
+ String unboxedString = unboxedBind.typeSymbol == null ? null : unboxedBind.typeSymbol.toString();
+ // TODO very rudimentary, fix it, add more
+ if( boxingMap.get(boxedString) != null ) {
+ if( unboxedString.equals(boxingMap.get(boxedString))) {
+ return true;
+ }
+ }
+ // Alternate case, they might be converting some types
+ if( boxingMap.keySet().contains(boxedString) && boxingMap.values().contains(unboxedString)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ @Override
+ boolean resolveUnboxing(Expression expression) {
+ Type t = null;
+ if( expression instanceof ClassInstanceCreation cic ) {
+ t = cic.getType();
+ }
+ if( t != null && expression.getParent() instanceof MethodInvocation mi) {
+ int foundArg = -1;
+ if( mi != null ) {
+ for( int i = 0; i < mi.arguments().size() && foundArg == -1; i++ ) {
+ if( mi.arguments().get(i) == expression) {
+ foundArg = i;
+ }
+ }
+ if( foundArg != -1 ) {
+ IMethodBinding mb = resolveMethod(mi);
+ ITypeBinding[] tbs = mb.getParameterTypes();
+ if( tbs.length > foundArg) {
+ ITypeBinding unboxed = tbs[foundArg];
+ ITypeBinding boxed = resolveType(t);
+ if( isBoxedVersion(unboxed, boxed)) {
+ return true;
+ }
+ } else if( tbs.length > 0 && mb.isVarargs()) {
+ ITypeBinding lastArg = tbs[tbs.length - 1];
+ if( lastArg.isArray()) {
+ ITypeBinding el = lastArg.getElementType();
+ if( el.isPrimitive()) {
+ if( isBoxedVersion(el, resolveType(t))) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ }
+ return false;
+ }
+ public boolean isRecoveringBindings() {
+ return isRecoveringBindings;
+ }
+}
diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacCompilationUnitResolver.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacCompilationUnitResolver.java
new file mode 100644
index 00000000000..cf1aa27b9b3
--- /dev/null
+++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacCompilationUnitResolver.java
@@ -0,0 +1,1134 @@
+/*******************************************************************************
+ * Copyright (c) 2023, Red Hat, Inc. and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.jdt.core.dom;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.CharBuffer;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import javax.lang.model.element.TypeElement;
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticListener;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.ILog;
+import org.eclipse.core.runtime.IProduct;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.Signature;
+import org.eclipse.jdt.core.WorkingCopyOwner;
+import org.eclipse.jdt.core.compiler.CategorizedProblem;
+import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.core.compiler.IProblem;
+import org.eclipse.jdt.core.compiler.InvalidInputException;
+import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.env.AccessRestriction;
+import org.eclipse.jdt.internal.compiler.env.IBinaryType;
+import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
+import org.eclipse.jdt.internal.compiler.env.ISourceType;
+import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
+import org.eclipse.jdt.internal.compiler.impl.ITypeRequestor;
+import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
+import org.eclipse.jdt.internal.compiler.lookup.PackageBinding;
+import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
+import org.eclipse.jdt.internal.compiler.util.Util;
+import org.eclipse.jdt.internal.core.CancelableNameEnvironment;
+import org.eclipse.jdt.internal.core.JavaModelManager;
+import org.eclipse.jdt.internal.core.JavaProject;
+import org.eclipse.jdt.internal.core.dom.ICompilationUnitResolver;
+import org.eclipse.jdt.internal.core.util.BindingKeyParser;
+import org.eclipse.jdt.internal.javac.CachingJDKPlatformArguments;
+import org.eclipse.jdt.internal.javac.CachingJarsJavaFileManager;
+import org.eclipse.jdt.internal.javac.JavacProblemConverter;
+import org.eclipse.jdt.internal.javac.JavacUtils;
+import org.eclipse.jdt.internal.javac.UnusedProblemFactory;
+import org.eclipse.jdt.internal.javac.UnusedTreeScanner;
+
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.JavacTask;
+import com.sun.source.util.TaskEvent;
+import com.sun.source.util.TaskListener;
+import com.sun.tools.javac.api.JavacTool;
+import com.sun.tools.javac.api.MultiTaskListener;
+import com.sun.tools.javac.code.Symbol.PackageSymbol;
+import com.sun.tools.javac.comp.CompileStates.CompileState;
+import com.sun.tools.javac.file.JavacFileManager;
+import com.sun.tools.javac.main.Option;
+import com.sun.tools.javac.parser.JavadocTokenizer;
+import com.sun.tools.javac.parser.Scanner;
+import com.sun.tools.javac.parser.ScannerFactory;
+import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle;
+import com.sun.tools.javac.parser.Tokens.TokenKind;
+import com.sun.tools.javac.tree.JCTree.JCClassDecl;
+import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.DiagnosticSource;
+import com.sun.tools.javac.util.Options;
+
+/**
+ * Allows to create and resolve DOM ASTs using Javac
+ * @implNote Cannot move to another package because parent class is package visible only
+ */
+public class JavacCompilationUnitResolver implements ICompilationUnitResolver {
+
+ private final class ForwardDiagnosticsAsDOMProblems implements DiagnosticListener {
+ public final Map filesToUnits;
+ private final JavacProblemConverter problemConverter;
+
+ private ForwardDiagnosticsAsDOMProblems(Map filesToUnits,
+ JavacProblemConverter problemConverter) {
+ this.filesToUnits = filesToUnits;
+ this.problemConverter = problemConverter;
+ }
+
+ @Override
+ public void report(Diagnostic extends JavaFileObject> diagnostic) {
+ findTargetDOM(filesToUnits, diagnostic).ifPresent(dom -> {
+ var newProblem = problemConverter.createJavacProblem(diagnostic);
+ if (newProblem != null) {
+ IProblem[] previous = dom.getProblems();
+ IProblem[] newProblems = Arrays.copyOf(previous, previous.length + 1);
+ newProblems[newProblems.length - 1] = newProblem;
+ dom.setProblems(newProblems);
+ }
+ });
+ }
+
+ private static Optional findTargetDOM(Map filesToUnits, Object obj) {
+ if (obj == null) {
+ return Optional.empty();
+ }
+ if (obj instanceof JavaFileObject o) {
+ return Optional.ofNullable(filesToUnits.get(o));
+ }
+ if (obj instanceof DiagnosticSource source) {
+ return findTargetDOM(filesToUnits, source.getFile());
+ }
+ if (obj instanceof Diagnostic diag) {
+ return findTargetDOM(filesToUnits, diag.getSource());
+ }
+ return Optional.empty();
+ }
+ }
+
+ private interface GenericRequestor {
+ public void acceptBinding(String bindingKey, IBinding binding);
+ }
+
+ public JavacCompilationUnitResolver() {
+ // 0-arg constructor
+ }
+
+ private List createSourceUnitList(String[] sourceFilePaths, String[] encodings) {
+ // make list of source unit
+ int length = sourceFilePaths.length;
+ List sourceUnitList = new ArrayList<>(length);
+ for (int i = 0; i < length; i++) {
+ org.eclipse.jdt.internal.compiler.env.ICompilationUnit obj = createSourceUnit(sourceFilePaths[i], encodings[i]);
+ if( obj != null )
+ sourceUnitList.add(obj);
+ }
+ return sourceUnitList;
+ }
+
+ private org.eclipse.jdt.internal.compiler.env.ICompilationUnit createSourceUnit(String sourceFilePath, String encoding) {
+ char[] contents = null;
+ try {
+ contents = Util.getFileCharContent(new File(sourceFilePath), encoding);
+ } catch(IOException e) {
+ return null;
+ }
+ if (contents == null) {
+ return null;
+ }
+ return new org.eclipse.jdt.internal.compiler.batch.CompilationUnit(contents, sourceFilePath, encoding);
+ }
+
+
+ @Override
+ public void resolve(String[] sourceFilePaths, String[] encodings, String[] bindingKeys, FileASTRequestor requestor,
+ int apiLevel, Map compilerOptions, List classpaths, int flags,
+ IProgressMonitor monitor) {
+ List sourceUnitList = createSourceUnitList(sourceFilePaths, encodings);
+ JavacBindingResolver bindingResolver = null;
+
+ // parse source units
+ Map res =
+ parse(sourceUnitList.toArray(org.eclipse.jdt.internal.compiler.env.ICompilationUnit[]::new), apiLevel, compilerOptions, true, flags, (IJavaProject)null, null, -1, monitor);
+
+ for (var entry : res.entrySet()) {
+ CompilationUnit cu = entry.getValue();
+ requestor.acceptAST(new String(entry.getKey().getFileName()), cu);
+ if (bindingResolver == null && (JavacBindingResolver)cu.ast.getBindingResolver() != null) {
+ bindingResolver = (JavacBindingResolver)cu.ast.getBindingResolver();
+ }
+ }
+
+ resolveRequestedBindingKeys(bindingResolver, bindingKeys,
+ (a,b) -> requestor.acceptBinding(a,b),
+ classpaths.stream().toArray(Classpath[]::new),
+ new CompilerOptions(compilerOptions),
+ res.values(), null, new HashMap<>(), monitor);
+ }
+
+ @Override
+ public void resolve(ICompilationUnit[] compilationUnits, String[] bindingKeys, ASTRequestor requestor, int apiLevel,
+ Map compilerOptions, IJavaProject project, WorkingCopyOwner workingCopyOwner, int flags,
+ IProgressMonitor monitor) {
+ Map units = parse(compilationUnits, apiLevel, compilerOptions, true, flags, workingCopyOwner, monitor);
+ if (requestor != null) {
+ final JavacBindingResolver[] bindingResolver = new JavacBindingResolver[1];
+ bindingResolver[0] = null;
+
+ final Map bindingMap = new HashMap<>();
+ {
+ INameEnvironment environment = null;
+ if (project instanceof JavaProject javaProject) {
+ try {
+ environment = new CancelableNameEnvironment(javaProject, workingCopyOwner, monitor);
+ } catch (JavaModelException e) {
+ // fall through
+ }
+ }
+ if (environment == null) {
+ environment = new NameEnvironmentWithProgress(new Classpath[0], null, monitor);
+ }
+ LookupEnvironment lu = new LookupEnvironment(new ITypeRequestor() {
+
+ @Override
+ public void accept(IBinaryType binaryType, PackageBinding packageBinding,
+ AccessRestriction accessRestriction) {
+ // do nothing
+ }
+
+ @Override
+ public void accept(org.eclipse.jdt.internal.compiler.env.ICompilationUnit unit,
+ AccessRestriction accessRestriction) {
+ // do nothing
+ }
+
+ @Override
+ public void accept(ISourceType[] sourceType, PackageBinding packageBinding,
+ AccessRestriction accessRestriction) {
+ // do nothing
+ }
+
+ }, new CompilerOptions(compilerOptions), null, environment);
+ requestor.additionalBindingResolver = javacAdditionalBindingCreator(bindingMap, environment, lu, bindingResolver);
+ }
+
+ units.forEach((a,b) -> {
+ if (bindingResolver[0] == null && (JavacBindingResolver)b.ast.getBindingResolver() != null) {
+ bindingResolver[0] = (JavacBindingResolver)b.ast.getBindingResolver();
+ }
+ requestor.acceptAST(a,b);
+ resolveBindings(b, bindingMap, apiLevel);
+ });
+
+ resolveRequestedBindingKeys(bindingResolver[0], bindingKeys,
+ (a,b) -> requestor.acceptBinding(a,b),
+ new Classpath[0], // TODO need some classpaths
+ new CompilerOptions(compilerOptions),
+ units.values(), project, bindingMap, monitor);
+ } else {
+ Iterator it = units.values().iterator();
+ while(it.hasNext()) {
+ resolveBindings(it.next(), apiLevel);
+ }
+ }
+ }
+
+ private void resolveRequestedBindingKeys(JavacBindingResolver bindingResolver, String[] bindingKeys, GenericRequestor requestor,
+ Classpath[] cp,CompilerOptions opts,
+ Collection units,
+ IJavaProject project,
+ Map bindingMap,
+ IProgressMonitor monitor) {
+ if (bindingResolver == null) {
+ var compiler = ToolProvider.getSystemJavaCompiler();
+ var context = new Context();
+ JavacTask task = (JavacTask) compiler.getTask(null, null, null, List.of(), List.of(), List.of());
+ bindingResolver = new JavacBindingResolver(null, task, context, new JavacConverter(null, null, context, null, true, -1), null, null);
+ }
+
+ for (CompilationUnit cu : units) {
+ cu.accept(new BindingBuilder(bindingMap));
+ }
+
+ INameEnvironment environment = null;
+ if (project instanceof JavaProject javaProject) {
+ try {
+ environment = new CancelableNameEnvironment(javaProject, null, monitor);
+ } catch (JavaModelException e) {
+ // do nothing
+ }
+ }
+ if (environment == null) {
+ environment = new NameEnvironmentWithProgress(cp, null, monitor);
+ }
+
+ LookupEnvironment lu = new LookupEnvironment(new ITypeRequestor() {
+
+ @Override
+ public void accept(IBinaryType binaryType, PackageBinding packageBinding,
+ AccessRestriction accessRestriction) {
+ // do nothing
+ }
+
+ @Override
+ public void accept(org.eclipse.jdt.internal.compiler.env.ICompilationUnit unit,
+ AccessRestriction accessRestriction) {
+ // do nothing
+ }
+
+ @Override
+ public void accept(ISourceType[] sourceType, PackageBinding packageBinding,
+ AccessRestriction accessRestriction) {
+ // do nothing
+ }
+
+ }, opts, null, environment);
+
+ // resolve the requested bindings
+ for (String bindingKey : bindingKeys) {
+
+ int arrayCount = Signature.getArrayCount(bindingKey);
+ IBinding bindingFromMap = bindingMap.get(bindingKey);
+ if (bindingFromMap != null) {
+ // from parsed files
+ requestor.acceptBinding(bindingKey, bindingFromMap);
+ } else {
+
+ if (arrayCount > 0) {
+ String elementKey = Signature.getElementType(bindingKey);
+ IBinding elementBinding = bindingMap.get(elementKey);
+ if (elementBinding instanceof ITypeBinding elementTypeBinding) {
+ requestor.acceptBinding(bindingKey, elementTypeBinding.createArrayType(arrayCount));
+ continue;
+ }
+ }
+
+ CustomBindingKeyParser bkp = new CustomBindingKeyParser(bindingKey);
+ bkp.parse(true);
+ char[][] name = bkp.compoundName;
+
+// // from ECJ
+// char[] charArrayFQN = Signature.toCharArray(bindingKey.toCharArray());
+// char[][] twoDimensionalCharArrayFQN = Stream.of(new String(charArrayFQN).split("/")) //
+// .map(myString -> myString.toCharArray()) //
+// .toArray(char[][]::new);
+// char[][] twoDimensionalCharArrayFQN = new char[][] {};
+ NameEnvironmentAnswer answer = environment.findType(name);
+ if( answer != null ) {
+ IBinaryType binaryType = answer.getBinaryType();
+ if (binaryType != null) {
+ BinaryTypeBinding binding = lu.cacheBinaryType(binaryType, null);
+ requestor.acceptBinding(bindingKey, new TypeBinding(bindingResolver, binding));
+ }
+ }
+ }
+
+ }
+
+ }
+
+ private static class CustomBindingKeyParser extends BindingKeyParser {
+
+ private char[] secondarySimpleName;
+ private char[][] compoundName;
+
+ public CustomBindingKeyParser(String key) {
+ super(key);
+ }
+
+ @Override
+ public void consumeSecondaryType(char[] simpleTypeName) {
+ this.secondarySimpleName = simpleTypeName;
+ }
+
+ @Override
+ public void consumeFullyQualifiedName(char[] fullyQualifiedName) {
+ this.compoundName = CharOperation.splitOn('/', fullyQualifiedName);
+ }
+ }
+
+ @Override
+ public void parse(ICompilationUnit[] compilationUnits, ASTRequestor requestor, int apiLevel,
+ Map compilerOptions, int flags, IProgressMonitor monitor) {
+ WorkingCopyOwner workingCopyOwner = Arrays.stream(compilationUnits)
+ .filter(ICompilationUnit.class::isInstance)
+ .map(ICompilationUnit.class::cast)
+ .map(ICompilationUnit::getOwner)
+ .filter(Objects::nonNull)
+ .findFirst()
+ .orElse(null);
+ Map units = parse(compilationUnits, apiLevel, compilerOptions, false, flags, workingCopyOwner, monitor);
+ if (requestor != null) {
+ units.forEach(requestor::acceptAST);
+ }
+ }
+
+ private Map parse(ICompilationUnit[] compilationUnits, int apiLevel,
+ Map compilerOptions, boolean resolveBindings, int flags, WorkingCopyOwner workingCopyOwner, IProgressMonitor monitor) {
+ // TODO ECJCompilationUnitResolver has support for dietParse and ignore method body
+ // is this something we need?
+ if (compilationUnits.length > 0
+ && Arrays.stream(compilationUnits).map(ICompilationUnit::getJavaProject).distinct().count() == 1
+ && Arrays.stream(compilationUnits).allMatch(org.eclipse.jdt.internal.compiler.env.ICompilationUnit.class::isInstance)) {
+ // all in same project, build together
+ Map res =
+ parse(Arrays.stream(compilationUnits)
+ .map(org.eclipse.jdt.internal.compiler.env.ICompilationUnit.class::cast)
+ .toArray(org.eclipse.jdt.internal.compiler.env.ICompilationUnit[]::new),
+ apiLevel, compilerOptions, resolveBindings, flags, compilationUnits[0].getJavaProject(), workingCopyOwner, -1, monitor)
+ .entrySet().stream().collect(Collectors.toMap(entry -> (ICompilationUnit)entry.getKey(), entry -> entry.getValue()));
+ for (ICompilationUnit in : compilationUnits) {
+ CompilationUnit c = res.get(in);
+ if( c != null )
+ c.setTypeRoot(in);
+ }
+ return res;
+ }
+ // build individually
+ Map res = new HashMap<>(compilationUnits.length, 1.f);
+ for (ICompilationUnit in : compilationUnits) {
+ if (in instanceof org.eclipse.jdt.internal.compiler.env.ICompilationUnit compilerUnit) {
+ res.put(in, parse(new org.eclipse.jdt.internal.compiler.env.ICompilationUnit[] { compilerUnit },
+ apiLevel, compilerOptions, resolveBindings, flags, in.getJavaProject(), workingCopyOwner, -1, monitor).get(compilerUnit));
+ res.get(in).setTypeRoot(in);
+ }
+ }
+ return res;
+ }
+
+ @Override
+ public void parse(String[] sourceFilePaths, String[] encodings, FileASTRequestor requestor, int apiLevel,
+ Map compilerOptions, int flags, IProgressMonitor monitor) {
+
+ for( int i = 0; i < sourceFilePaths.length; i++ ) {
+ org.eclipse.jdt.internal.compiler.env.ICompilationUnit ast = createSourceUnit(sourceFilePaths[i], encodings[i]);
+ Map res =
+ parse(new org.eclipse.jdt.internal.compiler.env.ICompilationUnit[] {ast}, apiLevel, compilerOptions, false, flags, (IJavaProject)null, null, -1, monitor);
+ CompilationUnit result = res.get(ast);
+ requestor.acceptAST(sourceFilePaths[i], result);
+ }
+ }
+
+
+ private void resolveBindings(CompilationUnit unit, int apiLevel) {
+ resolveBindings(unit, new HashMap<>(), apiLevel);
+ }
+
+ private void resolveBindings(CompilationUnit unit, Map bindingMap, int apiLevel) {
+ try {
+ if (unit.getPackage() != null) {
+ IPackageBinding pb = unit.getPackage().resolveBinding();
+ if (pb != null) {
+ bindingMap.put(pb.getKey(), pb);
+ }
+ }
+ if (!unit.types().isEmpty()) {
+ List types = unit.types();
+ for( int i = 0; i < types.size(); i++ ) {
+ ITypeBinding tb = types.get(i).resolveBinding();
+ if (tb != null) {
+ bindingMap.put(tb.getKey(), tb);
+ }
+ }
+ }
+ if( apiLevel >= AST.JLS9_INTERNAL) {
+ if (unit.getModule() != null) {
+ IModuleBinding mb = unit.getModule().resolveBinding();
+ if (mb != null) {
+ bindingMap.put(mb.getKey(), mb);
+ }
+ }
+ }
+ } catch (Exception e) {
+ ILog.get().warn("Failed to resolve binding", e);
+ }
+ }
+
+ @Override
+ public CompilationUnit toCompilationUnit(org.eclipse.jdt.internal.compiler.env.ICompilationUnit sourceUnit,
+ boolean resolveBindings, IJavaProject project, List classpaths,
+ int focalPoint, int apiLevel, Map compilerOptions,
+ WorkingCopyOwner workingCopyOwner, WorkingCopyOwner typeRootWorkingCopyOwner, int flags, IProgressMonitor monitor) {
+
+ // collect working copies
+ var workingCopies = JavaModelManager.getJavaModelManager().getWorkingCopies(workingCopyOwner, true);
+ if (workingCopies == null) {
+ workingCopies = new ICompilationUnit[0];
+ }
+ Map pathToUnit = new HashMap<>();
+ Arrays.stream(workingCopies) //
+ .filter(inMemoryCu -> {
+ try {
+ return inMemoryCu.hasUnsavedChanges() && (project == null || (inMemoryCu.getElementName() != null && !inMemoryCu.getElementName().contains("module-info")) || inMemoryCu.getJavaProject() == project);
+ } catch (JavaModelException e) {
+ return project == null || (inMemoryCu.getElementName() != null && !inMemoryCu.getElementName().contains("module-info")) || inMemoryCu.getJavaProject() == project;
+ }
+ })
+ .map(org.eclipse.jdt.internal.compiler.env.ICompilationUnit.class::cast) //
+ .forEach(inMemoryCu -> {
+ pathToUnit.put(new String(inMemoryCu.getFileName()), inMemoryCu);
+ });
+
+ // `sourceUnit`'s path might contain only the last segment of the path.
+ // this presents a problem, since if there is a working copy of the class,
+ // we want to use `sourceUnit` instead of the working copy,
+ // and this is accomplished by replacing the working copy's entry in the path-to-CompilationUnit map
+ String pathOfClassUnderAnalysis = new String(sourceUnit.getFileName());
+ if (!pathToUnit.keySet().contains(pathOfClassUnderAnalysis)) {
+ // try to find the project-relative path for the class under analysis by looking through the work copy paths
+ List potentialPaths = pathToUnit.keySet().stream() //
+ .filter(path -> path.endsWith(pathOfClassUnderAnalysis)) //
+ .toList();
+ if (potentialPaths.isEmpty()) {
+ // there is no conflicting class in the working copies,
+ // so it's okay to use the 'broken' path
+ pathToUnit.put(pathOfClassUnderAnalysis, sourceUnit);
+ } else if (potentialPaths.size() == 1) {
+ // we know exactly which one is the duplicate,
+ // so replace it
+ pathToUnit.put(potentialPaths.get(0), sourceUnit);
+ } else {
+ // we don't know which one is the duplicate,
+ // so remove all potential duplicates
+ for (String potentialPath : potentialPaths) {
+ pathToUnit.remove(potentialPath);
+ }
+ pathToUnit.put(pathOfClassUnderAnalysis, sourceUnit);
+ }
+ } else {
+ // intentionally overwrite the existing working copy entry for the same file
+ pathToUnit.put(pathOfClassUnderAnalysis, sourceUnit);
+ }
+
+ // 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();
+ CachingJarsJavaFileManager.preRegister(context);
+ CachingJDKPlatformArguments.preRegister(context);
+ Map result = new HashMap<>(sourceUnits.length, 1.f);
+ Map filesToUnits = new HashMap<>();
+ final UnusedProblemFactory unusedProblemFactory = new UnusedProblemFactory(new DefaultProblemFactory(), compilerOptions);
+ var problemConverter = new JavacProblemConverter(compilerOptions, context);
+ DiagnosticListener diagnosticListener = new ForwardDiagnosticsAsDOMProblems(filesToUnits, problemConverter);
+ MultiTaskListener.instance(context).add(new TaskListener() {
+ @Override
+ public void finished(TaskEvent e) {
+ if (e.getCompilationUnit() instanceof JCCompilationUnit u) {
+ problemConverter.registerUnit(e.getSourceFile(), u);
+ }
+
+ if (e.getKind() == TaskEvent.Kind.ANALYZE) {
+ final JavaFileObject file = e.getSourceFile();
+ final CompilationUnit dom = filesToUnits.get(file);
+ if (dom == null) {
+ return;
+ }
+
+ final TypeElement currentTopLevelType = e.getTypeElement();
+ UnusedTreeScanner scanner = new UnusedTreeScanner<>() {
+ @Override
+ public Void visitClass(ClassTree node, Void p) {
+ if (node instanceof JCClassDecl classDecl) {
+ /**
+ * If a Java file contains multiple top-level types, it will
+ * trigger multiple ANALYZE taskEvents for the same compilation
+ * unit. Each ANALYZE taskEvent corresponds to the completion
+ * of analysis for a single top-level type. Therefore, in the
+ * ANALYZE task event listener, we only visit the class and nested
+ * classes that belong to the currently analyzed top-level type.
+ */
+ if (Objects.equals(currentTopLevelType, classDecl.sym)
+ || !(classDecl.sym.owner instanceof PackageSymbol)) {
+ return super.visitClass(node, p);
+ } else {
+ return null; // Skip if it does not belong to the currently analyzed top-level type.
+ }
+ }
+
+ return super.visitClass(node, p);
+ }
+ };
+ final CompilationUnitTree unit = e.getCompilationUnit();
+ try {
+ scanner.scan(unit, null);
+ } catch (Exception ex) {
+ ILog.get().error("Internal error when visiting the AST Tree. " + ex.getMessage(), ex);
+ }
+
+ List unusedProblems = scanner.getUnusedPrivateMembers(unusedProblemFactory);
+ if (!unusedProblems.isEmpty()) {
+ addProblemsToDOM(dom, unusedProblems);
+ }
+
+ List unusedImports = scanner.getUnusedImports(unusedProblemFactory);
+ List 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 javacOptions = Options.instance(context);
+ javacOptions.put("allowStringFolding", Boolean.FALSE.toString()); // we need to keep strings as authored
+ if ((focalPoint >= 0 || !resolveBindings) && (flags & ICompilationUnit.FORCE_PROBLEM_DETECTION) == 0) {
+ // most likely no need for linting
+ // resolveBindings still seems requested for tests
+ javacOptions.remove(Option.XLINT.primaryName);
+ javacOptions.remove(Option.XLINT_CUSTOM.primaryName);
+ javacOptions.remove(Option.XDOCLINT.primaryName);
+ javacOptions.remove(Option.XDOCLINT_CUSTOM.primaryName);
+ }
+ javacOptions.put(Option.PROC, "only");
+ Optional.ofNullable(Platform.getProduct())
+ .map(IProduct::getApplication)
+ // if application is not a test runner (so we don't have regressions with JDT test suite because of too many problems
+ .or(() -> Optional.ofNullable(System.getProperty("eclipse.application")))
+ .filter(name -> !name.contains("test") && !name.contains("junit"))
+ // continue as far as possible to get extra warnings about unused
+ .ifPresent(id ->javacOptions.put("should-stop.ifError", CompileState.GENERATE.toString()));
+ var fileManager = (JavacFileManager)context.get(JavaFileManager.class);
+ List fileObjects = new ArrayList<>(); // we need an ordered list of them
+ for (var sourceUnit : sourceUnits) {
+ File unitFile;
+ if (javaProject != null && javaProject.getResource() != null) {
+ // path is relative to the workspace, make it absolute
+ IResource asResource = javaProject.getProject().getParent().findMember(new String(sourceUnit.getFileName()));
+ if (asResource != null) {
+ unitFile = asResource.getLocation().toFile();
+ } else {
+ unitFile = new File(new String(sourceUnit.getFileName()));
+ }
+ } else {
+ unitFile = new File(new String(sourceUnit.getFileName()));
+ }
+ Path sourceUnitPath = null;
+ if (!unitFile.getName().endsWith(".java") || sourceUnit.getFileName() == null || sourceUnit.getFileName().length == 0) {
+ String uri1 = unitFile.toURI().toString().replaceAll("%7C", "/");
+ if( uri1.endsWith(".class")) {
+ String[] split= uri1.split("/");
+ String lastSegment = split[split.length-1].replace(".class", ".java");
+ sourceUnitPath = Path.of(lastSegment);
+ }
+ if( sourceUnitPath == null )
+ sourceUnitPath = Path.of(new File("whatever.java").toURI());
+ } else {
+ sourceUnitPath = Path.of(unitFile.toURI());
+ }
+ var fileObject = fileManager.getJavaFileObject(sourceUnitPath);
+ fileManager.cache(fileObject, CharBuffer.wrap(sourceUnit.getContents()));
+ AST ast = createAST(compilerOptions, apiLevel, context, flags);
+ CompilationUnit res = ast.newCompilationUnit();
+ result.put(sourceUnit, res);
+ filesToUnits.put(fileObject, res);
+ fileObjects.add(fileObject);
+ }
+
+
+ JCCompilationUnit javacCompilationUnit = null;
+ Iterable options = configureAPIfNecessary(fileManager) ? null : Arrays.asList("-proc:none");
+ JavacTask task = ((JavacTool)compiler).getTask(null, fileManager, null /* already added to context */, options, List.of() /* already set */, fileObjects, context);
+ {
+ // don't know yet a better way to ensure those necessary flags get configured
+ var javac = com.sun.tools.javac.main.JavaCompiler.instance(context);
+ javac.keepComments = true;
+ javac.genEndPos = true;
+ javac.lineDebugInfo = true;
+ }
+
+ List javacCompilationUnits = new ArrayList<>();
+ try {
+ var elements = task.parse().iterator();
+ // after parsing, we already have the comments and we don't care about reading other comments
+ // during resolution
+ {
+ // The tree we have are complete and good enough for further processing.
+ // Disable extra features that can affect how other trees (source path elements)
+ // are parsed during resolution so we stick to the mininal useful data generated
+ // and stored during analysis
+ var javac = com.sun.tools.javac.main.JavaCompiler.instance(context);
+ javac.keepComments = false;
+ javac.genEndPos = false;
+ javac.lineDebugInfo = false;
+ }
+ var aptPath = fileManager.getLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH);
+ if ((flags & ICompilationUnit.FORCE_PROBLEM_DETECTION) != 0
+ || (aptPath != null && aptPath.iterator().hasNext())) {
+ task.analyze();
+ }
+
+ Throwable cachedThrown = null;
+
+ for (int i = 0 ; i < sourceUnits.length; i++) {
+ if (elements.hasNext() && elements.next() instanceof JCCompilationUnit u) {
+ javacCompilationUnit = u;
+ javacCompilationUnits.add(u);
+ if (sourceUnits.length == 1 && focalPoint >= 0) {
+ JavacUtils.trimUnvisibleContent(u, focalPoint, context);
+ }
+ } else {
+ return Map.of();
+ }
+ try {
+ String rawText = null;
+ try {
+ rawText = fileObjects.get(i).getCharContent(true).toString();
+ } catch( IOException ioe) {
+ ILog.get().error(ioe.getMessage(), ioe);
+ return null;
+ }
+ CompilationUnit res = result.get(sourceUnits[i]);
+ AST ast = res.ast;
+ JavacConverter converter = new JavacConverter(ast, javacCompilationUnit, context, rawText, docEnabled, focalPoint);
+ converter.populateCompilationUnit(res, javacCompilationUnit);
+ // javadoc problems explicitly set as they're not sent to DiagnosticListener (maybe find a flag to do it?)
+ var javadocProblems = converter.javadocDiagnostics.stream()
+ .map(problemConverter::createJavacProblem)
+ .filter(Objects::nonNull)
+ .toArray(IProblem[]::new);
+ if (javadocProblems.length > 0) {
+ int initialSize = res.getProblems().length;
+ var newProblems = Arrays.copyOf(res.getProblems(), initialSize + javadocProblems.length);
+ System.arraycopy(javadocProblems, 0, newProblems, initialSize, javadocProblems.length);
+ res.setProblems(newProblems);
+ }
+ List javadocComments = new ArrayList<>();
+ res.accept(new ASTVisitor(true) {
+ @Override
+ public void postVisit(ASTNode node) { // fix some positions
+ if( node.getParent() != null ) {
+ 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, javacCompilationUnits);
+ resolver.isRecoveringBindings = (flags & ICompilationUnit.ENABLE_BINDINGS_RECOVERY) != 0;
+ ast.setBindingResolver(resolver);
+ }
+ //
+ ast.setOriginalModificationCount(ast.modificationCount()); // "un-dirty" AST so Rewrite can process it
+ ast.setDefaultNodeFlag(ast.getDefaultNodeFlag() & ~ASTNode.ORIGINAL);
+ } catch (Throwable thrown) {
+ if (cachedThrown == null) {
+ cachedThrown = thrown;
+ }
+ ILog.get().error("Internal failure while parsing or converting AST for unit " + new String(sourceUnits[i].getFileName()));
+ ILog.get().error(thrown.getMessage(), thrown);
+ }
+ }
+ if (!resolveBindings) {
+ destroy(context);
+ }
+ if (cachedThrown != null) {
+ throw new RuntimeException(cachedThrown);
+ }
+ } catch (IOException ex) {
+ ILog.get().error(ex.getMessage(), ex);
+ }
+
+ return result;
+ }
+
+ /// cleans up context after analysis (nothing left to process)
+ /// but remain it usable by bindings by keeping filemanager available.
+ public static void cleanup(Context context) {
+ MultiTaskListener.instance(context).clear();
+ if (context.get(DiagnosticListener.class) instanceof ForwardDiagnosticsAsDOMProblems listener) {
+ listener.filesToUnits.clear(); // no need to keep handle on generated ASTs in the context
+ }
+ // based on com.sun.tools.javac.api.JavacTaskImpl.cleanup()
+ var javac = com.sun.tools.javac.main.JavaCompiler.instance(context);
+ if (javac != null) {
+ javac.close();
+ }
+ }
+ /// destroys the context, it's not usable at all after
+ public void destroy(Context context) {
+ cleanup(context);
+ try {
+ context.get(JavaFileManager.class).close();
+ } catch (IOException e) {
+ ILog.get().error(e.getMessage(), e);
+ }
+ }
+
+ private void addProblemsToDOM(CompilationUnit dom, Collection problems) {
+ if (problems == null) {
+ return;
+ }
+ IProblem[] previous = dom.getProblems();
+ IProblem[] newProblems = Arrays.copyOf(previous, previous.length + problems.size());
+ int start = previous.length;
+ for (CategorizedProblem problem : problems) {
+ newProblems[start] = problem;
+ start++;
+ }
+ dom.setProblems(newProblems);
+ }
+
+ private AST createAST(Map options, int level, Context context, int flags) {
+ AST ast = AST.newAST(level, JavaCore.ENABLED.equals(options.get(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES)));
+ ast.setFlag(flags);
+ ast.setDefaultNodeFlag(ASTNode.ORIGINAL);
+ String sourceModeSetting = options.get(JavaCore.COMPILER_SOURCE);
+ long sourceLevel = CompilerOptions.versionToJdkLevel(sourceModeSetting);
+ if (sourceLevel == 0) {
+ // unknown sourceModeSetting
+ sourceLevel = ClassFileConstants.getLatestJDKLevel();
+ }
+ ast.scanner.sourceLevel = sourceLevel;
+ String compliance = options.get(JavaCore.COMPILER_COMPLIANCE);
+ long complianceLevel = CompilerOptions.versionToJdkLevel(compliance);
+ if (complianceLevel == 0) {
+ // unknown sourceModeSetting
+ complianceLevel = sourceLevel;
+ }
+ ast.scanner.complianceLevel = complianceLevel;
+ ast.scanner.previewEnabled = JavaCore.ENABLED.equals(options.get(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES));
+ return ast;
+ }
+
+//
+ /**
+ * Currently re-scans the doc to build the list of comments and then
+ * attach them to the already built AST.
+ * @param res
+ * @param context
+ * @param fileObject
+ * @param converter
+ * @param compilerOptions
+ */
+ private void attachMissingComments(CompilationUnit unit, Context context, String rawText, JavacConverter converter, Map compilerOptions) {
+ ScannerFactory scannerFactory = ScannerFactory.instance(context);
+ List missingComments = new ArrayList<>();
+ JavadocTokenizer commentTokenizer = new JavadocTokenizer(scannerFactory, rawText.toCharArray(), rawText.length()) {
+ @Override
+ protected com.sun.tools.javac.parser.Tokens.Comment processComment(int pos, int endPos, CommentStyle style) {
+ // workaround Java bug 9077218
+ if (style == CommentStyle.JAVADOC_BLOCK && endPos - pos <= 4) {
+ style = CommentStyle.BLOCK;
+ }
+ var res = super.processComment(pos, endPos, style);
+ if (noCommentAt(unit, pos)) { // not already processed
+ var comment = converter.convert(res, pos, endPos);
+ missingComments.add(comment);
+ }
+ return res;
+ }
+ };
+ Scanner javacScanner = new Scanner(scannerFactory, commentTokenizer) {
+ // subclass just to access constructor
+ // TODO DefaultCommentMapper.this.scanner.linePtr == -1?
+ };
+ do { // consume all tokens to populate comments
+ javacScanner.nextToken();
+ } while (javacScanner.token() != null && javacScanner.token().kind != TokenKind.EOF);
+ org.eclipse.jdt.internal.compiler.parser.Scanner ecjScanner = new ASTConverter(compilerOptions, false, null).scanner;
+ ecjScanner.recordLineSeparator = true;
+ ecjScanner.skipComments = false;
+ try {
+ ecjScanner.setSource(rawText.toCharArray());
+ do {
+ ecjScanner.getNextToken();
+ } while (!ecjScanner.atEnd());
+ } catch (InvalidInputException ex) {
+ 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..a5269350bbd
--- /dev/null
+++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacConverter.java
@@ -0,0 +1,3665 @@
+/*******************************************************************************
+ * 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;
+ 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 types appear to not keep the trailing semicolon in source range
+ if (res instanceof Name || res instanceof FieldAccess || res instanceof SuperFieldAccess ) {
+ while (endPos > start && this.rawText.length()>= endPos && this.rawText.charAt(endPos - 1) == ';') {
+ endPos--;
+ }
+ }
+ length = endPos - start;
+ if (start + Math.max(0, length) > this.rawText.length()) {
+ length = this.rawText.length() - start;
+ }
+ }
+ return Math.max(0, length);
+ }
+ return length;
+ }
+
+ void commonSettings(ASTNode res, JCTree javac, int length, boolean removeWhitespace) {
+ if( javac != null && length >= 0) {
+ res.setSourceRange(javac.getStartPosition(), Math.max(0, length));
+ if( removeWhitespace ) {
+ removeSurroundingWhitespaceFromRange(res);
+ }
+ this.domToJavac.put(res, javac);
+ setJavadocForNode(javac, res);
+ }
+ }
+
+ private void nameSettings(SimpleName name, JCMethodDecl javac, String selector, boolean isConstructor) {
+ if ((selector.equals(ERROR) || selector.equals(FAKE_IDENTIFIER)))
+ return;
+ var start = javac.getPreferredPosition();
+ if (start > -1) {
+ // handle constructor length using type name instead of selector.
+ var length = isConstructor ? name.toString().length() : selector.length();
+ name.setSourceRange(start, length);
+ }
+ }
+
+ private void nameSettings(SimpleName name, JCVariableDecl javac, String varName) {
+ if (varName.equals(ERROR) || varName.equals(FAKE_IDENTIFIER))
+ return;
+ var start = javac.getPreferredPosition();
+ if (start > -1) {
+ name.setSourceRange(start, varName.length());
+ }
+ }
+
+ private Name toName(JCTree expression) {
+ return toName(expression, null);
+ }
+
+ Name toName(JCTree expression, BiConsumer extraSettings ) {
+ if (expression instanceof JCIdent ident) {
+ Name res = convertName(ident.getName());
+ commonSettings(res, expression);
+ if( extraSettings != null )
+ extraSettings.accept(res, ident);
+ return res;
+ }
+ if (expression instanceof JCFieldAccess fieldAccess) {
+ JCExpression faExpression = fieldAccess.getExpression();
+ SimpleName n = (SimpleName)convertName(fieldAccess.getIdentifier());
+ if (n == null) {
+ n = this.ast.newSimpleName(FAKE_IDENTIFIER);
+ n.setFlags(ASTNode.RECOVERED);
+ }
+ commonSettings(n, fieldAccess);
+
+ Name qualifier = toName(faExpression, extraSettings);
+ QualifiedName res = this.ast.newQualifiedName(qualifier, n);
+ commonSettings(res, fieldAccess);
+ if( extraSettings != null )
+ extraSettings.accept(res, fieldAccess);
+ // don't calculate source range if the identifier is not valid.
+ if (!fieldAccess.getIdentifier().contentEquals(FAKE_IDENTIFIER)
+ && !fieldAccess.getIdentifier().contentEquals(ERROR)) {
+ // fix name position according to qualifier position
+ int nameIndex = this.rawText.indexOf(fieldAccess.getIdentifier().toString(),
+ qualifier.getStartPosition() + qualifier.getLength());
+ if (nameIndex >= 0) {
+ n.setSourceRange(nameIndex, fieldAccess.getIdentifier().toString().length());
+ }
+ }
+ return res;
+ }
+ if (expression instanceof JCAnnotatedType jcat) {
+ Name n = toName(jcat.underlyingType, extraSettings);
+ commonSettings(n, jcat.underlyingType);
+ return n;
+ }
+ if (expression instanceof JCTypeApply jcta) {
+ Name n = toName(jcta.clazz, extraSettings);
+ commonSettings(n, jcta.clazz);
+ return n;
+ }
+ throw new UnsupportedOperationException("toName for " + expression + " (" + expression == null ? "null" : expression.getClass().getName() + ")");
+ }
+
+ private AbstractTypeDeclaration convertClassDecl(JCClassDecl javacClassDecl, ASTNode parent) {
+ if( javacClassDecl.getKind() == Kind.ANNOTATION_TYPE &&
+ (this.ast.apiLevel <= AST.JLS2_INTERNAL || this.ast.scanner.complianceLevel < ClassFileConstants.JDK1_5)) {
+ return null;
+ }
+ if( javacClassDecl.getKind() == Kind.ENUM &&
+ (this.ast.apiLevel <= AST.JLS2_INTERNAL || this.ast.scanner.complianceLevel < ClassFileConstants.JDK1_5)) {
+ return null;
+ }
+ if( javacClassDecl.getKind() == Kind.RECORD &&
+ (this.ast.apiLevel < AST.JLS16_INTERNAL || this.ast.scanner.complianceLevel < ClassFileConstants.JDK16)) {
+ return null;
+ }
+
+ AbstractTypeDeclaration res = switch (javacClassDecl.getKind()) {
+ case ANNOTATION_TYPE -> this.ast.newAnnotationTypeDeclaration();
+ case ENUM -> this.ast.newEnumDeclaration();
+ case RECORD -> this.ast.newRecordDeclaration();
+ case INTERFACE -> {
+ TypeDeclaration decl = this.ast.newTypeDeclaration();
+ decl.setInterface(true);
+ yield decl;
+ }
+ case CLASS -> javacClassDecl.getModifiers() != null && (javacClassDecl.getModifiers().flags & Flags.IMPLICIT_CLASS) != 0 ?
+ new ImplicitTypeDeclaration(this.ast) :
+ this.ast.newTypeDeclaration();
+ default -> throw new IllegalStateException();
+ };
+ return convertClassDecl(javacClassDecl, parent, res);
+ }
+
+ private AbstractTypeDeclaration convertClassDecl(JCClassDecl javacClassDecl, ASTNode parent, AbstractTypeDeclaration res) {
+ commonSettings(res, javacClassDecl);
+ SimpleName simpName = (SimpleName)convertName(javacClassDecl.getSimpleName());
+ if(!(res instanceof ImplicitTypeDeclaration) && simpName != null) {
+ res.setName(simpName);
+ int searchNameFrom = javacClassDecl.getPreferredPosition();
+ if (javacClassDecl.getModifiers() != null) {
+ searchNameFrom = Math.max(searchNameFrom, TreeInfo.getEndPos(javacClassDecl.getModifiers(), this.javacCompilationUnit.endPositions));
+ }
+ int namePosition = this.rawText.indexOf(simpName.getIdentifier(), searchNameFrom);
+ if (namePosition >= 0) {
+ simpName.setSourceRange(namePosition, simpName.getIdentifier().length());
+ }
+ }
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ res.modifiers().addAll(convert(javacClassDecl.mods, res));
+ } else {
+ int jls2Flags = getJLS2ModifiersFlags(javacClassDecl.mods);
+ jls2Flags &= ~Flags.INTERFACE; // remove AccInterface flags, see ASTConverter
+ res.internalSetModifiers(jls2Flags);
+ }
+ if (res instanceof TypeDeclaration typeDeclaration) {
+ if (javacClassDecl.getExtendsClause() != null) {
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ typeDeclaration.setSuperclassType(convertToType(javacClassDecl.getExtendsClause()));
+ } else {
+ JCExpression e = javacClassDecl.getExtendsClause();
+ Name m = toName(e);
+ if( m != null ) {
+ typeDeclaration.setSuperclass(m);
+ }
+ }
+ }
+ if (javacClassDecl.getImplementsClause() != null) {
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ javacClassDecl.getImplementsClause().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(typeDeclaration.superInterfaceTypes()::add);
+ } else {
+ Iterator it = javacClassDecl.getImplementsClause().iterator();
+ while(it.hasNext()) {
+ JCExpression next = it.next();
+ Name m = toName(next);
+ if( m != null ) {
+ typeDeclaration.superInterfaces().add(m);
+ }
+ }
+ }
+ }
+
+ if( javacClassDecl.getTypeParameters() != null ) {
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ Iterator i = javacClassDecl.getTypeParameters().iterator();
+ while(i.hasNext()) {
+ JCTypeParameter next = i.next();
+ typeDeclaration.typeParameters().add(convert(next));
+ }
+ }
+ }
+
+ if (javacClassDecl.getPermitsClause() != null) {
+ if( this.ast.apiLevel >= AST.JLS17_INTERNAL) {
+ javacClassDecl.getPermitsClause().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(typeDeclaration.permittedTypes()::add);
+ if (!javacClassDecl.getPermitsClause().isEmpty()) {
+ int permitsOffset = this.rawText.substring(javacClassDecl.pos).indexOf("permits") + javacClassDecl.pos;
+ typeDeclaration.setRestrictedIdentifierStartPosition(permitsOffset);
+ }
+ }
+ }
+ if (javacClassDecl.getMembers() != null) {
+ List members = javacClassDecl.getMembers();
+ ASTNode previous = null;
+ for( int i = 0; i < members.size(); i++ ) {
+ ASTNode decl = convertBodyDeclaration(members.get(i), res);
+ if( decl != null ) {
+ typeDeclaration.bodyDeclarations().add(decl);
+ if (previous != null) {
+ int istart = decl.getStartPosition();
+ int siblingEnds = previous.getStartPosition() + previous.getLength();
+ if(previous.getStartPosition() >= 0 && siblingEnds > istart && istart > previous.getStartPosition()) {
+ previous.setSourceRange(previous.getStartPosition(), istart - previous.getStartPosition()-1);
+ }
+ }
+ previous = decl;
+ }
+ }
+ }
+ } else if (res instanceof EnumDeclaration enumDecl) {
+ List enumStatements= enumDecl.enumConstants();
+ if (javacClassDecl.getMembers() != null) {
+ for(JCTree member : javacClassDecl.getMembers()) {
+ EnumConstantDeclaration dec1 = convertEnumConstantDeclaration(member, parent, enumDecl);
+ if( dec1 != null ) {
+ enumStatements.add(dec1);
+ } else {
+ // body declaration
+ ASTNode bodyDecl = convertBodyDeclaration(member, res);
+ if( bodyDecl != null ) {
+ res.bodyDeclarations().add(bodyDecl);
+ }
+ }
+ }
+ }
+ } else if (res instanceof AnnotationTypeDeclaration annotDecl) {
+ //setModifiers(annotationTypeMemberDeclaration2, annotationTypeMemberDeclaration);
+ final SimpleName name = new SimpleName(this.ast);
+ name.internalSetIdentifier(new String(annotDecl.typeName.toString()));
+ res.setName(name);
+ if( javacClassDecl.defs != null ) {
+ for( Iterator i = javacClassDecl.defs.iterator(); i.hasNext(); ) {
+ ASTNode converted = convertBodyDeclaration(i.next(), res);
+ if( converted != null ) {
+ res.bodyDeclarations.add(converted);
+ }
+ }
+ }
+
+// org.eclipse.jdt.internal.compiler.ast.TypeReference typeReference = annotDecl.get
+// if (typeReference != null) {
+// Type returnType = convertType(typeReference);
+// setTypeForMethodDeclaration(annotationTypeMemberDeclaration2, returnType, 0);
+// }
+// int declarationSourceStart = annotationTypeMemberDeclaration.declarationSourceStart;
+// int declarationSourceEnd = annotationTypeMemberDeclaration.bodyEnd;
+// annotationTypeMemberDeclaration2.setSourceRange(declarationSourceStart, declarationSourceEnd - declarationSourceStart + 1);
+// // The javadoc comment is now got from list store in compilation unit declaration
+// convert(annotationTypeMemberDeclaration.javadoc, annotationTypeMemberDeclaration2);
+// org.eclipse.jdt.internal.compiler.ast.Expression memberValue = annotationTypeMemberDeclaration.defaultValue;
+// if (memberValue != null) {
+// annotationTypeMemberDeclaration2.setDefault(convert(memberValue));
+// }
+
+ } else if (res instanceof RecordDeclaration recordDecl) {
+ int start = javacClassDecl.getPreferredPosition();
+ if( start != -1 ) {
+ recordDecl.setRestrictedIdentifierStartPosition(start);
+ }
+ for (JCTree node : javacClassDecl.getMembers()) {
+ if (node instanceof JCVariableDecl vd && !vd.getModifiers().getFlags().contains(javax.lang.model.element.Modifier.STATIC)) {
+ SingleVariableDeclaration vdd = (SingleVariableDeclaration)convertVariableDeclaration(vd);
+ // Records cannot have modifiers
+ vdd.modifiers().clear();
+ // Add only annotation modifiers
+ vdd.modifiers().addAll(convertModifierAnnotations(vd.getModifiers(), vdd));
+ recordDecl.recordComponents().add(vdd);
+ } else {
+ ASTNode converted = convertBodyDeclaration(node, res);
+ if( converted != null ) {
+ res.bodyDeclarations.add(converted);
+ }
+ }
+ }
+ } else if (res instanceof ImplicitTypeDeclaration) {
+ javacClassDecl.getMembers().stream()
+ .map(member -> convertBodyDeclaration(member, res))
+ .filter(Objects::nonNull)
+ .forEach(res.bodyDeclarations()::add);
+ }
+ return res;
+ }
+
+ private TypeParameter convert(JCTypeParameter typeParameter) {
+ final TypeParameter ret = new TypeParameter(this.ast);
+ commonSettings(ret, typeParameter);
+ final SimpleName simpleName = new SimpleName(this.ast);
+ simpleName.internalSetIdentifier(typeParameter.getName().toString());
+ int start = typeParameter.pos;
+ int end = typeParameter.pos + typeParameter.getName().length();
+ simpleName.setSourceRange(start, end - start);
+ ret.setName(simpleName);
+ List bounds = typeParameter.getBounds();
+ Iterator i = bounds.iterator();
+ while(i.hasNext()) {
+ JCTree t = i.next();
+ Type type = convertToType(t);
+ ret.typeBounds().add(type);
+ end = typeParameter.getEndPosition(this.javacCompilationUnit.endPositions);
+ }
+ if (typeParameter.getAnnotations() != null && this.ast.apiLevel() >= AST.JLS8_INTERNAL) {
+ typeParameter.getAnnotations().stream()
+ .map(this::convert)
+ .forEach(ret.modifiers()::add);
+ }
+// org.eclipse.jdt.internal.compiler.ast.Annotation[] annotations = typeParameter.annotations;
+// if (annotations != null) {
+// if (annotations[0] != null)
+// annotationsStart = annotations[0].sourceStart;
+// annotateTypeParameter(typeParameter2, typeParameter.annotations);
+// }
+// final TypeReference superType = typeParameter.type;
+// end = typeParameter.declarationSourceEnd;
+// if (superType != null) {
+// Type type = convertType(superType);
+// typeParameter2.typeBounds().add(type);
+// end = type.getStartPosition() + type.getLength() - 1;
+// }
+// TypeReference[] bounds = typeParameter.bounds;
+// if (bounds != null) {
+// Type type = null;
+// for (int index = 0, length = bounds.length; index < length; index++) {
+// type = convertType(bounds[index]);
+// typeParameter2.typeBounds().add(type);
+// end = type.getStartPosition() + type.getLength() - 1;
+// }
+// }
+// start = annotationsStart < typeParameter.declarationSourceStart ? annotationsStart : typeParameter.declarationSourceStart;
+// end = retrieveClosingAngleBracketPosition(end);
+// if (this.resolveBindings) {
+// recordName(simpleName, typeParameter);
+// recordNodes(typeParameter2, typeParameter);
+// typeParameter2.resolveBinding();
+// }
+ ret.setSourceRange(start, end - start);
+ return ret;
+ }
+
+ private ASTNode convertBodyDeclaration(JCTree tree, ASTNode parent) {
+ if( parent instanceof AnnotationTypeDeclaration && tree instanceof JCMethodDecl methodDecl) {
+ return convertMethodInAnnotationTypeDecl(methodDecl, parent);
+ }
+ if (tree instanceof JCMethodDecl methodDecl) {
+ return convertMethodDecl(methodDecl, parent);
+ }
+ if (tree instanceof JCClassDecl jcClassDecl) {
+ return convertClassDecl(jcClassDecl, parent);
+ }
+ if (tree instanceof JCVariableDecl jcVariableDecl) {
+ return convertFieldDeclaration(jcVariableDecl, parent);
+ }
+ if (tree instanceof JCBlock block) {
+ Initializer res = this.ast.newInitializer();
+ commonSettings(res, tree);
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ // Now we have the tough task of going from a flags number to actual modifiers with source ranges
+ res.modifiers().addAll(convertModifiersFromFlags(block.getStartPosition(), block.endpos, block.flags));
+ } else {
+ res.internalSetModifiers(getJLS2ModifiersFlags(block.flags));
+ }
+ boolean fillBlock = shouldFillBlock(block, this.focalPoint);
+ if( fillBlock ) {
+ res.setBody(convertBlock(block));
+ } else {
+ Block b = this.ast.newBlock();
+ commonSettings(res, block);
+ res.setBody(b);
+ }
+ return res;
+ }
+ if (tree instanceof JCErroneous || tree instanceof JCSkip) {
+ return null;
+ }
+ ILog.get().error("Unsupported " + tree + " of type" + tree.getClass());
+ Block substitute = this.ast.newBlock();
+ commonSettings(substitute, tree);
+ return substitute;
+ }
+
+ private ASTNode convertMethodInAnnotationTypeDecl(JCMethodDecl javac, ASTNode parent) {
+ AnnotationTypeMemberDeclaration res = new AnnotationTypeMemberDeclaration(this.ast);
+ commonSettings(res, javac);
+ res.modifiers().addAll(convert(javac.getModifiers(), res));
+ res.setType(convertToType(javac.getReturnType()));
+ if( javac.defaultValue != null) {
+ res.setDefault(convertExpression(javac.defaultValue));
+ }
+ if (convertName(javac.getName()) instanceof SimpleName simpleName) {
+ res.setName(simpleName);
+ int start = javac.getPreferredPosition();
+ if (start > -1) {
+ simpleName.setSourceRange(start, javac.getName().toString().length());
+ }
+ }
+ return res;
+ }
+
+ private String getNodeName(ASTNode node) {
+ if( node instanceof AbstractTypeDeclaration atd) {
+ return atd.getName().toString();
+ }
+ if( node instanceof EnumDeclaration ed) {
+ return ed.getName().toString();
+ }
+ return null;
+ }
+
+ private String getMethodDeclName(JCMethodDecl javac, ASTNode parent, boolean records) {
+ String name = javac.getName().toString();
+ boolean javacIsConstructor = Objects.equals(javac.getName(), Names.instance(this.context).init);
+ if( javacIsConstructor) {
+ // sometimes javac mistakes a method with no return type as a constructor
+ String parentName = getNodeName(parent);
+ String tmpString1 = this.rawText.substring(javac.pos);
+ int openParen = tmpString1.indexOf("(");
+ int openBrack = tmpString1.indexOf("{");
+ int endPos = -1;
+ if( openParen != -1 ) {
+ endPos = openParen;
+ }
+ if( records && openBrack != -1 ) {
+ endPos = endPos == -1 ? openBrack : Math.min(openBrack, endPos);
+ }
+ if( endPos != -1 ) {
+ String methodName = tmpString1.substring(0, endPos).trim();
+ if (!methodName.isEmpty() &&
+ Character.isJavaIdentifierStart(methodName.charAt(0)) &&
+ methodName.substring(1).chars().allMatch(Character::isJavaIdentifierPart) &&
+ !methodName.equals(parentName)) {
+ return methodName;
+ }
+ }
+ return parentName;
+ }
+ return name;
+ }
+
+ private MethodDeclaration convertMethodDecl(JCMethodDecl javac, ASTNode parent) {
+ if (TreeInfo.getEndPos(javac, this.javacCompilationUnit.endPositions) <= javac.getStartPosition()) {
+ // not really existing, analysis sugar; let's skip
+ return null;
+ }
+ MethodDeclaration res = this.ast.newMethodDeclaration();
+ commonSettings(res, javac);
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ res.modifiers().addAll(convert(javac.getModifiers(), res));
+ } else {
+ res.internalSetModifiers(getJLS2ModifiersFlags(javac.mods));
+ }
+ String javacName = javac.getName().toString();
+ String methodDeclName = getMethodDeclName(javac, parent, parent instanceof RecordDeclaration);
+ boolean methodDeclNameMatchesInit = Objects.equals(methodDeclName, Names.instance(this.context).init.toString());
+ boolean javacNameMatchesInit = javacName.equals("");
+ boolean javacNameMatchesError = javacName.equals(ERROR);
+ boolean javacNameMatchesInitAndMethodNameMatchesTypeName = javacNameMatchesInit && methodDeclName.equals(getNodeName(parent));
+ boolean isConstructor = methodDeclNameMatchesInit || javacNameMatchesInitAndMethodNameMatchesTypeName;
+ res.setConstructor(isConstructor);
+ if (isConstructor && javac.getParameters().isEmpty()
+ && javac.getBody() != null && javac.getBody().endpos == Position.NOPOS) { // probably generated
+ return null;
+ }
+ boolean isCompactConstructor = false;
+ if(isConstructor && parent instanceof RecordDeclaration) {
+ String postName = this.rawText.substring(javac.pos + methodDeclName.length()).trim();
+ String firstChar = postName != null && postName.length() > 0 ? postName.substring(0,1) : null;
+ isCompactConstructor = ("{".equals(firstChar));
+ if( this.ast.apiLevel >= AST.JLS16_INTERNAL) {
+ res.setCompactConstructor(isCompactConstructor);
+ }
+ }
+ boolean malformed = false;
+ if(isConstructor && !javacNameMatchesInitAndMethodNameMatchesTypeName) {
+ malformed = true;
+ }
+ if( javacNameMatchesError || (javacNameMatchesInit && !isConstructor )) {
+ malformed = true;
+ }
+
+ JCTree retTypeTree = javac.getReturnType();
+ Type retType = null;
+ if( !javacNameMatchesError) {
+ var name = this.ast.newSimpleName(methodDeclName);
+ nameSettings(name, javac, methodDeclName, isConstructor);
+ res.setName(name);
+ } else {
+ // javac name is an error, so let's treat the return type as the name
+ if (retTypeTree instanceof JCIdent jcid) {
+ var name = this.ast.newSimpleName(jcid.getName().toString());
+ nameSettings(name, javac, javacName, isConstructor);
+ res.setName(name);
+ retTypeTree = null;
+ if (jcid.toString().equals(getNodeName(parent))) {
+ res.setConstructor(true);
+ isConstructor = true;
+ }
+ }
+ }
+
+ if( retTypeTree == null ) {
+ if( isConstructor && this.ast.apiLevel == AST.JLS2_INTERNAL ) {
+ retType = this.ast.newPrimitiveType(convert(TypeKind.VOID));
+ // // TODO need to find the right range
+ retType.setSourceRange(javac.mods.pos + getJLS2ModifiersFlagsAsStringLength(javac.mods.flags), 0);
+ }
+ } else {
+ retType = convertToType(retTypeTree);
+ }
+ var dims = convertDimensionsAfterPosition(retTypeTree, javac.pos);
+ if (!dims.isEmpty() && retTypeTree.pos > javac.pos ) {
+ // The array dimensions are part of the variable name
+ if( this.ast.apiLevel < AST.JLS8_INTERNAL) {
+ res.setExtraDimensions(dims.size());
+ } else {
+ res.extraDimensions().addAll(dims);
+ }
+ retType = convertToType(unwrapDimensions(retTypeTree, dims.size()));
+ }
+
+ if( retType != null || isConstructor) {
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ res.setReturnType2(retType);
+ } else {
+ res.internalSetReturnType(retType);
+ }
+ } else {
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ res.setReturnType2(null);
+ }
+ }
+
+ if( !isCompactConstructor) {
+ // Compact constructor does not show the parameters even though javac finds them
+ javac.getParameters().stream().map(this::convertVariableDeclaration).forEach(res.parameters()::add);
+ }
+ if (javac.getReceiverParameter() != null) {
+ Type receiverType = convertToType(javac.getReceiverParameter().getType());
+ if (receiverType instanceof AnnotatableType annotable) {
+ javac.getReceiverParameter().getModifiers().getAnnotations().stream() //
+ .map(this::convert)
+ .forEach(annotable.annotations()::add);
+ }
+ if (receiverType != null) {
+ res.setReceiverType(receiverType);
+ }
+ if (javac.getReceiverParameter().getNameExpression() instanceof JCFieldAccess qualifiedName) {
+ res.setReceiverQualifier((SimpleName)toName(qualifiedName.getExpression()));
+ }
+ }
+
+ if( javac.getTypeParameters() != null ) {
+ Iterator i = javac.getTypeParameters().iterator();
+ while(i.hasNext()) {
+ JCTypeParameter next = i.next();
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ res.typeParameters().add(convert(next));
+ } else {
+ // TODO
+ }
+ }
+ }
+
+ if (javac.getBody() != null
+ && javac.getBody().endpos > javac.getBody().getStartPosition()) { // otherwise, it's probably generated by lombok
+ boolean fillBlock = shouldFillBlock(javac, this.focalPoint);
+ if( fillBlock ) {
+ Block b = convertBlock(javac.getBody());
+ if (b != null) {
+ AbstractTypeDeclaration td = findSurroundingTypeDeclaration(parent);
+ boolean isInterface = td instanceof TypeDeclaration td1 && td1.isInterface();
+ long modFlags = javac.getModifiers() == null ? 0 : javac.getModifiers().flags;
+ boolean isAbstractOrNative = (modFlags & (Flags.ABSTRACT | Flags.NATIVE)) != 0;
+ boolean isJlsBelow8 = this.ast.apiLevel < AST.JLS8_INTERNAL;
+ boolean isJlsAbove8 = this.ast.apiLevel > AST.JLS8_INTERNAL;
+ long flagsToCheckForAboveJLS8 = Flags.STATIC | Flags.DEFAULT | (isJlsAbove8 ? Flags.PRIVATE : 0);
+ boolean notAllowed = (isAbstractOrNative || (isInterface && (isJlsBelow8 || (modFlags & flagsToCheckForAboveJLS8) == 0)));
+ if (notAllowed) {
+ res.setFlags(res.getFlags() | ASTNode.MALFORMED);
+ }
+ res.setBody(b);
+ if( (b.getFlags() & ASTNode.MALFORMED) > 0 ) {
+ malformed = true;
+ }
+ }
+ } else {
+ Block b = this.ast.newBlock();
+ commonSettings(res, javac);
+ res.setBody(b);
+ }
+ }
+
+ for (JCExpression thrown : javac.getThrows()) {
+ if (this.ast.apiLevel < AST.JLS8_INTERNAL) {
+ res.thrownExceptions().add(toName(thrown));
+ } else {
+ Type type = convertToType(thrown);
+ if (type != null) {
+ res.thrownExceptionTypes().add(type);
+ }
+ }
+ }
+ if( malformed ) {
+ res.setFlags(res.getFlags() | ASTNode.MALFORMED);
+ }
+ return res;
+ }
+
+ private boolean shouldFillBlock(JCTree tree, int focalPoint2) {
+ int start = tree.getStartPosition();
+ int endPos = tree.getEndPosition(this.javacCompilationUnit.endPositions);
+ if( focalPoint == -1 || (focalPoint >= start && focalPoint <= endPos)) {
+ return true;
+ }
+ return false;
+ }
+ private AbstractTypeDeclaration findSurroundingTypeDeclaration(ASTNode parent) {
+ if( parent == null )
+ return null;
+ if( parent instanceof AbstractTypeDeclaration t) {
+ return t;
+ }
+ return findSurroundingTypeDeclaration(parent.getParent());
+ }
+
+ private VariableDeclaration convertVariableDeclarationForLambda(JCVariableDecl javac) {
+ if(javac.getType() == null && javac.getStartPosition() == javac.getPreferredPosition() /* check no "var" */) {
+ return createVariableDeclarationFragment(javac);
+ } else if (javac.getType() != null && javac.getType().getPreferredPosition() == Position.NOPOS) { // "virtual" node added for analysis, not part of AST
+ return createVariableDeclarationFragment(javac);
+ } else {
+ return convertVariableDeclaration(javac);
+ }
+ }
+ private VariableDeclaration convertVariableDeclaration(JCVariableDecl javac) {
+ // if (singleDecl) {
+ SingleVariableDeclaration res = this.ast.newSingleVariableDeclaration();
+ commonSettings(res, javac);
+ if (convertName(javac.getName()) instanceof SimpleName simpleName) {
+ nameSettings(simpleName, javac, simpleName.toString());
+ res.setName(simpleName);
+ }
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ res.modifiers().addAll(convert(javac.getModifiers(), res));
+ } else {
+ res.internalSetModifiers(getJLS2ModifiersFlags(javac.mods));
+ }
+
+ JCTree type = javac.getType();
+ if (type instanceof JCAnnotatedType annotatedType) {
+ annotatedType.getAnnotations().stream()
+ .map(this::convert)
+ .forEach(res.varargsAnnotations()::add);
+ type = annotatedType.getUnderlyingType();
+ }
+
+ if ( (javac.mods.flags & VARARGS) != 0) {
+ // We have varity
+ if(type instanceof JCArrayTypeTree arr) {
+ type = unwrapDimensions(arr, 1);
+ }
+ if( this.ast.apiLevel > AST.JLS2_INTERNAL) {
+ res.setVarargs(true);
+ }
+ }
+
+ List dims = convertDimensionsAfterPosition(javac.getType(), javac.getPreferredPosition()); // +1 to exclude part of the type declared before name
+ if(!dims.isEmpty() ) {
+ // Some of the array dimensions are part of the variable name
+ if( this.ast.apiLevel < AST.JLS8_INTERNAL) {
+ res.setExtraDimensions(dims.size()); // the type is 1-dim array
+ } else {
+ res.extraDimensions().addAll(dims);
+ }
+ type = unwrapDimensions(type, dims.size());
+ }
+
+ // the array dimensions are part of the type
+ if (type != null) {
+ if( !(type instanceof JCErroneous)) {
+ Type converted = convertToType(type);
+ if (converted != null) {
+ res.setType(converted);
+ }
+ }
+ } else if (javac.getStartPosition() != javac.getPreferredPosition()
+ && this.rawText.substring(javac.getStartPosition(), javac.getPreferredPosition()).matches("var(\\s)+")) {
+ SimpleName varName = this.ast.newSimpleName("var");
+ varName.setSourceRange(javac.getStartPosition(), varName.getIdentifier().length());
+ Type varType = this.ast.newSimpleType(varName);
+ varType.setSourceRange(varName.getStartPosition(), varName.getLength());
+ res.setType(varType);
+ }
+ if (javac.getInitializer() != null) {
+ res.setInitializer(convertExpression(javac.getInitializer()));
+ }
+ return res;
+ }
+
+ private int getJLS2ModifiersFlags(JCModifiers mods) {
+ return getJLS2ModifiersFlags(mods.flags);
+ }
+
+ private VariableDeclarationFragment createVariableDeclarationFragment(JCVariableDecl javac) {
+ VariableDeclarationFragment fragment = this.ast.newVariableDeclarationFragment();
+ commonSettings(fragment, javac);
+ int fragmentEnd = javac.getEndPosition(this.javacCompilationUnit.endPositions);
+ int fragmentStart = javac.pos;
+ int fragmentLength = fragmentEnd - fragmentStart; // ???? - 1;
+ fragment.setSourceRange(fragmentStart, Math.max(0, fragmentLength));
+ removeSurroundingWhitespaceFromRange(fragment);
+ removeTrailingCharFromRange(fragment, new char[] {';', ','});
+ removeSurroundingWhitespaceFromRange(fragment);
+ if (convertName(javac.getName()) instanceof SimpleName simpleName) {
+ fragment.setName(simpleName);
+ }
+ var dims = convertDimensionsAfterPosition(javac.getType(), fragmentStart);
+ if( this.ast.apiLevel < AST.JLS8_INTERNAL) {
+ fragment.setExtraDimensions(dims.size());
+ } else {
+ fragment.extraDimensions().addAll(dims);
+ }
+ if (javac.getInitializer() != null) {
+ Expression initializer = convertExpression(javac.getInitializer());
+ if( initializer != null ) {
+ fragment.setInitializer(initializer);
+ // we may receive range for `int i = 0;` (with semicolon and newline). If we
+ // have an initializer, use it's endPos instead for the fragment
+ int length = initializer.getStartPosition() + initializer.getLength() - fragment.getStartPosition();
+ if (length >= 0) {
+ fragment.setSourceRange(fragment.getStartPosition(), length);
+ }
+ }
+ }
+ return fragment;
+ }
+
+ private FieldDeclaration convertFieldDeclaration(JCVariableDecl javac, ASTNode parent) {
+ VariableDeclarationFragment fragment = createVariableDeclarationFragment(javac);
+ List sameStartPosition = new ArrayList<>();
+ if( parent instanceof AbstractTypeDeclaration decl) {
+ decl.bodyDeclarations().stream().filter(x -> x instanceof FieldDeclaration)
+ .filter(x -> ((FieldDeclaration)x).getType().getStartPosition() == javac.vartype.getStartPosition())
+ .forEach(x -> sameStartPosition.add((ASTNode)x));
+ }
+ if( parent instanceof AnonymousClassDeclaration decl) {
+ decl.bodyDeclarations().stream().filter(x -> x instanceof FieldDeclaration)
+ .filter(x -> ((FieldDeclaration)x).getType().getStartPosition() == javac.vartype.getStartPosition())
+ .forEach(x -> sameStartPosition.add((ASTNode)x));
+ }
+ if( sameStartPosition.size() >= 1 ) {
+ FieldDeclaration fd = (FieldDeclaration)sameStartPosition.get(0);
+ if( fd != null ) {
+ fd.fragments().add(fragment);
+ int newParentEnd = fragment.getStartPosition() + fragment.getLength();
+ fd.setSourceRange(fd.getStartPosition(), newParentEnd - fd.getStartPosition() + 1);
+ }
+ return null;
+ } else {
+ FieldDeclaration res = this.ast.newFieldDeclaration(fragment);
+ commonSettings(res, javac);
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ res.modifiers().addAll(convert(javac.getModifiers(), res));
+ } else {
+ res.internalSetModifiers(getJLS2ModifiersFlags(javac.mods));
+ }
+
+ Type resType = null;
+ int count = fragment.getExtraDimensions();
+ if( count > 0 ) {
+ // must do simple type here
+ JCTree t = javac.getType();
+ if( t instanceof JCArrayTypeTree jcatt) {
+ // unwrap the jcatt count times?
+ JCTree working = jcatt;
+ for( int i = 0; i < count; i++ ) {
+ if( working instanceof JCArrayTypeTree work2) {
+ working = work2.getType();
+ }
+ }
+ resType = convertToType(working);
+ } else {
+ resType = convertToType(javac.getType());
+ }
+ } else {
+ resType = convertToType(javac.getType());
+ }
+ if (resType != null) {
+ res.setType(resType);
+ }
+ if( javac.getType() instanceof JCErroneous && resType instanceof SimpleType st && st.getName() instanceof SimpleName sn && sn.toString().equals(FAKE_IDENTIFIER)) {
+ if( fragment.getName() instanceof SimpleName fragName && fragName.toString().equals(FAKE_IDENTIFIER)) {
+ return null;
+ }
+ }
+
+ return res;
+ }
+ }
+
+
+ private void setJavadocForNode(JCTree javac, ASTNode node) {
+ Comment c = this.javacCompilationUnit.docComments.getComment(javac);
+ if(c != null && (c.getStyle() == Comment.CommentStyle.JAVADOC_BLOCK || c.getStyle() == CommentStyle.JAVADOC_LINE)) {
+ 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());
+ commonSettings(qualifierName, qualifier);
+ SimpleName qualifiedName = (SimpleName)convertName(fieldAccess.getIdentifier());
+ if (qualifiedName == null) {
+ // when there are syntax errors where the statement is not completed.
+ qualifiedName = this.ast.newSimpleName(FAKE_IDENTIFIER);
+ qualifiedName.setFlags(ASTNode.RECOVERED);
+ }
+ QualifiedName res = this.ast.newQualifiedName(qualifierName, qualifiedName);
+ commonSettings(res, javac);
+ return res;
+ }
+ useQualifiedName: if (fieldAccess.getExpression() instanceof JCFieldAccess parentFieldAccess) {
+ JCFieldAccess cursor = parentFieldAccess;
+ if (Objects.equals(Names.instance(this.context)._class, cursor.getIdentifier())
+ || Objects.equals(Names.instance(this.context)._this, cursor.getIdentifier())
+ || Objects.equals(Names.instance(this.context)._super, cursor.getIdentifier())) {
+ break useQualifiedName;
+ }
+ while (cursor.getExpression() instanceof JCFieldAccess newParent) {
+ cursor = newParent;
+ if (Objects.equals(Names.instance(this.context)._class, cursor.getIdentifier())
+ || Objects.equals(Names.instance(this.context)._this, cursor.getIdentifier())
+ || Objects.equals(Names.instance(this.context)._super, cursor.getIdentifier())) {
+ break useQualifiedName;
+ }
+ }
+
+ if (cursor.getExpression() instanceof JCIdent oldestIdentifier
+ && !Objects.equals(Names.instance(this.context)._class, oldestIdentifier.getName())
+ && !Objects.equals(Names.instance(this.context)._this, oldestIdentifier.getName())
+ && !Objects.equals(Names.instance(this.context)._super, oldestIdentifier.getName())) {
+ // all segments are simple names
+ return convertQualifiedName(fieldAccess);
+ }
+ }
+ FieldAccess res = this.ast.newFieldAccess();
+ commonSettings(res, javac);
+ res.setExpression(convertExpression(fieldAccess.getExpression()));
+ if (convertName(fieldAccess.getIdentifier()) instanceof SimpleName name) {
+ res.setName(name);
+ }
+ return res;
+ }
+ if (javac instanceof JCMethodInvocation methodInvocation) {
+ JCExpression nameExpr = methodInvocation.getMethodSelect();
+ if (nameExpr instanceof JCFieldAccess access) {
+ // Handle super method calls first
+ boolean superCall1 = access.getExpression() instanceof JCFieldAccess && Objects.equals(Names.instance(this.context)._super, ((JCFieldAccess)access.getExpression()).getIdentifier());
+ boolean superCall2 = Objects.equals(Names.instance(this.context)._super.toString(), access.getExpression().toString());
+ if (superCall1 || superCall2) {
+ JCFieldAccess fa = superCall1 ? ((JCFieldAccess)access.getExpression()) : access;
+ SuperMethodInvocation res2 = this.ast.newSuperMethodInvocation();
+ commonSettings(res2, javac);
+ methodInvocation.getArguments().stream().map(this::convertExpression).forEach(res2.arguments()::add);
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ methodInvocation.getTypeArguments().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(res2.typeArguments()::add);
+ }
+ if( superCall1 ) {
+ res2.setQualifier(toName(fa.getExpression()));
+ }
+ res2.setName((SimpleName)convertName(access.getIdentifier()));
+ return res2;
+ }
+ }
+
+ MethodInvocation res = this.ast.newMethodInvocation();
+ commonSettings(res, methodInvocation);
+ if (nameExpr instanceof JCIdent ident) {
+ if (Objects.equals(ident.getName(), Names.instance(this.context)._super)) {
+ return convertSuperMethodInvocation(methodInvocation);
+ }
+ SimpleName name = (SimpleName)convertName(ident.getName());
+ commonSettings(name, ident);
+ res.setName(name);
+ } else if (nameExpr instanceof JCFieldAccess access) {
+ boolean superCall1 = access.getExpression() instanceof JCFieldAccess && Objects.equals(Names.instance(this.context)._super, ((JCFieldAccess)access.getExpression()).getIdentifier());
+ boolean superCall2 = Objects.equals(Names.instance(this.context)._super.toString(), access.getExpression().toString());
+ if (superCall1 || superCall2) {
+ JCFieldAccess fa = superCall1 ? ((JCFieldAccess)access.getExpression()) : access;
+ SuperMethodInvocation res2 = this.ast.newSuperMethodInvocation();
+ commonSettings(res2, javac);
+ methodInvocation.getArguments().stream().map(this::convertExpression).forEach(res.arguments()::add);
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ methodInvocation.getTypeArguments().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(res.typeArguments()::add);
+ }
+ if( superCall1 ) {
+ res2.setQualifier(toName(fa.getExpression()));
+ }
+ res2.setName((SimpleName)convertName(access.getIdentifier()));
+ return res2;
+ }
+ if (convertName(access.getIdentifier()) instanceof SimpleName simpleName) {
+ res.setName(simpleName);
+ String asString = access.getIdentifier().toString();
+ commonSettings(simpleName, access);
+ int foundOffset = this.rawText.indexOf(asString, access.getPreferredPosition());
+ if (foundOffset > 0) {
+ simpleName.setSourceRange(foundOffset, asString.length());
+ }
+ }
+ res.setExpression(convertExpression(access.getExpression()));
+ }
+ if (methodInvocation.getArguments() != null) {
+ methodInvocation.getArguments().stream()
+ .map(this::convertExpression)
+ .forEach(res.arguments()::add);
+ }
+ if (methodInvocation.getTypeArguments() != null) {
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ methodInvocation.getTypeArguments().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(res.typeArguments()::add);
+ }
+ }
+ return res;
+ }
+ if (javac instanceof JCNewClass newClass) {
+ ClassInstanceCreation res = this.ast.newClassInstanceCreation();
+ commonSettings(res, javac);
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ res.setType(convertToType(newClass.getIdentifier()));
+ } else {
+ Name n = toName(newClass.getIdentifier());
+ if( n != null )
+ res.setName(n);
+ }
+ if (newClass.getClassBody() != null && newClass.getClassBody() instanceof JCClassDecl javacAnon) {
+ AnonymousClassDeclaration anon = createAnonymousClassDeclaration(javacAnon, res);
+ res.setAnonymousClassDeclaration(anon);
+ }
+ if (newClass.getArguments() != null) {
+ newClass.getArguments().stream()
+ .map(this::convertExpression)
+ .forEach(res.arguments()::add);
+ }
+ if (newClass.encl != null) {
+ res.setExpression(convertExpression(newClass.encl));
+ }
+ if( newClass.getTypeArguments() != null && this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ Iterator it = newClass.getTypeArguments().iterator();
+ while(it.hasNext()) {
+ Type e = convertToType(it.next());
+ if( e != null ) {
+ res.typeArguments().add(e);
+ }
+ }
+ }
+ return res;
+ }
+ if (javac instanceof JCBinary binary) {
+ return handleInfixExpression(binary, javac);
+
+ }
+ if (javac instanceof JCUnary unary) {
+ if (unary.getTag() != Tag.POSTINC && unary.getTag() != Tag.POSTDEC) {
+ PrefixExpression res = this.ast.newPrefixExpression();
+ commonSettings(res, javac);
+ res.setOperand(convertExpression(unary.getExpression()));
+ res.setOperator(switch (unary.getTag()) {
+ case POS -> PrefixExpression.Operator.PLUS;
+ case NEG -> PrefixExpression.Operator.MINUS;
+ case NOT -> PrefixExpression.Operator.NOT;
+ case COMPL -> PrefixExpression.Operator.COMPLEMENT;
+ case PREINC -> PrefixExpression.Operator.INCREMENT;
+ case PREDEC -> PrefixExpression.Operator.DECREMENT;
+ default -> null;
+ });
+ return res;
+ } else {
+ PostfixExpression res = this.ast.newPostfixExpression();
+ commonSettings(res, javac);
+ res.setOperand(convertExpression(unary.getExpression()));
+ res.setOperator(switch (unary.getTag()) {
+ case POSTINC -> PostfixExpression.Operator.INCREMENT;
+ case POSTDEC -> PostfixExpression.Operator.DECREMENT;
+ default -> null;
+ });
+ return res;
+ }
+ }
+ if (javac instanceof JCParens parens) {
+ ParenthesizedExpression res = this.ast.newParenthesizedExpression();
+ commonSettings(res, javac);
+ res.setExpression(convertExpression(parens.getExpression()));
+ return res;
+ }
+ if (javac instanceof JCAssign assign) {
+ Assignment res = this.ast.newAssignment();
+ commonSettings(res, javac);
+ res.setLeftHandSide(convertExpression(assign.getVariable()));
+ res.setRightHandSide(convertExpression(assign.getExpression()));
+ return res;
+ }
+ if (javac instanceof JCAssignOp assignOp) {
+ Assignment res = this.ast.newAssignment();
+ commonSettings(res, javac);
+ res.setLeftHandSide(convertExpression(assignOp.getVariable()));
+ res.setRightHandSide(convertExpression(assignOp.getExpression()));
+ res.setOperator(switch (assignOp.getTag()) {
+ case PLUS_ASG -> Assignment.Operator.PLUS_ASSIGN;
+ case BITOR_ASG -> Assignment.Operator.BIT_OR_ASSIGN;
+ case BITXOR_ASG-> Assignment.Operator.BIT_XOR_ASSIGN;
+ case BITAND_ASG-> Assignment.Operator.BIT_AND_ASSIGN;
+ case SL_ASG-> Assignment.Operator.LEFT_SHIFT_ASSIGN;
+ case SR_ASG-> Assignment.Operator.RIGHT_SHIFT_SIGNED_ASSIGN;
+ case USR_ASG-> Assignment.Operator.RIGHT_SHIFT_UNSIGNED_ASSIGN;
+ case MINUS_ASG-> Assignment.Operator.MINUS_ASSIGN;
+ case MUL_ASG-> Assignment.Operator.TIMES_ASSIGN;
+ case DIV_ASG-> Assignment.Operator.DIVIDE_ASSIGN;
+ case MOD_ASG-> Assignment.Operator.REMAINDER_ASSIGN;
+ default -> null;
+ });
+ return res;
+ }
+ if (javac instanceof JCInstanceOf jcInstanceOf) {
+ JCPattern jcPattern = jcInstanceOf.getPattern();
+ if (jcInstanceOf.getType() != null && jcPattern == null) {
+ InstanceofExpression res = this.ast.newInstanceofExpression();
+ commonSettings(res, javac);
+ res.setLeftOperand(convertExpression(jcInstanceOf.getExpression()));
+ res.setRightOperand(convertToType(jcInstanceOf.getType()));
+ return res;
+ }
+ if (jcPattern instanceof JCAnyPattern) {
+ InstanceofExpression res = this.ast.newInstanceofExpression();
+ commonSettings(res, javac);
+ res.setLeftOperand(convertExpression(jcInstanceOf.getExpression()));
+ throw new UnsupportedOperationException("Right operand not supported yet");
+// return res;
+ }
+ PatternInstanceofExpression res = this.ast.newPatternInstanceofExpression();
+ commonSettings(res, javac);
+ res.setLeftOperand(convertExpression(jcInstanceOf.getExpression()));
+ Pattern p = convert(jcPattern);
+ if( p != null && this.ast.apiLevel >= AST.JLS20_INTERNAL)
+ res.setPattern(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; j < totalCreated && !found; j++ ) {
+ Dimension d = (Dimension)arrayType.dimensions().get(j);
+ if( d.getStartPosition() == absoluteStart && (d.getStartPosition() + d.getLength()) == absoluteEnd) {
+ found = true;
+ }
+ }
+ if( !found ) {
+ // Need to make a new one
+ Dimension d = this.ast.newDimension();
+ d.setSourceRange(absoluteStart, absoluteEnd - absoluteStart);
+ arrayType.dimensions().add(i, d);
+ totalCreated++;
+ }
+ }
+ }
+ }
+ } else {
+ // JLS < 8, just wrap underlying type
+ arrayType = this.ast.newArrayType(childArrayType);
+ }
+ } else if(jcNewArray.dims != null && jcNewArray.dims.size() > 0 ){
+ // Child is not array type
+ arrayType = this.ast.newArrayType(type);
+ int dims = jcNewArray.dims.size();
+ for( int i = 0; i < dims - 1; i++ ) {
+ if( this.ast.apiLevel >= AST.JLS8_INTERNAL) {
+ // TODO, this dimension needs source range
+ arrayType.dimensions().addFirst(this.ast.newDimension());
+ } else {
+ // JLS < 8, wrap underlying
+ arrayType = this.ast.newArrayType(arrayType);
+ }
+ }
+ } else {
+ // Child is not array type, and 0 dims for underlying
+ arrayType = this.ast.newArrayType(type);
+ }
+ commonSettings(arrayType, jcNewArray.getType());
+ res.setType(arrayType);
+ }
+ jcNewArray.getDimensions().map(this::convertExpression).forEach(res.dimensions()::add);
+ if (jcNewArray.getInitializers() != null) {
+ res.setInitializer(createArrayInitializerFromJCNewArray(jcNewArray));
+ }
+ return res;
+ }
+ if (javac instanceof JCAnnotation jcAnnot) {
+ return convert(jcAnnot);
+ }
+ if (javac instanceof JCPrimitiveTypeTree primitiveTree) {
+ SimpleName res = this.ast.newSimpleName(primitiveTree.getPrimitiveTypeKind().name());
+ commonSettings(res, javac);
+ return res;
+ }
+ if (javac instanceof JCSwitchExpression jcSwitch) {
+ SwitchExpression res = this.ast.newSwitchExpression();
+ commonSettings(res, javac);
+ JCExpression switchExpr = jcSwitch.getExpression();
+ if( switchExpr instanceof JCParens jcp) {
+ switchExpr = jcp.getExpression();
+ }
+ res.setExpression(convertExpression(switchExpr));
+
+ List cases = jcSwitch.getCases();
+ Iterator it = cases.iterator();
+ ArrayList bodyList = new ArrayList<>();
+ while(it.hasNext()) {
+ JCCase switchCase = it.next();
+ bodyList.add(switchCase);
+ if( switchCase.getCaseKind() == CaseKind.STATEMENT ) {
+ if( switchCase.getStatements() != null && switchCase.getStatements().size() > 0 ) {
+ bodyList.addAll(switchCase.getStatements());
+ }
+ } else {
+ bodyList.add(switchCase.getBody());
+ }
+ }
+
+ Iterator stmtIterator = bodyList.iterator();
+ while(stmtIterator.hasNext()) {
+ JCTree next = stmtIterator.next();
+ if( next instanceof JCStatement jcs) {
+ Statement s1 = convertStatement(jcs, res);
+ if( s1 != null ) {
+ res.statements().add(s1);
+ }
+ } else if( next instanceof JCExpression jce) {
+ Expression s1 = convertExpression(jce);
+ if( s1 != null ) {
+ // make a yield statement out of it??
+ YieldStatement r1 = this.ast.newYieldStatement();
+ commonSettings(r1, jce);
+ r1.setExpression(s1);
+ res.statements().add(r1);
+ }
+ }
+ }
+ return res;
+ }
+ if (javac instanceof JCTree.JCArrayTypeTree arrayTypeTree) {
+ Type type = convertToType(javac);
+ TypeLiteral res = this.ast.newTypeLiteral();
+ res.setType(type);
+ commonSettings(res, arrayTypeTree);
+ return res;
+ }
+ return null;
+ }
+
+ private SingleVariableDeclaration convertToSingleVarDecl(JCPattern jcPattern) {
+ if( jcPattern instanceof JCBindingPattern jcbp && jcbp.var instanceof JCVariableDecl decl) {
+ SingleVariableDeclaration vdd = (SingleVariableDeclaration)convertVariableDeclaration(decl);
+ return vdd;
+ }
+ return null;
+ }
+
+ private List consecutiveInfixExpressionsWithEqualOps(JCBinary binary, Tag opcode) {
+ return consecutiveInfixExpressionsWithEqualOps(binary, opcode, new ArrayList());
+ }
+ private List consecutiveInfixExpressionsWithEqualOps(
+ JCBinary binary, Tag opcode, List consecutive) {
+
+ if( opcode.equals(binary.getTag())) {
+ if( consecutive != null ) {
+ JCExpression left = binary.getLeftOperand();
+ if( left instanceof JCBinary jcb) {
+ consecutive = consecutiveInfixExpressionsWithEqualOps(jcb, opcode, consecutive);
+ } else {
+ consecutive.add(left);
+ }
+ }
+ if( consecutive != null ) {
+ JCExpression right = binary.getRightOperand();
+ if( right instanceof JCBinary jcb) {
+ consecutive = consecutiveInfixExpressionsWithEqualOps(jcb, opcode, consecutive);
+ } else {
+ consecutive.add(right);
+ }
+ }
+ return consecutive;
+ }
+ return null;
+ }
+
+ private Expression handleInfixExpression(JCBinary binary, JCExpression javac) {
+ List conseq = consecutiveInfixExpressionsWithEqualOps(binary, binary.getTag());
+ if( conseq != null && conseq.size() > 2 ) {
+ return handleConsecutiveInfixExpression(binary, javac, conseq);
+ }
+
+ InfixExpression res = this.ast.newInfixExpression();
+ commonSettings(res, javac);
+
+ Expression left = convertExpression(binary.getLeftOperand());
+ if (left != null) {
+ res.setLeftOperand(left);
+ }
+ Expression right = convertExpression(binary.getRightOperand());
+ if (right != null) {
+ res.setRightOperand(right);
+ }
+ res.setOperator(binaryTagToInfixOperator(binary.getTag()));
+ return res;
+ }
+
+ private Expression handleConsecutiveInfixExpression(JCBinary binary, JCExpression javac,
+ List conseq) {
+
+ InfixExpression res = this.ast.newInfixExpression();
+ commonSettings(res, javac);
+
+ Expression left = convertExpression(conseq.get(0));
+ if (left != null) {
+ res.setLeftOperand(left);
+ }
+ Expression right = convertExpression(conseq.get(1));
+ if (right != null) {
+ res.setRightOperand(right);
+ }
+ for( int i = 2; i < conseq.size(); i++ ) {
+ res.extendedOperands().add(convertExpression(conseq.get(i)));
+ }
+
+ res.setOperator(binaryTagToInfixOperator(binary.getTag()));
+ return res;
+ }
+
+ private InfixExpression.Operator binaryTagToInfixOperator(Tag t) {
+ return switch (t) {
+ case OR -> InfixExpression.Operator.CONDITIONAL_OR;
+ case AND -> InfixExpression.Operator.CONDITIONAL_AND;
+ case BITOR -> InfixExpression.Operator.OR;
+ case BITXOR -> InfixExpression.Operator.XOR;
+ case BITAND -> InfixExpression.Operator.AND;
+ case EQ -> InfixExpression.Operator.EQUALS;
+ case NE -> InfixExpression.Operator.NOT_EQUALS;
+ case LT -> InfixExpression.Operator.LESS;
+ case GT -> InfixExpression.Operator.GREATER;
+ case LE -> InfixExpression.Operator.LESS_EQUALS;
+ case GE -> InfixExpression.Operator.GREATER_EQUALS;
+ case SL -> InfixExpression.Operator.LEFT_SHIFT;
+ case SR -> InfixExpression.Operator.RIGHT_SHIFT_SIGNED;
+ case USR -> InfixExpression.Operator.RIGHT_SHIFT_UNSIGNED;
+ case PLUS -> InfixExpression.Operator.PLUS;
+ case MINUS -> InfixExpression.Operator.MINUS;
+ case MUL -> InfixExpression.Operator.TIMES;
+ case DIV -> InfixExpression.Operator.DIVIDE;
+ case MOD -> InfixExpression.Operator.REMAINDER;
+ default -> null;
+ };
+ }
+
+
+ /**
+ * precondition: you've checked all the segments are identifier that can be used in a qualified name
+ */
+ private Name convertQualifiedName(JCFieldAccess fieldAccess) {
+ JCExpression parent = fieldAccess.getExpression();
+ Name parentName;
+ if (parent instanceof JCFieldAccess parentFieldAccess) {
+ parentName = convertQualifiedName(parentFieldAccess);
+ } else if (parent instanceof JCIdent parentIdent) {
+ parentName = convertName(parentIdent.getName());
+ } else {
+ throw new IllegalArgumentException("Unrecognized javac AST node type: " + parent.getClass().getCanonicalName());
+ }
+ commonSettings(parentName, parent);
+ SimpleName segmentName = (SimpleName)convertName(fieldAccess.getIdentifier());
+ int endPos = fieldAccess.getEndPosition(this.javacCompilationUnit.endPositions);
+ int startPos = endPos - fieldAccess.getIdentifier().length();
+ segmentName.setSourceRange(startPos, fieldAccess.getIdentifier().length());
+ QualifiedName res = this.ast.newQualifiedName(parentName, segmentName);
+ commonSettings(res, fieldAccess);
+ return res;
+ }
+
+ private Expression convertExpression(JCExpression javac) {
+ Expression ret = convertExpressionImpl(javac);
+ if( ret != null )
+ return ret;
+
+ // Handle errors or default situation
+ if (javac instanceof JCErroneous error) {
+ if (error.getErrorTrees().size() == 1) {
+ JCTree tree = error.getErrorTrees().get(0);
+ if (tree instanceof JCExpression nestedExpr) {
+ try {
+ return convertExpression(nestedExpr);
+ } catch (Exception ex) {
+ // pass-through: do not break when attempting such reconcile
+ }
+ }
+ }
+ }
+ if( shouldRecoverWithSimpleName(javac)) {
+ var res = this.ast.newSimpleName(FAKE_IDENTIFIER);
+ res.setFlags(ASTNode.RECOVERED);
+ commonSettings(res, javac);
+ return res;
+ }
+ return null;
+ }
+
+ private boolean shouldRecoverWithSimpleName(JCExpression javac) {
+ if( javac instanceof JCNewClass)
+ return false;
+ return true;
+ }
+ private Pattern convert(JCPattern jcPattern) {
+ if (this.ast.apiLevel >= AST.JLS21_INTERNAL) {
+ if (jcPattern instanceof JCBindingPattern jcBindingPattern) {
+ TypePattern jdtPattern = this.ast.newTypePattern();
+ commonSettings(jdtPattern, jcBindingPattern);
+ jdtPattern.setPatternVariable((SingleVariableDeclaration)convertVariableDeclaration(jcBindingPattern.var));
+ return jdtPattern;
+ } else if (jcPattern instanceof JCRecordPattern jcRecordPattern) {
+ RecordPattern jdtPattern = this.ast.newRecordPattern();
+ commonSettings(jdtPattern, jcRecordPattern);
+ jdtPattern.setPatternType(convertToType(jcRecordPattern.deconstructor));
+ for (JCPattern nestedJcPattern : jcRecordPattern.nested) {
+ jdtPattern.patterns().add(convert(nestedJcPattern));
+ }
+ return jdtPattern;
+ } else if (jcPattern instanceof JCAnyPattern jcAnyPattern) {
+ TypePattern jdtPattern = this.ast.newTypePattern();
+ commonSettings(jdtPattern, jcAnyPattern);
+ VariableDeclarationFragment variable = this.ast.newVariableDeclarationFragment();
+ commonSettings(variable, jcAnyPattern);
+ variable.setName(this.ast.newSimpleName("_"));
+ jdtPattern.setPatternVariable(variable);
+ return jdtPattern;
+ }
+ }
+ return null;
+ }
+
+ private ArrayInitializer createArrayInitializerFromJCNewArray(JCNewArray jcNewArray) {
+ ArrayInitializer initializer = this.ast.newArrayInitializer();
+ commonSettings(initializer, jcNewArray);
+ if (!jcNewArray.getInitializers().isEmpty()) {
+ jcNewArray.getInitializers().stream().map(this::convertExpression).filter(Objects::nonNull).forEach(initializer.expressions()::add);
+ 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);
+ commonSettings(name, ident);
+ return res;
+ }
+ if (javac instanceof JCFieldAccess qualified) {
+ try {
+ if( qualified.getExpression() == null ) {
+ Name qn = toName(qualified);
+ commonSettings(qn, javac);
+ SimpleType res = this.ast.newSimpleType(qn);
+ commonSettings(res, qualified);
+ return res;
+ }
+ } catch (Exception ex) {
+ }
+ // case of not translatable name, eg because of generics
+ // TODO find a better check instead of relying on exception
+ Type qualifierType = convertToType(qualified.getExpression());
+ SimpleName simpleName = (SimpleName)convertName(qualified.getIdentifier());
+ int simpleNameStart = this.rawText.indexOf(simpleName.getIdentifier(), qualifierType.getStartPosition() + qualifierType.getLength());
+ if (simpleNameStart > 0) {
+ simpleName.setSourceRange(simpleNameStart, simpleName.getIdentifier().length());
+ } else if (simpleName.getIdentifier().isEmpty()){
+ // the name second segment is invalid
+ simpleName.delete();
+ return qualifierType;
+ } else {
+ // lombok case
+ // or empty (eg `test.`)
+ simpleName.setSourceRange(qualifierType.getStartPosition(), 0);
+ }
+ if(qualifierType instanceof SimpleType simpleType && (ast.apiLevel() < AST.JLS8 || simpleType.annotations().isEmpty())) {
+ simpleType.delete();
+ Name parentName = simpleType.getName();
+ parentName.setParent(null, null);
+ QualifiedName name = this.ast.newQualifiedName(simpleType.getName(), simpleName);
+ commonSettings(name, javac);
+ int length = simpleType.getName().getLength() + 1 + simpleName.getLength();
+ if (name.getStartPosition() >= 0) {
+ name.setSourceRange(name.getStartPosition(), Math.max(0, length));
+ }
+ SimpleType res = this.ast.newSimpleType(name);
+ commonSettings(res, javac);
+ if (name.getStartPosition() >= 0) {
+ res.setSourceRange(name.getStartPosition(), Math.max(0, length));
+ }
+ return res;
+ } else {
+ QualifiedType res = this.ast.newQualifiedType(qualifierType, simpleName);
+ commonSettings(res, qualified);
+ return res;
+ }
+ }
+ if (javac instanceof JCPrimitiveTypeTree primitiveTypeTree) {
+ PrimitiveType res = this.ast.newPrimitiveType(convert(primitiveTypeTree.getPrimitiveTypeKind()));
+ commonSettings(res, primitiveTypeTree);
+ return res;
+ }
+ if (javac instanceof JCTypeUnion union) {
+ if (this.ast.apiLevel() > AST.JLS3) {
+ UnionType res = this.ast.newUnionType();
+ commonSettings(res, javac);
+ union.getTypeAlternatives().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(res.types()::add);
+ return res;
+ } else {
+ Optional lastType = union.getTypeAlternatives().reverse().stream().map(this::convertToType).filter(Objects::nonNull).findFirst();
+ lastType.ifPresent(a -> a.setFlags(a.getFlags() | ASTNode.MALFORMED));
+ return lastType.get();
+ }
+ }
+ if (javac instanceof JCArrayTypeTree jcArrayType) {
+ Type t = convertToType(jcArrayType.getType());
+ if (t == null) {
+ return null;
+ }
+ ArrayType res;
+ if (t instanceof ArrayType childArrayType && this.ast.apiLevel > AST.JLS4_INTERNAL) {
+ res = childArrayType;
+ res.dimensions().addFirst(this.ast.newDimension());
+ commonSettings(res, jcArrayType.getType());
+ } else {
+ int dims = countDimensions(jcArrayType);
+ res = this.ast.newArrayType(t);
+ if( dims == 0 ) {
+ commonSettings(res, jcArrayType);
+ } else {
+ int endPos = jcArrayType.getEndPosition(this.javacCompilationUnit.endPositions);
+ if (endPos == -1) {
+ endPos = jcArrayType.pos;
+ }
+ int startPos = jcArrayType.getStartPosition();
+ try {
+ String raw = this.rawText.substring(startPos, endPos);
+ int ordinalEnd = ordinalIndexOf(raw, "]", dims);
+ int ordinalStart = ordinalIndexOf(raw, "[", dims);
+ if( ordinalEnd != -1 ) {
+ commonSettings(res, jcArrayType, ordinalEnd + 1, true);
+ if( this.ast.apiLevel >= AST.JLS8_INTERNAL ) {
+ if( res.dimensions().size() > 0 ) {
+ ((Dimension)res.dimensions().get(0)).setSourceRange(startPos + ordinalStart, ordinalEnd - ordinalStart + 1);
+ }
+ }
+ return res;
+ }
+ } catch( Throwable tErr) {
+ }
+ commonSettings(res, jcArrayType);
+ }
+ }
+ return res;
+ }
+ if (javac instanceof JCTypeApply jcTypeApply) {
+ if( this.ast.apiLevel != AST.JLS2_INTERNAL) {
+ ParameterizedType res = this.ast.newParameterizedType(convertToType(jcTypeApply.getType()));
+ commonSettings(res, javac);
+ jcTypeApply.getTypeArguments().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(res.typeArguments()::add);
+ return res;
+ } else {
+ return convertToType(jcTypeApply.clazz);
+ }
+ }
+ if (javac instanceof JCWildcard wc) {
+ WildcardType res = this.ast.newWildcardType();
+ if( wc.kind.kind == BoundKind.SUPER) {
+ final Type bound = convertToType(wc.inner);
+ res.setBound(bound, false);
+ } else if( wc.kind.kind == BoundKind.EXTENDS) {
+ final Type bound = convertToType(wc.inner);
+ res.setBound(bound, true);
+ }
+ commonSettings(res, javac);
+ return res;
+ }
+ if (javac instanceof JCTypeIntersection jcTypeIntersection) {
+ IntersectionType res = this.ast.newIntersectionType();
+ commonSettings(res, javac);
+ jcTypeIntersection.getBounds().stream()
+ .map(this::convertToType)
+ .filter(Objects::nonNull)
+ .forEach(res.types()::add);
+ return res;
+ }
+ if (javac instanceof JCAnnotatedType jcAnnotatedType) {
+ Type res = null;
+ JCExpression jcpe = jcAnnotatedType.getUnderlyingType();
+ if( jcAnnotatedType.getAnnotations() != null //
+ && !jcAnnotatedType.getAnnotations().isEmpty() //
+ && this.ast.apiLevel >= AST.JLS8_INTERNAL
+ && !(jcpe instanceof JCWildcard)) {
+ if( jcpe instanceof JCFieldAccess jcfa2) {
+ if( jcfa2.selected instanceof JCAnnotatedType || jcfa2.selected instanceof JCTypeApply) {
+ QualifiedType nameQualifiedType = new QualifiedType(this.ast);
+ commonSettings(nameQualifiedType, javac);
+ nameQualifiedType.setQualifier(convertToType(jcfa2.selected));
+ nameQualifiedType.setName(this.ast.newSimpleName(jcfa2.name.toString()));
+ res = nameQualifiedType;
+ } else {
+ NameQualifiedType nameQualifiedType = new NameQualifiedType(this.ast);
+ commonSettings(nameQualifiedType, javac);
+ nameQualifiedType.setQualifier(toName(jcfa2.selected));
+ nameQualifiedType.setName(this.ast.newSimpleName(jcfa2.name.toString()));
+ res = nameQualifiedType;
+ }
+ } else if (jcpe instanceof JCIdent simpleType) {
+ res = this.ast.newSimpleType(convertName(simpleType.getName()));
+ commonSettings(res, javac);
+ }
+ }
+ if (res == null) { // nothing specific
+ res = convertToType(jcAnnotatedType.getUnderlyingType());
+ }
+ if (res instanceof AnnotatableType annotatableType && this.ast.apiLevel() >= AST.JLS8_INTERNAL) {
+ for (JCAnnotation annotation : jcAnnotatedType.getAnnotations()) {
+ annotatableType.annotations().add(convert(annotation));
+ }
+ } else if (res instanceof ArrayType arrayType) {
+ if (this.ast.apiLevel() >= AST.JLS8 && !arrayType.dimensions().isEmpty()) {
+ for (JCAnnotation annotation : jcAnnotatedType.getAnnotations()) {
+ ((Dimension)arrayType.dimensions().get(0)).annotations().add(convert(annotation));
+ }
+ }
+ }
+ return res;
+ }
+ if (javac instanceof JCErroneous || javac == null /* when there are syntax errors */) {
+ // returning null could result in upstream errors, so return a fake type
+ var res = this.ast.newSimpleType(this.ast.newSimpleName(FAKE_IDENTIFIER));
+ res.setFlags(ASTNode.RECOVERED);
+ return res;
+ }
+ ILog.get().warn("Not supported yet, converting to type type " + javac + " of class" + javac.getClass());
+ return null;
+ }
+ public static int ordinalIndexOf(String str, String substr, int n) {
+ int pos = str.indexOf(substr);
+ while (--n > 0 && pos != -1) {
+ pos = str.indexOf(substr, pos + 1);
+ }
+ return pos;
+ }
+ private Code convert(TypeKind javac) {
+ return switch(javac) {
+ case BOOLEAN -> PrimitiveType.BOOLEAN;
+ case BYTE -> PrimitiveType.BYTE;
+ case SHORT -> PrimitiveType.SHORT;
+ case INT -> PrimitiveType.INT;
+ case LONG -> PrimitiveType.LONG;
+ case CHAR -> PrimitiveType.CHAR;
+ case FLOAT -> PrimitiveType.FLOAT;
+ case DOUBLE -> PrimitiveType.DOUBLE;
+ case VOID -> PrimitiveType.VOID;
+ default -> throw new IllegalArgumentException(javac.toString());
+ };
+ }
+
+ private Annotation convert(JCAnnotation javac) {
+ int startPos = javac.getStartPosition();
+ int length = javac.getEndPosition(this.javacCompilationUnit.endPositions) - startPos;
+ String content = this.rawText.substring(startPos, startPos+length);
+ boolean mustUseNormalAnnot = content != null && content.contains("(");
+ if( javac.getArguments().size() == 0 && !mustUseNormalAnnot) {
+ MarkerAnnotation res = this.ast.newMarkerAnnotation();
+ commonSettings(res, javac);
+ res.setTypeName(toName(javac.getAnnotationType()));
+ return res;
+ } else if( javac.getArguments().size() == 1 && !(javac.getArguments().get(0) instanceof JCAssign)) {
+ SingleMemberAnnotation result= ast.newSingleMemberAnnotation();
+ commonSettings(result, javac);
+ result.setTypeName(toName(javac.annotationType));
+ JCTree value = javac.getArguments().get(0);
+ if (value != null) {
+ if( value instanceof JCExpression jce) {
+ result.setValue(convertExpression(jce));
+ } else {
+ result.setValue(toName(value));
+ }
+ }
+ return result;
+ } else if (javac.getArguments().size() == 1
+ && javac.getArguments().get(0) instanceof JCAssign namedArg
+ && (namedArg.getVariable().getPreferredPosition() == Position.NOPOS
+ || namedArg.getVariable().getPreferredPosition() == namedArg.getExpression().getPreferredPosition())) {
+ // actually a @Annotation(value), but returned as a @Annotation(field = value)
+ SingleMemberAnnotation result= ast.newSingleMemberAnnotation();
+ commonSettings(result, javac);
+ result.setTypeName(toName(javac.annotationType));
+ JCTree value = namedArg.getExpression();
+ if (value != null) {
+ if( value instanceof JCExpression jce) {
+ result.setValue(convertExpression(jce));
+ } else {
+ result.setValue(toName(value));
+ }
+ }
+ return result;
+ } else {
+ NormalAnnotation res = this.ast.newNormalAnnotation();
+ commonSettings(res, javac);
+ res.setTypeName(toName(javac.getAnnotationType()));
+ Iterator it = javac.getArguments().iterator();
+ while(it.hasNext()) {
+ JCExpression expr = it.next();
+ if( expr instanceof JCAssign jcass) {
+ if( jcass.lhs instanceof JCIdent jcid ) {
+ final MemberValuePair pair = new MemberValuePair(this.ast);
+ 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) {
+ // testBug113108b expects /// comments to be Line comments, not Javadoc comments
+ if (javac.getStyle() == CommentStyle.JAVADOC_BLOCK || javac.getStyle() == CommentStyle.JAVADOC_LINE) {
+ var parser = new com.sun.tools.javac.parser.DocCommentParser(ParserFactory.instance(this.context), Log.instance(this.context).currentSource(), javac);
+ JavadocConverter javadocConverter = new JavadocConverter(this, parser.parse(), pos, endPos, this.buildJavadoc);
+ this.javadocConverters.add(javadocConverter);
+ Javadoc javadoc = javadocConverter.convertJavadoc();
+ if (this.ast.apiLevel() >= AST.JLS23) {
+ javadoc.setMarkdown(javac.getStyle() == CommentStyle.JAVADOC_LINE);
+ }
+ this.javadocDiagnostics.addAll(javadocConverter.getDiagnostics());
+ return javadoc;
+ }
+ org.eclipse.jdt.core.dom.Comment jdt = switch (javac.getStyle()) {
+ case LINE -> this.ast.newLineComment();
+ case BLOCK -> this.ast.newBlockComment();
+ case JAVADOC_BLOCK -> this.ast.newJavadoc();
+ case JAVADOC_LINE -> this.ast.newLineComment();
+ };
+ javac.isDeprecated(); javac.getText(); // initialize docComment
+ jdt.setSourceRange(pos, endPos - pos);
+ return jdt;
+ }
+
+ class FixPositions extends ASTVisitor {
+ private final String contents;
+
+ FixPositions() {
+ super(true);
+ String s = null;
+ try {
+ s = JavacConverter.this.javacCompilationUnit.getSourceFile().getCharContent(true).toString();
+ } catch (IOException ex) {
+ ILog.get().error(ex.getMessage(), ex);
+ }
+ this.contents = s;
+ }
+
+ @Override
+ public boolean visit(QualifiedName node) {
+ if (node.getStartPosition() < 0) {
+ int foundOffset = findPositionOfText(node.getFullyQualifiedName(), node.getParent(), siblingsOf(node));
+ if (foundOffset >= 0) {
+ node.setSourceRange(foundOffset, node.getFullyQualifiedName().length());
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void endVisit(QualifiedName node) {
+ if (node.getName().getStartPosition() >= 0) {
+ node.setSourceRange(node.getQualifier().getStartPosition(), node.getName().getStartPosition() + node.getName().getLength() - node.getQualifier().getStartPosition());
+ }
+ }
+
+ @Override
+ public boolean visit(SimpleName name) {
+ if (name.getStartPosition() < 0) {
+ int foundOffset = findPositionOfText(name.getIdentifier(), name.getParent(), siblingsOf(name));
+ if (foundOffset >= 0) {
+ name.setSourceRange(foundOffset, name.getIdentifier().length());
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean visit(Modifier modifier) {
+ int parentStart = modifier.getParent().getStartPosition();
+ int relativeStart = this.contents.substring(parentStart, parentStart + modifier.getParent().getLength()).indexOf(modifier.getKeyword().toString());
+ if (relativeStart >= 0 && relativeStart < modifier.getParent().getLength()) {
+ modifier.setSourceRange(parentStart + relativeStart, modifier.getKeyword().toString().length());
+ }
+ return true;
+ }
+
+ @Override
+ public void endVisit(TagElement tagElement) {
+ if (tagElement.getStartPosition() < 0) {
+ OptionalInt start = ((List)tagElement.fragments()).stream()
+ .filter(node -> node.getStartPosition() >= 0 && node.getLength() >= 0)
+ .mapToInt(ASTNode::getStartPosition)
+ .min();
+ OptionalInt end = ((List)tagElement.fragments()).stream()
+ .filter(node -> node.getStartPosition() >= 0 && node.getLength() >= 0)
+ .mapToInt(node -> node.getStartPosition() + node.getLength())
+ .max();
+ if (start.isPresent() && end.isPresent()) {
+ if (JavadocConverter.isInline(tagElement)) {
+ // include some extra wrapping chars ( `{...}` or `[...]`)
+ // current heuristic is very approximative as it will fail with whitespace
+ tagElement.setSourceRange(start.getAsInt() - 1, end.getAsInt() - start.getAsInt() + 2);
+ } else {
+ tagElement.setSourceRange(start.getAsInt(), end.getAsInt() - start.getAsInt());
+ }
+ }
+ }
+ if (TagElement.TAG_DEPRECATED.equals(tagElement.getTagName())
+ && tagElement.getParent() instanceof Javadoc javadoc
+ && javadoc.getParent() != null) {
+ javadoc.getParent().setFlags(javadoc.getParent().getFlags() | ClassFileConstants.AccDeprecated);
+ }
+ }
+
+ private int findPositionOfText(String text, ASTNode in, List excluding) {
+ int current = in.getStartPosition();
+ PriorityQueue excluded = new PriorityQueue<>(Comparator.comparing(ASTNode::getStartPosition));
+ if( current == -1 ) {
+ return -1;
+ }
+ if (excluded.isEmpty()) {
+ int position = this.contents.indexOf(text, current, current + in.getLength());
+ if (position >= 0) {
+ return position;
+ }
+ } else {
+ ASTNode currentExclusion = null;
+ while ((currentExclusion = excluded.poll()) != null) {
+ if (currentExclusion.getStartPosition() >= current) {
+ int rangeEnd = currentExclusion.getStartPosition();
+ int position = this.contents.indexOf(text, current, rangeEnd);
+ if (position >= 0) {
+ return position;
+ }
+ current = rangeEnd + currentExclusion.getLength();
+ }
+ }
+ }
+ return -1;
+ }
+ }
+
+ private EnumConstantDeclaration convertEnumConstantDeclaration(JCTree var, ASTNode parent, EnumDeclaration enumDecl) {
+ EnumConstantDeclaration enumConstantDeclaration = null;
+ String enumName = null;
+ if( var instanceof JCVariableDecl enumConstant && (enumConstant.getModifiers().flags & Flags.ENUM) != 0 ) {
+ if( enumConstant.getType() instanceof JCIdent jcid) {
+ String o = jcid.getName().toString();
+ String o2 = enumDecl.getName().toString();
+ if( o.equals(o2)) {
+ enumConstantDeclaration = new EnumConstantDeclaration(this.ast);
+ commonSettings(enumConstantDeclaration, enumConstant);
+ final SimpleName typeName = new SimpleName(this.ast);
+ enumName = enumConstant.getName().toString();
+ typeName.internalSetIdentifier(enumName);
+ typeName.setSourceRange(enumConstant.getStartPosition(), Math.max(0, enumName.length()));
+ enumConstantDeclaration.setName(typeName);
+ if (enumConstant.getModifiers() != null && enumConstant.getPreferredPosition() != Position.NOPOS) {
+ enumConstantDeclaration.modifiers()
+ .addAll(convert(enumConstant.getModifiers(), enumConstantDeclaration));
+ }
+ }
+ if( enumConstant.init instanceof JCNewClass jcnc ) {
+ if( jcnc.def instanceof JCClassDecl jccd) {
+ int blockStarts = jcnc.getStartPosition() + (enumName == null ? 0 : enumName.length());
+ if(jcnc.getArguments() != null && !jcnc.getArguments().isEmpty() && jcnc.getArguments().get(jcnc.getArguments().length()-1) instanceof JCTree lastArg) {
+ blockStarts = lastArg.getEndPosition(this.javacCompilationUnit.endPositions);
+ }
+ int endPos = jcnc.getEndPosition(this.javacCompilationUnit.endPositions);
+ AnonymousClassDeclaration e = createAnonymousClassDeclaration(jccd, enumConstantDeclaration);
+ if( e != null ) {
+ String tmp = this.rawText.substring(blockStarts);
+ int bracket = tmp.indexOf("{");
+ if( bracket != -1 ) {
+ blockStarts += bracket;
+ }
+ e.setSourceRange(blockStarts, endPos - blockStarts);
+ enumConstantDeclaration.setAnonymousClassDeclaration(e);
+ }
+ }
+ if( jcnc.getArguments() != null ) {
+ Iterator it = jcnc.getArguments().iterator();
+ while(it.hasNext()) {
+ Expression e = convertExpression(it.next());
+ if( e != null ) {
+ enumConstantDeclaration.arguments().add(e);
+ }
+ }
+ }
+ }
+ }
+ }
+ return enumConstantDeclaration;
+ }
+
+ private static List siblingsOf(ASTNode node) {
+ return childrenOf(node.getParent());
+ }
+
+ public static Name toName(String val, int startPosition, AST ast) {
+ try {
+ String stripped = val.stripLeading();
+ int strippedAmt = val.length() - stripped.length();
+ int lastDot = stripped.lastIndexOf(".");
+ if( lastDot == -1 ) {
+ SimpleName sn = ast.newSimpleName(stripped); // TODO error here, testBug51600
+ sn.setSourceRange(startPosition + strippedAmt, stripped.length());
+ return sn;
+ } else {
+ SimpleName sn = ast.newSimpleName(stripped.substring(lastDot+1));
+ sn.setSourceRange(startPosition + strippedAmt + lastDot+1, sn.getIdentifier().length());
+
+ QualifiedName qn = ast.newQualifiedName(toName(stripped.substring(0,lastDot), startPosition + strippedAmt, ast), sn);
+ qn.setSourceRange(startPosition + strippedAmt, stripped.length());
+ return qn;
+ }
+ } catch(IllegalArgumentException iae) {
+ return null;
+ }
+ //return null;
+ }
+ private static List childrenOf(ASTNode node) {
+ return ((Collection