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 Symbols 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 elements; + // long start = System.currentTimeMillis(); + if (this.javac instanceof JavacTaskImpl javacTaskImpl) { + if (javacCompilationUnits != null && !javacCompilationUnits.isEmpty()) { + Iterable trees = javacCompilationUnits; + elements = javacTaskImpl.enter(trees); + } else { + elements = javacTaskImpl.enter(); + } + elements = javacTaskImpl.analyze(elements); +// long count = StreamSupport.stream(elements.spliterator(), false).count(); +// String name = elements.iterator().hasNext() +// ? elements.iterator().next().getSimpleName().toString() +// : ""; +// ILog.get().info("enter/analyze elements=" + count + ", took: " +// + (System.currentTimeMillis() - start) + ", first=" + name); + } else { + elements = this.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 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 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 apPaths = fileManager.getLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH); + if (apPaths != null) { + return true; + } + + Iterable apModulePaths = fileManager.getLocation(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH); + if (apModulePaths != null) { + return true; + } + + Iterable classPaths = fileManager.getLocation(StandardLocation.CLASS_PATH); + if (classPaths != null) { + for(File cp : classPaths) { + String fileName = cp.getName(); + if (fileName != null && fileName.startsWith("lombok") && fileName.endsWith(".jar")) { + try { + fileManager.setLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH, List.of(cp)); + return true; + } catch (IOException ex) { + ILog.get().error(ex.getMessage(), ex); + } + } + } + } + + Iterable modulePaths = fileManager.getLocation(StandardLocation.MODULE_PATH); + if (modulePaths != null) { + for(File mp : modulePaths) { + String fileName = mp.getName(); + if (fileName != null && fileName.startsWith("lombok") && fileName.endsWith(".jar")) { + try { + fileManager.setLocation(StandardLocation.ANNOTATION_PROCESSOR_MODULE_PATH, List.of(mp)); + return true; + } catch (IOException ex) { + ILog.get().error(ex.getMessage(), ex); + } + } + } + } + + return false; + } +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacConverter.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavacConverter.java new file mode 100644 index 00000000000..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)node.properties().values()).stream() + .filter(ASTNode.class::isInstance) + .map(ASTNode.class::cast) + .filter(Predicate.not(node::equals)) + .toList(); + } + + public DocTreePath findDocTreePath(ASTNode node) { + return this.javadocConverters.stream() + .map(javadocConverter -> javadocConverter.converted.get(node)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } + + public DocTreePath[] searchRelatedDocTreePath(MethodRef ref) { + ArrayList possibleNodes = new ArrayList<>(); + this.javadocConverters.forEach(x -> possibleNodes.addAll(x.converted.keySet())); + DocTreePath[] r = possibleNodes.stream().filter(x -> x != ref && x instanceof MethodRef mr + && mr.getName().toString().equals(ref.getName().toString()) + && Objects.equals(mr.getQualifier() == null ? null : mr.getQualifier().toString(), + ref.getQualifier() == null ? null : ref.getQualifier().toString())) + .map(x -> findDocTreePath(x)) + .toArray(size -> new DocTreePath[size]); + return r; + } + + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavadocConverter.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavadocConverter.java new file mode 100644 index 00000000000..e1f81f8cad1 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/core/dom/JavadocConverter.java @@ -0,0 +1,914 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jdt.core.dom; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import org.eclipse.core.runtime.ILog; + +import com.sun.source.doctree.DocTree.Kind; +import com.sun.source.util.DocTreePath; +import com.sun.source.util.TreePath; +import com.sun.tools.javac.tree.DCTree; +import com.sun.tools.javac.tree.DCTree.DCAuthor; +import com.sun.tools.javac.tree.DCTree.DCBlockTag; +import com.sun.tools.javac.tree.DCTree.DCComment; +import com.sun.tools.javac.tree.DCTree.DCDeprecated; +import com.sun.tools.javac.tree.DCTree.DCDocComment; +import com.sun.tools.javac.tree.DCTree.DCEndElement; +import com.sun.tools.javac.tree.DCTree.DCEntity; +import com.sun.tools.javac.tree.DCTree.DCErroneous; +import com.sun.tools.javac.tree.DCTree.DCIdentifier; +import com.sun.tools.javac.tree.DCTree.DCInheritDoc; +import com.sun.tools.javac.tree.DCTree.DCLink; +import com.sun.tools.javac.tree.DCTree.DCLiteral; +import com.sun.tools.javac.tree.DCTree.DCParam; +import com.sun.tools.javac.tree.DCTree.DCRawText; +import com.sun.tools.javac.tree.DCTree.DCReference; +import com.sun.tools.javac.tree.DCTree.DCReturn; +import com.sun.tools.javac.tree.DCTree.DCSee; +import com.sun.tools.javac.tree.DCTree.DCSince; +import com.sun.tools.javac.tree.DCTree.DCSnippet; +import com.sun.tools.javac.tree.DCTree.DCStartElement; +import com.sun.tools.javac.tree.DCTree.DCText; +import com.sun.tools.javac.tree.DCTree.DCThrows; +import com.sun.tools.javac.tree.DCTree.DCUnknownBlockTag; +import com.sun.tools.javac.tree.DCTree.DCUnknownInlineTag; +import com.sun.tools.javac.tree.DCTree.DCUses; +import com.sun.tools.javac.tree.DCTree.DCValue; +import com.sun.tools.javac.tree.DCTree.DCVersion; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree; +import com.sun.tools.javac.tree.TreeScanner; +import com.sun.tools.javac.util.Convert; +import com.sun.tools.javac.util.JCDiagnostic; + +import jdk.javadoc.internal.doclets.formats.html.taglets.snippet.Attribute; +import jdk.javadoc.internal.doclets.formats.html.taglets.snippet.MarkupParser; + +class JavadocConverter { + + // Both copied from jdk.javadoc.internal.doclets.formats.html.taglets.snippet.Parser + private static final Pattern JAVA_COMMENT = Pattern.compile( + "^(?.*)//(?\\s*@\\s*\\w+.+?)$"); + private static final Pattern PROPERTIES_COMMENT = Pattern.compile( + "^(?[ \t]*([#!].*)?)[#!](?\\s*@\\s*\\w+.+?)$"); + + private final AST ast; + private final JavacConverter javacConverter; + private final DCDocComment docComment; + private final int initialOffset; + private final int endOffset; + private boolean buildJavadoc; + private final TreePath contextTreePath; + + public final Map converted = new HashMap<>(); + + final private Set diagnostics = new HashSet<>(); + + private static final Pattern BEGIN_CHOPPER = Pattern.compile("(?:\\s+\\*)(.*)"); + + private JavadocConverter(JavacConverter javacConverter, DCDocComment docComment, TreePath contextTreePath, int initialOffset, int endPos, boolean buildJavadoc) { + this.javacConverter = javacConverter; + this.ast = javacConverter.ast; + this.docComment = docComment; + this.contextTreePath = contextTreePath; + this.buildJavadoc = buildJavadoc; + this.initialOffset = initialOffset; + this.endOffset = endPos; + } + + JavadocConverter(JavacConverter javacConverter, DCDocComment docComment, int initialOffset, int endPos, boolean buildJavadoc) { + this(javacConverter, docComment, null, initialOffset, endPos, buildJavadoc); + } + + JavadocConverter(JavacConverter javacConverter, DCDocComment docComment, TreePath contextTreePath, boolean buildJavadoc) { + this(javacConverter, docComment, contextTreePath, docComment.comment.getPos().getStartPosition(), docComment.comment.getPos().getEndPosition(javacConverter.javacCompilationUnit.endPositions), buildJavadoc); + } + + private void commonSettings(ASTNode res, DCTree javac) { + if (javac != null) { + int startPosition = this.docComment.getSourcePosition(javac.getStartPosition()); + int endPosition = this.docComment.getSourcePosition(javac.getEndPosition()); + int length = endPosition - startPosition; +// if (res instanceof TextElement) { +// length++; +// } + if (startPosition >= 0 && length >= 0) { + res.setSourceRange(startPosition, length); + } + if (this.contextTreePath != null) { + this.converted.put(res, DocTreePath.getPath(this.contextTreePath, this.docComment, javac)); + } + } + } + + Javadoc convertJavadoc() { + Javadoc res = this.ast.newJavadoc(); + res.setSourceRange(this.initialOffset, this.endOffset - this.initialOffset); + try { + if( this.javacConverter.ast.apiLevel == AST.JLS2_INTERNAL) { + String rawContent = this.javacConverter.rawText.substring(this.initialOffset, this.endOffset); + try { + res.setComment(rawContent); + } catch( IllegalArgumentException iae) { + // Ignore + } + } + if (this.buildJavadoc) { + List treeElements = Stream.of(docComment.preamble, docComment.fullBody, docComment.postamble, docComment.tags) + .flatMap(List::stream).toList(); + List elements2 = convertElementCombiningNodes(treeElements); + List elements = convertNestedTagElements(elements2); + + TagElement host = null; + for (IDocElement docElement : elements) { + if (docElement instanceof TagElement tag && !isInline(tag)) { + if (host != null) { + res.tags().add(host); + host = null; + } + res.tags().add(tag); + } else { + if (host == null) { + host = this.ast.newTagElement(); + if(docElement instanceof ASTNode astn) { + host.setSourceRange(astn.getStartPosition(), astn.getLength()); + } + } else if (docElement instanceof ASTNode extraNode && extraNode.getStartPosition() >= 0 && extraNode.getLength() >= 0){ + host.setSourceRange(host.getStartPosition(), extraNode.getStartPosition() + extraNode.getLength() - host.getStartPosition()); + } + + host.fragments().add(docElement); + } + } + if (host != null) { + res.tags().add(host); + } + } + } catch (Exception ex) { + ILog.get().error("Failed to convert Javadoc", ex); + } + return res; + } + + private List convertNestedTagElements(List elements2) { + return elements2.stream().map(x -> { + if( x instanceof TextElement te) { + String s = te.getText(); + if( s != null && s.startsWith("{@") && s.trim().endsWith("}")) { + String txt = this.javacConverter.rawText.substring(te.getStartPosition(), te.getStartPosition() + te.getLength()); + TextElement innerMost = this.ast.newTextElement(); + innerMost.setSourceRange(te.getStartPosition()+2, te.getLength()-3); + innerMost.setText(txt.substring(2, txt.length() - 1)); + + TagElement nested = this.ast.newTagElement(); + int atLoc = txt.indexOf("@"); + String name = atLoc == -1 ? txt : ("@" + txt.substring(atLoc + 1)).split("\\s+")[0]; + nested.setTagName(name); + nested.setSourceRange(te.getStartPosition(), te.getLength()); + nested.fragments().add(innerMost); + + TagElement wrapper = this.ast.newTagElement(); + wrapper.setSourceRange(te.getStartPosition(), te.getLength()); + wrapper.fragments().add(nested); + return wrapper; + } + } + return x; + }).toList(); + } + + Set getDiagnostics() { + return diagnostics; + } + + static boolean isInline(TagElement tag) { + return tag.getTagName() == null || switch (tag.getTagName()) { + case TagElement.TAG_CODE, + TagElement.TAG_DOCROOT, + TagElement.TAG_INHERITDOC, + TagElement.TAG_LINK, + TagElement.TAG_LINKPLAIN, + TagElement.TAG_LITERAL, + TagElement.TAG_SNIPPET, + TagElement.TAG_VALUE -> true; + default -> false; + }; + } + + private Optional convertBlockTag(DCTree javac) { + TagElement res = this.ast.newTagElement(); + commonSettings(res, javac); + if (javac instanceof DCAuthor author) { + res.setTagName(TagElement.TAG_AUTHOR); + convertElementCombiningNodes(author.name.stream().filter(x -> x != null).toList()).forEach(res.fragments::add); + } else if (javac instanceof DCSince since) { + res.setTagName(TagElement.TAG_SINCE); + convertElementCombiningNodes(since.body.stream().filter(x -> x != null).toList()).forEach(res.fragments::add); + } else if (javac instanceof DCVersion version) { + res.setTagName(TagElement.TAG_VERSION); + convertElementCombiningNodes(version.body.stream().filter(x -> x != null).toList()).forEach(res.fragments::add); + } else if (javac instanceof DCSee see) { + res.setTagName(TagElement.TAG_SEE); + convertElementCombiningNodes(see.reference.stream().filter(x -> x != null).toList()).forEach(res.fragments::add); + } else if (javac instanceof DCDeprecated deprecated) { + res.setTagName(TagElement.TAG_DEPRECATED); + convertElementCombiningNodes(deprecated.body.stream().filter(x -> x != null).toList()).forEach(res.fragments::add); + } else if (javac instanceof DCParam param) { + res.setTagName(TagElement.TAG_PARAM); + int tagNameEnds = javac.getStartPosition() + res.getTagName().length(); + if( param.isTypeParameter()) { + int stopSearchRelative = param.getEndPosition(); + if( param.description != null && param.description.size() > 0 ) { + stopSearchRelative = param.description.get(0).getEndPosition(); + } + int stopSearchAbsolute = this.docComment.getSourcePosition(stopSearchRelative); + int start = this.docComment.getSourcePosition(param.getStartPosition()); + int ltRaw = this.javacConverter.rawText.indexOf("<", start, stopSearchAbsolute); + int gtRaw = this.javacConverter.rawText.indexOf(">", start, stopSearchAbsolute); + if( ltRaw != -1 ) { + int ltStart = this.docComment.getSourcePosition(tagNameEnds+1); + // must include spaces + Region r = new Region(ltStart, 1 + (ltRaw - ltStart)); + res.fragments().add(toTextElement(r)); + res.fragments().addAll(convertElement(param.name).toList()); + } else { + res.fragments().addAll(convertElement(param.name).toList()); + } + if( gtRaw != -1 ) { + Region r = new Region(gtRaw, 1); + res.fragments().add(toTextElement(r)); + } + } else { + res.fragments().addAll(convertElement(param.name).toList()); + } + convertElementCombiningNodes(param.description.stream().filter(x -> x != null).toList()).forEach(res.fragments::add); + } else if (javac instanceof DCReturn ret) { + res.setTagName(TagElement.TAG_RETURN); + convertElementCombiningNodes(ret.description.stream().filter(x -> x != null).toList()).forEach(res.fragments::add); + } else if (javac instanceof DCThrows thrown) { + String tagName = thrown.kind == Kind.THROWS ? TagElement.TAG_THROWS : TagElement.TAG_EXCEPTION; + res.setTagName(tagName); + res.fragments().addAll(convertElement(thrown.name).toList()); + convertElementCombiningNodes(thrown.description.stream().filter(x -> x != null).toList()).forEach(res.fragments::add); + } else if (javac instanceof DCUses uses) { + res.setTagName(TagElement.TAG_USES); + // According to SemanticTokensHandlerTest.testSemanticTokens_Modules, + // we want directly a TextElement rather than a name here + //res.fragments().addAll(convertElement(uses.serviceType).toList()); + TextElement serviceName = this.ast.newTextElement(); + serviceName.setText(uses.serviceType.getSignature()); + commonSettings(serviceName, uses.serviceType); + res.fragments().add(serviceName); + convertElementCombiningNodes(uses.description.stream().filter(x -> x != null).toList()).forEach(res.fragments::add); + } else if (javac instanceof DCUnknownBlockTag unknown) { + res.setTagName("@" + unknown.getTagName()); + convertElementCombiningNodes(unknown.content.stream().filter(x -> x != null).toList()).forEach(res.fragments::add); + } else { + return Optional.empty(); + } + if( res != null ) { + if( res.fragments().size() != 0 ) { + // Make sure the tag wrapper has a proper source range + ASTNode lastFrag = ((ASTNode)res.fragments().get(res.fragments().size() - 1)); + int trueEnd = lastFrag.getStartPosition() + lastFrag.getLength(); + if( trueEnd > (res.getStartPosition() + res.getLength())) { + res.setSourceRange(res.getStartPosition(), trueEnd - res.getStartPosition()); + } + } + } + return Optional.of(res); + } + + + + private Stream convertInlineTag(DCTree javac) { + ArrayList collector = new ArrayList<>(); + TagElement res = this.ast.newTagElement(); + commonSettings(res, javac); + collector.add(res); + if (javac instanceof DCLiteral literal) { + res.setTagName(switch (literal.getKind()) { + case CODE -> TagElement.TAG_CODE; + case LITERAL -> TagElement.TAG_LITERAL; + default -> TagElement.TAG_LITERAL; + }); + List fragments = convertElement(literal.body).toList(); + ArrayList tmp = new ArrayList<>(fragments); + if( fragments.size() > 0 ) { + res.fragments().add(fragments.get(0)); + tmp.remove(0); + } + collector.addAll(tmp); + } else if (javac instanceof DCLink link) { + res.setTagName(switch (link.getKind()) { + case LINK -> TagElement.TAG_LINK; + case LINK_PLAIN -> TagElement.TAG_LINKPLAIN; + default -> TagElement.TAG_LINK; + }); + res.fragments().addAll(convertElement(link.ref).toList()); + link.label.stream().flatMap(this::convertElement).forEach(res.fragments()::add); + } else if (javac instanceof DCValue dcv) { + res.setTagName(TagElement.TAG_VALUE); + res.fragments().addAll(convertElement(dcv.ref).toList()); + } else if (javac instanceof DCInheritDoc inheritDoc) { + res.setTagName(TagElement.TAG_INHERITDOC); + } else if (javac instanceof DCSnippet snippet) { + res.setTagName(TagElement.TAG_SNIPPET); + res.setProperty(TagProperty.TAG_PROPERTY_SNIPPET_IS_VALID, true); + res.fragments().addAll(splitLines(snippet.body, true) + .map(this::toSnippetFragment) + .toList()); + } else if (javac instanceof DCUnknownInlineTag unknown) { + res.fragments().add(toDefaultTextElement(unknown)); + } else { + return Stream.empty(); + } + return collector.stream(); + } + + private Name toName(JCTree expression, int parentOffset) { + Name n = this.javacConverter.toName(expression, (dom, javac) -> { + int start = parentOffset + javac.getStartPosition(); + int length = javac.toString().length(); + dom.setSourceRange(start, Math.max(0,length)); + this.javacConverter.domToJavac.put(dom, javac); + }); + // We need to clean all the sub-names + if( n instanceof QualifiedName qn ) { + SimpleName sn = qn.getName(); + if( sn.getStartPosition() == 0 || sn.getStartPosition() == -1) { + int qnEnd = qn.getStartPosition() + qn.getLength(); + int start = qnEnd - sn.toString().length(); + sn.setSourceRange(start, qnEnd-start); + } + cleanNameQualifierLocations(qn); + } + return n; + } + + private void cleanNameQualifierLocations(QualifiedName qn) { + Name qualifier = qn.getQualifier(); + if( qualifier != null ) { + qualifier.setSourceRange(qn.getStartPosition(), qualifier.toString().length()); + if( qualifier instanceof QualifiedName qn2) { + cleanNameQualifierLocations(qn2); + } + } + } + + private class Region { + final int startOffset; + final int length; + + Region(int startOffset, int length) { + this.startOffset = startOffset; + this.length = length; + } + + String getContents() { + return JavadocConverter.this.javacConverter.rawText.substring(this.startOffset, this.startOffset + this.length); + } + + public int endPosition() { + return this.startOffset + this.length; + } + } + + private TextElement toTextElement(Region line) { + TextElement res = this.ast.newTextElement(); + String suggestedText = this.javacConverter.rawText.substring(line.startOffset, line.startOffset + line.length); + String strippedLeading = suggestedText.stripLeading(); + int leadingWhitespace = suggestedText.length() - strippedLeading.length(); + res.setSourceRange(line.startOffset + leadingWhitespace, line.length - leadingWhitespace); + res.setText(strippedLeading); + return res; + } + + private TextElement toTextElementPreserveWhitespace(Region line) { + TextElement res = this.ast.newTextElement(); + res.setSourceRange(line.startOffset, line.length); + res.setText(line.getContents()); + return res; + } + + private Stream splitLines(DCText text, boolean keepWhitespaces) { + return splitLines(text.getBody(), text.getStartPosition(), text.getEndPosition(), keepWhitespaces); + } + + private Stream splitLines(String body, int startPos, int endPos, boolean keepWhitespaces) { + String[] bodySplit = body.split("\n"); + ArrayList regions = new ArrayList<>(); + int workingIndexWithinComment = startPos; + for( int i = 0; i < bodySplit.length; i++ ) { + int lineStart = this.docComment.getSourcePosition(workingIndexWithinComment); + int lineEnd = this.docComment.getSourcePosition(workingIndexWithinComment + bodySplit[i].length()); + if (!keepWhitespaces) { + String tmp = this.javacConverter.rawText.substring(lineStart, lineEnd); + int leadingWhite = tmp.length() - tmp.stripLeading().length(); + Region r = new Region(lineStart + leadingWhite, lineEnd - lineStart - leadingWhite); + regions.add(r); + } else { +// if (lineEnd < this.javacConverter.rawText.length() && this.javacConverter.rawText.charAt(lineEnd) == '\n') { +// lineEnd++; +// } + regions.add(new Region(lineStart, lineEnd - lineStart)); + } + workingIndexWithinComment += bodySplit[i].length() + 1; + } + return regions.stream(); + } + + private Stream splitLines(DCTree[] allPositions) { + if( allPositions.length > 0 ) { + int[] startPosition = { this.docComment.getSourcePosition(allPositions[0].getStartPosition()) }; + int lastNodeStart = this.docComment.getSourcePosition(allPositions[allPositions.length - 1].getStartPosition()); + int endPosition = this.docComment.getSourcePosition(allPositions[allPositions.length - 1].getEndPosition()); + if( allPositions[allPositions.length-1] instanceof DCText dct) { + String lastText = dct.text; + String lastTextFromSrc = this.javacConverter.rawText.substring(lastNodeStart, endPosition); + if( !lastTextFromSrc.equals(lastText)) { + // We need to fix this. There might be unicode in here + String convertedText = Convert.escapeUnicode(lastText); + if( convertedText.startsWith(lastTextFromSrc)) { + endPosition = lastNodeStart + convertedText.length(); + } + } + } + String sub = this.javacConverter.rawText.substring(startPosition[0], endPosition); + String[] split = sub.split("(\r)?\n\\s*[*][ \t]*"); + List regions = new ArrayList<>(); + for( int i = 0; i < split.length; i++ ) { + int index = this.javacConverter.rawText.indexOf(split[i], startPosition[0]); + if (index >= 0) { + regions.add(new Region(index, split[i].length())); + startPosition[0] = index + split[i].length(); + } + } + return regions.stream(); + } + return Stream.empty(); + } + + private IDocElement /* TextElement or TagElement for highlight/link... */ toSnippetFragment(Region region) { + TextElement defaultElement = toTextElementPreserveWhitespace(region); + if (!defaultElement.getText().endsWith("\n")) { + defaultElement.setText(defaultElement.getText() + '\n'); + } + String line = region.getContents(); + Matcher markedUpLine = JAVA_COMMENT.matcher(line); + if (!markedUpLine.matches()) { + return defaultElement; + } + int markupStart = markedUpLine.start("markup"); + String markup = line.substring(markupStart); + MarkupParser markupParser = new MarkupParser(null); + try { + List tags = markupParser.parse(markup); + if (tags.isEmpty()) { + return defaultElement; + } + TextElement initialTextElement = this.ast.newTextElement(); + initialTextElement.setSourceRange(region.startOffset, markupStart - 2 /* 2 is length of `//` */); + initialTextElement.setText(line.substring(0, markupStart - 2) + '\n'); + IDocElement currentElement = initialTextElement; + Class tagClass = tags.getFirst().getClass(); + Field nameField = tagClass.getDeclaredField("name"); //$NON-NLS-1$ + nameField.setAccessible(true); + Field attributesFields = tagClass.getDeclaredField("attributes"); //$NON-NLS-1$ + attributesFields.setAccessible(true); + for (Object tag : tags) { + String name = (String)nameField.get(tag); + List attributes = (List)attributesFields.get(tag); + TagElement newElement = this.ast.newTagElement(); + newElement.setSourceRange(region.startOffset, region.length); + newElement.setTagName('@' + name); + newElement.setProperty(TagProperty.TAG_PROPERTY_SNIPPET_INLINE_TAG_COUNT, 1); // TODO what? + attributes.stream().map(this::toTagProperty).forEach(newElement.tagProperties()::add); + newElement.fragments().add(currentElement); + currentElement = newElement; + } + return currentElement; + } catch (Exception ex) { + ILog.get().error("While trying to build snippet line " + line + ": " + ex.getMessage(), ex); + } + return defaultElement; + } + private TagProperty toTagProperty(Attribute snippetMarkupAttribute) { + TagProperty res = this.ast.newTagProperty(); + try { + Field name = Attribute.class.getDeclaredField("name"); //$NON-NLS-1$ + name.setAccessible(true); + res.setName((String)name.get(snippetMarkupAttribute)); + Field value = snippetMarkupAttribute.getClass().getDeclaredField("value"); //$NON-NLS-1$ + value.setAccessible(true); + res.setStringValue((String)value.get(snippetMarkupAttribute)); + } catch (Exception ex) { + ILog.get().error(ex.getMessage(), ex); + } + return res; + } + + private Stream convertElementGroup(DCTree[] javac) { + return splitLines(javac).filter(x -> x.length != 0).flatMap(this::toTextOrTag); + } + private Stream toTextOrTag(Region line) { + String suggestedText = this.javacConverter.rawText.substring(line.startOffset, line.startOffset + line.length); + TextElement postElement = null; + if( suggestedText.startsWith("{@")) { + int closeBracket = suggestedText.indexOf("}"); + int firstWhite = findFirstWhitespace(suggestedText); + if( closeBracket > firstWhite && firstWhite != -1 ) { + Region postRegion = new Region(line.startOffset + closeBracket + 1, line.length - closeBracket - 1); + if( postRegion.length > 0 ) + postElement = toTextElement(postRegion); + String tagName = suggestedText.substring(1, firstWhite).trim(); + TagElement res = this.ast.newTagElement(); + res.setTagName(tagName); + res.fragments.add(toTextElementPreserveWhitespace(new Region(line.startOffset + firstWhite, closeBracket - firstWhite))); + res.setSourceRange(line.startOffset, closeBracket + 1); + if( postElement == null ) + return Stream.of(res); + else + return Stream.of(res, postElement); + } + } + + return Stream.of(toTextElement(line)); + } + + private int findFirstWhitespace(String s) { + int len = s.length(); + for (int index = 0; index < len; index++) { + if (Character.isWhitespace(s.charAt(index))) { + return index; + } + } + return -1; + } + + private List convertElementCombiningNodes(List treeElements) { + List elements = new ArrayList<>(); + List combinable = new ArrayList<>(); + int size = treeElements.size(); + DCTree prev = null; + for( int i = 0; i < size; i++ ) { + boolean shouldCombine = false; + boolean lineBreakBefore = false; + DCTree oneTree = treeElements.get(i); + if(oneTree instanceof DCText || oneTree instanceof DCStartElement || oneTree instanceof DCEndElement || oneTree instanceof DCEntity) { + shouldCombine = true; + if((oneTree instanceof DCText dct && dct.text.startsWith("\n")) + || (oneTree instanceof DCRawText raw && raw.getContent().endsWith("\n"))) { + lineBreakBefore = true; + } + } else if( oneTree instanceof DCErroneous derror) { + Stream de = convertDCErroneousElement(derror); + if( de == null ) { + shouldCombine = true; + if( derror.body.startsWith("{@")) { + lineBreakBefore = true; + } + } + } + + if( lineBreakBefore || !shouldCombine) { + if( combinable.size() > 0 ) { + elements.addAll(convertElementGroup(combinable.toArray(new DCTree[0])).toList()); + combinable.clear(); + } + } + + if( shouldCombine ) { + combinable.add(oneTree); + } else { + elements.addAll(convertElement(oneTree).toList()); + } + prev = oneTree; + } + if( combinable.size() > 0 ) + elements.addAll(convertElementGroup(combinable.toArray(new DCTree[0])).toList()); + return elements; + } + private Stream convertElement(DCTree javac) { + if (javac instanceof DCText text) { + return splitLines(text, false).map(this::toTextElement); + } else if (javac instanceof DCRawText rawText) { + TextElement element = this.ast.newTextElement(); + commonSettings(element, javac); + element.setText(rawText.getContent()); + return Stream.of(element); + } else if (javac instanceof DCIdentifier identifier) { + Name res = this.ast.newName(identifier.getName().toString()); + commonSettings(res, javac); + return Stream.of(res); + } else if (javac instanceof DCReference reference) { + return convertReference(javac, reference); + } else if (javac instanceof DCStartElement || javac instanceof DCEndElement || javac instanceof DCEntity) { + return Stream.of(toDefaultTextElement(javac)); + } else if (javac instanceof DCBlockTag || javac instanceof DCReturn) { + Optional> blockTag = convertBlockTag(javac).map(Stream::of); + if (blockTag.isPresent()) { + return blockTag.get(); + } + } else if (javac instanceof DCErroneous erroneous) { + Stream docE = convertDCErroneousElement(erroneous); + if( docE != null ) { + return docE; + } + TextElement res = this.ast.newTextElement(); + commonSettings(res, erroneous); + res.setText(erroneous.body); + diagnostics.add(erroneous.diag); + return Stream.of(res); + } else if (javac instanceof DCComment comment) { + TextElement res = this.ast.newTextElement(); + commonSettings(res, comment); + res.setText(res.text); + return Stream.of(res); + } else { + Stream inlineTag = convertInlineTag(javac); + return inlineTag; + } + var message = "💥🐛 Not supported yet conversion of " + javac.getClass().getSimpleName() + " to element"; + ILog.get().error(message); + JavaDocTextElement res = this.ast.newJavaDocTextElement(); + commonSettings(res, javac); + res.setText(this.docComment.comment.getText().substring(javac.getStartPosition(), javac.getEndPosition()) + System.lineSeparator() + message); + return Stream.of(res); + } + + private Stream convertReference(DCTree javac, DCReference reference) { + String signature = reference.getSignature(); + if (reference.memberName == null) { + if( reference.qualifierExpression != null ) { + Name res = convertReferenceToNameOnly(reference); + return Stream.of(res); + } + } else if (reference.memberName != null) { + if (signature.charAt(signature.length() - 1) == ')') { + return Stream.of(convertMemberReferenceWithParens(reference)); + } else { + return Stream.of(convertReferenceToMemberRef(reference)); + } + } + // just return it as text + int startPosition = this.docComment.getSourcePosition(reference.getPreferredPosition()); + TextElement res = this.ast.newTextElement(); + res.setText(signature); + res.setSourceRange(startPosition, reference.getEndPos() - reference.pos); + return Stream.of(res); + } + + private IDocElement convertMemberReferenceWithParens(DCReference reference) { + MethodRef res = this.ast.newMethodRef(); + commonSettings(res, reference); + int currentOffset = this.docComment.getSourcePosition(reference.getStartPosition()); + + // TODO missing module reference + if (reference.qualifierExpression != null) { + Name qualifierExpressionName = toName(reference.qualifierExpression, res.getStartPosition()); + qualifierExpressionName.setSourceRange(currentOffset, Math.max(0, reference.qualifierExpression.toString().length())); + res.setQualifier(qualifierExpressionName); + currentOffset += qualifierExpressionName.getLength(); + } + currentOffset++; // # + SimpleName name = this.ast.newSimpleName(reference.memberName.toString()); + name.setSourceRange(currentOffset, Math.max(0, reference.memberName.toString().length())); + currentOffset += name.getLength(); + res.setName(name); + if (this.contextTreePath != null) { + this.converted.put(name, DocTreePath.getPath(this.contextTreePath, this.docComment, reference)); + } + currentOffset++; // ( + final int paramListOffset = currentOffset; + List params = new ArrayList<>(); + int separatorOffset = currentOffset; + while (separatorOffset < res.getStartPosition() + res.getLength() + && this.javacConverter.rawText.charAt(separatorOffset) != ')') { + while (separatorOffset < res.getStartPosition() + res.getLength() + && this.javacConverter.rawText.charAt(separatorOffset) != ')' + && this.javacConverter.rawText.charAt(separatorOffset) != ',') { + separatorOffset++; + } + params.add(new Region(currentOffset, separatorOffset - currentOffset)); + separatorOffset++; // consume separator + currentOffset = separatorOffset; + } + for (int i = 0; i < reference.paramTypes.size(); i++) { + JCTree type = reference.paramTypes.get(i); + Region range = i < params.size() ? params.get(i) : null; + res.parameters().add(toMethodRefParam(type, range, paramListOffset)); + } + return res; + } + + private IDocElement convertReferenceToMemberRef(DCReference reference) { + MemberRef res = this.ast.newMemberRef(); + commonSettings(res, reference); + if (this.contextTreePath != null) { + this.converted.put(res, DocTreePath.getPath(this.contextTreePath, this.docComment, reference)); + } + + int startPos = this.docComment.getSourcePosition(reference.pos); + int memberNameStart = startPos; + + Name qualifier = convertReferenceToNameOnly(reference); + if( qualifier != null ) { + res.setQualifier(qualifier); + memberNameStart = qualifier.getStartPosition() + qualifier.getLength() + 1; + } + if( reference.memberName != null ) { + SimpleName name = this.ast.newSimpleName(reference.memberName.toString()); + name.setSourceRange(memberNameStart, Math.max(0, reference.memberName.toString().length())); + res.setName(name); + } + return res; + } + + private Name convertReferenceToNameOnly(DCReference reference) { + int startPos = this.docComment.getSourcePosition(reference.getStartPosition()); + if (reference.qualifierExpression != null) { + int moduleQualifierLen = 0; + if( reference.moduleName != null) { + moduleQualifierLen = this.javacConverter.commonSettingsGetLength(null, reference.moduleName) + 1; + } + Name qualifierExpressionName = toName(reference.qualifierExpression, startPos + moduleQualifierLen); + int len = Math.max(0, reference.qualifierExpression.toString().length()); + qualifierExpressionName.setSourceRange(startPos + moduleQualifierLen, len); + if( reference.moduleName == null ) { + return qualifierExpressionName; + } else { + ModuleQualifiedName mqn = new ModuleQualifiedName(this.ast); + Name moduleName = toName(reference.moduleName, startPos); + moduleName.setSourceRange(startPos, moduleQualifierLen); + mqn.setModuleQualifier(moduleName); + mqn.setName(qualifierExpressionName); + mqn.setSourceRange(startPos, qualifierExpressionName.getStartPosition() + qualifierExpressionName.getLength() - startPos); + return mqn; + } + } + return null; + } + + // Return a stream, or null if empty + private Stream convertDCErroneousElement(DCErroneous erroneous) { + String body = erroneous.body; + MethodRef match = null; + try { + match = matchesMethodReference(erroneous, body); + } catch(Exception e) { + // ignore + } + int start = this.docComment.getSourcePosition(erroneous.getStartPosition()); + int endInd = erroneous.getEndPosition(); + int endPosition = this.docComment.getSourcePosition(endInd); + if( match != null) { + TagElement res = this.ast.newTagElement(); + res.setTagName(TagElement.TAG_SEE); + res.fragments.add(match); + res.setSourceRange(start, endPosition - start); + return Stream.of(res); + } else if( body.startsWith("@")) { + TagElement res = this.ast.newTagElement(); + String tagName = body.split("\\s+")[0]; + res.setTagName(tagName); + int newStart = erroneous.getStartPosition() + tagName.length(); + List l = splitLines(body.substring(tagName.length()), newStart, endInd, false).map(x -> toTextElement(x)).toList(); + res.fragments.addAll(l); + TextElement lastFragment = l.size() == 0 ? null : l.get(l.size() - 1); + int newEnd = lastFragment == null ? tagName.length() : (lastFragment.getStartPosition() + lastFragment.getLength()); + res.setSourceRange(start, endPosition - start); + return Stream.of(res); +// } else if( body.startsWith("{@")) { +// return convertElementGroup(new DCTree[] {erroneous}); + } + return null; + } + + private MethodRef matchesMethodReference(DCErroneous tree, String body) { + if( body.startsWith("@see")) { + String value = body.substring(4); + int hash = value.indexOf("#"); + if( hash != -1 ) { + int startPosition = this.docComment.getSourcePosition(tree.getStartPosition()) + 4; + String prefix = value.substring(0, hash); + int link = prefix.indexOf("@link"); + if (link != -1) { + prefix = prefix.substring(link + 5); + startPosition = startPosition + link + 5; + } + MethodRef ref = this.ast.newMethodRef(); + if( prefix != null && !prefix.isBlank()) { + Name n = toName(prefix, startPosition); + ref.setQualifier(n); + } + String suffix = hash+1 > value.length() ? "" : value.substring(hash+1); + if( suffix.indexOf("(") != -1 ) { + String qualifiedMethod = suffix.substring(0, suffix.indexOf("(")); + int methodNameStart = qualifiedMethod.lastIndexOf(".") + 1; + String methodName = qualifiedMethod.substring(methodNameStart); + SimpleName sn = (SimpleName)toName(methodName, startPosition + prefix.length() + 1 + methodNameStart); + ref.setName(sn); + commonSettings(ref, tree); + diagnostics.add(tree.diag); + return ref; + } + } + } + return null; + } + private Name toName(String val, int startPosition) { + return JavacConverter.toName(val, startPosition, this.ast); + } + + + private TextElement toDefaultTextElement(DCTree javac) { + TextElement res = this.ast.newTextElement(); + commonSettings(res, javac); + String r = this.docComment.comment.getText(); + String s1 = r.substring(javac.getStartPosition(), javac.getEndPosition()); + res.setText(s1); + return res; + } + + private MethodRefParameter toMethodRefParam(JCTree type, Region range, int paramListOffset) { + MethodRefParameter res = this.ast.newMethodRefParameter(); + res.setSourceRange( + range != null ? range.startOffset : paramListOffset + type.getStartPosition(), + range != null ? range.length : type.toString().length()); + // Make positons absolute + var fixPositions = new TreeScanner() { + @Override + public void scan(JCTree tree) { + tree.setPos(tree.pos + paramListOffset); + super.scan(tree); + } + }; + fixPositions.scan(type); + + String[] segments = Stream.of(range.getContents().split("\n")) + .map(t -> { + Matcher m = BEGIN_CHOPPER.matcher(t); + if (m.find()) { + return m.group(1); + } + return t; + }) + .map(String::trim) + .flatMap(t -> Stream.of(t.split("\s"))) + .filter(t -> !t.isEmpty()) + .toArray(String[]::new); + + Type jdtType = null; + if( segments.length > 0 && segments[segments.length-1].endsWith("...")) { + res.setVarargs(true); + if( type instanceof JCArrayTypeTree att) { + jdtType = this.javacConverter.convertToType(att.getType()); + } + } + if( jdtType == null ) { + jdtType = this.javacConverter.convertToType(type); + } + res.setType(jdtType); + + // some lengths may be missing + jdtType.accept(new ASTVisitor() { + @Override + public void preVisit(ASTNode node) { + if (node.getLength() == 0 && node.getStartPosition() >= 0) { + node.setSourceRange(node.getStartPosition(), node.toString().length()); + } + super.preVisit(node); + } + }); + if (jdtType.getStartPosition() + jdtType.getLength() < res.getStartPosition() + res.getLength()) { + if (segments.length > 1) { + String nameSegment = segments[segments.length - 1]; + SimpleName name = this.ast.newSimpleName(nameSegment); + name.setSourceRange(this.javacConverter.rawText.lastIndexOf(nameSegment, range.endPosition()), nameSegment.length()); + res.setName(name); + } + } + return res; + } +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/javac/BuildNotifierCompilationProgress.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/javac/BuildNotifierCompilationProgress.java new file mode 100644 index 00000000000..308958e228f --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/javac/BuildNotifierCompilationProgress.java @@ -0,0 +1,83 @@ +/******************************************************************************* +* Copyright (c) 2024 Red Hat, Inc. and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +*******************************************************************************/ +package org.eclipse.jdt.internal.javac; + +import java.lang.reflect.Field; + +import org.eclipse.core.runtime.ILog; +import org.eclipse.jdt.core.compiler.CompilationProgress; +import org.eclipse.jdt.internal.compiler.ICompilerRequestor; +import org.eclipse.jdt.internal.core.builder.AbstractImageBuilder; +import org.eclipse.jdt.internal.core.builder.BuildNotifier; + +/******************************************************************************* + * Copyright (c) 2024 Red Hat, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +public class BuildNotifierCompilationProgress extends CompilationProgress { + + private BuildNotifier buildNotifier; + + public BuildNotifierCompilationProgress(ICompilerRequestor requestor) { + this.buildNotifier = findBuildNotifier(requestor); + } + + private BuildNotifier findBuildNotifier(ICompilerRequestor requestor) { + if (requestor instanceof AbstractImageBuilder) { + try { + Field notifierField = AbstractImageBuilder.class.getDeclaredField("notifier"); + notifierField.setAccessible(true); + return notifierField.get(requestor) instanceof BuildNotifier notifier ? notifier : null; + } catch (Exception ex) { + ILog.get().warn(ex.getMessage(), ex); + } + } + return null; + } + + @Override + public void begin(int remainingWork) { + if (this.buildNotifier != null) { + this.buildNotifier.checkCancelWithinCompiler(); + } + } + + @Override + public void done() { + // do not forward as done() is sent via requestor + } + + @Override + public boolean isCanceled() { + if (this.buildNotifier != null) { + this.buildNotifier.checkCancel(); + } + return false; + } + + @Override + public void setTaskName(String name) { + if (this.buildNotifier != null) { + this.buildNotifier.subTask(name); + } + } + + @Override + public void worked(int workIncrement, int remainingWork) { + // TODO Auto-generated method stub + } + +} diff --git a/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/javac/CachingJDKPlatformArguments.java b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/javac/CachingJDKPlatformArguments.java new file mode 100644 index 00000000000..1b1057ca169 --- /dev/null +++ b/org.eclipse.jdt.core.javac/src/org/eclipse/jdt/internal/javac/CachingJDKPlatformArguments.java @@ -0,0 +1,143 @@ +/******************************************************************************* + * Copyright (c) 2024 Red Hat, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.jdt.internal.javac; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import javax.annotation.processing.Processor; +import javax.tools.ForwardingJavaFileManager; +import javax.tools.JavaFileManager; + +import com.sun.source.util.Plugin; +import com.sun.tools.javac.main.Arguments; +import com.sun.tools.javac.main.DelegatingJavaFileManager; +import com.sun.tools.javac.main.Option; +import com.sun.tools.javac.platform.PlatformDescription; +import com.sun.tools.javac.platform.PlatformUtils; +import com.sun.tools.javac.resources.CompilerProperties.Errors; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Context.Factory; +import com.sun.tools.javac.util.Log; +import com.sun.tools.javac.util.Options; + +public class CachingJDKPlatformArguments extends Arguments { + + private static Map platformFMCache = new ConcurrentHashMap<>(); + + private final Options options; + private final Context context; + + public static void preRegister(Context context) { + context.put(Arguments.argsKey, (Factory) c -> new CachingJDKPlatformArguments(c)); + } + + private CachingJDKPlatformArguments(Context context) { + super(context); + this.options = Options.instance(context); + this.context = context; + } + + @Override + public boolean handleReleaseOptions(Predicate> additionalOptions) { + // mostly copied from super, only wrapping the platformDescription so its + // fileManager is reusable + String platformString = options.get(Option.RELEASE); + + checkOptionAllowed(platformString == null, + option -> Log.instance(this.context).error(Errors.ReleaseBootclasspathConflict(option)), + Option.BOOT_CLASS_PATH, Option.XBOOTCLASSPATH, Option.XBOOTCLASSPATH_APPEND, + Option.XBOOTCLASSPATH_PREPEND, Option.ENDORSEDDIRS, Option.DJAVA_ENDORSED_DIRS, Option.EXTDIRS, + Option.DJAVA_EXT_DIRS, Option.SOURCE, Option.TARGET, Option.SYSTEM, Option.UPGRADE_MODULE_PATH); + + if (platformString != null) { + PlatformDescription platformDescription = toReusable( + PlatformUtils.lookupPlatformDescription(platformString)); + if (platformDescription == null) { + Log.instance(this.context).error(Errors.UnsupportedReleaseVersion(platformString)); + return false; + } + + options.put(Option.SOURCE, platformDescription.getSourceVersion()); + options.put(Option.TARGET, platformDescription.getTargetVersion()); + + context.put(PlatformDescription.class, platformDescription); + + if (!additionalOptions.test(platformDescription.getAdditionalOptions())) + return false; + + JavaFileManager platformFM = platformDescription.getFileManager(); + DelegatingJavaFileManager.installReleaseFileManager(context, platformFM, + context.get(JavaFileManager.class)); + } + return true; + } + + private static PlatformDescription toReusable(PlatformDescription delegate) { + if (delegate == null) { + return null; + } + return new PlatformDescription() { + @Override + public JavaFileManager getFileManager() { + return platformFMCache.computeIfAbsent(getSourceVersion(), _ -> new ForwardingJavaFileManager(delegate.getFileManager()) { + @Override + public void close() { + // do nothing, keep instance usable + } + }); + } + + @Override + public String getSourceVersion() { + return delegate.getSourceVersion(); + } + + @Override + public String getTargetVersion() { + return delegate.getTargetVersion(); + } + + @Override + public List> getAnnotationProcessors() { + return delegate.getAnnotationProcessors(); + } + + @Override + public List> getPlugins() { + return delegate.getPlugins(); + } + + @Override + public List getAdditionalOptions() { + return delegate.getAdditionalOptions(); + } + + @Override + public void close() throws IOException { + // DO NOTHING! + } + + }; + } + + void checkOptionAllowed(boolean allowed, Consumer