diff --git a/.classpath b/.classpath deleted file mode 100644 index 010a7d42b69..00000000000 --- a/.classpath +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.github/workflows/ant-test.yml b/.github/workflows/ant-test.yml index 5f9c9e27bf3..b5f8a3af048 100644 --- a/.github/workflows/ant-test.yml +++ b/.github/workflows/ant-test.yml @@ -19,17 +19,17 @@ jobs: fail-fast: false matrix: # test against latest update of each major Java version, as well as specific updates of LTS versions: - java: [8, 11, 17, 21, 22-ea] + java: [11, 17, 21, 22] os: [ubuntu-latest, macos-latest, windows-latest] name: Java ${{ matrix.java }} on ${{ matrix.os }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 256 - name: Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.ivy2/cache/ @@ -37,13 +37,13 @@ jobs: key: ${{ runner.os }}-ivy2-${{ hashFiles('build.xml', 'ivy.xml', 'tools/ivy.xml') }} - name: Setup Java ${{ matrix.java }} - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: ${{ matrix.java }} - name: Install Ant - uses: JOSM/JOSMPluginAction/actions/setup-ant@v1 + uses: JOSM/JOSMPluginAction/actions/setup-ant@v2 - name: Test with Ant run: | @@ -56,12 +56,13 @@ jobs: - name: Upload Ant reports if: ${{ always() }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Ant reports for JOSM ${{ needs.createrelease.outputs.josm_revision }} on java ${{ matrix.java }} on ${{ matrix.os }} path: | test/report/*.txt test/report/TEST*.xml + hs_err* publish-test-results: name: "Publish Unit Tests Results" @@ -72,13 +73,13 @@ jobs: steps: - name: Download Artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: path: artifacts - name: Publish Test Report with action-junit-report if: ${{ always() }} - uses: mikepenz/action-junit-report@v3 + uses: mikepenz/action-junit-report@v4 with: report_paths: 'artifacts/**/*.xml' token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ant.yml b/.github/workflows/ant.yml index 070453f263c..2632a59fee8 100644 --- a/.github/workflows/ant.yml +++ b/.github/workflows/ant.yml @@ -1,6 +1,5 @@ name: Java CI Build env: - junit_platform_version: '1.9.3' JAVAFX_VERSION: '17.0.7' on: push: @@ -15,6 +14,8 @@ jobs: createrelease: name: Create Release runs-on: ubuntu-latest + permissions: + contents: write # Needed to create actual release env: LANG: en_US.UTF-8 outputs: @@ -24,7 +25,7 @@ jobs: josm_release_tag: ${{ steps.create_revision.outputs.josm_release_tag }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 256 @@ -49,12 +50,10 @@ jobs: - name: Create release id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + uses: softprops/action-gh-release@v2 with: tag_name: ${{ env.josm_release_tag }} - release_name: JOSM release ${{ env.josm_release_tag }} + name: JOSM release ${{ env.josm_release_tag }} body: | JOSM release ${{ env.josm_release_tag }} draft: false @@ -69,17 +68,17 @@ jobs: fail-fast: false matrix: # test against latest update of each major Java version, as well as specific updates of LTS versions: - java: [8, 11, 17, 21, 22-ea] - os: [ubuntu-latest, macos-latest, windows-latest] + java: [11, 17, 21, 22] + os: [ubuntu-latest, macos-14, windows-latest] name: Java ${{ matrix.java }} on ${{ matrix.os }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 256 - name: Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.ivy2/cache/ @@ -87,13 +86,21 @@ jobs: key: ${{ runner.os }}-ivy2-${{ hashFiles('build.xml', 'ivy.xml', 'tools/ivy.xml') }} - name: Setup Java ${{ matrix.java }} - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: ${{ matrix.java }} + - name: Setup x64 Java (Mac) ${{ matrix.java }} + if: ${{ runner.os == 'macos' && runner.arch == 'ARM64' && always() }} + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: ${{ matrix.java }} + architecture: x64 + - name: Install Ant - uses: JOSM/JOSMPluginAction/actions/setup-ant@v1 + uses: JOSM/JOSMPluginAction/actions/setup-ant@v2 - name: Optimise images if: ${{ runner.os == 'macos' && always() }} @@ -113,29 +120,37 @@ jobs: SIGN_TSA: ${{ secrets.SIGN_TSA }} # Calls ant with -Dreleasebuild=true if we're a 'tested' build run: | - export SIGN_KEYSTORE=certificate.p12 - echo "$SIGN_CERT" | base64 --decode > $SIGN_KEYSTORE + if [ ! -z "${SIGN_CERT}" ]; then + export SIGN_KEYSTORE=certificate.p12 + echo "$SIGN_CERT" | base64 --decode > $SIGN_KEYSTORE + fi if [ "${{ needs.createrelease.outputs.josm_prerelease }}" == "true" ]; then ANT="ant" else ANT="ant -Dreleasebuild=true" fi $ANT dist - rm $SIGN_KEYSTORE + if [ ! -z "${SIGN_KEYSTORE}" ]; then rm $SIGN_KEYSTORE; fi - name: Upload jar - if: ${{ always() }} + if: ${{ always() && needs.createrelease.outputs.upload_url }} env: + ASSET: dist/josm-custom.jar + ASSET_NAME: JOSM-${{ runner.os}}-java${{ matrix.java }}-${{ needs.createrelease.outputs.josm_revision }}.jar + CONTENT_TYPE: application/java-archive GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: actions/upload-release-asset@v1 - with: - upload_url: ${{ needs.createrelease.outputs.upload_url }} # This pulls from the CREATE RELEASE job above, referencing its ID to get its outputs object, which include a `upload_url`. - asset_path: dist/josm-custom.jar - asset_name: JOSM-${{ runner.os}}-java${{ matrix.java }}-${{ needs.createrelease.outputs.josm_revision }}.jar - asset_content_type: application/java-archive + # Use `gh api` instead of `gh release upload` so we can set the content type -- it also lets us avoid renaming files + run: | + gh api --verbose --method POST \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: Bearer ${{ env.GITHUB_TOKEN }}" \ + --header "Content-Type: ${{ env.CONTENT_TYPE }}" \ + --header "X-GitHub-Api-Version: 2022-11-28" \ + "$(sed 's/{?name,label}/?name=${{ env.ASSET_NAME }}/g' <<< ${{ needs.createrelease.outputs.upload_url }})" \ + --input "${{ env.ASSET }}" - name: Build and package for macOS - if: ${{ runner.os == 'macos' && matrix.java != '8' && matrix.java != '11' && always() }} + if: ${{ runner.os == 'macos' && matrix.java != '11' && always() }} env: CERT_MACOS_P12: ${{ secrets.CERT_MACOS_P12 }} CERT_MACOS_PW: ${{ secrets.CERT_MACOS_PW }} @@ -143,22 +158,32 @@ jobs: APPLE_ID_PW: ${{ secrets.APPLE_ID_PW }} APPLE_ID_TEAM: ${{ secrets.APPLE_ID_TEAM }} run: | - if [ ! -f tools/openjfx-${JAVAFX_VERSION}_${{ runner.os }}-jmods.zip ]; then - curl -o tools/openjfx-${JAVAFX_VERSION}_${{ runner.os }}-jmods.zip https://download2.gluonhq.com/openjfx/${JAVAFX_VERSION}/openjfx-${JAVAFX_VERSION}_osx-x64_bin-jmods.zip + function setup_openjfx() { + if [ ! -f tools/openjfx-${JAVAFX_VERSION}_${{ runner.os }}_${2}-jmods.zip ]; then + curl -o tools/openjfx-${JAVAFX_VERSION}_${{ runner.os }}_${2}-jmods.zip https://download2.gluonhq.com/openjfx/${JAVAFX_VERSION}/openjfx-${JAVAFX_VERSION}_osx-${1}_bin-jmods.zip + fi + unzip tools/openjfx-${JAVAFX_VERSION}_${{ runner.os }}_${2}-jmods.zip + mv javafx-jmods-${JAVAFX_VERSION}/*.jmod $JAVA_HOME/jmods/ + } + + if [ ${{ runner.arch }} == "ARM64" ]; then + JAVA_HOME="${JAVA_HOME_${{ matrix.java }}_ARM64}" PATH="${JAVA_HOME_${{ matrix.java }}_ARM64}/bin:${PATH}" setup_openjfx aarch64 ARM64 + JAVA_HOME="${JAVA_HOME_${{ matrix.java }}_X64}" PATH="${JAVA_HOME_${{ matrix.java }}_X64}/bin:${PATH}" setup_openjfx x64 X64 + JAVA_HOME="${JAVA_HOME_${{ matrix.java }}_ARM64}" ./native/macosx/macos-jpackage.sh ${{ needs.createrelease.outputs.josm_revision }} "${JAVA_HOME_${{ matrix.java }}_X64}" + else + setup_openjfx x64 X64 + ./native/macosx/macos-jpackage.sh ${{ needs.createrelease.outputs.josm_revision }} fi - unzip tools/openjfx-${JAVAFX_VERSION}_${{ runner.os }}-jmods.zip - mv javafx-jmods-${JAVAFX_VERSION}/*.jmod $JAVA_HOME/jmods/ - ./native/macosx/macos-jpackage.sh ${{ needs.createrelease.outputs.josm_revision }} - name: Setup Windows PATH - if: ${{ runner.os == 'windows' && matrix.java != '8' && matrix.java != '11' && always() }} + if: ${{ runner.os == 'windows' && matrix.java != '11' && always() }} run: | echo "C:\Program Files (x86)\WiX Toolset v3.11\bin" >> $GITHUB_PATH echo "C:\Program Files (x86)\Windows Kits\10\bin\x64" >> $GITHUB_PATH echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH - name: Build and package for Windows - if: ${{ runner.os == 'windows' && matrix.java != '8' && matrix.java != '11' && always() }} + if: ${{ runner.os == 'windows' && matrix.java != '11' && always() }} env: SIGN_CERT: ${{ secrets.SIGN_CERT }} SIGN_STOREPASS: ${{ secrets.SIGN_STOREPASS }} @@ -172,34 +197,85 @@ jobs: ./native/windows/win-jpackage.sh ${{ needs.createrelease.outputs.josm_revision }} - name: Upload macOS app - if: ${{ runner.os == 'macos' && matrix.java != '8' && matrix.java != '11' && always() }} - uses: actions/upload-release-asset@v1 + if: ${{ runner.os == 'macos' && matrix.java != '11' && always() && needs.createrelease.outputs.upload_url }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.createrelease.outputs.upload_url }} # This pulls from the CREATE RELEASE job above, referencing its ID to get its outputs object, which include a `upload_url`. - asset_path: app/JOSM.zip - asset_name: JOSM-${{ runner.os}}-java${{ matrix.java }}-${{ needs.createrelease.outputs.josm_revision }}.zip - asset_content_type: application/zip + CONTENT_TYPE: application/zip + ASSET_NAME: JOSM-${{ runner.os}}-java${{ matrix.java }}-${{ needs.createrelease.outputs.josm_revision }}.zip + run: | + curl --location \ + --request POST \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: Bearer ${{ env.GITHUB_TOKEN }}" \ + --header "Content-Type: ${{ env.CONTENT_TYPE }}" \ + --header "X-GitHub-Api-Version: 2022-11-28" \ + "$(sed 's/{?name,label}/?name=${{ env.ASSET_NAME }}/g' <<< ${{ needs.createrelease.outputs.upload_url }})" \ + --data-binary "@app/JOSM.zip" + + - name: Upload macOS app (x64) + if: ${{ runner.os == 'macos' && matrix.java != '11' && always() && runner.arch == 'ARM64' && needs.createrelease.outputs.upload_url }} + env: + ASSET_NAME: JOSM-${{ runner.os}}-java${{ matrix.java }}-${{ needs.createrelease.outputs.josm_revision }}-x64.zip + CONTENT_TYPE: application/zip + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Use `curl` instead of `gh release upload` so we can set the content type + run: | + curl --location \ + --request POST \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: Bearer ${{ env.GITHUB_TOKEN }}" \ + --header "Content-Type: ${{ env.CONTENT_TYPE }}" \ + --header "X-GitHub-Api-Version: 2022-11-28" \ + "$(sed 's/{?name,label}/?name=${{ env.ASSET_NAME }}/g' <<< ${{ needs.createrelease.outputs.upload_url }})" \ + --data-binary "@app/JOSM_${{ matrix.java }}_x86_64.zip" + + - name: Upload macOS app (aarch64) + if: ${{ runner.os == 'macos' && matrix.java != '11' && always() && runner.arch == 'ARM64' && needs.createrelease.outputs.upload_url }} + env: + ASSET_NAME: JOSM-${{ runner.os}}-java${{ matrix.java }}-${{ needs.createrelease.outputs.josm_revision }}-aarch64.zip + CONTENT_TYPE: application/zip + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Use `curl` instead of `gh release upload` so we can set the content type + run: | + curl --location \ + --request POST \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: Bearer ${{ env.GITHUB_TOKEN }}" \ + --header "Content-Type: ${{ env.CONTENT_TYPE }}" \ + --header "X-GitHub-Api-Version: 2022-11-28" \ + "$(sed 's/{?name,label}/?name=${{ env.ASSET_NAME }}/g' <<< ${{ needs.createrelease.outputs.upload_url }})" \ + --data-binary "@app/JOSM_${{ matrix.java }}_arm64.zip" - name: Upload Windows Installer executable - if: ${{ runner.os == 'windows' && matrix.java != '8' && matrix.java != '11' && always() }} - uses: actions/upload-release-asset@v1 + if: ${{ runner.os == 'windows' && matrix.java != '11' && always() && needs.createrelease.outputs.upload_url }} env: + ASSET_NAME: JOSM-${{ runner.os}}-java${{ matrix.java }}-${{ needs.createrelease.outputs.josm_revision }}.exe + CONTENT_TYPE: application/vnd.microsoft.portable-executable GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.createrelease.outputs.upload_url }} # This pulls from the CREATE RELEASE job above, referencing its ID to get its outputs object, which include a `upload_url`. - asset_path: app/JOSM.exe - asset_name: JOSM-${{ runner.os}}-java${{ matrix.java }}-${{ needs.createrelease.outputs.josm_revision }}.exe - asset_content_type: application/vnd.microsoft.portable-executable + # Use `curl` instead of `gh release upload` so we can set the content type + run: | + curl --location \ + --request POST \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: Bearer ${{ env.GITHUB_TOKEN }}" \ + --header "Content-Type: ${{ env.CONTENT_TYPE }}" \ + --header "X-GitHub-Api-Version: 2022-11-28" \ + "$(sed 's/{?name,label}/?name=${{ env.ASSET_NAME }}/g' <<< ${{ needs.createrelease.outputs.upload_url }})" \ + --data-binary "@app/JOSM.exe" - name: Upload Windows Installer package - if: ${{ runner.os == 'windows' && matrix.java != '8' && matrix.java != '11' && always() }} - uses: actions/upload-release-asset@v1 + if: ${{ runner.os == 'windows' && matrix.java != '11' && always() && needs.createrelease.outputs.upload_url }} env: + ASSET_NAME: JOSM-${{ runner.os}}-java${{ matrix.java }}-${{ needs.createrelease.outputs.josm_revision }}.msi + CONTENT_TYPE: application/x-ole-storage GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.createrelease.outputs.upload_url }} # This pulls from the CREATE RELEASE job above, referencing its ID to get its outputs object, which include a `upload_url`. - asset_path: app/JOSM.msi - asset_name: JOSM-${{ runner.os}}-java${{ matrix.java }}-${{ needs.createrelease.outputs.josm_revision }}.msi - asset_content_type: application/x-ole-storage + # Use `curl` instead of `gh release upload` so we can set the content type + run: | + curl --location \ + --request POST \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: Bearer ${{ env.GITHUB_TOKEN }}" \ + --header "Content-Type: ${{ env.CONTENT_TYPE }}" \ + --header "X-GitHub-Api-Version: 2022-11-28" \ + "$(sed 's/{?name,label}/?name=${{ env.ASSET_NAME }}/g' <<< ${{ needs.createrelease.outputs.upload_url }})" \ + --data-binary "@app/JOSM.msi" diff --git a/.github/workflows/checkstyle-analysis.yml b/.github/workflows/checkstyle-analysis.yml index 10db916c9b3..796f915e2a7 100644 --- a/.github/workflows/checkstyle-analysis.yml +++ b/.github/workflows/checkstyle-analysis.yml @@ -14,12 +14,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 256 - name: Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.ivy2/cache/ @@ -31,10 +31,10 @@ jobs: ant checkstyle - name: Upload report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: path: checkstyle-josm.xml - - uses: JOSM/JOSMPluginAction/actions/checkstyle@v1 + - uses: JOSM/JOSMPluginAction/actions/checkstyle@v2 with: file: "checkstyle-josm.xml" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 5094b9995aa..e481df37e17 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,6 +13,8 @@ jobs: analyze: name: Analyze runs-on: ubuntu-latest + permissions: + security-events: write strategy: fail-fast: false @@ -21,12 +23,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 256 - name: Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.ivy2/cache/ @@ -34,7 +36,7 @@ jobs: key: ${{ runner.os }}-ivy2-${{ hashFiles('build.xml', 'ivy.xml', 'tools/ivy.xml') }} - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} @@ -43,4 +45,4 @@ jobs: ant compile extract-libraries epsg - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/pmd-analysis.yml b/.github/workflows/pmd-analysis.yml index 77aab389489..0f24198ed74 100644 --- a/.github/workflows/pmd-analysis.yml +++ b/.github/workflows/pmd-analysis.yml @@ -14,12 +14,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 256 - name: Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.ivy2/cache/ @@ -31,11 +31,11 @@ jobs: ant pmd - name: Upload report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: path: pmd-josm.xml - - uses: JOSM/JOSMPluginAction/actions/pmd@v1 + - uses: JOSM/JOSMPluginAction/actions/pmd@v2 with: src: 'src' file: "pmd-josm.xml" diff --git a/.github/workflows/spotbugs-analysis.yml b/.github/workflows/spotbugs-analysis.yml index e165db084a8..85e537e9a49 100644 --- a/.github/workflows/spotbugs-analysis.yml +++ b/.github/workflows/spotbugs-analysis.yml @@ -14,12 +14,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 256 - name: Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.ivy2/cache/ @@ -31,7 +31,7 @@ jobs: ant spotbugs - name: Upload report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: path: spotbugs-josm.xml diff --git a/.project b/.project deleted file mode 100644 index c82979ddfaa..00000000000 --- a/.project +++ /dev/null @@ -1,53 +0,0 @@ - - - JOSM - - - - - - sf.eclipse.javacc.javaccbuilder - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.ui.externaltools.ExternalToolBuilder - full,incremental, - - - LaunchConfigHandle - <project>/.externalToolBuilders/revision.launch - - - - - net.sf.eclipsecs.core.CheckstyleBuilder - - - - - org.sonarlint.eclipse.core.sonarlintBuilder - - - - - net.sourceforge.pmd.eclipse.plugin.pmdBuilder - - - - - - org.sonarlint.eclipse.core.sonarlintNature - org.eclipse.jdt.core.javanature - org.sonar.ide.eclipse.core.sonarNature - sf.eclipse.javacc.javaccnature - net.sf.eclipsecs.core.CheckstyleNature - org.apache.ivyde.eclipse.ivynature - net.sourceforge.pmd.eclipse.plugin.pmdNature - - diff --git a/.settings/edu.umd.cs.findbugs.core.prefs b/.settings/edu.umd.cs.findbugs.core.prefs deleted file mode 100644 index 7d1dbfecaae..00000000000 --- a/.settings/edu.umd.cs.findbugs.core.prefs +++ /dev/null @@ -1,208 +0,0 @@ -#FindBugs User Preferences -#Fri Aug 19 00:50:13 CEST 2016 -cloud_id=edu.umd.cs.findbugs.cloud.doNothingCloud -detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true -detectorAtomicityProblem=AtomicityProblem|true -detectorBadAppletConstructor=BadAppletConstructor|false -detectorBadHexadecimalConversionDetector=BadHexadecimalConversionDetector|true -detectorBadResultSetAccess=BadResultSetAccess|true -detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true -detectorBadUseOfReturnValue=BadUseOfReturnValue|true -detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true -detectorBooleanReturnNull=BooleanReturnNull|true -detectorBroadcastDetector=BroadcastDetector|true -detectorCallToUnsupportedMethod=CallToUnsupportedMethod|true -detectorCheckExpectedWarnings=CheckExpectedWarnings|false -detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true -detectorCheckRelaxingNullnessAnnotation=CheckRelaxingNullnessAnnotation|true -detectorCheckTypeQualifiers=CheckTypeQualifiers|true -detectorCipherWithNoIntegrityDetector=CipherWithNoIntegrityDetector|true -detectorCloneIdiom=CloneIdiom|true -detectorCommandInjectionDetector=CommandInjectionDetector|true -detectorComparatorIdiom=ComparatorIdiom|true -detectorConfusedInheritance=ConfusedInheritance|true -detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true -detectorConstantPasswordDetector=ConstantPasswordDetector|true -detectorCookieFlagsDetector=CookieFlagsDetector|true -detectorCookieReadDetector=CookieReadDetector|true -detectorCovariantArrayAssignment=CovariantArrayAssignment|true -detectorCrlfLogInjectionDetector=CrlfLogInjectionDetector|true -detectorCrossSiteScripting=CrossSiteScripting|true -detectorCustomInjectionDetector=CustomInjectionDetector|true -detectorCustomMessageDigestDetector=CustomMessageDigestDetector|true -detectorDefaultEncodingDetector=DefaultEncodingDetector|true -detectorDesUsageDetector=DesUsageDetector|true -detectorDeserializationGadgetDetector=DeserializationGadgetDetector|true -detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true -detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true -detectorDontIgnoreResultOfPutIfAbsent=DontIgnoreResultOfPutIfAbsent|true -detectorDontUseEnum=DontUseEnum|true -detectorDroppedException=DroppedException|true -detectorDumbMethodInvocations=DumbMethodInvocations|true -detectorDumbMethods=DumbMethods|true -detectorDuplicateBranches=DuplicateBranches|true -detectorEmptyZipFileEntry=EmptyZipFileEntry|true -detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true -detectorEsapiEncryptorDetector=EsapiEncryptorDetector|true -detectorExplicitSerialization=ExplicitSerialization|true -detectorExternalConfigurationControlDetector=ExternalConfigurationControlDetector|true -detectorExternalFileAccessDetector=ExternalFileAccessDetector|true -detectorFileUploadFilenameDetector=FileUploadFilenameDetector|true -detectorFinalizerNullsFields=FinalizerNullsFields|true -detectorFindBadCast2=FindBadCast2|true -detectorFindBadForLoop=FindBadForLoop|true -detectorFindCircularDependencies=FindCircularDependencies|false -detectorFindComparatorProblems=FindComparatorProblems|true -detectorFindDeadLocalStores=FindDeadLocalStores|true -detectorFindDoubleCheck=FindDoubleCheck|true -detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true -detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true -detectorFindFinalizeInvocations=FindFinalizeInvocations|true -detectorFindFloatEquality=FindFloatEquality|true -detectorFindHEmismatch=FindHEmismatch|true -detectorFindInconsistentSync2=FindInconsistentSync2|true -detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true -detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true -detectorFindMaskedFields=FindMaskedFields|true -detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true -detectorFindNakedNotify=FindNakedNotify|true -detectorFindNonShortCircuit=FindNonShortCircuit|true -detectorFindNullDeref=FindNullDeref|true -detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true -detectorFindOpenStream=FindOpenStream|true -detectorFindPuzzlers=FindPuzzlers|true -detectorFindRefComparison=FindRefComparison|true -detectorFindReturnRef=FindReturnRef|true -detectorFindRoughConstants=FindRoughConstants|true -detectorFindRunInvocations=FindRunInvocations|true -detectorFindSelfComparison=FindSelfComparison|true -detectorFindSelfComparison2=FindSelfComparison2|true -detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true -detectorFindSpinLoop=FindSpinLoop|true -detectorFindSqlInjection=FindSqlInjection|true -detectorFindTwoLockWait=FindTwoLockWait|true -detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true -detectorFindUnconditionalWait=FindUnconditionalWait|true -detectorFindUninitializedGet=FindUninitializedGet|true -detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true -detectorFindUnreleasedLock=FindUnreleasedLock|true -detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true -detectorFindUnsyncGet=FindUnsyncGet|true -detectorFindUseOfNonSerializableValue=FindUseOfNonSerializableValue|true -detectorFindUselessControlFlow=FindUselessControlFlow|true -detectorFindUselessObjects=FindUselessObjects|true -detectorFormatStringChecker=FormatStringChecker|true -detectorGeolocationDetector=GeolocationDetector|true -detectorGoogleApiKeyDetector=GoogleApiKeyDetector|true -detectorHazelcastSymmetricEncryptionDetector=HazelcastSymmetricEncryptionDetector|true -detectorHttpResponseSplittingDetector=HttpResponseSplittingDetector|true -detectorHugeSharedStringConstants=HugeSharedStringConstants|true -detectorIDivResultCastToDouble=IDivResultCastToDouble|true -detectorIncompatMask=IncompatMask|true -detectorInconsistentAnnotations=InconsistentAnnotations|true -detectorInefficientIndexOf=InefficientIndexOf|true -detectorInefficientInitializationInsideLoop=InefficientInitializationInsideLoop|true -detectorInefficientMemberAccess=InefficientMemberAccess|false -detectorInefficientToArray=InefficientToArray|true -detectorInfiniteLoop=InfiniteLoop|true -detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true -detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true -detectorInitializationChain=InitializationChain|true -detectorInitializeNonnullFieldsInConstructor=InitializeNonnullFieldsInConstructor|true -detectorInstantiateStaticClass=InstantiateStaticClass|true -detectorInsufficientKeySizeBlowfishDetector=InsufficientKeySizeBlowfishDetector|true -detectorInsufficientKeySizeRsaDetector=InsufficientKeySizeRsaDetector|true -detectorIntCast2LongAsInstant=IntCast2LongAsInstant|true -detectorInvalidJUnitTest=InvalidJUnitTest|true -detectorIteratorIdioms=IteratorIdioms|true -detectorJaxRsEndpointDetector=JaxRsEndpointDetector|true -detectorJaxWsEndpointDetector=JaxWsEndpointDetector|true -detectorJndiCredentialsDetector=JndiCredentialsDetector|true -detectorJspIncludeDetector=JspIncludeDetector|true -detectorJspSpringEvalDetector=JspSpringEvalDetector|true -detectorJstlOutDetector=JstlOutDetector|true -detectorLazyInit=LazyInit|true -detectorLdapInjectionDetector=LdapInjectionDetector|true -detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true -detectorLostLoggerDueToWeakReference=LostLoggerDueToWeakReference|true -detectorMethodReturnCheck=MethodReturnCheck|true -detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true -detectorMutableEnum=MutableEnum|true -detectorMutableLock=MutableLock|true -detectorMutableStaticFields=MutableStaticFields|true -detectorNaming=Naming|true -detectorNoteUnconditionalParamDerefs=NoteUnconditionalParamDerefs|true -detectorNullCipherDetector=NullCipherDetector|true -detectorNumberConstructor=NumberConstructor|true -detectorObjectDeserializationDetector=ObjectDeserializationDetector|true -detectorOptionalReturnNull=OptionalReturnNull|true -detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true -detectorPathTraversalDetector=PathTraversalDetector|true -detectorPlayUnvalidatedRedirectDetector=PlayUnvalidatedRedirectDetector|true -detectorPredictableRandomDetector=PredictableRandomDetector|true -detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true -detectorPublicSemaphores=PublicSemaphores|true -detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true -detectorReDosDetector=ReDosDetector|true -detectorReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass=ReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass|true -detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true -detectorRedundantConditions=RedundantConditions|true -detectorRedundantInterfaces=RedundantInterfaces|true -detectorRepeatedConditionals=RepeatedConditionals|true -detectorRsaNoPaddingDetector=RsaNoPaddingDetector|true -detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true -detectorScriptInjectionDetector=ScriptInjectionDetector|true -detectorSerializableIdiom=SerializableIdiom|true -detectorServletEndpointDetector=ServletEndpointDetector|true -detectorSpringMvcEndpointDetector=SpringMvcEndpointDetector|true -detectorSqlInjectionDetector=SqlInjectionDetector|true -detectorSslDisablerDetector=SslDisablerDetector|true -detectorStartInConstructor=StartInConstructor|true -detectorStaticCalendarDetector=StaticCalendarDetector|true -detectorStaticIvDetector=StaticIvDetector|true -detectorStdXmlTransformDetector=StdXmlTransformDetector|true -detectorStringConcatenation=StringConcatenation|true -detectorStruts1EndpointDetector=Struts1EndpointDetector|true -detectorStruts2EndpointDetector=Struts2EndpointDetector|true -detectorStrutsValidatorFormDetector=StrutsValidatorFormDetector|true -detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true -detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true -detectorSwitchFallthrough=SwitchFallthrough|true -detectorSynchronizationOnSharedBuiltinConstant=SynchronizationOnSharedBuiltinConstant|true -detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true -detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true -detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true -detectorTapestryEndpointDetector=TapestryEndpointDetector|true -detectorTrustBoundaryViolationDetector=TrustBoundaryViolationDetector|true -detectorURLProblems=URLProblems|true -detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true -detectorUnencryptedSocketDetector=UnencryptedSocketDetector|true -detectorUnnecessaryMath=UnnecessaryMath|true -detectorUnreadFields=UnreadFields|true -detectorUnvalidatedRedirectDetector=UnvalidatedRedirectDetector|true -detectorUselessSubclassMethod=UselessSubclassMethod|true -detectorVarArgsProblems=VarArgsProblems|true -detectorVolatileUsage=VolatileUsage|true -detectorWaitInLoop=WaitInLoop|true -detectorWeakFilenameUtilsMethodDetector=WeakFilenameUtilsMethodDetector|true -detectorWeakMessageDigestDetector=WeakMessageDigestDetector|true -detectorWeakTrustManagerDetector=WeakTrustManagerDetector|true -detectorWebViewJavascriptEnabledDetector=WebViewJavascriptEnabledDetector|true -detectorWebViewJavascriptInterfaceDetector=WebViewJavascriptInterfaceDetector|true -detectorWicketEndpointDetector=WicketEndpointDetector|true -detectorWorldWritableDetector=WorldWritableDetector|true -detectorWrongMapIterator=WrongMapIterator|true -detectorXMLFactoryBypass=XMLFactoryBypass|true -detectorXPathInjectionDetector=XPathInjectionDetector|true -detectorXSSRequestWrapperDetector=XSSRequestWrapperDetector|true -detectorXmlDecoderDetector=XmlDecoderDetector|true -detectorXslTransformJspDetector=XslTransformJspDetector|true -detectorXssJspDetector=XssJspDetector|true -detectorXssServletDetector=XssServletDetector|true -detectorXxeDetector=XxeDetector|true -detector_threshold=3 -effort=max -excludefilter0=tools/spotbugs/josm-filter.xml|true -filter_settings=Low|BAD_PRACTICE,CORRECTNESS,EXPERIMENTAL,I18N,MALICIOUS_CODE,MT_CORRECTNESS,PERFORMANCE,SECURITY,STYLE|false|20 -filter_settings_neg=NOISE| -run_at_full_build=true diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs deleted file mode 100644 index 5eb2eeb7892..00000000000 --- a/.settings/org.eclipse.core.resources.prefs +++ /dev/null @@ -1,14 +0,0 @@ -eclipse.preferences.version=1 -encoding//resources/data/validator/addresses.mapcss=UTF-8 -encoding//resources/data/validator/combinations.mapcss=UTF-8 -encoding//resources/data/validator/deprecated.mapcss=UTF-8 -encoding//resources/data/validator/geometry.mapcss=UTF-8 -encoding//resources/data/validator/highway.mapcss=UTF-8 -encoding//resources/data/validator/multiple.mapcss=UTF-8 -encoding//resources/data/validator/numeric.mapcss=UTF-8 -encoding//resources/data/validator/relation.mapcss=UTF-8 -encoding//resources/data/validator/religion.mapcss=UTF-8 -encoding//resources/data/validator/territories.mapcss=UTF-8 -encoding//resources/data/validator/unnecessary.mapcss=UTF-8 -encoding//resources/data/validator/wikipedia.mapcss=UTF-8 -encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index bf54e3e3798..00000000000 --- a/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,401 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled -org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore -org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull -org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault -org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable -org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled -org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 -org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.8 -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.doc.comment.support=enabled -org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning -org.eclipse.jdt.core.compiler.problem.assertIdentifier=error -org.eclipse.jdt.core.compiler.problem.autoboxing=ignore -org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning -org.eclipse.jdt.core.compiler.problem.deadCode=warning -org.eclipse.jdt.core.compiler.problem.deprecation=warning -org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled -org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled -org.eclipse.jdt.core.compiler.problem.discouragedReference=warning -org.eclipse.jdt.core.compiler.problem.emptyStatement=warning -org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore -org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore -org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled -org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore -org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning -org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning -org.eclipse.jdt.core.compiler.problem.forbiddenReference=error -org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning -org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled -org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning -org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore -org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore -org.eclipse.jdt.core.compiler.problem.invalidJavadoc=warning -org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled -org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=disabled -org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=disabled -org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private -org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore -org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning -org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore -org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning -org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled -org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning -org.eclipse.jdt.core.compiler.problem.missingJavadocComments=warning -org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled -org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=public -org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag -org.eclipse.jdt.core.compiler.problem.missingJavadocTags=warning -org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=enabled -org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=enabled -org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=private -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled -org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore -org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore -org.eclipse.jdt.core.compiler.problem.noEffectAssignment=error -org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning -org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore -org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning -org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error -org.eclipse.jdt.core.compiler.problem.nullReference=warning -org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error -org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning -org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning -org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore -org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning -org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning -org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore -org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning -org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning -org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning -org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning -org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore -org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore -org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore -org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled -org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning -org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled -org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled -org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled -org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore -org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning -org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled -org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning -org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning -org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore -org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning -org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore -org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning -org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled -org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore -org.eclipse.jdt.core.compiler.problem.unusedImport=warning -org.eclipse.jdt.core.compiler.problem.unusedLabel=warning -org.eclipse.jdt.core.compiler.problem.unusedLocal=warning -org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore -org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore -org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled -org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled -org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled -org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning -org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore -org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning -org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning -org.eclipse.jdt.core.compiler.source=1.8 -org.eclipse.jdt.core.formatter.align_type_members_on_columns=false -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_assignment=0 -org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 -org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 -org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 -org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 -org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 -org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 -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_resources_in_try=80 -org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=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_type_declaration=16 -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_union_type_in_multicatch=16 -org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 -org.eclipse.jdt.core.formatter.blank_lines_after_package=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_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_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_switch=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.comment.clear_blank_lines=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.format_block_comments=false -org.eclipse.jdt.core.formatter.comment.format_comments=true -org.eclipse.jdt.core.formatter.comment.format_header=false -org.eclipse.jdt.core.formatter.comment.format_html=false -org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=false -org.eclipse.jdt.core.formatter.comment.format_line_comments=false -org.eclipse.jdt.core.formatter.comment.format_source_code=false -org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true -org.eclipse.jdt.core.formatter.comment.indent_root_tags=false -org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=do not insert -org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert -org.eclipse.jdt.core.formatter.comment.line_length=100 -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=true -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_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_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_new_line_in_empty_annotation_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=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_binary_operator=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=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_superinterfaces=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_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_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_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_unary_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=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_binary_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_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_superinterfaces=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_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_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_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_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_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_lines_in_comments=true -org.eclipse.jdt.core.formatter.join_wrapped_lines=true -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_imple_if_on_one_line=false -org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false -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_at_beginning_of_method_body=0 -org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 -org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true -org.eclipse.jdt.core.formatter.tabulation.char=space -org.eclipse.jdt.core.formatter.tabulation.size=4 -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_binary_operator=true -org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true -org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs deleted file mode 100644 index 99c98f45352..00000000000 --- a/.settings/org.eclipse.jdt.ui.prefs +++ /dev/null @@ -1,65 +0,0 @@ -cleanup_settings_version=2 -eclipse.preferences.version=1 -editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true -formatter_profile=_josm -formatter_settings_version=12 -org.eclipse.jdt.ui.javadoc=false -org.eclipse.jdt.ui.text.custom_code_templates= -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=false -sp_cleanup.add_serial_version_id=false -sp_cleanup.always_use_blocks=false -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.convert_functional_interfaces=false -sp_cleanup.convert_to_enhanced_for_loop=false -sp_cleanup.correct_indentation=false -sp_cleanup.format_source_code=false -sp_cleanup.format_source_code_changes_only=true -sp_cleanup.insert_inferred_type_arguments=false -sp_cleanup.make_local_variable_final=false -sp_cleanup.make_parameters_final=false -sp_cleanup.make_private_fields_final=false -sp_cleanup.make_type_abstract_if_missing_method=false -sp_cleanup.make_variable_declarations_final=false -sp_cleanup.never_use_blocks=false -sp_cleanup.never_use_parentheses_in_expressions=true -sp_cleanup.on_save_use_additional_actions=true -sp_cleanup.organize_imports=true -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.remove_private_constructors=true -sp_cleanup.remove_redundant_type_arguments=false -sp_cleanup.remove_trailing_whitespaces=true -sp_cleanup.remove_trailing_whitespaces_all=true -sp_cleanup.remove_trailing_whitespaces_ignore_empty=false -sp_cleanup.remove_unnecessary_casts=false -sp_cleanup.remove_unnecessary_nls_tags=false -sp_cleanup.remove_unused_imports=false -sp_cleanup.remove_unused_local_variables=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.sort_members=false -sp_cleanup.sort_members_all=false -sp_cleanup.use_anonymous_class_creation=false -sp_cleanup.use_blocks=false -sp_cleanup.use_blocks_only_for_return_and_throw=true -sp_cleanup.use_lambda=false -sp_cleanup.use_parentheses_in_expressions=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_type_arguments=false diff --git a/.settings/org.eclipse.wst.validation.prefs b/.settings/org.eclipse.wst.validation.prefs deleted file mode 100644 index 8cfcd9b1a26..00000000000 --- a/.settings/org.eclipse.wst.validation.prefs +++ /dev/null @@ -1,9 +0,0 @@ -DELEGATES_PREFERENCE=delegateValidatorList -USER_BUILD_PREFERENCE=enabledBuildValidatorListorg.eclipse.wst.wsi.ui.internal.WSIMessageValidator; -USER_MANUAL_PREFERENCE=enabledManualValidatorListorg.eclipse.wst.wsi.ui.internal.WSIMessageValidator; -USER_PREFERENCE=overrideGlobalPreferencestruedisableAllValidationfalseversion1.2.600.v201501211647 -eclipse.preferences.version=1 -override=true -suspend=false -vals/org.eclipse.wst.xml.core.xml/groups=0107include05111contentType128org.eclipse.core.runtime.xmlT111contentType134org.eclipse.wst.xml.core.xmlsourceT111contentType134org.eclipse.wst.xml.core.xslsourceT111contentType134org.eclipse.jst.jsp.core.tldsourceT07fileext03xmlF0107exclude06113projectNature134org.eclipse.jst.j2ee.ejb.EJBNature113projectNature130org.eclipse.jst.j2ee.EARNature04file08.projectT0104file110.classpathT0104file110.settings/T0204file114test/data/wmtsF02 -vf.version=3 diff --git a/.settings/org.sonar.ide.eclipse.core.prefs b/.settings/org.sonar.ide.eclipse.core.prefs deleted file mode 100644 index 4b9cf970849..00000000000 --- a/.settings/org.sonar.ide.eclipse.core.prefs +++ /dev/null @@ -1,6 +0,0 @@ -eclipse.preferences.version=1 -extraProperties= -lastAnalysisDate=1444606545000 -projectKey=josm -serverUrl=https\://josm.openstreetmap.de/sonar -version=2 diff --git a/.settings/org.sonarlint.eclipse.core.prefs b/.settings/org.sonarlint.eclipse.core.prefs deleted file mode 100644 index de4f4421a15..00000000000 --- a/.settings/org.sonarlint.eclipse.core.prefs +++ /dev/null @@ -1,5 +0,0 @@ -autoEnabled=true -eclipse.preferences.version=1 -extraProperties= -moduleKey=josm -serverId=josm diff --git a/.settings/sf.eclipse.javacc.prefs b/.settings/sf.eclipse.javacc.prefs deleted file mode 100644 index 41e4d2d7107..00000000000 --- a/.settings/sf.eclipse.javacc.prefs +++ /dev/null @@ -1,14 +0,0 @@ -CLEAR_CONSOLE=true -FORMAT_BEFORE_SAVE=false -JAVACC_OPTIONS=-JDK_VERSION\=1.8 -GRAMMAR_ENCODING\=UTF-8 -JJDOC_OPTIONS= -JJTREE_OPTIONS= -JJ_NATURE=true -JTB_OPTIONS=-ia -jd -tk -KEEP_DEL_FILES_IN_HISTORY=false -MARK_GEN_FILES_AS_DERIVED=true -RUNTIME_JJJAR=${project_loc}/lib/tools/javacc-jar.jar -RUNTIME_JTBJAR=${eclipse_home}/plugins/sf.eclipse.javacc_1.5.27/jtb-1.4.7.jar -RUNTIME_JVMOPTIONS= -SUPPRESS_WARNINGS=false -eclipse.preferences.version=1 diff --git a/README b/README index 6fbc68f13c6..420f90045ec 100644 --- a/README +++ b/README @@ -9,12 +9,12 @@ Installation notes To run JOSM, you need: * The JOSM .jar file, e.g., josm-tested.jar or josm-latest.jar -* Java Runtime Environment (JRE) 8, or later. +* Java Runtime Environment (JRE) 11, or later. How to get Java Runtime Environment ----------------------------------- -You need JRE Version 8, or later. +You need JRE Version 11, or later. Microsoft Windows and Apple macOS users should visit one of: - https://www.azul.com/downloads/?package=jdk#download-openjdk diff --git a/build.xml b/build.xml index e361bf7ddd4..9a061b4f0f1 100644 --- a/build.xml +++ b/build.xml @@ -71,13 +71,14 @@ - - + + @@ -92,16 +93,15 @@ - - - - + - + + - + + - + @@ -109,9 +109,7 @@ - - - + @@ -171,6 +169,8 @@ Build-Date: ${build.tstamp} + + @@ -220,16 +220,9 @@ Build-Date: ${build.tstamp} - - - - - - - @@ -255,7 +248,16 @@ Build-Date: ${build.tstamp} - + + + + + + + + + + @@ -300,12 +302,6 @@ Build-Date: ${build.tstamp} - - - - - - @@ -344,8 +340,7 @@ Build-Date: ${build.tstamp} private="true" linksource="true" author="false"> - - + JOSM — Javadoc @@ -353,16 +348,15 @@ Build-Date: ${build.tstamp} JOSM]]> - - - + + + - @@ -466,33 +460,29 @@ Build-Date: ${build.tstamp} - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + @@ -716,11 +706,7 @@ Build-Date: ${build.tstamp} - - - - - + @@ -882,29 +868,21 @@ Build-Date: ${build.tstamp} - + - - - - - - + - + - + + - - + ${pmd.dir}/josm-ruleset.xml @@ -948,6 +926,7 @@ Build-Date: ${build.tstamp} + diff --git a/ivy.xml b/ivy.xml index 84376f0baa9..009f2684245 100644 --- a/ivy.xml +++ b/ivy.xml @@ -20,66 +20,64 @@ - + - + - - - + + + - + - - + - + - + - - - - + + + + - + - - - + + - + - + - + - - - - - - - - - + + + + + + + + + - - - + + + diff --git a/ivysettings.xml b/ivysettings.xml index f02e7f83064..6e9cdfcff9f 100644 --- a/ivysettings.xml +++ b/ivysettings.xml @@ -5,7 +5,4 @@ - - - diff --git a/native/josm-latest.jnlp b/native/josm-latest.jnlp index 31fbd49f6fb..2c4522e78ed 100644 --- a/native/josm-latest.jnlp +++ b/native/josm-latest.jnlp @@ -20,9 +20,8 @@ - + - diff --git a/native/josm.jnlp b/native/josm.jnlp index db8f96a4da1..3e31513c3cc 100644 --- a/native/josm.jnlp +++ b/native/josm.jnlp @@ -20,8 +20,9 @@ - + + diff --git a/native/linux/latest/DEBIAN/control b/native/linux/latest/DEBIAN/control index c940cb9b0ec..957f01cded7 100644 --- a/native/linux/latest/DEBIAN/control +++ b/native/linux/latest/DEBIAN/control @@ -5,7 +5,7 @@ Maintainer: josm developers Homepage: https://josm.openstreetmap.de Priority: extra Architecture: all -Depends: default-jre (>= 2:1.17) | java8-runtime, +Depends: default-jre (>= 2:1.17) | java11-runtime, proj-data, fonts-noto, openjfx Description: Editor for OpenStreetMap (daily development snapshot) JOSM is an editor for OpenStreetMap (OSM) written in Java. diff --git a/native/linux/latest/usr/bin/josm-latest b/native/linux/latest/usr/bin/josm-latest index 72028f04759..6b13e674d0f 100755 --- a/native/linux/latest/usr/bin/josm-latest +++ b/native/linux/latest/usr/bin/josm-latest @@ -21,20 +21,22 @@ dpkg_java() { if dpkg --get-selections "openjdk-*-jre" | grep install$ > /dev/null \ || dpkg --get-selections "openjdk-*-jre:$ARCH" | grep install$ > /dev/null ; then # LTS versions in decreased order + JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-21-openjdk/bin/java /usr/lib/jvm/java-21-openjdk-$ARCH/bin/java" JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-17-openjdk/bin/java /usr/lib/jvm/java-17-openjdk-$ARCH/bin/java" JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-11-openjdk/bin/java /usr/lib/jvm/java-11-openjdk-$ARCH/bin/java" - JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-8-openjdk/bin/java /usr/lib/jvm/java-8-openjdk-$ARCH/bin/java" # Released versions in decreased order + JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-22-openjdk/bin/java /usr/lib/jvm/java-22-openjdk-$ARCH/bin/java" + # EOL versions in decreased order + JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-20-openjdk/bin/java /usr/lib/jvm/java-20-openjdk-$ARCH/bin/java" + JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-19-openjdk/bin/java /usr/lib/jvm/java-19-openjdk-$ARCH/bin/java" JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-18-openjdk/bin/java /usr/lib/jvm/java-18-openjdk-$ARCH/bin/java" JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-16-openjdk/bin/java /usr/lib/jvm/java-16-openjdk-$ARCH/bin/java" JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-15-openjdk/bin/java /usr/lib/jvm/java-15-openjdk-$ARCH/bin/java" JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-14-openjdk/bin/java /usr/lib/jvm/java-14-openjdk-$ARCH/bin/java" JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-13-openjdk/bin/java /usr/lib/jvm/java-13-openjdk-$ARCH/bin/java" JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-12-openjdk/bin/java /usr/lib/jvm/java-12-openjdk-$ARCH/bin/java" - JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-10-openjdk/bin/java /usr/lib/jvm/java-10-openjdk-$ARCH/bin/java" - JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-9-openjdk/bin/java /usr/lib/jvm/java-9-openjdk-$ARCH/bin/java" # Development version(s) - JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-19-openjdk/bin/java /usr/lib/jvm/java-19-openjdk-$ARCH/bin/java" + JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-23-openjdk/bin/java /usr/lib/jvm/java-23-openjdk-$ARCH/bin/java" fi } diff --git a/native/linux/tested/DEBIAN/control b/native/linux/tested/DEBIAN/control index 10087099cc2..02961f8929e 100644 --- a/native/linux/tested/DEBIAN/control +++ b/native/linux/tested/DEBIAN/control @@ -5,7 +5,7 @@ Maintainer: josm developers Homepage: https://josm.openstreetmap.de Priority: extra Architecture: all -Depends: default-jre (>= 2:1.17) | java8-runtime, +Depends: default-jre (>= 2:1.17) | java11-runtime, proj-data, fonts-noto, openjfx Conflicts: josm-plugins Replaces: josm-plugins diff --git a/native/linux/tested/usr/bin/josm b/native/linux/tested/usr/bin/josm index 03a0963489f..d2e18bac141 100755 --- a/native/linux/tested/usr/bin/josm +++ b/native/linux/tested/usr/bin/josm @@ -21,20 +21,22 @@ dpkg_java() { if dpkg --get-selections "openjdk-*-jre" | grep install$ > /dev/null \ || dpkg --get-selections "openjdk-*-jre:$ARCH" | grep install$ > /dev/null ; then # LTS versions in decreased order + JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-21-openjdk/bin/java /usr/lib/jvm/java-21-openjdk-$ARCH/bin/java" JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-17-openjdk/bin/java /usr/lib/jvm/java-17-openjdk-$ARCH/bin/java" JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-11-openjdk/bin/java /usr/lib/jvm/java-11-openjdk-$ARCH/bin/java" - JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-8-openjdk/bin/java /usr/lib/jvm/java-8-openjdk-$ARCH/bin/java" # Released versions in decreased order + JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-22-openjdk/bin/java /usr/lib/jvm/java-22-openjdk-$ARCH/bin/java" + # EOL versions in decreased order + JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-20-openjdk/bin/java /usr/lib/jvm/java-20-openjdk-$ARCH/bin/java" + JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-19-openjdk/bin/java /usr/lib/jvm/java-19-openjdk-$ARCH/bin/java" JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-18-openjdk/bin/java /usr/lib/jvm/java-18-openjdk-$ARCH/bin/java" JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-16-openjdk/bin/java /usr/lib/jvm/java-16-openjdk-$ARCH/bin/java" JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-15-openjdk/bin/java /usr/lib/jvm/java-15-openjdk-$ARCH/bin/java" JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-14-openjdk/bin/java /usr/lib/jvm/java-14-openjdk-$ARCH/bin/java" JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-13-openjdk/bin/java /usr/lib/jvm/java-13-openjdk-$ARCH/bin/java" JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-12-openjdk/bin/java /usr/lib/jvm/java-12-openjdk-$ARCH/bin/java" - JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-10-openjdk/bin/java /usr/lib/jvm/java-10-openjdk-$ARCH/bin/java" - JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-9-openjdk/bin/java /usr/lib/jvm/java-9-openjdk-$ARCH/bin/java" # Development version(s) - JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-19-openjdk/bin/java /usr/lib/jvm/java-19-openjdk-$ARCH/bin/java" + JAVA_CMDS="${JAVA_CMDS} /usr/lib/jvm/java-23-openjdk/bin/java /usr/lib/jvm/java-23-openjdk-$ARCH/bin/java" fi } diff --git a/native/macosx/macos-jpackage.sh b/native/macosx/macos-jpackage.sh index dbbb948bae6..8e45abb56a4 100755 --- a/native/macosx/macos-jpackage.sh +++ b/native/macosx/macos-jpackage.sh @@ -15,7 +15,7 @@ IMPORT_AND_UNLOCK_KEYCHAIN=${IMPORT_AND_UNLOCK_KEYCHAIN:-1} if [ -z "${1-}" ] then - echo "Usage: $0 josm_revision" + echo "Usage: $0 josm_revision [other_arch_jdk]" exit 1 fi @@ -52,46 +52,117 @@ fi set -u -echo "Building and signing app" -# We specifically need the options to not be quoted -- we _want_ the word splitting. -# shellcheck disable=SC2086 -jpackage $JPACKAGEOPTIONS -n "JOSM" --input dist --main-jar josm-custom.jar \ - --main-class org.openstreetmap.josm.gui.MainApplication \ - --icon ./native/macosx/JOSM.icns --type app-image --dest app \ - --java-options "--add-modules java.scripting,java.sql,javafx.controls,javafx.media,javafx.swing,javafx.web" \ - --java-options "--add-exports=java.base/sun.security.action=ALL-UNNAMED" \ - --java-options "--add-exports=java.desktop/com.apple.eawt=ALL-UNNAMED" \ - --java-options "--add-exports=java.desktop/com.sun.imageio.plugins.jpeg=ALL-UNNAMED" \ - --java-options "--add-exports=java.desktop/com.sun.imageio.spi=ALL-UNNAMED" \ - --java-options "--add-opens=java.base/java.lang=ALL-UNNAMED" \ - --java-options "--add-opens=java.base/java.nio=ALL-UNNAMED" \ - --java-options "--add-opens=java.base/jdk.internal.loader=ALL-UNNAMED" \ - --java-options "--add-opens=java.base/jdk.internal.ref=ALL-UNNAMED" \ - --java-options "--add-opens=java.desktop/javax.imageio.spi=ALL-UNNAMED" \ - --java-options "--add-opens=java.desktop/javax.swing.text.html=ALL-UNNAMED" \ - --java-options "--add-opens=java.prefs/java.util.prefs=ALL-UNNAMED" \ - --app-version "$1" \ - --copyright "JOSM, and all its integral parts, are released under the GNU General Public License v2 or later" \ - --vendor "JOSM" \ - --mac-package-identifier de.openstreetmap.josm \ - --mac-package-signing-prefix de.openstreetmap.josm \ - --file-associations native/file-associations/bz2.properties \ - --file-associations native/file-associations/geojson.properties \ - --file-associations native/file-associations/gpx.properties \ - --file-associations native/file-associations/gz.properties \ - --file-associations native/file-associations/jos.properties \ - --file-associations native/file-associations/joz.properties \ - --file-associations native/file-associations/osm.properties \ - --file-associations native/file-associations/xz.properties \ - --file-associations native/file-associations/zip.properties \ - --add-modules java.compiler,java.base,java.datatransfer,java.desktop,java.logging,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.sql,java.transaction.xa,java.xml,jdk.crypto.ec,jdk.jfr,jdk.jsobject,jdk.unsupported,jdk.unsupported.desktop,jdk.xml.dom,javafx.controls,javafx.media,javafx.swing,javafx.web - -echo "Building done." - -if $SIGNAPP; then - echo "Preparing for notarization" - ditto -c -k --zlibCompressionLevel 9 --keepParent app/JOSM.app app/JOSM.zip - - echo "Uploading to Apple" - xcrun notarytool submit --apple-id "$APPLE_ID" --password "$APPLE_ID_PW" --team-id "$APPLE_ID_TEAM" --wait app/JOSM.zip +function do_jpackage() { + echo "Building app (${JAVA_HOME})" + # We specifically need the options to not be quoted -- we _want_ the word splitting. + # shellcheck disable=SC2086 + "${JAVA_HOME}/bin/jpackage" $JPACKAGEOPTIONS -n "JOSM" --input dist --main-jar josm-custom.jar \ + --main-class org.openstreetmap.josm.gui.MainApplication \ + --icon ./native/macosx/JOSM.icns --type app-image --dest app \ + --java-options "--add-modules java.scripting,java.sql,javafx.controls,javafx.media,javafx.swing,javafx.web" \ + --java-options "--add-exports=java.base/sun.security.action=ALL-UNNAMED" \ + --java-options "--add-exports=java.desktop/com.apple.eawt=ALL-UNNAMED" \ + --java-options "--add-exports=java.desktop/com.sun.imageio.plugins.jpeg=ALL-UNNAMED" \ + --java-options "--add-exports=java.desktop/com.sun.imageio.spi=ALL-UNNAMED" \ + --java-options "--add-opens=java.base/java.lang=ALL-UNNAMED" \ + --java-options "--add-opens=java.base/java.nio=ALL-UNNAMED" \ + --java-options "--add-opens=java.base/jdk.internal.loader=ALL-UNNAMED" \ + --java-options "--add-opens=java.base/jdk.internal.ref=ALL-UNNAMED" \ + --java-options "--add-opens=java.desktop/javax.imageio.spi=ALL-UNNAMED" \ + --java-options "--add-opens=java.desktop/javax.swing.text.html=ALL-UNNAMED" \ + --java-options "--add-opens=java.prefs/java.util.prefs=ALL-UNNAMED" \ + --app-version "$1" \ + --copyright "JOSM, and all its integral parts, are released under the GNU General Public License v2 or later" \ + --vendor "JOSM" \ + --mac-package-identifier de.openstreetmap.josm \ + --mac-package-signing-prefix de.openstreetmap.josm \ + --file-associations native/file-associations/bz2.properties \ + --file-associations native/file-associations/geojson.properties \ + --file-associations native/file-associations/gpx.properties \ + --file-associations native/file-associations/gz.properties \ + --file-associations native/file-associations/jos.properties \ + --file-associations native/file-associations/joz.properties \ + --file-associations native/file-associations/osm.properties \ + --file-associations native/file-associations/xz.properties \ + --file-associations native/file-associations/zip.properties \ + --add-modules java.compiler,java.base,java.datatransfer,java.desktop,java.logging,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.sql,java.transaction.xa,java.xml,jdk.crypto.ec,jdk.jfr,jdk.jsobject,jdk.unsupported,jdk.unsupported.desktop,jdk.xml.dom,javafx.controls,javafx.media,javafx.swing,javafx.web + echo "Building done (${JAVA_HOME})." +} +function do_signapp() { + echo "Compressing app (${1})" + ditto -c -k --zlibCompressionLevel 9 --keepParent "app/${1}.app" "app/${1}.zip" + if $SIGNAPP; then + echo "Signing app (${1})" + echo "Preparing for notarization" + echo "Uploading to Apple" + xcrun notarytool submit --apple-id "$APPLE_ID" --password "$APPLE_ID_PW" --team-id "$APPLE_ID_TEAM" --wait "app/${1}.zip" + fi +} + +function merge() { + if [ "$(command -v lipo)" ]; then + lipo -create -output "${1}" "${2}" "${3}" + elif [ "$(command -v llvm-lipo-15)" ]; then + llvm-lipo-15 -create -output "${1}" "${2}" "${3}" + fi +} + +function copy() { + # Trim the root path + FILE="${1#*/}" + if [ ! -e "${2}/${FILE}" ]; then + # Only make directories if we aren't looking at the root files + if [[ "${FILE}" == *"/"* ]]; then mkdir -p "${2}/${FILE%/*}"; fi + if file "${1}" | grep -q 'Mach-O' ; then + merge "${2}/${FILE}" "${3}/${FILE}" "${4}/${FILE}" + if file "${1}" | grep -q 'executable'; then + chmod 755 "${2}/${FILE}" + fi + else + cp -a "${1}" "${2}/${FILE}" + fi + fi +} + +function directory_iterate() { + while IFS= read -r -d '' file + do + copy "${file}" "${2}" "${3}" "${4}" & + done < <(find "${1}" -type f,l -print0) + wait +} + +do_jpackage "${1}" +if [ -n "${2}" ]; then + function get_name() { + echo "$("${JAVA_HOME}/bin/java" --version | head -n1 | awk '{print $2}' | awk -F'.' '{print $1}')_$(file "${JAVA_HOME}/bin/java" | awk -F' executable ' '{print $2}')" + } + first="$(get_name)" + JAVA_HOME="${2}" second="$(get_name)" + mv app/JOSM.app "app/JOSM_${first}.app" + JAVA_HOME="${2}" do_jpackage "${1}" + mv app/JOSM.app "app/JOSM_${second}.app" + mkdir app/JOSM.app + (cd app + directory_iterate "JOSM_${first}.app" "JOSM.app" "JOSM_${first}.app" "JOSM_${second}.app" + directory_iterate "JOSM_${second}.app" "JOSM.app" "JOSM_${first}.app" "JOSM_${second}.app" + ) + do_signapp "JOSM_${first}" + do_signapp "JOSM_${second}" + if [ "${KEYCHAINPATH}" != "false" ]; then + function do_codesign() { + codesign --sign "FOSSGIS e.V." \ + --force \ + --keychain "${KEYCHAINPATH}" \ + --timestamp \ + --prefix "de.openstreetmap.josm" \ + --identifier "${2}" \ + --options runtime \ + --entitlements "$(dirname "${BASH_SOURCE[0]}")/josm.entitlements" \ + --verbose=4 "${1}" + } + do_codesign app/JOSM.app/Contents/runtime "com.oracle.java.de.openstreetmap.josm" + do_codesign app/JOSM.app/ "de.openstreetmap.josm" + fi fi +do_signapp JOSM diff --git a/native/windows/MLConsole.properties b/native/windows/MLConsole.properties index a8391ac5c5d..50ee17df91b 100644 --- a/native/windows/MLConsole.properties +++ b/native/windows/MLConsole.properties @@ -1 +1,2 @@ win-console=true +description=Start JOSM with console output diff --git a/native/windows/win-jpackage.sh b/native/windows/win-jpackage.sh index 03146156242..5626cedb70a 100755 --- a/native/windows/win-jpackage.sh +++ b/native/windows/win-jpackage.sh @@ -81,7 +81,7 @@ do --file-associations native/file-associations/osm.properties \ --file-associations native/file-associations/xz.properties \ --file-associations native/file-associations/zip.properties \ - --add-launcher HWConsole=native/windows/MLConsole.properties \ + --add-launcher "JOSM (Debug)"=native/windows/MLConsole.properties \ --add-modules java.compiler,java.base,java.datatransfer,java.desktop,java.logging,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.sql,java.transaction.xa,java.xml,jdk.crypto.ec,jdk.jfr,jdk.jsobject,jdk.unsupported,jdk.unsupported.desktop,jdk.xml.dom,javafx.controls,javafx.media,javafx.swing,javafx.web done diff --git a/nodist/data/9304-examples.osm b/nodist/data/9304-examples.osm new file mode 100644 index 00000000000..2360a41cd49 --- /dev/null +++ b/nodist/data/9304-examples.osm @@ -0,0 +1,406 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nodist/pom.xml b/nodist/pom.xml new file mode 100644 index 00000000000..09275d5cc35 --- /dev/null +++ b/nodist/pom.xml @@ -0,0 +1,372 @@ + + + 4.0.0 + org.openstreetmap.josm + josm-parent + 1.0-SNAPSHOT + pom + + US-ASCII + US-ASCII + 11 + ${java.lang.version} + ${maven.compiler.release} + ${maven.compiler.release} + true + https://josm.openstreetmap.de/sonar/ + JOSM + 1.49.a + 7.2.0 + + + .. + + ../test/pom.xml + + + + JOSM-central + https://josm.openstreetmap.de/nexus/content/repositories/central/ + + + JOSM-releases + https://josm.openstreetmap.de/nexus/content/repositories/releases/ + + + JOSM-snapshots + https://josm.openstreetmap.de/nexus/content/repositories/snapshots/ + + + JOSM-osgeo + https://josm.openstreetmap.de/nexus/content/repositories/osgeo/ + + + + + JOSM-central + https://josm.openstreetmap.de/nexus/content/repositories/central/ + + + + + + + org.codehaus.mojo + javacc-maven-plugin + 3.1.0 + + + org.apache.maven.plugins + maven-clean-plugin + 3.3.2 + + + org.codehaus.mojo + exec-maven-plugin + 3.2.0 + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + org.apache.maven.plugins + maven-pmd-plugin + 3.23.0 + + + net.sourceforge.pmd + pmd-core + ${pmd.version} + + + net.sourceforge.pmd + pmd-java + ${pmd.version} + + + net.sourceforge.pmd + pmd-javascript + ${pmd.version} + + + net.sourceforge.pmd + pmd-jsp + ${pmd.version} + + + + ${java.lang.version} + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + + com.github.spotbugs + spotbugs-maven-plugin + 4.8.5.0 + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.3.1 + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.3 + + + package + + shade + + + + + *:* + + + + META-INF/versions/* + META-INF/maven/** + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.4.1 + + + false + + + + + org.sonarsource.scanner.maven + sonar-maven-plugin + 3.7.0.1746 + + + + org.codehaus.mojo + buildnumber-maven-plugin + 3.2.0 + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.4.1 + + + enforce-versions + + enforce + + + + + 3.6.3 + + + 8 + + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + copy-root-resources + generate-resources + + copy-resources + + + ${project.basedir}/target/classes + + + ${project.basedir} + + GPL-v2.0.txt + GPL-v3.0.txt + LICENSE + LICENSE.md + README + README.md + + + + + + + + + + + + + org.jmockit + jmockit + ${jmockit.version} + test + + + org.junit + junit-bom + 5.10.2 + pom + import + + + org.openstreetmap.jmapviewer + jmapviewer + 2.19 + provided + + + jakarta.json + jakarta.json-api + 2.1.3 + provided + + + org.eclipse.parsson + parsson + 1.1.6 + provided + + + org.apache.commons + commons-jcs3-core + 3.2.1 + provided + + + org.apache.commons + commons-compress + 1.26.2 + provided + + + jakarta.annotation + jakarta.annotation-api + 2.1.1 + provided + + + org.tukaani + xz + 1.9 + provided + + + com.adobe.xmp + xmpcore + 6.1.11 + provided + + + com.drewnoakes + metadata-extractor + 2.19.0 + provided + + + com.formdev + svgSalamander + 1.1.4 + provided + + + ch.poole + OpeningHoursParser + 0.28.2 + provided + + + + org.webjars.npm + tag2link + 2024.2.8 + provided + + + org.jacoco + org.jacoco.ant + 0.8.12 + test + + + com.github.spotbugs + spotbugs-annotations + 4.8.5 + test + + + com.ginsberg + junit5-system-exit + 1.1.2 + test + + + org.wiremock + wiremock + 3.6.0 + test + + + io.github.classgraph + classgraph + 4.8.173 + test + + + net.trajano.commons + commons-testing + 2.1.0 + test + + + nl.jqno.equalsverifier + equalsverifier + 3.16.1 + test + + + org.apache.commons + commons-lang3 + 3.14.0 + test + + + org.awaitility + awaitility + 4.2.1 + test + + + + diff --git a/nodist/trans/fr.lang b/nodist/trans/fr.lang index 2c0f27e5d5f..b7f2d28fc26 100644 Binary files a/nodist/trans/fr.lang and b/nodist/trans/fr.lang differ diff --git a/nodist/trans/nl.lang b/nodist/trans/nl.lang index 94ece7f1c39..586fb2c13f1 100644 Binary files a/nodist/trans/nl.lang and b/nodist/trans/nl.lang differ diff --git a/nodist/trans/pt_BR.lang b/nodist/trans/pt_BR.lang index 1c247fd8bd1..4d8ae16421f 100644 Binary files a/nodist/trans/pt_BR.lang and b/nodist/trans/pt_BR.lang differ diff --git a/nodist/trans/sr-latin.lang b/nodist/trans/sr-latin.lang index 71d166643c1..d30e108dc89 100644 Binary files a/nodist/trans/sr-latin.lang and b/nodist/trans/sr-latin.lang differ diff --git a/nodist/trans/uk.lang b/nodist/trans/uk.lang index b60d7440028..885e887b1fa 100644 Binary files a/nodist/trans/uk.lang and b/nodist/trans/uk.lang differ diff --git a/nodist/trans/zh_TW.lang b/nodist/trans/zh_TW.lang index 02db6464e25..59d8b1e3373 100644 Binary files a/nodist/trans/zh_TW.lang and b/nodist/trans/zh_TW.lang differ diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000000..db2cb4c39a2 --- /dev/null +++ b/pom.xml @@ -0,0 +1,763 @@ + + + 4.0.0 + josm + + org.openstreetmap.josm + josm-parent + 1.0-SNAPSHOT + ./nodist/pom.xml + + + josm + https://josm.openstreetmap.de + + ${project.basedir}/src + ${project.basedir}/test + ${project.basedir}/scripts + ${project.basedir}/build + ${project.basedir}/dist + ${project.basedir}/build2 + ${tools.dir}/checkstyle + ${src.dir}/org/openstreetmap/josm/gui/mappaint/mapcss + ${dist.dir}/modules + ${tools.dir}/pmd + ${project.basedir}/build2 + ${project.basedir}/resources + ${project.basedir}/build2 + ${tools.dir}/spotbugs + ${project.basedir}/tools + ${resources.dir}/data/projection/custom-epsg + ${tools.dir} + ${dist.dir}/josm-custom.jar + ${dist.dir}/josm-custom-optimized.jar + ${dist.dir}/josm-custom-sources.jar + org.openstreetmap.josm.* + false + false + on + **/*Test.class + **/*TestIT.class + UTF-8 + + jar + 2005 + + + GPL-2.0-or-later + https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html + repo + + + + JOSM + https://josm.openstreetmap.de + + + scm:svn:https://josm.openstreetmap.de/svn/trunk + https://josm.openstreetmap.de/browser/josm/trunk + + + Trac + https://josm.openstreetmap.de + + + + josm-dev + https://lists.openstreetmap.org/pipermail/josm-dev/ + josm-dev@openstreetmap.org + + + + + + org.jmockit + jmockit + + + + javax.json + javax.json-api + 1.1.4 + compile + + + org.glassfish + javax.json + 1.1.4 + compile + + + com.google.code.findbugs + jsr305 + 3.0.2 + compile + + + + org.openstreetmap.jmapviewer + jmapviewer + compile + + + jakarta.json + jakarta.json-api + compile + + + org.eclipse.parsson + parsson + runtime + + + org.apache.commons + commons-jcs3-core + compile + + + org.apache.commons + commons-compress + compile + + + jakarta.annotation + jakarta.annotation-api + compile + + + org.tukaani + xz + compile + + + com.adobe.xmp + xmpcore + compile + + + com.drewnoakes + metadata-extractor + compile + + + com.formdev + svgSalamander + compile + + + ch.poole + OpeningHoursParser + compile + + + + org.webjars.npm + tag2link + compile + + + org.jacoco + org.jacoco.ant + + + com.github.spotbugs + spotbugs-annotations + + + com.ginsberg + junit5-system-exit + + + org.wiremock + wiremock + + + io.github.classgraph + classgraph + + + org.junit.platform + junit-platform-launcher + test + + + org.junit.platform + junit-platform-suite + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-migrationsupport + test + + + net.trajano.commons + commons-testing + + + nl.jqno.equalsverifier + equalsverifier + + + org.apache.commons + commons-lang3 + + + org.awaitility + awaitility + + + + ${src.dir} + ${test.dir}/unit + ${scripts.src.dir} + + + ${resources.dir} + + + ${project.basedir} + + CONTRIBUTION + gpl-2.0.txt + gpl-3.0.txt + LICENSE + README + REVISION + + + + + + ${test.dir}/data + + + + + + org.codehaus.mojo + javacc-maven-plugin + + + javacc + + javacc + + + false + false + ${java.lang.version} + UTF-8 + true + ${src.dir} + ${src.dir} + **/MapCSSParser.jj + **/*.java + + + + + + + org.codehaus.mojo + exec-maven-plugin + + + + update-proj-reference-files + + test + org.openstreetmap.josm.data.projection.ProjectionRefTest + + + java + + + + + update-proj-regression-files + + test + org.openstreetmap.josm.data.projection.ProjectionRegressionTest + + + java + + + + + SyncEditorLayerIndex + + java + + -Djava.awt.headless=true + -classpath + + ${scripts.src.dir}/SyncEditorLayerIndex.java + ${basedir} + + + test + + + exec + + + + + epsg-touch + + java + + -Djava.awt.headless=true + -classpath + + ${basedir}/scripts/BuildProjectionDefinitions.java + ${basedir} + + + process-classes + + exec + + + + epsg + + java + + -Djava.awt.headless=true + -classpath + + ${basedir}/scripts/BuildProjectionDefinitions.java + ${basedir} + + + generate-test-resources + + exec + + + + + + + maven-resources-plugin + + + + copy-resources-epsg-touch + process-classes + + copy-resources + + + ${project.build.outputDirectory}/data/projection + + + ${resources.dir}/data/projection/ + + custom-epsg + + + + + + + copy-resources-epsg + generate-test-resources + + copy-resources + + + ${project.build.outputDirectory}/data/projection + + + ${resources.dir}/data/projection/ + + custom-epsg + + + + + + + + + + org.apache.maven.plugins + maven-pmd-plugin + + true + ${pmd.dir}/cache + java + ${java.lang.version} + + ${pmd.dir}/josm-ruleset.xml + + + **/*.java + + + org/openstreetmap/josm/gui/mappaint/mapcss/parsergen/*.java + + true + ${project.basedir} + + + + com.github.spotbugs + spotbugs-maven-plugin + + true + spotbugs-josm.xml + max + ${spotbugs.dir}/josm-filter.xml + org.openstreetmap.josm.- + LOW + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + ${checkstyle.dir}/josm_checks.xml + true + ${project.basedir}/checkstyle-josm.xml + + module-info.java,org/openstreetmap/josm/gui/mappaint/mapcss/parsergen/*.java + + + + validate + validate + + check + + + + + + + maven-surefire-plugin + + + **/*TestIT + + 1 + + + -javaagent:"${settings.localRepository}"/org/jmockit/jmockit/${jmockit.version}/jmockit-${jmockit.version}.jar + --add-opens java.base/java.io=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + --add-opens java.base/java.nio=ALL-UNNAMED + --add-opens java.base/java.text=ALL-UNNAMED + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/jdk.internal.loader=ALL-UNNAMED + --add-opens java.desktop/java.awt=ALL-UNNAMED + --add-opens java.prefs/java.util.prefs=ALL-UNNAMED + + + + file.encoding = UTF-8 + java.locale.providers = SPI,CLDR + junit.jupiter.extensions.autodetection.enabled = true + junit.jupiter.execution.parallel.enabled = true + + + + ${test.dir}/config/josm.home + ${test.dir}/data + ${test.headless} + Monocle + Headless + sw + + + + + default-tests + test + + test + + + + integration-tests + integration-test + + test + + + **/*TestIT + + + + functional-tests + integration-test + + test + + + ${test.dir}/functional + + + + performance-tests + integration-test + + test + + + ${test.dir}/performance + + + + + + + org.apache.maven.plugins + maven-clean-plugin + + + + ${proj-build.dir} + + + ${build.dir} + + + ${script-build.dir} + + + ${checkstyle-build.dir} + + + ${dist.dir} + + + ${mapcss.dir}/parsergen + + + ${src.dir}/org/w3/_2001/xmlschema + + Adapter1.java + + + + ${src.dir}/org/openstreetmap/josm/data/imagery/types + + + + ${resources.dir}/data/projection/ + + custom-epsg + + + + ${pmd.dir} + + cache + + + + + + + org.codehaus.mojo + buildnumber-maven-plugin + + + validate + + create + + + + + false + + + + org.apache.maven.plugins + maven-shade-plugin + + + package + + shade + + + + + + org.webjars.npm:tag2link + + META-INF/resources/webjars/tag2link/*/LICENSE + META-INF/resources/webjars/tag2link/*/README.md + META-INF/resources/webjars/tag2link/*/build.js + META-INF/resources/webjars/tag2link/*/package.json + META-INF/resources/webjars/tag2link/*/schema.json + META-INF/resources/webjars/tag2link/*/tag2link.sophox.sparql + META-INF/resources/webjars/tag2link/*/tag2link.wikidata.sparql + + + + org.openstreetmap.jmapviewer:jmapviewer + + org/openstreetmap/gui/jmapviewer/Demo* + + + + com.drewnoakes:metadata-extractor + + com/drew/imaging/FileTypeDetector* + com/drew/imaging/ImageMetadataReader* + com/drew/imaging/avi/** + com/drew/imaging/bmp/** + com/drew/imaging/eps/** + com/drew/imaging/gif/** + com/drew/imaging/heif/** + com/drew/imaging/ico/** + com/drew/imaging/mp3/** + com/drew/imaging/mp4/** + com/drew/imaging/pcx/** + com/drew/imaging/psd/** + com/drew/imaging/quicktime/** + com/drew/imaging/raf/** + com/drew/imaging/riff/** + com/drew/imaging/wav/** + com/drew/imaging/webp/** + com/drew/metadata/avi/** + com/drew/metadata/bmp/** + com/drew/metadata/eps/** + com/drew/metadata/gif/** + com/drew/metadata/heif/** + com/drew/metadata/ico/** + com/drew/metadata/mov/** + com/drew/metadata/mp3/** + com/drew/metadata/mp4/** + com/drew/metadata/pcx/** + com/drew/metadata/wav/** + com/drew/metadata/webp/** + com/drew/tools/** + + + + com.formdev:svgSalamander + + com/kitfox/svg/app/ant/** + com/kitfox/svg/app/*Dialog* + com/kitfox/svg/app/*Frame* + com/kitfox/svg/app/*Player* + com/kitfox/svg/app/*Viewer* + + + + org.apache.commons:commons-compress + + org/apache/commons/compress/PasswordRequiredException* + org/apache/commons/compress/archivers/** + org/apache/commons/compress/changes/** + org/apache/commons/compress/compressors/bzip2/BZip2Utils* + org/apache/commons/compress/compressors/brotli/** + org/apache/commons/compress/compressors/CompressorStreamFactory* + org/apache/commons/compress/compressors/CompressorStreamProvider* + org/apache/commons/compress/compressors/CompressorException* + org/apache/commons/compress/compressors/FileNameUtil* + org/apache/commons/compress/compressors/deflate/** + org/apache/commons/compress/compressors/gzip/** + org/apache/commons/compress/compressors/lz4/** + org/apache/commons/compress/compressors/lzma/** + org/apache/commons/compress/compressors/lz77support/** + org/apache/commons/compress/compressors/pack200/** + org/apache/commons/compress/compressors/snappy/** + org/apache/commons/compress/compressors/xz/XZUtils* + org/apache/commons/compress/compressors/z/** + org/apache/commons/compress/compressors/zstandard/** + org/apache/commons/compress/java/util/jar/Pack200* + org/apache/commons/compress/harmony/pack200/** + org/apache/commons/compress/harmony/unpack200/** + org/apache/commons/compress/parallel/** + org/apache/commons/compress/utils/ArchiveUtils* + + + + org.apache.commons:commons-jcs3-core + + org/apache/commons/jcs3/auxiliary/disk/jdbc/** + org/apache/commons/jcs3/auxiliary/remote/http/client/** + org/apache/commons/jcs3/auxiliary/remote/http/server/RemoteHttpCacheServlet* + org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheStartupServlet* + org/apache/commons/jcs3/log/Log4j2Factory* + org/apache/commons/jcs3/log/Log4j2LogAdapter* + org/apache/commons/jcs3/utils/servlet/** + + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.openstreetmap.josm.gui.MainApplication + true + true + + + ${buildNumber} SVN + + ${timestamp} + all-permissions + josm.openstreetmap.de + JOSM - Java OpenStreetMap Editor + java.base/sun.security.action java.desktop/com.apple.eawt java.desktop/com.sun.imageio.spi java.desktop/com.sun.imageio.plugins.jpeg javafx.graphics/com.sun.javafx.application jdk.deploy/com.sun.deploy.config + java.base/java.lang java.base/java.nio java.base/jdk.internal.loader java.base/jdk.internal.ref java.desktop/javax.imageio.spi java.desktop/javax.swing.text.html java.prefs/java.util.prefs + true + true + + + + + + + diff --git a/resources/data/ar.lang b/resources/data/ar.lang index a9b6eaa5204..20eed4cf372 100644 Binary files a/resources/data/ar.lang and b/resources/data/ar.lang differ diff --git a/resources/data/ast.lang b/resources/data/ast.lang index f71b472d1fc..fa7ab77e69d 100644 Binary files a/resources/data/ast.lang and b/resources/data/ast.lang differ diff --git a/resources/data/be.lang b/resources/data/be.lang index a97a4fa8951..bb769a432d4 100644 Binary files a/resources/data/be.lang and b/resources/data/be.lang differ diff --git a/resources/data/bg.lang b/resources/data/bg.lang index 1175de2cadb..497d3cf73f3 100644 Binary files a/resources/data/bg.lang and b/resources/data/bg.lang differ diff --git a/resources/data/boundaries.osm b/resources/data/boundaries.osm index c25f58e2bf1..0ca3ddb564d 100644 --- a/resources/data/boundaries.osm +++ b/resources/data/boundaries.osm @@ -82785,6 +82785,7 @@ + diff --git a/resources/data/ca-valencia.lang b/resources/data/ca-valencia.lang index 733cf07bd72..2446bcb5547 100644 Binary files a/resources/data/ca-valencia.lang and b/resources/data/ca-valencia.lang differ diff --git a/resources/data/ca.lang b/resources/data/ca.lang index 1c90b998e79..549c37dca77 100644 Binary files a/resources/data/ca.lang and b/resources/data/ca.lang differ diff --git a/resources/data/cs.lang b/resources/data/cs.lang index c2f5971f870..ee9a316a5b5 100644 Binary files a/resources/data/cs.lang and b/resources/data/cs.lang differ diff --git a/resources/data/da.lang b/resources/data/da.lang index 9d129f3bf25..618c0e9c00a 100644 Binary files a/resources/data/da.lang and b/resources/data/da.lang differ diff --git a/resources/data/de.lang b/resources/data/de.lang index 9d35ee0aa34..3b5e0bf18c4 100644 Binary files a/resources/data/de.lang and b/resources/data/de.lang differ diff --git a/resources/data/defaultpresets.xml b/resources/data/defaultpresets.xml index 9e85f3f96a9..600727b653f 100644 --- a/resources/data/defaultpresets.xml +++ b/resources/data/defaultpresets.xml @@ -650,20 +650,20 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -682,7 +682,7 @@ - + @@ -1465,7 +1465,7 @@ - + @@ -1516,7 +1516,7 @@ - + @@ -1588,6 +1588,10 @@ + + + + @@ -2901,47 +2905,51 @@ - + + + - - - - - - + + + @@ -3100,6 +3108,19 @@ + + + + + + + + + + + + + @@ -7308,10 +7329,10 @@ - + - + @@ -8261,7 +8282,7 @@ - + @@ -8280,12 +8301,12 @@ - - - - - - + + + + + + @@ -9968,11 +9989,11 @@ - - - - - + + + + + @@ -9980,6 +10001,7 @@ + diff --git a/resources/data/el.lang b/resources/data/el.lang index 9fd30e40004..1566ab1b2d3 100644 Binary files a/resources/data/el.lang and b/resources/data/el.lang differ diff --git a/resources/data/en.lang b/resources/data/en.lang index 4f278e9834d..31a8caac704 100644 Binary files a/resources/data/en.lang and b/resources/data/en.lang differ diff --git a/resources/data/en_AU.lang b/resources/data/en_AU.lang index 7ae0bc2504b..ec0b3eb9c21 100644 Binary files a/resources/data/en_AU.lang and b/resources/data/en_AU.lang differ diff --git a/resources/data/en_GB.lang b/resources/data/en_GB.lang index 7981c15a409..13ecfefd402 100644 Binary files a/resources/data/en_GB.lang and b/resources/data/en_GB.lang differ diff --git a/resources/data/es.lang b/resources/data/es.lang index 96e40a5ab44..9461609093d 100644 Binary files a/resources/data/es.lang and b/resources/data/es.lang differ diff --git a/resources/data/et.lang b/resources/data/et.lang index 26645e8af4c..dd9b786b57e 100644 Binary files a/resources/data/et.lang and b/resources/data/et.lang differ diff --git a/resources/data/fa.lang b/resources/data/fa.lang index a51af03d752..73b90a047f3 100644 Binary files a/resources/data/fa.lang and b/resources/data/fa.lang differ diff --git a/resources/data/fi.lang b/resources/data/fi.lang index 535d03ea62a..e1dc96eabd7 100644 Binary files a/resources/data/fi.lang and b/resources/data/fi.lang differ diff --git a/resources/data/fr.lang b/resources/data/fr.lang index 131f8c7e17c..8e0f182aca0 100644 Binary files a/resources/data/fr.lang and b/resources/data/fr.lang differ diff --git a/resources/data/gl.lang b/resources/data/gl.lang index 4df58219c91..4bfdb9e28cc 100644 Binary files a/resources/data/gl.lang and b/resources/data/gl.lang differ diff --git a/resources/data/hu.lang b/resources/data/hu.lang index 73b3f3fbe76..bb3232133c2 100644 Binary files a/resources/data/hu.lang and b/resources/data/hu.lang differ diff --git a/resources/data/id.lang b/resources/data/id.lang index f1476dd0523..99fb7562efc 100644 Binary files a/resources/data/id.lang and b/resources/data/id.lang differ diff --git a/resources/data/is.lang b/resources/data/is.lang index 4264e1b81f1..bf240ee9837 100644 Binary files a/resources/data/is.lang and b/resources/data/is.lang differ diff --git a/resources/data/it.lang b/resources/data/it.lang index 686d6aae272..c909d55e4b4 100644 Binary files a/resources/data/it.lang and b/resources/data/it.lang differ diff --git a/resources/data/ja.lang b/resources/data/ja.lang index c3fefe41b62..5908a89d386 100644 Binary files a/resources/data/ja.lang and b/resources/data/ja.lang differ diff --git a/resources/data/km.lang b/resources/data/km.lang index 8776eb0f263..49c49f55fce 100644 Binary files a/resources/data/km.lang and b/resources/data/km.lang differ diff --git a/resources/data/ko.lang b/resources/data/ko.lang index ec1e90cec92..cfa3bf52250 100644 Binary files a/resources/data/ko.lang and b/resources/data/ko.lang differ diff --git a/resources/data/lt.lang b/resources/data/lt.lang index b577eb528cd..98f58001792 100644 Binary files a/resources/data/lt.lang and b/resources/data/lt.lang differ diff --git a/resources/data/mr.lang b/resources/data/mr.lang index 24f2d2479e2..ea845913572 100644 Binary files a/resources/data/mr.lang and b/resources/data/mr.lang differ diff --git a/resources/data/nb.lang b/resources/data/nb.lang index 18eb008179a..b96a8d6cf05 100644 Binary files a/resources/data/nb.lang and b/resources/data/nb.lang differ diff --git a/resources/data/nl.lang b/resources/data/nl.lang index 615c69adf60..181d689d873 100644 Binary files a/resources/data/nl.lang and b/resources/data/nl.lang differ diff --git a/resources/data/pl.lang b/resources/data/pl.lang index 4de064d878f..1a6ec4fa5dd 100644 Binary files a/resources/data/pl.lang and b/resources/data/pl.lang differ diff --git a/resources/data/pt.lang b/resources/data/pt.lang index dab4ca645bb..ac0877f5790 100644 Binary files a/resources/data/pt.lang and b/resources/data/pt.lang differ diff --git a/resources/data/pt_BR.lang b/resources/data/pt_BR.lang index 76de6b7070a..7f4586927ae 100644 Binary files a/resources/data/pt_BR.lang and b/resources/data/pt_BR.lang differ diff --git a/resources/data/ru.lang b/resources/data/ru.lang index 606850db7ba..f5510607401 100644 Binary files a/resources/data/ru.lang and b/resources/data/ru.lang differ diff --git a/resources/data/sk.lang b/resources/data/sk.lang index c2f59503928..be25b639633 100644 Binary files a/resources/data/sk.lang and b/resources/data/sk.lang differ diff --git a/resources/data/sr-latin.lang b/resources/data/sr-latin.lang index 7f4f72ff0b4..a62dbb079a9 100644 Binary files a/resources/data/sr-latin.lang and b/resources/data/sr-latin.lang differ diff --git a/resources/data/sv.lang b/resources/data/sv.lang index 7919c92f2ce..61a4dd7e0f5 100644 Binary files a/resources/data/sv.lang and b/resources/data/sv.lang differ diff --git a/resources/data/tagging-preset.xsd b/resources/data/tagging-preset.xsd index e89b40a45ba..a8e5f043a56 100644 --- a/resources/data/tagging-preset.xsd +++ b/resources/data/tagging-preset.xsd @@ -106,6 +106,7 @@ + @@ -121,7 +122,7 @@ - Every item is one annotation set to select from. name is required, type and preset_name_label are recommended, icon and name_template are optional attributes. + Every item is one annotation set to select from. name is required, type and preset_name_label are recommended and icon, name_template, regions and exclude_regions are optional attributes. @@ -134,6 +135,7 @@ + + and . More information see short_descriptions below. The attributes are value, display_value, short_description, icon and icon_size. + Used in and . More information see short_descriptions below. The attributes are value, display_value, short_description, icon, icon_size, regions, and exclude_regions. ]]> @@ -378,6 +381,7 @@ + @@ -385,7 +389,7 @@

- Displays a multiple choice combo box. key and values are mandatory, text, default, editable, delimiter, values_from, display_values, short_descriptions, use_last_as_default, values_searchable, length, values_no_i18n, values_sort and match is optional. + Displays a multiple choice combo box. key and values are mandatory, text, default, editable, delimiter, values_from, display_values, short_descriptions, use_last_as_default, values_searchable, length, values_no_i18n, values_sort, match, regions, and exclude_regions are optional.

If editable is "true" (default), combo boxes can be edited as if they were text fields (additional to the drop down menu). Non editable combo boxes can only contain one of the specified values. @@ -413,6 +417,7 @@ + @@ -431,6 +436,7 @@ + @@ -489,6 +495,7 @@ + @@ -558,7 +565,7 @@ - To specify possible roles of members in relations. The key attribute is required, text, requisite, count, type and member_expression are optional. + To specify possible roles of members in relations. The key attribute is required, text, requisite, count, type, member_expression, regions, and exclude_regions are optional. @@ -574,6 +581,7 @@ + @@ -729,4 +737,21 @@ + + + + + Comma separated list of countries this preset group or item is applicable for. If not specified, the preset is applicable for all countries. + + + + + + + If true, invert the meaning of regions. + + + + + diff --git a/resources/data/uk.lang b/resources/data/uk.lang index 9aa85ef9373..acb948b8c14 100644 Binary files a/resources/data/uk.lang and b/resources/data/uk.lang differ diff --git a/resources/data/validator/combinations.mapcss b/resources/data/validator/combinations.mapcss index 1776ff3beec..f8a30d975f0 100644 --- a/resources/data/validator/combinations.mapcss +++ b/resources/data/validator/combinations.mapcss @@ -541,8 +541,8 @@ way[highway][natural][natural!=ridge], fixRemove: "natural"; } -/* #9593, #11183, #12418, #12761, #17254, #19311 */ -*[sport][sport!=skiing][!building][!club][tourism != hotel][highway !~ /^(pedestrian|raceway)$/][!leisure][natural !~ /^(beach|bare_rock|cliff|peak|water)$/][amenity !~ /^(bar|dojo|pub|restaurant|swimming_pool)$/][landuse !~ /^(recreation_ground|piste|farm|farmland)$/][barrier !~ /^(wall|retaining_wall)$/][!"piste:type"][shop!=sports][attraction!=summer_toboggan] { +/* #9593, #11183, #12418, #12761, #17254, #19311,#23604 */ +*[sport][sport!=skiing][!building][!"building:part"][!club][tourism != hotel][highway !~ /^(pedestrian|raceway)$/][!leisure][natural !~ /^(beach|bare_rock|cliff|peak|water)$/][amenity !~ /^(bar|dojo|pub|restaurant|swimming_pool)$/][landuse !~ /^(recreation_ground|piste|farm|farmland)$/][barrier !~ /^(wall|retaining_wall)$/][!"piste:type"][shop!=sports][attraction!=summer_toboggan] { throwWarning: tr("sport without physical feature"); group: tr("missing tag"); assertMatch: "node sport=tennis"; @@ -551,6 +551,7 @@ way[highway][natural][natural!=ridge], assertNoMatch: "node sport=skiing"; /* skiing has deprecated warning */ assertNoMatch: "node sport=swimming tourism=hotel"; assertNoMatch: "node sport=10pin amenity=restaurant"; + assertNoMatch: "node sport=boxing building:part=yes"; } /* {0.key} without {1.key} or {2.key} see #10140 */ @@ -666,27 +667,6 @@ way[waterway][layer][layer=~/^(-1|-2|-3|-4|-5)$/][!tunnel][culvert!=yes][covered group: tr("suspicious tag combination"); } -/* #13144, #15536 */ -*[unisex=yes][female=yes][male!=yes][shop=hairdresser], -*[unisex=yes][male=yes][female!=yes][shop=hairdresser] { - throwWarning: tr("{0} together with {1}", "{0.tag}", "{1.tag}"); - group: tr("suspicious tag combination"); -} -*[unisex=yes][female=yes][male=yes][shop=hairdresser] { - throwWarning: tr("{0} together with {1} and {2}. Remove {1} and {2}", "{0.tag}", "{1.tag}", "{2.tag}"); - group: tr("suspicious tag combination"); - fixRemove: "female"; - fixRemove: "male"; -} -*[female=yes][male=yes][!unisex][shop=hairdresser] { - throwWarning: tr("{0} together with {1}", "{0.tag}", "{1.tag}"); - suggestAlternative: "unisex=yes"; - group: tr("suspicious tag combination"); - fixRemove: "female"; - fixRemove: "male"; - fixAdd: "unisex=yes"; -} - /* {0.key} without {1.tag} see #13138, #14468 */ way[water][natural!~/water|bay|strait/][water!=intermittent][amenity!=lavoir]!:closed, /* water=intermittent is deprecated and has an own warning */ area[water][natural!~/water|bay|strait/][water!=intermittent][amenity!=lavoir]:closed { diff --git a/resources/data/validator/deprecated.mapcss b/resources/data/validator/deprecated.mapcss index 87fe15cffda..ec70e48a0e7 100644 --- a/resources/data/validator/deprecated.mapcss +++ b/resources/data/validator/deprecated.mapcss @@ -1617,11 +1617,6 @@ way[/^is_in:/] { suggestAlternative: "highway=steps + conveying=*"; group: tr("deprecated tagging"); } -*[fenced] { - throwWarning: tr("{0} is deprecated", "{0.key}"); - suggestAlternative: "barrier=fence"; - group: tr("deprecated tagging"); -} *[historic_name][!old_name] { throwWarning: tr("{0} is deprecated", "{0.key}"); suggestAlternative: "old_name"; @@ -2514,4 +2509,25 @@ area[parking:orientation][orientation]["parking:orientation"!=*orientation] { suggestAlternative: "{1.key}={1.value}"; } +/* Tags were consolidated, see #23177 */ +*[gnis:id][!gnis:feature_id], +*[tiger:PLACENS][!gnis:feature_id], +*[NHD:GNIS_ID][!gnis:feature_id], +*[nhd:gnis_id][!gnis:feature_id], +*[ref:gnis][!gnis:feature_id] { + throwWarning: tr("{0} is deprecated", "{0.key}"); + group: tr("deprecated tagging"); + suggestAlternative: "{1.key}={0.value}"; + fixChangeKey: "{0.key} => {1.key}"; +} +*[gnis:id][gnis:feature_id], +*[tiger:PLACENS][gnis:feature_id], +*[NHD:GNIS_ID][gnis:feature_id], +*[nhd:gnis_id][gnis:feature_id], +*[ref:gnis][gnis:feature_id] { + throwWarning: tr("{0} is deprecated", "{0.key}"); + group: tr("deprecated tagging"); + suggestAlternative: "{1.key}"; +} + /* When tags are deprecated they should be added to ignoretags.cfg too. */ diff --git a/resources/data/validator/geometry.mapcss b/resources/data/validator/geometry.mapcss index f4d583b84ad..de43241551d 100644 --- a/resources/data/validator/geometry.mapcss +++ b/resources/data/validator/geometry.mapcss @@ -198,7 +198,10 @@ area[building][building!~/no|entrance/] ⧉ area[building][building!~/no|entranc } /* Overlapping areas (spatial test) */ -area[natural =~ /^(water|wetland|coastline)$/], area[waterway=riverbank], area[landuse=reservoir] { +area[natural =~ /^(water|wetland)$/], +area[natural=coastline]:clockwise, +area[waterway=riverbank], +area[landuse=reservoir] { set water_area; } @@ -210,10 +213,12 @@ area:closed[landuse=reservoir] ⧉ area:closed.water_area } /* Water area inside water area (spatial test) */ -area:closed[natural =~ /^(water|wetland|coastline)$/] ⊆ area:closed.water_area, +area:closed[natural =~ /^(water|wetland)$/] ⊆ area:closed.water_area, +area:closed[natural=coastline]:clockwise ⊆ area:closed.water_area, area:closed[waterway=riverbank] ⊆ area:closed.water_area, area:closed[landuse=reservoir] ⊆ area:closed.water_area, -area:closed[natural =~ /^(water|wetland|coastline)$/] ⊇ area:closed.water_area, +area:closed[natural =~ /^(water|wetland)$/] ⊇ area:closed.water_area, +area:closed[natural=coastline]:clockwise ⊇ area:closed.water_area, area:closed[waterway=riverbank] ⊇ area:closed.water_area, area:closed[landuse=reservoir] ⊇ area:closed.water_area { throwWarning: tr("Water area inside water area"); @@ -273,10 +278,12 @@ node:unconnected:in-downloaded-area[highway=give_way], node:unconnected:in-downloaded-area[highway=traffic_signals], node:unconnected:in-downloaded-area[highway=crossing], node:unconnected:in-downloaded-area[crossing], -node:unconnected:in-downloaded-area[railway=milestone], +node:unconnected:in-downloaded-area[railway=buffer_stop], node:unconnected:in-downloaded-area[railway=crossing], node:unconnected:in-downloaded-area[railway=level_crossing], -node:unconnected:in-downloaded-area[railway=buffer_stop], +node:unconnected:in-downloaded-area[railway=milestone], +node:unconnected:in-downloaded-area[railway=railway_crossing], +node:unconnected:in-downloaded-area[railway=switch], node:unconnected:in-downloaded-area[public_transport=stop_position], node:unconnected:in-downloaded-area[aeroway=holding_position], node:unconnected:in-downloaded-area[noexit], diff --git a/resources/data/validator/ignoretags.cfg b/resources/data/validator/ignoretags.cfg index e9536912377..3051639c51e 100644 --- a/resources/data/validator/ignoretags.cfg +++ b/resources/data/validator/ignoretags.cfg @@ -746,6 +746,11 @@ K:type=turnlanes:turns K:surface=paving_stones:30 E:site_type E:parking:orientation +E:NHD:GNIS_ID +E:gnis:id +E:nhd:gnis_id +E:ref:gnis +E:tiger:PLACENS ; ; Tags not yet decided (to remove from this section when added or moved up when deprecated) ; see josm tickets: 17770 15309 15774 16315 16658 16793 19982 21396 diff --git a/resources/data/validator/unnecessary.mapcss b/resources/data/validator/unnecessary.mapcss index 9c2b6dbe0e7..eac8d59d323 100644 --- a/resources/data/validator/unnecessary.mapcss +++ b/resources/data/validator/unnecessary.mapcss @@ -85,7 +85,7 @@ node[emergency=fire_hydrant][fire_hydrant:count=1] { fixRemove: "{1.key}"; } -/* #17100, #17471, #17629, #17633, #19274, #19395, #19409 */ +/* #17100, #17471, #17629, #17633, #19274, #19395, #19409, #23596 */ *[name][name=~/^(?i)(library|biblioteca|biblioteka|bibliothek|bibliotheek)$/][amenity=library], *[name][name=~/^(?i)(parc|park)$/][leisure=park], *[name][name=~/^(?i)(pond)$/][water=pond], @@ -97,7 +97,7 @@ node[emergency=fire_hydrant][fire_hydrant:count=1] { *[name][name=~/^(?i)(toilets?)$/][amenity=toilets], *[name][name=~/^(?i)(playground|spielplatz)$/][leisure=playground], *[name][name=~/^(?i)(shop|boutique)$/][shop][shop!=no], -*[name][name=~/^(?i)(building|bangunan)$/][building][building!=no], +*[name][name=~/^(?i)(building|bangunan|bâtiment|batiment)$/][building][building!=no], *[name][name=~/^(?i)(house|maison|rumah|vivienda)$/][building=house], *[name][name=~/^(?i)(casa)$/][building=house][outside("FR")], /* spanish for house but it is a brand name in France */ *[name][name=~/^(?i)(kiosk)$/][shop=kiosk][outside("NL")], /* it is a brand name in the Netherlands */ diff --git a/resources/data/zh_CN.lang b/resources/data/zh_CN.lang index 32b38f2952c..69472ec2bcb 100644 Binary files a/resources/data/zh_CN.lang and b/resources/data/zh_CN.lang differ diff --git a/resources/data/zh_TW.lang b/resources/data/zh_TW.lang index d5a9715c9ce..e4b2b9d0020 100644 Binary files a/resources/data/zh_TW.lang and b/resources/data/zh_TW.lang differ diff --git a/resources/images/presets/vehicle/bicycle_wash.svg b/resources/images/presets/vehicle/bicycle_wash.svg new file mode 100644 index 00000000000..c042f7b4412 --- /dev/null +++ b/resources/images/presets/vehicle/bicycle_wash.svg @@ -0,0 +1,43 @@ + + + + + + + + diff --git a/resources/images/presets/vehicle/crossing_markings_dashes.svg b/resources/images/presets/vehicle/crossing_markings_dashes.svg new file mode 100644 index 00000000000..5624c292985 --- /dev/null +++ b/resources/images/presets/vehicle/crossing_markings_dashes.svg @@ -0,0 +1,107 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/resources/images/presets/vehicle/crossing_markings_dots.svg b/resources/images/presets/vehicle/crossing_markings_dots.svg new file mode 100644 index 00000000000..b4a2d938235 --- /dev/null +++ b/resources/images/presets/vehicle/crossing_markings_dots.svg @@ -0,0 +1,107 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/resources/images/presets/vehicle/crossing_markings_ladder.svg b/resources/images/presets/vehicle/crossing_markings_ladder.svg new file mode 100644 index 00000000000..81d3e80a1f6 --- /dev/null +++ b/resources/images/presets/vehicle/crossing_markings_ladder.svg @@ -0,0 +1,100 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/resources/images/presets/vehicle/crossing_markings_ladder_paired.svg b/resources/images/presets/vehicle/crossing_markings_ladder_paired.svg new file mode 100644 index 00000000000..9c0f4dc6617 --- /dev/null +++ b/resources/images/presets/vehicle/crossing_markings_ladder_paired.svg @@ -0,0 +1,121 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/resources/images/presets/vehicle/crossing_markings_ladder_skewed.svg b/resources/images/presets/vehicle/crossing_markings_ladder_skewed.svg new file mode 100644 index 00000000000..feb31e9dfd2 --- /dev/null +++ b/resources/images/presets/vehicle/crossing_markings_ladder_skewed.svg @@ -0,0 +1,103 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/resources/images/presets/vehicle/crossing_markings_lines.svg b/resources/images/presets/vehicle/crossing_markings_lines.svg new file mode 100644 index 00000000000..29991f86aa6 --- /dev/null +++ b/resources/images/presets/vehicle/crossing_markings_lines.svg @@ -0,0 +1,79 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/resources/images/presets/vehicle/crossing_markings_lines_paired.svg b/resources/images/presets/vehicle/crossing_markings_lines_paired.svg new file mode 100644 index 00000000000..10ba8b7aef0 --- /dev/null +++ b/resources/images/presets/vehicle/crossing_markings_lines_paired.svg @@ -0,0 +1,93 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/resources/images/presets/vehicle/crossing_markings_surface.svg b/resources/images/presets/vehicle/crossing_markings_surface.svg new file mode 100644 index 00000000000..f4c022d9804 --- /dev/null +++ b/resources/images/presets/vehicle/crossing_markings_surface.svg @@ -0,0 +1,72 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/resources/images/presets/vehicle/crossing_markings_zebra_bicolour.svg b/resources/images/presets/vehicle/crossing_markings_zebra_bicolour.svg new file mode 100644 index 00000000000..bde431e7568 --- /dev/null +++ b/resources/images/presets/vehicle/crossing_markings_zebra_bicolour.svg @@ -0,0 +1,114 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/resources/images/presets/vehicle/crossing_markings_zebra_double.svg b/resources/images/presets/vehicle/crossing_markings_zebra_double.svg new file mode 100644 index 00000000000..13d8e531909 --- /dev/null +++ b/resources/images/presets/vehicle/crossing_markings_zebra_double.svg @@ -0,0 +1,106 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/resources/images/presets/vehicle/crossing_markings_zebra_paired.svg b/resources/images/presets/vehicle/crossing_markings_zebra_paired.svg new file mode 100644 index 00000000000..7f76cac9c49 --- /dev/null +++ b/resources/images/presets/vehicle/crossing_markings_zebra_paired.svg @@ -0,0 +1,93 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/resources/images/presets/vehicle/crossing_ref_zebra.svg b/resources/images/presets/vehicle/crossing_ref_zebra.svg index df88fc4e6d8..210d4d06617 100644 --- a/resources/images/presets/vehicle/crossing_ref_zebra.svg +++ b/resources/images/presets/vehicle/crossing_ref_zebra.svg @@ -2,43 +2,17 @@ + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> - @@ -47,70 +21,66 @@ image/svg+xml - + id="layer1"> + x="0" + y="0" /> + x="5" + y="1" /> + x="5" + y="3" /> + x="5" + y="5" /> + x="2" + y="3" /> + x="0" + y="3" /> + x="12" + y="3" /> + x="15" + y="3" /> diff --git a/resources/styles/standard/elemstyles.mapcss b/resources/styles/standard/elemstyles.mapcss index 082dd37677f..9e4ba2e7e7c 100644 --- a/resources/styles/standard/elemstyles.mapcss +++ b/resources/styles/standard/elemstyles.mapcss @@ -833,7 +833,52 @@ node[highway=crossing][crossing=unmarked] { icon-image: "presets/vehicle/crossing_unmarked.svg"; set icon_z17; } -node[highway=crossing][crossing_ref=zebra] { +node[highway=crossing]["crossing:markings"=surface] { + icon-image: "presets/vehicle/crossing_markings_surface.svg"; + set icon_z17; +} +node[highway=crossing]["crossing:markings"=lines] { + icon-image: "presets/vehicle/crossing_markings_lines.svg"; + set icon_z17; +} +node[highway=crossing]["crossing:markings"="lines:paired"] { + icon-image: "presets/vehicle/crossing_markings_lines_paired.svg"; + set icon_z17; +} +node[highway=crossing]["crossing:markings"=dashes] { + icon-image: "presets/vehicle/crossing_markings_dashes.svg"; + set icon_z17; +} +node[highway=crossing]["crossing:markings"=dots] { + icon-image: "presets/vehicle/crossing_markings_dots.svg"; + set icon_z17; +} +node[highway=crossing]["crossing:markings"="zebra:double"] { + icon-image: "presets/vehicle/crossing_markings_zebra_double.svg"; + set icon_z17; +} +node[highway=crossing]["crossing:markings"="zebra:paired"] { + icon-image: "presets/vehicle/crossing_markings_zebra_paired.svg"; + set icon_z17; +} +node[highway=crossing]["crossing:markings"="zebra:bicolour"] { + icon-image: "presets/vehicle/crossing_markings_zebra_bicolour.svg"; + set icon_z17; +} +node[highway=crossing]["crossing:markings"=ladder] { + icon-image: "presets/vehicle/crossing_markings_ladder.svg"; + set icon_z17; +} +node[highway=crossing]["crossing:markings"="ladder:skewed"] { + icon-image: "presets/vehicle/crossing_markings_ladder_skewed.svg"; + set icon_z17; +} +node[highway=crossing]["crossing:markings"="ladder:paired"] { + icon-image: "presets/vehicle/crossing_markings_ladder_paired.svg"; + set icon_z17; +} +node[highway=crossing][crossing_ref=zebra], +node[highway=crossing]["crossing:markings"=zebra] { icon-image: "presets/vehicle/crossing_ref_zebra.svg"; set icon_z17; } @@ -3040,6 +3085,7 @@ area[amenity=parking], area[amenity=motorcycle_parking], area[amenity=bicycle_rental], area[amenity=bicycle_repair_station], +area[amenity=bicycle_wash], area[amenity=car_rental], area[amenity=car_pooling], area[amenity=car_sharing], @@ -3094,6 +3140,10 @@ node[amenity=bicycle_repair_station] { icon-image: "presets/vehicle/bicycle_repair_station.svg"; set icon_z17; } +node[amenity=bicycle_wash] { + icon-image: "presets/vehicle/bicycle_wash.svg"; + set icon_z17; +} node[amenity=car_rental] { icon-image: "presets/vehicle/car_rental.svg"; set icon_z17; diff --git a/scripts/BuildProjectionDefinitions.java b/scripts/BuildProjectionDefinitions.java index 5463bc449e8..6a486e3c718 100644 --- a/scripts/BuildProjectionDefinitions.java +++ b/scripts/BuildProjectionDefinitions.java @@ -13,6 +13,7 @@ import java.util.Locale; import java.util.Map; import java.util.TreeMap; +import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -84,6 +85,19 @@ static List initList(String baseDir, String ext) throws IOException { .collect(Collectors.toList()); } + static boolean touchCustomEpsg(String baseDir) throws IOException { + final Path path = Paths.get(baseDir).resolve(OUTPUT_EPSG_FILE); + if (!Files.exists(path)) { + Files.createDirectories(path.getParent()); + Files.createFile(path); + Logger.getLogger(BuildProjectionDefinitions.class.getCanonicalName()) + .info("Could not generate custom-epsg; an empty custom-epsg file was not available on the classpath. " + + "This should now be fixed, please rerun the command."); + return true; + } + return false; + } + static void initMap(String baseDir, String file, Map map) throws IOException { final Path path = Paths.get(baseDir).resolve(PROJ_DIR).resolve(file); final List list; @@ -106,6 +120,9 @@ static void initMap(String baseDir, String file, Map skip = new HashMap<>(); - private Map skipStart = new HashMap<>(); + private final Map skip = new HashMap<>(); + private final Map skipStart = new HashMap<>(); /** * Main method. @@ -236,7 +237,7 @@ void setupProj() { } void loadSkip() throws IOException { - final Pattern pattern = Pattern.compile("^\\|\\| *(ELI|Ignore) *\\|\\| *\\{\\{\\{(.+)\\}\\}\\} *\\|\\|"); + final Pattern pattern = Pattern.compile("^\\|\\| *(ELI|Ignore) *\\|\\| *\\{\\{\\{(.+)}}} *\\|\\|"); try (BufferedReader fr = Files.newBufferedReader(Paths.get(ignoreInputFile), UTF_8)) { String line; @@ -477,8 +478,8 @@ void printentries(List entries, Writer stream) throws IOException { } shapes += sep + "\n"; } - } catch (IllegalArgumentException ignored) { - Logging.trace(ignored); + } catch (IllegalArgumentException illegalArgumentException) { + Logging.trace(illegalArgumentException); } if (!shapes.isEmpty()) { stream.write(" getMirrors(Object e) { static List getProjections(Object e) { List r = new ArrayList<>(); List u = getProjectionsUnstripped(e); - if (u != null) { - for (String p : u) { - if (!oldproj.containsKey(p) && !("CRS:84".equals(p) && !(getUrlStripped(e).matches("(?i)version=1\\.3")))) { - r.add(p); - } + for (String p : u) { + if (!oldproj.containsKey(p) && !("CRS:84".equals(p) && !(getUrlStripped(e).matches("(?i)version=1\\.3")))) { + r.add(p); } } return r; diff --git a/scripts/TagInfoExtract.java b/scripts/TagInfoExtract.java index 3e9c4319950..b2d3a2babe4 100644 --- a/scripts/TagInfoExtract.java +++ b/scripts/TagInfoExtract.java @@ -1,4 +1,5 @@ // License: GPL. For details, see LICENSE file. + import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.BufferedReader; @@ -31,11 +32,6 @@ import java.util.stream.Stream; import javax.imageio.ImageIO; -import jakarta.json.Json; -import jakarta.json.JsonArrayBuilder; -import jakarta.json.JsonObjectBuilder; -import jakarta.json.JsonWriter; -import jakarta.json.stream.JsonGenerator; import org.openstreetmap.josm.actions.DeleteAction; import org.openstreetmap.josm.command.DeleteCommand; @@ -84,6 +80,12 @@ import org.openstreetmap.josm.tools.Utils; import org.xml.sax.SAXException; +import jakarta.json.Json; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.JsonWriter; +import jakarta.json.stream.JsonGenerator; + /** * Extracts tag information for the taginfo project. *

@@ -100,9 +102,12 @@ public class TagInfoExtract { /** * Main method. * @param args Main program arguments - * @throws Exception if any error occurs + * @throws IOException if an IO exception occurs + * @throws OsmTransferException if something happened when communicating with the OSM server + * @throws ParseException if there was an issue parsing MapCSS + * @throws SAXException if there was an issue parsing XML */ - public static void main(String[] args) throws Exception { + public static void main(String[] args) throws IOException, OsmTransferException, ParseException, SAXException { HttpClient.setFactory(Http1Client::new); TagInfoExtract script = new TagInfoExtract(); script.parseCommandLineArguments(args); @@ -162,7 +167,7 @@ private void usage() { System.exit(0); } - private static class Options { + private static final class Options { Mode mode; int josmSvnRevision = Version.getInstance().getVersion(); Path baseDir = Paths.get(""); @@ -221,7 +226,7 @@ private String findImageUrl(String path) { } private abstract class Extractor { - abstract void run() throws Exception; + abstract void run() throws IOException, OsmTransferException, ParseException, SAXException; void writeJson(String name, String description, Iterable tags) throws IOException { try (Writer writer = options.outputFile != null ? Files.newBufferedWriter(options.outputFile) : new StringWriter(); @@ -313,7 +318,7 @@ private Collection values(KeyedItem item) { } } - private class ExternalPresets extends Presets { + private final class ExternalPresets extends Presets { @Override void run() throws IOException, OsmTransferException, SAXException { @@ -340,7 +345,7 @@ void run() throws IOException, OsmTransferException, SAXException { } } - private class StyleSheet extends Extractor { + private final class StyleSheet extends Extractor { private MapCSSStyleSource styleSource; @Override diff --git a/scripts/TaggingPresetSchemeWikiGenerator.java b/scripts/TaggingPresetSchemeWikiGenerator.java index af408eace97..17dd68aa155 100644 --- a/scripts/TaggingPresetSchemeWikiGenerator.java +++ b/scripts/TaggingPresetSchemeWikiGenerator.java @@ -34,7 +34,7 @@ private TaggingPresetSchemeWikiGenerator() { // Hide public constructor for utility class } - public static void main(String[] args) throws Exception { + public static void main(String[] args) throws IOException, ParserConfigurationException, SAXException, XPathExpressionException { document = parseTaggingPresetSchema(); xPath = XPathFactory.newInstance().newXPath(); xPath.setNamespaceContext(new TaggingNamespaceContext()); @@ -63,7 +63,7 @@ private static void printAttributes() throws XPathExpressionException { node.getTextContent().trim())); } - private static class TaggingNamespaceContext implements NamespaceContext { + private static final class TaggingNamespaceContext implements NamespaceContext { @Override public String getNamespaceURI(String prefix) { switch (prefix) { diff --git a/scripts/since_xxx.py b/scripts/since_xxx.py index c6ecf25c794..be026cc7f55 100755 --- a/scripts/since_xxx.py +++ b/scripts/since_xxx.py @@ -1,8 +1,9 @@ -#!/usr/bin/python +#!/usr/bin/python3 # License: CC0 """ -Helper script to replace "@since xxx" in Javadoc by the upcoming revision number. +Helper script to replace "@since xxx" in Javadoc by the upcoming revision +number. Will retrieve the current revision number from the server. It runs over all modified and added .java files and replaces xxx in "since xxx" by the revision @@ -12,33 +13,86 @@ import xml.etree.ElementTree as ElementTree import subprocess import re +import sys revision = None -def main(): + +def main() -> None: + """ + Do the main work of the script. + :return: Nothing + """ + args = sys.argv svn_status = subprocess.check_output("svn status --xml".split(" ")) - for el in ElementTree.fromstring(svn_status).findall("./target/entry"): - if el.find('wc-status').get("item") not in ["added", "modified"]: - continue - path = el.get("path") - if not path.endswith('.java'): - continue - with open(path, 'r') as f: - filedata = f.read() - filedata2 = re.sub("since xxx", lambda _: "since {}".format(get_revision()), filedata) - if filedata != filedata2: - print("replacing 'since xxx' with 'since {}' in '{}'".format(get_revision(), path)) - with open(path, 'w') as f: - f.write(filedata2) - -def get_revision(): + tree = ElementTree.fromstring(svn_status) + rev = get_revision() + if len(args) == 2: + for change_set in tree.findall("./changelist"): + if ( + "name" in change_set.attrib + and change_set.attrib["name"] == args[1] + ): + for el in change_set.findall("./entry"): + write_xxx(rev, el) + elif len(args) > 2: + raise ValueError( + "Too many args: only one changelist should be passed at a time, " + "or none at all " + ) + else: + for el in tree.findall("./target/entry"): + write_xxx(rev, el) + + +def write_xxx(rev: int, el: ElementTree.Element) -> None: + """ + Write a revision to a changed file + :param rev: The revision to write + :param el: The element containing the path to the file to update + :return: Nothing + """ + if el.find("wc-status").get("item") not in ["added", "modified"]: + return + path = el.get("path") + if not path.endswith(".java"): + return + with open(path, "r") as f: + old_text = f.read() + new_text = re.sub("since xxx", lambda _: "since {}".format(rev), old_text) + if old_text != new_text: + print( + "replacing 'since xxx' with 'since {}' in '{}'".format(rev, path) + ) + with open(path, "w") as f: + f.write(new_text) + + +def get_revision() -> int: + """ + Get the next revision + :return: The current revision + 1 + """ global revision if revision is not None: return revision svn_info_local = subprocess.check_output("svn info --xml".split(" ")) - rep_url = ElementTree.fromstring(svn_info_local).findtext("./entry/repository/root") - svn_info_server = subprocess.check_output("svn info --xml".split(" ") + [rep_url]) - revision = int(ElementTree.fromstring(svn_info_server).find("./entry").get("revision")) + 1 + rep_url = ElementTree.fromstring(svn_info_local).findtext( + "./entry/repository/root" + ) + svn_info_server = subprocess.check_output( + "svn info --xml".split(" ") + [rep_url] + ) + revision = ( + int( + ElementTree.fromstring(svn_info_server) + .find("./entry") + .get("revision") + ) + + 1 + ) return revision - -main() + + +if __name__ == "__main__": + main() diff --git a/src/org/openstreetmap/josm/actions/AddImageryLayerAction.java b/src/org/openstreetmap/josm/actions/AddImageryLayerAction.java index 7465565f3f0..d3f4e62ad4f 100644 --- a/src/org/openstreetmap/josm/actions/AddImageryLayerAction.java +++ b/src/org/openstreetmap/josm/actions/AddImageryLayerAction.java @@ -6,6 +6,7 @@ import java.awt.Dimension; import java.awt.GraphicsEnvironment; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.io.IOException; @@ -65,7 +66,7 @@ static class SelectWmsLayersDialog extends ExtendedDialog { scrollPane.setPreferredSize(new Dimension(400, 400)); final JPanel panel = new JPanel(new GridBagLayout()); panel.add(scrollPane, GBC.eol().fill()); - panel.add(formats, GBC.eol().fill(GBC.HORIZONTAL)); + panel.add(formats, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); setContent(panel); } } @@ -114,7 +115,7 @@ private static ImageryInfo convertImagery(ImageryInfo info) { info.setDate(userDate); // TODO persist new {time} value (via ImageryLayerInfo.save?) } - switch(info.getImageryType()) { + switch (info.getImageryType()) { case WMS_ENDPOINT: // convert to WMS type if (Utils.isEmpty(info.getDefaultLayers())) { @@ -232,8 +233,8 @@ private static LayerSelection askToSelectLayers(WMSImagery wms) { scrollPane.setPreferredSize(new Dimension(400, 400)); final JPanel panel = new JPanel(new GridBagLayout()); panel.add(scrollPane, GBC.eol().fill()); - panel.add(checkBounds, GBC.eol().fill(GBC.HORIZONTAL)); - panel.add(formats, GBC.eol().fill(GBC.HORIZONTAL)); + panel.add(checkBounds, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); + panel.add(formats, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); dialog.setContent(panel); if (dialog.showDialog().getValue() != 1) { diff --git a/src/org/openstreetmap/josm/actions/AlignInCircleAction.java b/src/org/openstreetmap/josm/actions/AlignInCircleAction.java index ac00573a99c..faa40c9e5ed 100644 --- a/src/org/openstreetmap/josm/actions/AlignInCircleAction.java +++ b/src/org/openstreetmap/josm/actions/AlignInCircleAction.java @@ -1,6 +1,7 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.actions; +import static java.util.function.Predicate.not; import static org.openstreetmap.josm.gui.help.HelpUtil.ht; import static org.openstreetmap.josm.tools.I18n.tr; @@ -134,12 +135,12 @@ public void actionPerformed(ActionEvent e) { * All other nodes are uniformly distributed. *

* Case 1: One unclosed way. - * --> allow action, and align selected way nodes + * → allow action, and align selected way nodes * If nodes contained by this way are selected, there are fix. * If nodes outside from the way are selected there are ignored. *

* Case 2: One or more ways are selected and can be joined into a polygon - * --> allow action, and align selected ways nodes + * → allow action, and align selected ways nodes * If 1 node outside of way is selected, it became center * If 1 node outside and 1 node inside are selected there define center and radius * If no outside node and 2 inside nodes are selected those 2 nodes define diameter @@ -148,10 +149,10 @@ public void actionPerformed(ActionEvent e) { * (first referrer is the selected way) *

* Case 3: Only nodes are selected - * --> Align these nodes, all are fix + * → Align these nodes, all are fix *

* Case 4: Circularize selected ways - * --> Circularize each way of the selection. + * → Circularize each way of the selection. * @param ds data set in which the command operates * @return the resulting command to execute to perform action, or null if nothing was changed * @throws InvalidSelection if selection cannot be used @@ -271,9 +272,9 @@ public static Command buildCommand(DataSet ds) throws InvalidSelection { } fixNodes.addAll(collectNodesWithExternReferrers(ways)); - // Check if one or more nodes are outside of download area - if (nodes.stream().anyMatch(Node::isOutsideDownloadArea)) - throw new InvalidSelection(tr("One or more nodes involved in this action is outside of the downloaded area.")); + // Check if one or more nodes does not have all parents available + if (nodes.stream().anyMatch(not(Node::isReferrersDownloaded))) + throw new InvalidSelection(tr("One or more nodes involved in this action may have additional referrers.")); if (center == null) { diff --git a/src/org/openstreetmap/josm/actions/AutoScaleAction.java b/src/org/openstreetmap/josm/actions/AutoScaleAction.java index 7e92317caa4..ccbadd22616 100644 --- a/src/org/openstreetmap/josm/actions/AutoScaleAction.java +++ b/src/org/openstreetmap/josm/actions/AutoScaleAction.java @@ -438,7 +438,7 @@ protected final void installAdapters() { /** * Adapter for zoom change events */ - private class ZoomChangeAdapter implements ZoomChangeListener { + private final class ZoomChangeAdapter implements ZoomChangeListener { @Override public void zoomChanged() { updateEnabledState(); diff --git a/src/org/openstreetmap/josm/actions/ChangesetManagerToggleAction.java b/src/org/openstreetmap/josm/actions/ChangesetManagerToggleAction.java index 6d1387d735c..aef8c62c1a3 100644 --- a/src/org/openstreetmap/josm/actions/ChangesetManagerToggleAction.java +++ b/src/org/openstreetmap/josm/actions/ChangesetManagerToggleAction.java @@ -48,7 +48,7 @@ public void actionPerformed(ActionEvent e) { } } - private class ChangesetCacheManagerClosedHandler extends WindowAdapter { + private final class ChangesetCacheManagerClosedHandler extends WindowAdapter { @Override public void windowClosed(WindowEvent e) { setSelected(false); diff --git a/src/org/openstreetmap/josm/actions/CombineWayAction.java b/src/org/openstreetmap/josm/actions/CombineWayAction.java index c0157f2bef4..05deed4274c 100644 --- a/src/org/openstreetmap/josm/actions/CombineWayAction.java +++ b/src/org/openstreetmap/josm/actions/CombineWayAction.java @@ -10,12 +10,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Objects; -import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -279,20 +277,6 @@ public void actionPerformed(ActionEvent event) { return; } - // see #18083: check if we will combine ways at nodes outside of the download area - Set endNodesOutside = new HashSet<>(); - for (Way w : selectedWays) { - final Node[] endnodes = {w.firstNode(), w.lastNode()}; - for (Node n : endnodes) { - if (!n.isNew() && n.isOutsideDownloadArea() && !endNodesOutside.add(n)) { - new Notification(tr("Combine ways refused
" + "(A shared node is outside of the download area)")) - .setIcon(JOptionPane.INFORMATION_MESSAGE).show(); - return; - - } - } - } - // combine and update gui Pair combineResult; try { @@ -305,6 +289,10 @@ public void actionPerformed(ActionEvent event) { if (combineResult == null) return; + // see #18083: check if we will combine ways at nodes outside of the download area + if (!checkAndConfirmCombineOutlyingWays(selectedWays)) + return; + final Way selectedWay = combineResult.a; UndoRedoHandler.getInstance().add(combineResult.b); Test test = new OverlappingWays(); @@ -346,4 +334,27 @@ protected void updateEnabledState(Collection selection) setEnabled(numWays >= 2); } + /** + * Check whether user is about to combine ways with unknown parents. + * Request confirmation if he is. + * @param ways the primitives to operate on + * @return true, if operating on outlying primitives is OK; false, otherwise + */ + private static boolean checkAndConfirmCombineOutlyingWays(Collection ways) { + DownloadReferrersAction action = MainApplication.getMenu().downloadReferrers; + final String downloadHint = tr("You should use {0}->{1}({2}) first.", + MainApplication.getMenu().editMenu.getText(), action.getValue(NAME), action.getShortcut().toString()); + return Boolean.TRUE.equals(GuiHelper.runInEDTAndWaitAndReturn(() -> checkAndConfirmOutlyingOperation("combine", + tr("Combine confirmation"), + tr("You are about to combine ways which can be members of relations not yet downloaded." + + "
" + + "This can lead to damaging these parent relations (that you do not see)." + + "
" + + "{0}" + + "

" + + "Do you really want to combine without downloading?", downloadHint), + "", // not used, we never combine incomplete ways + ways, Collections.emptyList()))); + } + } diff --git a/src/org/openstreetmap/josm/actions/CreateMultipolygonAction.java b/src/org/openstreetmap/josm/actions/CreateMultipolygonAction.java index de2ba7a772e..6cda25343dd 100644 --- a/src/org/openstreetmap/josm/actions/CreateMultipolygonAction.java +++ b/src/org/openstreetmap/josm/actions/CreateMultipolygonAction.java @@ -464,7 +464,12 @@ public static List removeTagsFromWaysIfNeeded(Relation relation) { for (Entry entry : values.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); - List affectedWays = innerWays.stream().filter(way -> value.equals(way.get(key))).collect(Collectors.toList()); + List affectedWays; + if ("area".equals(key)) { + affectedWays = innerWays.stream().filter(way -> value.equals(way.get(key))).collect(Collectors.toList()); + } else { + affectedWays = new ArrayList<>(); + } if (moveTags) { // remove duplicated tags from outer ways diff --git a/src/org/openstreetmap/josm/actions/DeleteAction.java b/src/org/openstreetmap/josm/actions/DeleteAction.java index 96f17c67f94..585b22f04e7 100644 --- a/src/org/openstreetmap/josm/actions/DeleteAction.java +++ b/src/org/openstreetmap/josm/actions/DeleteAction.java @@ -16,6 +16,9 @@ import org.openstreetmap.josm.command.DeleteCommand.DeletionCallback; import org.openstreetmap.josm.data.osm.DefaultNameFormatter; +import org.openstreetmap.josm.data.osm.INode; +import org.openstreetmap.josm.data.osm.IRelation; +import org.openstreetmap.josm.data.osm.IWay; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.Relation; import org.openstreetmap.josm.data.osm.RelationToChildReference; @@ -103,13 +106,27 @@ protected void updateEnabledState(Collection selection) */ public static boolean checkAndConfirmOutlyingDelete(Collection primitives, Collection ignore) { + final boolean nodes = primitives.stream().anyMatch(INode.class::isInstance); + final boolean ways = primitives.stream().anyMatch(IWay.class::isInstance); + final boolean relations = primitives.stream().anyMatch(IRelation.class::isInstance); + final String type; + if (nodes && !ways && !relations) { + type = tr("You are about to delete nodes which can have other referrers not yet downloaded."); + } else if (!nodes && ways && !relations) { + type = tr("You are about to delete ways which can have other referrers not yet downloaded."); + } else if (!nodes && !ways && relations) { + type = tr("You are about to delete relations which can have other referrers not yet downloaded."); + } else { + // OK. We have multiple types being deleted. + type = tr("You are about to delete primitives which can have other referrers not yet downloaded."); + } return Boolean.TRUE.equals(GuiHelper.runInEDTAndWaitAndReturn(() -> checkAndConfirmOutlyingOperation("delete", tr("Delete confirmation"), - tr("You are about to delete nodes which can have other referrers not yet downloaded." + tr("{0}" + "
" + "This can cause problems because other objects (that you do not see) might use them." + "
" - + "Do you really want to delete?"), + + "Do you really want to delete?", type), tr("You are about to delete incomplete objects." + "
" + "This will cause problems because you don''t see the real object." diff --git a/src/org/openstreetmap/josm/actions/DownloadOsmInViewAction.java b/src/org/openstreetmap/josm/actions/DownloadOsmInViewAction.java index baf75f358bd..477aad6a6cb 100644 --- a/src/org/openstreetmap/josm/actions/DownloadOsmInViewAction.java +++ b/src/org/openstreetmap/josm/actions/DownloadOsmInViewAction.java @@ -54,7 +54,7 @@ protected void updateEnabledState() { && !NetworkManager.isOffline(OnlineResource.OSM_API)); } - private static class DownloadOsmInViewTask extends DownloadOsmTask { + private static final class DownloadOsmInViewTask extends DownloadOsmTask { Future download(Bounds downloadArea) { return download(new DownloadTask(new DownloadParams(), new BoundingBoxDownloader(downloadArea), null, false), downloadArea); } diff --git a/src/org/openstreetmap/josm/actions/FollowLineAction.java b/src/org/openstreetmap/josm/actions/FollowLineAction.java index b4094b4ed9f..1cbd020de97 100644 --- a/src/org/openstreetmap/josm/actions/FollowLineAction.java +++ b/src/org/openstreetmap/josm/actions/FollowLineAction.java @@ -83,6 +83,8 @@ public void actionPerformed(ActionEvent evt) { if (follower.lastNode().equals(last)) { prev = follower.getNode(follower.getNodesCount() - 2); reversed = false; + } else if (!follower.firstNode().equals(last)) { + return; // see #23442 } List referrers = last.getReferrers(); if (referrers.size() < 2) return; // There's nothing to follow diff --git a/src/org/openstreetmap/josm/actions/JoinAreasAction.java b/src/org/openstreetmap/josm/actions/JoinAreasAction.java index 3615ad59a86..300abc36cbe 100644 --- a/src/org/openstreetmap/josm/actions/JoinAreasAction.java +++ b/src/org/openstreetmap/josm/actions/JoinAreasAction.java @@ -102,6 +102,9 @@ public final List getPolygons() { } } + /** + * A record class to store how a multipolygon is constructed + */ public static class Multipolygon { private final Way outerWay; private final List innerWays; @@ -160,7 +163,7 @@ public boolean equals(Object other) { /** * HelperClass - saves a way and the "inside" side. - * + *

* insideToTheLeft: if true left side is "in", false -right side is "in". * Left and right are determined along the orientation of way. */ @@ -234,10 +237,19 @@ public void reverse() { } } + /** + * A multipolygon with a list of inner ways and an assembled polygon for the outer way + */ public static class AssembledMultipolygon { + /** The outer way of the multipolygon */ public AssembledPolygon outerWay; + /** The inner polygons of the multipolygon */ public List innerWays; + /** + * Create a new {@link AssembledMultipolygon} + * @param way The outer way + */ public AssembledMultipolygon(AssembledPolygon way) { outerWay = way; innerWays = new ArrayList<>(); @@ -401,7 +413,7 @@ public WayInPolygon walk() { } /** - * Search for an other way coming to the same head node at left side from last way. #9951 + * Search for another way coming to the same head node at left side from last way. #9951 * @return left way or null if none found */ public WayInPolygon leftComingWay() { @@ -651,8 +663,7 @@ private JoinAreasResult joinAreas(List areas) throws UserCancelExc allStartingWays.addAll(outerStartingWays); //first remove nodes in the same coordinate - boolean removedDuplicates = false; - removedDuplicates |= removeDuplicateNodes(allStartingWays); + boolean removedDuplicates = removeDuplicateNodes(allStartingWays); if (removedDuplicates) { hasChanges = true; @@ -661,7 +672,7 @@ private JoinAreasResult joinAreas(List areas) throws UserCancelExc commitCommands(marktr("Removed duplicate nodes")); // remove now unconnected nodes without tags List toRemove = oldNodes.stream().filter( - n -> (n.isNew() || !n.isOutsideDownloadArea()) && !n.hasKeys() && n.getReferrers().isEmpty()) + n -> n.isReferrersDownloaded() && !n.hasKeys() && n.getReferrers().isEmpty()) .collect(Collectors.toList()); if (!toRemove.isEmpty()) { cmds.add(new DeleteCommand(toRemove)); @@ -886,7 +897,7 @@ private boolean removeDuplicateNodes(List ways) { * @param description The description of what the commands do */ private void commitCommands(String description) { - switch(cmds.size()) { + switch (cmds.size()) { case 0: return; case 1: @@ -1134,7 +1145,7 @@ private List splitWayOnNodes(Way way, Set nodes, Map oldes if (chunks.size() > 1) { SplitWayCommand split = SplitWayCommand.splitWay(way, chunks, - Collections.emptyList(), SplitWayCommand.Strategy.keepFirstChunk()); + Collections.emptyList(), SplitWayCommand.Strategy.keepFirstChunk()); if (split != null) { //execute the command, we need the results diff --git a/src/org/openstreetmap/josm/actions/OpenFileAction.java b/src/org/openstreetmap/josm/actions/OpenFileAction.java index be8153cd796..80625bc5a23 100644 --- a/src/org/openstreetmap/josm/actions/OpenFileAction.java +++ b/src/org/openstreetmap/josm/actions/OpenFileAction.java @@ -83,7 +83,7 @@ public void actionPerformed(ActionEvent e) { final AbstractFileChooser fc; // If the user explicitly wants native file dialogs, let them use it. // Rather unfortunately, this means that they will not be able to select files and directories. - if (FileChooserManager.PROP_USE_NATIVE_FILE_DIALOG.get() + if (Boolean.TRUE.equals(FileChooserManager.PROP_USE_NATIVE_FILE_DIALOG.get()) // This is almost redundant, as the JDK currently doesn't support this with (all?) native file choosers. && !NativeFileChooser.supportsSelectionMode(FILES_AND_DIRECTORIES)) { fc = createAndOpenFileChooser(true, true, null); diff --git a/src/org/openstreetmap/josm/actions/OrthogonalizeAction.java b/src/org/openstreetmap/josm/actions/OrthogonalizeAction.java index 60d0fd1435f..b6d0cf55ffe 100644 --- a/src/org/openstreetmap/josm/actions/OrthogonalizeAction.java +++ b/src/org/openstreetmap/josm/actions/OrthogonalizeAction.java @@ -43,7 +43,7 @@ /** * Tools / Orthogonalize - * + *

* Align edges of a way so all angles are angles of 90 or 180 degrees. * See USAGE String below. */ @@ -72,7 +72,7 @@ public OrthogonalizeAction() { /** * excepted deviation from an angle of 0, 90, 180, 360 degrees * maximum value: 45 degrees - * + *

* Current policy is to except just everything, no matter how strange the result would be. */ private static final double TOLERANCE1 = Utils.toRadians(45.); // within a way @@ -85,10 +85,10 @@ public OrthogonalizeAction() { /** * Undo the previous orthogonalization for certain nodes. - * + *

* This is useful, if the way shares nodes that you don't like to change, e.g. imports or * work of another user. - * + *

* This action can be triggered by shortcut only. */ public static class Undo extends JosmAction { @@ -278,7 +278,8 @@ private static Command orthogonalize(List wayDataList, Node singleNode) for (WayData wd : wayDataList) { int n = wd.wayNodes.size(); int i = wd.wayNodes.indexOf(singleNode); - Node n0, n2; + final Node n0; + final Node n2; if (i == 0 && n >= 3 && singleNode.equals(wd.wayNodes.get(n-1))) { n0 = wd.wayNodes.get(n-2); n2 = wd.wayNodes.get(1); @@ -427,7 +428,7 @@ private static Collection orthogonalize(List wayDataList, List double average = 0; for (Node n : cs) { s.remove(n); - average += nC.get(n).doubleValue(); + average += nC.get(n); } average = average / cs.size(); @@ -546,6 +547,12 @@ public void calcDirections(Direction pInitialDirection) throws InvalidUserInputE enum Direction { RIGHT, UP, LEFT, DOWN; + /** + * Change a direction by the specified number of 90 degree increments counter-clockwise + * @param directionChange The number of increments to rotate counter-clockwise + * @return The new direction + */ + @SuppressWarnings("EnumOrdinal") // Yes, this is very dependent on order public Direction changeBy(int directionChange) { int tmp = (this.ordinal() + directionChange) % 4; if (tmp < 0) { diff --git a/src/org/openstreetmap/josm/actions/SaveAction.java b/src/org/openstreetmap/josm/actions/SaveAction.java index 310ea1d12e2..f0b0078b43a 100644 --- a/src/org/openstreetmap/josm/actions/SaveAction.java +++ b/src/org/openstreetmap/josm/actions/SaveAction.java @@ -22,8 +22,6 @@ import org.openstreetmap.josm.gui.layer.Layer; import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent; import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent; -import org.openstreetmap.josm.gui.layer.SaveToFile; -import org.openstreetmap.josm.gui.util.GuiHelper; import org.openstreetmap.josm.spi.preferences.Config; import org.openstreetmap.josm.tools.GBC; import org.openstreetmap.josm.tools.Shortcut; @@ -88,15 +86,6 @@ protected boolean listenToSelectionChange() { return false; } - @Override - protected void updateEnabledState() { - Layer activeLayer = getLayerManager().getActiveLayer(); - boolean en = activeLayer != null - && activeLayer.isSavable() && !(activeLayer.getAssociatedFile() != null - && activeLayer instanceof SaveToFile && !((SaveToFile) activeLayer).requiresSaveToFile()); - GuiHelper.runInEDT(() -> setEnabled(en)); - } - @Override public File getFile(Layer layer) { File f = layer.getAssociatedFile(); diff --git a/src/org/openstreetmap/josm/actions/SaveActionBase.java b/src/org/openstreetmap/josm/actions/SaveActionBase.java index 15f274d8ec0..f01e973b1b1 100644 --- a/src/org/openstreetmap/josm/actions/SaveActionBase.java +++ b/src/org/openstreetmap/josm/actions/SaveActionBase.java @@ -22,6 +22,7 @@ import org.openstreetmap.josm.gui.io.importexport.FileExporter; import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer; import org.openstreetmap.josm.gui.layer.Layer; +import org.openstreetmap.josm.gui.layer.SaveToFile; import org.openstreetmap.josm.gui.util.GuiHelper; import org.openstreetmap.josm.gui.widgets.AbstractFileChooser; import org.openstreetmap.josm.spi.preferences.Config; @@ -182,9 +183,14 @@ protected boolean listenToSelectionChange() { } @Override - protected void updateEnabledState() { + protected final void updateEnabledState() { Layer activeLayer = getLayerManager().getActiveLayer(); - setEnabled(activeLayer != null && activeLayer.isSavable()); + boolean en = activeLayer != null && activeLayer.isSavable(); + // see #12669 and #23648 + if (en && this instanceof SaveAction && activeLayer instanceof SaveToFile) { + en = activeLayer.getAssociatedFile() == null || ((SaveToFile) activeLayer).requiresSaveToFile(); + } + setEnabled(en); } /** diff --git a/src/org/openstreetmap/josm/actions/SelectAllAction.java b/src/org/openstreetmap/josm/actions/SelectAllAction.java index 494a616d2e8..bd1ef14d599 100644 --- a/src/org/openstreetmap/josm/actions/SelectAllAction.java +++ b/src/org/openstreetmap/josm/actions/SelectAllAction.java @@ -7,6 +7,7 @@ import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; +import org.openstreetmap.josm.data.osm.IPrimitive; import org.openstreetmap.josm.data.osm.OsmData; import org.openstreetmap.josm.tools.Shortcut; @@ -29,12 +30,7 @@ public void actionPerformed(ActionEvent e) { if (!isEnabled()) return; OsmData ds = getLayerManager().getActiveData(); - // Do not use method reference before the Java 11 migration - // Otherwise we face a compiler bug, see below: - // https://bugs.openjdk.java.net/browse/JDK-8141508 - // https://bugs.openjdk.java.net/browse/JDK-8142476 - // https://bugs.openjdk.java.net/browse/JDK-8191655 - ds.setSelected(ds.getPrimitives(t -> t.isSelectable())); + ds.setSelected(ds.getPrimitives(IPrimitive::isSelectable)); } @Override diff --git a/src/org/openstreetmap/josm/actions/SelectByInternalPointAction.java b/src/org/openstreetmap/josm/actions/SelectByInternalPointAction.java index 09b7831eae8..a76bdf51dac 100644 --- a/src/org/openstreetmap/josm/actions/SelectByInternalPointAction.java +++ b/src/org/openstreetmap/josm/actions/SelectByInternalPointAction.java @@ -103,18 +103,18 @@ public static OsmPrimitive getSmallestSurroundingObject(EastNorth internalPoint) public static void performSelection(EastNorth internalPoint, boolean doAdd, boolean doRemove) { final Collection surroundingObjects = getSurroundingObjects(internalPoint); final DataSet ds = MainApplication.getLayerManager().getActiveDataSet(); - if (surroundingObjects.isEmpty()) { - return; - } else if (doRemove) { - final Collection newSelection = new ArrayList<>(ds.getSelected()); - newSelection.removeAll(surroundingObjects); - ds.setSelected(newSelection); - } else if (doAdd) { - final Collection newSelection = new ArrayList<>(ds.getSelected()); - newSelection.add(surroundingObjects.iterator().next()); - ds.setSelected(newSelection); - } else { - ds.setSelected(surroundingObjects.iterator().next()); + if (!surroundingObjects.isEmpty()) { + if (doRemove) { + final Collection newSelection = new ArrayList<>(ds.getSelected()); + newSelection.removeAll(surroundingObjects); + ds.setSelected(newSelection); + } else if (doAdd) { + final Collection newSelection = new ArrayList<>(ds.getSelected()); + newSelection.add(surroundingObjects.iterator().next()); + ds.setSelected(newSelection); + } else { + ds.setSelected(surroundingObjects.iterator().next()); + } } } } diff --git a/src/org/openstreetmap/josm/actions/SelectNonBranchingWaySequences.java b/src/org/openstreetmap/josm/actions/SelectNonBranchingWaySequences.java index 13ed63d07d5..1eb153c8c98 100644 --- a/src/org/openstreetmap/josm/actions/SelectNonBranchingWaySequences.java +++ b/src/org/openstreetmap/josm/actions/SelectNonBranchingWaySequences.java @@ -55,10 +55,11 @@ public SelectNonBranchingWaySequences(final Collection ways) { */ private void addNodes(Node node) { if (node == null) return; - else if (!nodes.add(node)) + if (!nodes.add(node)) { outerNodes.remove(node); - else + } else { outerNodes.add(node); + } } /** diff --git a/src/org/openstreetmap/josm/actions/SessionSaveAction.java b/src/org/openstreetmap/josm/actions/SessionSaveAction.java index 9dbbcd4a649..acb1c629a26 100644 --- a/src/org/openstreetmap/josm/actions/SessionSaveAction.java +++ b/src/org/openstreetmap/josm/actions/SessionSaveAction.java @@ -2,11 +2,13 @@ package org.openstreetmap.josm.actions; import static org.openstreetmap.josm.gui.help.HelpUtil.ht; +import static org.openstreetmap.josm.tools.I18n.marktr; import static org.openstreetmap.josm.tools.I18n.tr; import static org.openstreetmap.josm.tools.I18n.trn; import java.awt.Component; import java.awt.Dimension; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; @@ -83,6 +85,7 @@ public class SessionSaveAction extends DiskAccessAction implements MapFrameListe private static final BooleanProperty SAVE_LOCAL_FILES_PROPERTY = new BooleanProperty("session.savelocal", true); private static final BooleanProperty SAVE_PLUGIN_INFORMATION_PROPERTY = new BooleanProperty("session.saveplugins", false); private static final String TOOLTIP_DEFAULT = tr("Save the current session."); + private static final String SAVE_SESSION = marktr("Save Session"); protected transient FileFilter joz = new ExtensionFileFilter("joz", "joz", tr("Session file (archive) (*.joz)")); protected transient FileFilter jos = new ExtensionFileFilter("jos", "jos", tr("Session file (*.jos)")); @@ -119,7 +122,7 @@ public SessionSaveAction() { * @param installAdapters False, if you don't want to install layer changed and selection changed adapters */ protected SessionSaveAction(boolean toolbar, boolean installAdapters) { - this(tr("Save Session"), "session", TOOLTIP_DEFAULT, + this(tr(SAVE_SESSION), "session", TOOLTIP_DEFAULT, Shortcut.registerShortcut("system:savesession", tr("File: {0}", tr("Save Session...")), KeyEvent.VK_S, Shortcut.ALT_CTRL), toolbar, "save-session", installAdapters); setHelpId(ht("/Action/SessionSave")); @@ -155,6 +158,14 @@ public void destroy() { * @throws UserCancelException when the user has cancelled the save process */ public boolean saveSession(boolean saveAs, boolean forceSaveAll) throws UserCancelException { + try { + return saveSessionImpl(saveAs, forceSaveAll); + } finally { + cleanup(); + } + } + + private boolean saveSessionImpl(boolean saveAs, boolean forceSaveAll) throws UserCancelException { if (!isEnabled()) { return false; } @@ -174,7 +185,7 @@ public boolean saveSession(boolean saveAs, boolean forceSaveAll) throws UserCanc .filter(layer -> exporters.get(layer) != null && exporters.get(layer).shallExport()) .collect(Collectors.toList()); - boolean zipRequired = layersOut.stream().map(l -> exporters.get(l)) + boolean zipRequired = layersOut.stream().map(exporters::get) .anyMatch(ex -> ex != null && ex.requiresZip()) || pluginsWantToSave(); saveAs = !doGetFile(saveAs, zipRequired); @@ -324,9 +335,9 @@ protected void doGetFileChooser(boolean zipRequired) throws UserCancelException AbstractFileChooser fc; if (zipRequired) { - fc = createAndOpenFileChooser(false, false, tr("Save Session"), joz, JFileChooser.FILES_ONLY, "lastDirectory"); + fc = createAndOpenFileChooser(false, false, tr(SAVE_SESSION), joz, JFileChooser.FILES_ONLY, "lastDirectory"); } else { - fc = createAndOpenFileChooser(false, false, tr("Save Session"), Arrays.asList(jos, joz), jos, + fc = createAndOpenFileChooser(false, false, tr(SAVE_SESSION), Arrays.asList(jos, joz), jos, JFileChooser.FILES_ONLY, "lastDirectory"); } @@ -357,7 +368,7 @@ public class SessionSaveAsDialog extends ExtendedDialog { * Constructs a new {@code SessionSaveAsDialog}. */ public SessionSaveAsDialog() { - super(MainApplication.getMainFrame(), tr("Save Session"), tr("Save As"), tr("Cancel")); + super(MainApplication.getMainFrame(), tr(SAVE_SESSION), tr("Save As"), tr("Cancel")); configureContextsensitiveHelp("Action/SessionSaveAs", true /* show help button */); initialize(); setButtonIcons("save_as", "cancel"); @@ -368,9 +379,9 @@ public SessionSaveAsDialog() { } /** - * Initializes action. + * Initializes some action fields. */ - public final void initialize() { + private void initialize() { layers = new ArrayList<>(getLayerManager().getLayers()); exporters = new HashMap<>(); dependencies = new MultiMap<>(); @@ -433,10 +444,10 @@ protected final Component build() { if (exportPanel == null) continue; JPanel wrapper = new JPanel(new GridBagLayout()); wrapper.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED)); - wrapper.add(exportPanel, GBC.std().fill(GBC.HORIZONTAL)); - ip.add(wrapper, GBC.eol().fill(GBC.HORIZONTAL).insets(2, 2, 4, 2)); + wrapper.add(exportPanel, GBC.std().fill(GridBagConstraints.HORIZONTAL)); + ip.add(wrapper, GBC.eol().fill(GridBagConstraints.HORIZONTAL).insets(2, 2, 4, 2)); } - ip.add(GBC.glue(0, 1), GBC.eol().fill(GBC.VERTICAL)); + ip.add(GBC.glue(0, 1), GBC.eol().fill(GridBagConstraints.VERTICAL)); JScrollPane sp = new JScrollPane(ip); sp.setBorder(BorderFactory.createEmptyBorder()); JPanel p = new JPanel(new GridBagLayout()); @@ -466,7 +477,7 @@ protected final Component getDisabledExportPanel(Layer layer) { lbl.setEnabled(false); p.add(include, GBC.std()); p.add(lbl, GBC.std()); - p.add(GBC.glue(1, 0), GBC.std().fill(GBC.HORIZONTAL)); + p.add(GBC.glue(1, 0), GBC.std().fill(GridBagConstraints.HORIZONTAL)); return p; } } @@ -529,7 +540,7 @@ private static void updateSessionFile(String fileName) throws UserCancelExceptio * @param layers layers that are currently represented in the session file * @deprecated since 18833, use {@link #setCurrentSession(File, List, SessionWriter.SessionWriterFlags...)} instead */ - @Deprecated + @Deprecated(since = "18833") public static void setCurrentSession(File file, boolean zip, List layers) { if (zip) { setCurrentSession(file, layers, SessionWriter.SessionWriterFlags.IS_ZIP); @@ -612,4 +623,10 @@ private static boolean pluginsWantToSave() { return false; } + protected void cleanup() { + layers = null; + exporters = null; + dependencies = null; + } + } diff --git a/src/org/openstreetmap/josm/actions/SimplifyWayAction.java b/src/org/openstreetmap/josm/actions/SimplifyWayAction.java index 5fca7d46f39..e16e92eb8dc 100644 --- a/src/org/openstreetmap/josm/actions/SimplifyWayAction.java +++ b/src/org/openstreetmap/josm/actions/SimplifyWayAction.java @@ -340,12 +340,21 @@ public static void simplifyWays(List ways, double threshold) { * @since 16566 (private) */ private static SequenceCommand buildSimplifyWaysCommand(List ways, double threshold) { - Collection allCommands = ways.stream() - .map(way -> createSimplifyCommand(way, threshold)) + List allCommands = ways.stream() + .map(way -> createSimplifyCommand(way, threshold, false)) .filter(Objects::nonNull) .collect(StreamUtils.toUnmodifiableList()); if (allCommands.isEmpty()) return null; + final List deletedPrimitives = allCommands.stream() + .map(Command::getChildren) + .flatMap(Collection::stream) + .filter(DeleteCommand.class::isInstance) + .map(DeleteCommand.class::cast) + .map(DeleteCommand::getParticipatingPrimitives) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + allCommands.get(0).getAffectedDataSet().clearSelection(deletedPrimitives); return new SequenceCommand( trn("Simplify {0} way", "Simplify {0} ways", allCommands.size(), allCommands.size()), allCommands); @@ -371,6 +380,18 @@ public static SequenceCommand createSimplifyCommand(Way w) { * @since 15419 */ public static SequenceCommand createSimplifyCommand(Way w, double threshold) { + return createSimplifyCommand(w, threshold, true); + } + + /** + * Creates the SequenceCommand to simplify a way with a given threshold. + * + * @param w the way to simplify + * @param threshold the max error threshold + * @param deselect {@code true} if we want to deselect the deleted nodes + * @return The sequence of commands to run + */ + private static SequenceCommand createSimplifyCommand(Way w, double threshold, boolean deselect) { int lower = 0; int i = 0; @@ -417,7 +438,9 @@ public static SequenceCommand createSimplifyCommand(Way w, double threshold) { Collection cmds = new LinkedList<>(); cmds.add(new ChangeNodesCommand(w, newNodes)); cmds.add(new DeleteCommand(w.getDataSet(), delNodes)); - w.getDataSet().clearSelection(delNodes); + if (deselect) { + w.getDataSet().clearSelection(delNodes); + } return new SequenceCommand( trn("Simplify Way (remove {0} node)", "Simplify Way (remove {0} nodes)", delNodes.size(), delNodes.size()), cmds); } diff --git a/src/org/openstreetmap/josm/actions/SplitWayAction.java b/src/org/openstreetmap/josm/actions/SplitWayAction.java index a4985c23bcb..8f1331b1059 100644 --- a/src/org/openstreetmap/josm/actions/SplitWayAction.java +++ b/src/org/openstreetmap/josm/actions/SplitWayAction.java @@ -6,6 +6,7 @@ import static org.openstreetmap.josm.tools.I18n.trn; import java.awt.Component; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; @@ -142,6 +143,14 @@ public static void runOn(DataSet ds) { .setIcon(JOptionPane.WARNING_MESSAGE) .show(); return; + } else if (!checkAndConfirmOutlyingOperation("splitway", tr("Split way confirmation"), + tr("You are about to split a way that may have referrers that are not yet downloaded.") + + "
" + + tr("This can lead to broken relations.") + "
" + + tr("Do you really want to split?"), + tr("The selected area is incomplete. Continue?"), + applicableWays, null)) { + return; } // Finally, applicableWays contains only one perfect way @@ -210,8 +219,8 @@ static class SegmentToKeepSelectionDialog extends ExtendedDialog { setButtonIcons("ok", "cancel"); final JPanel pane = new JPanel(new GridBagLayout()); - pane.add(new JLabel(getTitle()), GBC.eol().fill(GBC.HORIZONTAL)); - pane.add(list, GBC.eop().fill(GBC.HORIZONTAL)); + pane.add(new JLabel(getTitle()), GBC.eol().fill(GridBagConstraints.HORIZONTAL)); + pane.add(list, GBC.eop().fill(GridBagConstraints.HORIZONTAL)); setContent(pane); setDefaultCloseOperation(HIDE_ON_CLOSE); } @@ -236,7 +245,7 @@ private void configureList() { } protected void setHighlightedWaySegments(Collection segments) { - DataSet ds = selectedWay.getDataSet(); + final DataSet ds = selectedWay.getDataSet(); if (ds != null) { ds.setHighlightedWaySegments(segments); MainApplication.getMap().mapView.repaint(); @@ -246,7 +255,7 @@ protected void setHighlightedWaySegments(Collection segments) { @Override public void setVisible(boolean visible) { super.setVisible(visible); - DataSet ds = selectedWay.getDataSet(); + final DataSet ds = selectedWay.getDataSet(); if (visible) { DISPLAY_COUNT.incrementAndGet(); list.setSelectedValue(wayToKeep, true); @@ -275,7 +284,7 @@ protected void buttonAction(int buttonIndex, ActionEvent evt) { } } - private class SplitWayDataSetListener implements DataSetListener { + private final class SplitWayDataSetListener implements DataSetListener { @Override public void primitivesAdded(PrimitivesAddedEvent event) { @@ -358,7 +367,7 @@ static List getApplicableWays(List selectedWays, List selectedNo // Special case - one of the selected ways touches (not cross) way that we want to split if (selectedNodes.size() == 1) { - Node n = selectedNodes.get(0); + final Node n = selectedNodes.get(0); List referredWays = n.getParentWays(); Way inTheMiddle = null; for (Way w: referredWays) { diff --git a/src/org/openstreetmap/josm/actions/ToggleUploadDiscouragedLayerAction.java b/src/org/openstreetmap/josm/actions/ToggleUploadDiscouragedLayerAction.java index a15a58e7160..f5aab4867bd 100644 --- a/src/org/openstreetmap/josm/actions/ToggleUploadDiscouragedLayerAction.java +++ b/src/org/openstreetmap/josm/actions/ToggleUploadDiscouragedLayerAction.java @@ -11,10 +11,12 @@ import javax.swing.AbstractAction; import javax.swing.JCheckBoxMenuItem; +import org.openstreetmap.josm.gui.Notification; import org.openstreetmap.josm.gui.dialogs.LayerListDialog; import org.openstreetmap.josm.gui.layer.Layer; import org.openstreetmap.josm.gui.layer.Layer.LayerAction; import org.openstreetmap.josm.gui.layer.OsmDataLayer; +import org.openstreetmap.josm.gui.util.GuiHelper; import org.openstreetmap.josm.tools.ImageProvider; /** @@ -40,6 +42,8 @@ public ToggleUploadDiscouragedLayerAction(OsmDataLayer layer) { @Override public void actionPerformed(ActionEvent e) { layer.setUploadDiscouraged(!layer.isUploadDiscouraged()); + String msg = layer.isUploadDiscouraged() ? tr("Upload is discouraged") : tr("Upload is encouraged"); + GuiHelper.runInEDT(() -> new Notification(msg).show()); LayerListDialog.getInstance().repaint(); } diff --git a/src/org/openstreetmap/josm/actions/ValidateAction.java b/src/org/openstreetmap/josm/actions/ValidateAction.java index 300097a5f37..ebd92e0d750 100644 --- a/src/org/openstreetmap/josm/actions/ValidateAction.java +++ b/src/org/openstreetmap/josm/actions/ValidateAction.java @@ -12,7 +12,6 @@ import org.openstreetmap.josm.data.validation.OsmValidator; import org.openstreetmap.josm.data.validation.Test; import org.openstreetmap.josm.data.validation.ValidationTask; -import org.openstreetmap.josm.data.validation.util.AggregatePrimitivesVisitor; import org.openstreetmap.josm.gui.MainApplication; import org.openstreetmap.josm.gui.MapFrame; import org.openstreetmap.josm.tools.Shortcut; @@ -70,8 +69,6 @@ public void doValidate(boolean getSelectedItems) { selection = getLayerManager().getActiveDataSet().allNonDeletedPrimitives(); lastSelection = null; } else { - AggregatePrimitivesVisitor v = new AggregatePrimitivesVisitor(); - selection = v.visit(selection); lastSelection = selection; } } else { diff --git a/src/org/openstreetmap/josm/actions/corrector/ReverseWayNoTagCorrector.java b/src/org/openstreetmap/josm/actions/corrector/ReverseWayNoTagCorrector.java index 15ea587f140..76b7f623630 100644 --- a/src/org/openstreetmap/josm/actions/corrector/ReverseWayNoTagCorrector.java +++ b/src/org/openstreetmap/josm/actions/corrector/ReverseWayNoTagCorrector.java @@ -101,7 +101,7 @@ private static boolean confirmReverseWay(Way way, TagCollection tags) { null, null ); - switch(ret) { + switch (ret) { case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION: case JOptionPane.YES_OPTION: return true; diff --git a/src/org/openstreetmap/josm/actions/downloadtasks/AbstractDownloadTask.java b/src/org/openstreetmap/josm/actions/downloadtasks/AbstractDownloadTask.java index 521ad9c1675..39d170ffcb8 100644 --- a/src/org/openstreetmap/josm/actions/downloadtasks/AbstractDownloadTask.java +++ b/src/org/openstreetmap/josm/actions/downloadtasks/AbstractDownloadTask.java @@ -60,8 +60,7 @@ public void setFailed(boolean failed) { } protected static & UrlPattern> String[] patterns(Class urlPatternEnum) { - // Do not use a method reference until we switch to Java 11, as we face JDK-8141508 with Java 8 - return Arrays.stream(urlPatternEnum.getEnumConstants()).map(/* JDK-8141508 */ t -> t.pattern()).toArray(String[]::new); + return Arrays.stream(urlPatternEnum.getEnumConstants()).map(UrlPattern::pattern).toArray(String[]::new); } protected final void rememberErrorMessage(String message) { diff --git a/src/org/openstreetmap/josm/actions/downloadtasks/DownloadGeoJsonTask.java b/src/org/openstreetmap/josm/actions/downloadtasks/DownloadGeoJsonTask.java index 8fd4b61267e..44b5fc89509 100644 --- a/src/org/openstreetmap/josm/actions/downloadtasks/DownloadGeoJsonTask.java +++ b/src/org/openstreetmap/josm/actions/downloadtasks/DownloadGeoJsonTask.java @@ -5,6 +5,7 @@ import java.util.Optional; import java.util.concurrent.Future; +import java.util.function.Predicate; import org.openstreetmap.josm.data.Bounds; import org.openstreetmap.josm.data.osm.DataSet; @@ -56,7 +57,7 @@ class InternalDownloadTask extends DownloadTask { @Override protected String generateLayerName() { return Optional.of(url.substring(url.lastIndexOf('/')+1)) - .filter(it -> !Utils.isStripEmpty(it)) + .filter(Predicate.not(Utils::isStripEmpty)) .orElse(super.generateLayerName()); } diff --git a/src/org/openstreetmap/josm/actions/downloadtasks/DownloadOsmTask.java b/src/org/openstreetmap/josm/actions/downloadtasks/DownloadOsmTask.java index 34eeca71d8e..e140c4e4dd6 100644 --- a/src/org/openstreetmap/josm/actions/downloadtasks/DownloadOsmTask.java +++ b/src/org/openstreetmap/josm/actions/downloadtasks/DownloadOsmTask.java @@ -1,6 +1,7 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.actions.downloadtasks; +import static java.util.function.Predicate.not; import static org.openstreetmap.josm.tools.I18n.tr; import java.io.IOException; @@ -298,7 +299,7 @@ protected OsmDataLayer getFirstModifiableDataLayer() { */ protected String generateLayerName() { return Optional.ofNullable(settings.getLayerName()) - .filter(layerName -> !Utils.isStripEmpty(layerName)) + .filter(not(Utils::isStripEmpty)) .orElse(OsmDataLayer.createNewName()); } @@ -359,7 +360,7 @@ protected OsmDataLayer addNewLayerIfRequired(String newLayerName) { if (settings.isNewLayer() || numDataLayers == 0 || (numDataLayers > 1 && getEditLayer() == null)) { // the user explicitly wants a new layer, we don't have any layer at all // or it is not clear which layer to merge to - final OsmDataLayer layer = createNewLayer(Optional.ofNullable(newLayerName).filter(it -> !Utils.isStripEmpty(it))); + final OsmDataLayer layer = createNewLayer(Optional.ofNullable(newLayerName).filter(not(Utils::isStripEmpty))); MainApplication.getLayerManager().addLayer(layer, zoomAfterDownload); return layer; } diff --git a/src/org/openstreetmap/josm/actions/downloadtasks/DownloadReferrersTask.java b/src/org/openstreetmap/josm/actions/downloadtasks/DownloadReferrersTask.java index 221fc41d4ac..57317823c2f 100644 --- a/src/org/openstreetmap/josm/actions/downloadtasks/DownloadReferrersTask.java +++ b/src/org/openstreetmap/josm/actions/downloadtasks/DownloadReferrersTask.java @@ -123,6 +123,9 @@ protected void finish() { DataSetMerger visitor = new DataSetMerger(targetLayer.getDataSet(), parents); visitor.merge(); + this.children.stream().map(p -> targetLayer.getDataSet().getPrimitiveById(p)) + .forEach(p -> p.setReferrersDownloaded(true)); + SwingUtilities.invokeLater(targetLayer::onPostDownloadFromServer); if (visitor.getConflicts().isEmpty()) return; @@ -171,7 +174,7 @@ protected void realRun() throws SAXException, IOException, OsmTransferException return; String msg; String id = Long.toString(p.getUniqueId()); - switch(p.getType()) { + switch (p.getType()) { case NODE: msg = tr("({0}/{1}) Loading parents of node {2}", i, children.size(), id); break; case WAY: msg = tr("({0}/{1}) Loading parents of way {2}", i, children.size(), id); break; case RELATION: msg = tr("({0}/{1}) Loading parents of relation {2}", i, children.size(), id); break; diff --git a/src/org/openstreetmap/josm/actions/downloadtasks/DownloadTaskList.java b/src/org/openstreetmap/josm/actions/downloadtasks/DownloadTaskList.java index 0e57109788f..f8369409b16 100644 --- a/src/org/openstreetmap/josm/actions/downloadtasks/DownloadTaskList.java +++ b/src/org/openstreetmap/josm/actions/downloadtasks/DownloadTaskList.java @@ -212,8 +212,9 @@ protected void handlePotentiallyDeletedPrimitives(Set potentiallyD */ public Set getDownloadedPrimitives() { return tasks.stream() - .filter(t -> t instanceof DownloadOsmTask) - .map(t -> ((DownloadOsmTask) t).getDownloadedData()) + .filter(DownloadOsmTask.class::isInstance) + .map(DownloadOsmTask.class::cast) + .map(DownloadOsmTask::getDownloadedData) .filter(Objects::nonNull) .flatMap(ds -> ds.allPrimitives().stream()) .collect(Collectors.toSet()); @@ -239,7 +240,11 @@ public void run() { for (Future future : taskFutures) { try { future.get(); - } catch (InterruptedException | ExecutionException | CancellationException e) { + } catch (InterruptedException interruptedException) { + Thread.currentThread().interrupt(); + Logging.error(interruptedException); + return; + } catch (ExecutionException | CancellationException e) { Logging.error(e); return; } @@ -254,7 +259,6 @@ public void run() { + tr("The following errors occurred during mass download: {0}", Utils.joinAsHtmlUnorderedList(errors)) + "", tr("Errors during download"), JOptionPane.ERROR_MESSAGE); - return; } }); } @@ -268,7 +272,7 @@ public void run() { final DataSet editDataSet = MainApplication.getLayerManager().getEditDataSet(); if (editDataSet != null && osmData) { final List osmTasks = tasks.stream() - .filter(t -> t instanceof DownloadOsmTask).map(t -> (DownloadOsmTask) t) + .filter(DownloadOsmTask.class::isInstance).map(DownloadOsmTask.class::cast) .filter(t -> t.getDownloadedData() != null) .collect(Collectors.toList()); final Set tasksBounds = osmTasks.stream() diff --git a/src/org/openstreetmap/josm/actions/mapmode/DeleteAction.java b/src/org/openstreetmap/josm/actions/mapmode/DeleteAction.java index 493549a0e1e..111644e6be3 100644 --- a/src/org/openstreetmap/josm/actions/mapmode/DeleteAction.java +++ b/src/org/openstreetmap/josm/actions/mapmode/DeleteAction.java @@ -90,7 +90,7 @@ public Cursor cursor() { } } - private static class DeleteParameters { + private static final class DeleteParameters { private DeleteMode mode; private Node nearestNode; private WaySegment nearestSegment; diff --git a/src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java b/src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java index ce1fef120f4..6b90cd3b847 100644 --- a/src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java +++ b/src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java @@ -38,7 +38,6 @@ import org.openstreetmap.josm.data.UndoRedoHandler; import org.openstreetmap.josm.data.coor.EastNorth; import org.openstreetmap.josm.data.coor.ILatLon; -import org.openstreetmap.josm.data.osm.DataIntegrityProblemException; import org.openstreetmap.josm.data.osm.DataSet; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitive; @@ -81,6 +80,7 @@ enum Mode { extrude, translate, select, create_new, translate_node } private long mouseDownTime; private transient WaySegment selectedSegment; private transient Node selectedNode; + private transient Command lastCommandOnUndoStack; private Color mainColor; private transient Stroke mainStroke; @@ -331,6 +331,7 @@ public void exitMode() { map.keyDetector.removeModifierExListener(this); this.selectedNode = null; this.selectedSegment = null; + this.lastCommandOnUndoStack = null; super.exitMode(); } @@ -388,7 +389,7 @@ public void mousePressed(MouseEvent e) { MapFrame map = MainApplication.getMap(); if (!map.mapView.isActiveLayerVisible()) return; - if (!(Boolean) this.getValue("active")) + if (Boolean.FALSE.equals(this.getValue("active"))) return; if (e.getButton() != MouseEvent.BUTTON1) return; @@ -401,6 +402,7 @@ public void mousePressed(MouseEvent e) { // If nothing gets caught, stay in select mode if (selectedSegment == null && selectedNode == null) return; + lastCommandOnUndoStack = UndoRedoHandler.getInstance().getLastCommand(); if (selectedNode != null) { if (ctrl || nodeDragWithoutCtrl) { @@ -509,7 +511,7 @@ public void mouseDragged(MouseEvent e) { //move nodes to new position if (moveCommand == null) { //make a new move command - moveCommand = new MoveCommand(new ArrayList(movingNodeList), bestMovement); + moveCommand = new MoveCommand(new ArrayList<>(movingNodeList), bestMovement); UndoRedoHandler.getInstance().add(moveCommand); } else { //reuse existing move command @@ -545,13 +547,8 @@ public void mouseReleased(MouseEvent e) { // double click adds a new node addNewNode(e); } else if (e.getPoint().distance(initialMousePos) > initialMoveThreshold && newN1en != null && selectedSegment != null) { - try { - // main extrusion commands - performExtrusion(); - } catch (DataIntegrityProblemException ex) { - // Can occur if calling undo while extruding, see #12870 - Logging.error(ex); - } + // main extrusion commands + performExtrusion(); } } else if (mode == Mode.translate || mode == Mode.translate_node) { //Commit translate @@ -566,6 +563,7 @@ public void mouseReleased(MouseEvent e) { mapView.setNewCursor(ctrl ? cursorTranslate : alt ? cursorCreateNew : shift ? cursorCreateNodes : cursor, this); mapView.removeTemporaryLayer(this); selectedSegment = null; + lastCommandOnUndoStack = null; moveCommand = null; mode = Mode.select; dualAlignSegmentCollapsed = false; @@ -638,7 +636,13 @@ private void createNewRectangle() { * Uses {@link #newN1en}, {@link #newN2en} calculated by {@link #calculateBestMovementAndNewNodes} */ private void performExtrusion() { + // sanity checks, see #23447 and #12870: don't try to extrude when user pressed undo + if (lastCommandOnUndoStack != UndoRedoHandler.getInstance().getLastCommand()) + return; DataSet ds = getLayerManager().getEditDataSet(); + if (ds.getPrimitiveById(selectedSegment.getWay()) == null || !selectedSegment.isUsable()) + return; + // create extrusion Collection cmds = new LinkedList<>(); Way wnew = new Way(selectedSegment.getWay()); @@ -934,7 +938,8 @@ private void calculatePossibleDirectionsForDualAlign() { */ private EastNorth calculateBestMovementAndNewNodes(EastNorth mouseEn) { EastNorth bestMovement = calculateBestMovement(mouseEn); - EastNorth n1movedEn = initialN1en.add(bestMovement), n2movedEn; + EastNorth n1movedEn = initialN1en.add(bestMovement); + EastNorth n2movedEn; // find out the movement distance, in metres double distance = ProjectionRegistry.getProjection().eastNorth2latlon(initialN1en).greatCircleDistance( @@ -1153,7 +1158,8 @@ private void drawAngleSymbol(Graphics2D g2, Point2D center, Point2D normal, bool double raoffsetx = symbolSize*factor*normal.getX(); double raoffsety = symbolSize*factor*normal.getY(); - double cx = center.getX(), cy = center.getY(); + final double cx = center.getX(); + final double cy = center.getY(); double k = mirror ? -1 : 1; Point2D ra1 = new Point2D.Double(cx + raoffsetx, cy + raoffsety); Point2D ra3 = new Point2D.Double(cx - raoffsety*k, cy + raoffsetx*k); diff --git a/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java b/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java index 2ab6d49a948..7d05aa05fd2 100644 --- a/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java +++ b/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java @@ -31,6 +31,7 @@ import org.openstreetmap.josm.data.Bounds; import org.openstreetmap.josm.data.UndoRedoHandler; import org.openstreetmap.josm.data.coor.EastNorth; +import org.openstreetmap.josm.data.coor.ILatLon; import org.openstreetmap.josm.data.osm.DataSelectionListener; import org.openstreetmap.josm.data.osm.DataSet; import org.openstreetmap.josm.data.osm.Node; @@ -268,82 +269,117 @@ public void paint(Graphics2D g, MapView mv, Bounds bbox) { // that is going to be moved. // Non-native highlighting is used here as well. - // Finding endpoints - Node p1 = null; - Node p2 = null; if (ctrl && candidateSegment != null) { - g.setStroke(ADD_NODE_STROKE.get()); - try { - p1 = candidateSegment.getFirstNode(); - p2 = candidateSegment.getSecondNode(); - } catch (ArrayIndexOutOfBoundsException e) { - Logging.error(e); - } + paintAddNodeStroke(g); } else if (!alt && !ctrl && candidateNode != null) { - g.setStroke(MOVE_NODE_STROKE.get()); - List> wpps = targetWay.getNodePairs(false); - for (Pair wpp : wpps) { - if (wpp.a == candidateNode) { - p1 = wpp.b; - } - if (wpp.b == candidateNode) { - p2 = wpp.a; - } - if (p1 != null && p2 != null) { - break; - } - } + paintMoveNodeStroke(g); } else if (alt && !ctrl && candidateNode != null) { - g.setStroke(DELETE_NODE_STROKE.get()); - List nodes = targetWay.getNodes(); - int index = nodes.indexOf(candidateNode); - - // Only draw line if node is not first and/or last - if (index > 0 && index < (nodes.size() - 1)) { - p1 = nodes.get(index - 1); - p2 = nodes.get(index + 1); - } else if (targetWay.isClosed()) { - p1 = targetWay.getNode(1); - p2 = targetWay.getNode(nodes.size() - 2); - } - // TODO: indicate what part that will be deleted? (for end nodes) + paintDeleteStroke(g); } + } + } + /** + * Paint the add stroke for {@link State#IMPROVING} + * @param g The graphics + */ + private void paintAddNodeStroke(Graphics2D g) { + g.setStroke(ADD_NODE_STROKE.get()); + ILatLon p1 = null; + ILatLon p2 = null; + try { + p1 = candidateSegment.getFirstNode(); + p2 = candidateSegment.getSecondNode(); + } catch (ArrayIndexOutOfBoundsException e) { + Logging.error(e); + } + paintImprovingPreviewLines(g, p1, p2); + } - // Drawing preview lines - MapViewPath b = new MapViewPath(mv); - if (alt && !ctrl) { - // In delete mode - if (p1 != null && p2 != null) { - b.moveTo(p1); - b.lineTo(p2); - } - } else { - // In add or move mode - if (p1 != null) { - b.moveTo(mousePos.x, mousePos.y); - b.lineTo(p1); - } - if (p2 != null) { - b.moveTo(mousePos.x, mousePos.y); - b.lineTo(p2); - } + /** + * Paint the move stroke for {@link State#IMPROVING} + * @param g The graphics + */ + private void paintMoveNodeStroke(Graphics2D g) { + g.setStroke(MOVE_NODE_STROKE.get()); + List> wpps = targetWay.getNodePairs(false); + ILatLon p1 = null; + ILatLon p2 = null; + for (Pair wpp : wpps) { + if (wpp.a == candidateNode) { + p1 = wpp.b; } - g.draw(b.computeClippedLine(g.getStroke())); - - // Highlighting candidateNode - if (candidateNode != null) { - p1 = candidateNode; - g.fill(new MapViewPath(mv).shapeAround(p1, SymbolShape.SQUARE, DOT_SIZE.get())); + if (wpp.b == candidateNode) { + p2 = wpp.a; } + if (p1 != null && p2 != null) { + break; + } + } + paintImprovingPreviewLines(g, p1, p2); + } - if (!alt && !ctrl && candidateNode != null) { - b.reset(); - drawIntersectingWayHelperLines(mv, b); - g.setStroke(MOVE_NODE_INTERSECTING_STROKE.get()); - g.draw(b.computeClippedLine(g.getStroke())); + /** + * Paint the delete stroke for {@link State#IMPROVING} + * @param g The graphics + */ + private void paintDeleteStroke(Graphics2D g) { + g.setStroke(DELETE_NODE_STROKE.get()); + List nodes = targetWay.getNodes(); + int index = nodes.indexOf(candidateNode); + ILatLon p1 = null; + ILatLon p2 = null; + // Only draw line if node is not first and/or last + if (index > 0 && index < (nodes.size() - 1)) { + p1 = nodes.get(index - 1); + p2 = nodes.get(index + 1); + } else if (targetWay.isClosed()) { + p1 = targetWay.getNode(1); + p2 = targetWay.getNode(nodes.size() - 2); + } + paintImprovingPreviewLines(g, p1, p2); + // TODO: indicate what part that will be deleted? (for end nodes) + } + + /** + * Paint the preview lines for {@link State#IMPROVING} + * @param g The graphics + * @param p1 The first endpoint + * @param p2 The second endpoint + */ + private void paintImprovingPreviewLines(Graphics2D g, ILatLon p1, ILatLon p2) { + // Drawing preview lines + MapViewPath b = new MapViewPath(mv); + if (alt && !ctrl) { + // In delete mode + if (p1 != null && p2 != null) { + b.moveTo(p1); + b.lineTo(p2); } + } else { + // In add or move mode + if (p1 != null) { + b.moveTo(mousePos.x, mousePos.y); + b.lineTo(p1); + } + if (p2 != null) { + b.moveTo(mousePos.x, mousePos.y); + b.lineTo(p2); + } + } + g.draw(b.computeClippedLine(g.getStroke())); + // Highlighting candidateNode + if (candidateNode != null) { + p1 = candidateNode; + g.fill(new MapViewPath(mv).shapeAround(p1, SymbolShape.SQUARE, DOT_SIZE.get())); + } + + if (!alt && !ctrl && candidateNode != null) { + b.reset(); + drawIntersectingWayHelperLines(mv, b); + g.setStroke(MOVE_NODE_INTERSECTING_STROKE.get()); + g.draw(b.computeClippedLine(g.getStroke())); } } @@ -439,80 +475,11 @@ public void mouseReleased(MouseEvent e) { } if (ctrl && !alt && candidateSegment != null) { - // Add a new node to the highlighted segment. - Collection virtualSegments = new LinkedList<>(); - - // Check if other ways have the same segment. - // We have to make sure that we add the new node to all of them. - Set commonParentWays = new HashSet<>(candidateSegment.getFirstNode().getParentWays()); - commonParentWays.retainAll(candidateSegment.getSecondNode().getParentWays()); - for (Way w : commonParentWays) { - for (int i = 0; i < w.getNodesCount() - 1; i++) { - WaySegment testWS = new WaySegment(w, i); - if (testWS.isSimilar(candidateSegment)) { - virtualSegments.add(testWS); - } - } - } - - Collection virtualCmds = new LinkedList<>(); - // Create the new node - Node virtualNode = new Node(mv.getEastNorth(mousePos.x, mousePos.y)); - virtualCmds.add(new AddCommand(ds, virtualNode)); - - // Adding the node to all segments found - for (WaySegment virtualSegment : virtualSegments) { - Way w = virtualSegment.getWay(); - List modNodes = w.getNodes(); - modNodes.add(virtualSegment.getUpperIndex(), virtualNode); - virtualCmds.add(new ChangeNodesCommand(w, modNodes)); - } - - // Finishing the sequence command - String text = trn("Add a new node to way", - "Add a new node to {0} ways", - virtualSegments.size(), virtualSegments.size()); - - UndoRedoHandler.getInstance().add(new SequenceCommand(text, virtualCmds)); - + addNode(ds); } else if (alt && !ctrl && candidateNode != null) { - // Deleting the highlighted node - - //check to see if node is in use by more than one object - long referrersCount = candidateNode.referrers(OsmPrimitive.class).count(); - long referrerWayCount = candidateNode.referrers(Way.class).count(); - if (referrersCount != 1 || referrerWayCount != 1) { - // detach node from way - final List nodes = targetWay.getNodes(); - nodes.remove(candidateNode); - if (nodes.size() < 2) { - final Command deleteCmd = DeleteCommand.delete(Collections.singleton(targetWay), true); - if (deleteCmd != null) { - UndoRedoHandler.getInstance().add(deleteCmd); - } - } else { - UndoRedoHandler.getInstance().add(new ChangeNodesCommand(targetWay, nodes)); - } - } else if (candidateNode.isTagged()) { - JOptionPane.showMessageDialog(MainApplication.getMainFrame(), - tr("Cannot delete node that has tags"), - tr("Error"), JOptionPane.ERROR_MESSAGE); - } else { - final Command deleteCmd = DeleteCommand.delete(Collections.singleton(candidateNode), true); - if (deleteCmd != null) { - UndoRedoHandler.getInstance().add(deleteCmd); - } - } - + deleteHighlightedNode(); } else if (candidateNode != null) { - // Moving the highlighted node - EastNorth nodeEN = candidateNode.getEastNorth(); - EastNorth cursorEN = mv.getEastNorth(mousePos.x, mousePos.y); - - UndoRedoHandler.getInstance().add( - new MoveCommand(candidateNode, cursorEN.east() - nodeEN.east(), cursorEN.north() - nodeEN.north())); - - SelectAction.checkCommandForLargeDistance(UndoRedoHandler.getInstance().getLastCommand()); + moveHighlightedNode(); } } @@ -522,6 +489,95 @@ public void mouseReleased(MouseEvent e) { temporaryLayer.invalidate(); } + /** + * Add a new node to the currently highlighted segment + * @param ds The dataset to add the node to + */ + private void addNode(DataSet ds) { + // Add a new node to the highlighted segment. + Collection virtualSegments = new LinkedList<>(); + + // Check if other ways have the same segment. + // We have to make sure that we add the new node to all of them. + Set commonParentWays = new HashSet<>(candidateSegment.getFirstNode().getParentWays()); + commonParentWays.retainAll(candidateSegment.getSecondNode().getParentWays()); + for (Way w : commonParentWays) { + for (int i = 0; i < w.getNodesCount() - 1; i++) { + WaySegment testWS = new WaySegment(w, i); + if (testWS.isSimilar(candidateSegment)) { + virtualSegments.add(testWS); + } + } + } + + Collection virtualCmds = new LinkedList<>(); + // Create the new node + Node virtualNode = new Node(mv.getEastNorth(mousePos.x, mousePos.y)); + virtualCmds.add(new AddCommand(ds, virtualNode)); + + // Adding the node to all segments found + for (WaySegment virtualSegment : virtualSegments) { + Way w = virtualSegment.getWay(); + List modNodes = w.getNodes(); + modNodes.add(virtualSegment.getUpperIndex(), virtualNode); + virtualCmds.add(new ChangeNodesCommand(w, modNodes)); + } + + // Finishing the sequence command + String text = trn("Add a new node to way", + "Add a new node to {0} ways", + virtualSegments.size(), virtualSegments.size()); + + UndoRedoHandler.getInstance().add(new SequenceCommand(text, virtualCmds)); + } + + /** + * Delete the highlighted node + */ + private void deleteHighlightedNode() { + // Deleting the highlighted node + + //check to see if node is in use by more than one object + long referrersCount = candidateNode.referrers(OsmPrimitive.class).count(); + long referrerWayCount = candidateNode.referrers(Way.class).count(); + if (referrersCount != 1 || referrerWayCount != 1) { + // detach node from way + final List nodes = targetWay.getNodes(); + nodes.remove(candidateNode); + if (nodes.size() < 2) { + final Command deleteCmd = DeleteCommand.delete(Collections.singleton(targetWay), true); + if (deleteCmd != null) { + UndoRedoHandler.getInstance().add(deleteCmd); + } + } else { + UndoRedoHandler.getInstance().add(new ChangeNodesCommand(targetWay, nodes)); + } + } else if (candidateNode.isTagged()) { + JOptionPane.showMessageDialog(MainApplication.getMainFrame(), + tr("Cannot delete node that has tags"), + tr("Error"), JOptionPane.ERROR_MESSAGE); + } else { + final Command deleteCmd = DeleteCommand.delete(Collections.singleton(candidateNode), true); + if (deleteCmd != null) { + UndoRedoHandler.getInstance().add(deleteCmd); + } + } + } + + /** + * Move the highlighted node + */ + private void moveHighlightedNode() { + // Moving the highlighted node + EastNorth nodeEN = candidateNode.getEastNorth(); + EastNorth cursorEN = mv.getEastNorth(mousePos.x, mousePos.y); + + UndoRedoHandler.getInstance().add( + new MoveCommand(candidateNode, cursorEN.east() - nodeEN.east(), cursorEN.north() - nodeEN.north())); + + SelectAction.checkCommandForLargeDistance(UndoRedoHandler.getInstance().getLastCommand()); + } + @Override public void mouseExited(MouseEvent e) { if (!isEnabled()) { @@ -550,19 +606,26 @@ private void updateCursor() { mv.setNewCursor(targetWay == null ? cursorSelect : cursorSelectHover, this); } else if (state == State.IMPROVING) { - if (alt && !ctrl) { - mv.setNewCursor(cursorImproveDelete, this); - } else if (shift || dragging) { - if (ctrl) { - mv.setNewCursor(cursorImproveAddLock, this); - } else { - mv.setNewCursor(cursorImproveLock, this); - } - } else if (ctrl && !alt) { - mv.setNewCursor(cursorImproveAdd, this); + updateCursorImproving(); + } + } + + /** + * Update the mouse cursor for the {@link State#IMPROVING} mode + */ + private void updateCursorImproving() { + if (alt && !ctrl) { + mv.setNewCursor(cursorImproveDelete, this); + } else if (shift || dragging) { + if (ctrl) { + mv.setNewCursor(cursorImproveAddLock, this); } else { - mv.setNewCursor(cursorImprove, this); + mv.setNewCursor(cursorImproveLock, this); } + } else if (ctrl && !alt) { + mv.setNewCursor(cursorImproveAdd, this); + } else { + mv.setNewCursor(cursorImprove, this); } } @@ -641,38 +704,49 @@ public void startImproving(Way targetWay) { * the second case. */ private void updateStateByCurrentSelection() { + final DataSet ds = getLayerManager().getEditDataSet(); + if (ds != null && selectWay(ds)) { + return; + } + + // Starting selecting by default + startSelecting(); + } + + /** + * Select the initial way + * @param ds The dataset to get the selection from + * @return {@code true} if a way was selected + */ + private boolean selectWay(DataSet ds) { final List nodeList = new ArrayList<>(); final List wayList = new ArrayList<>(); - final DataSet ds = getLayerManager().getEditDataSet(); - if (ds != null) { - final Collection sel = ds.getSelected(); + final Collection sel = ds.getSelected(); - // Collecting nodes and ways from the selection - for (OsmPrimitive p : sel) { - if (p instanceof Way) { - wayList.add((Way) p); - } - if (p instanceof Node) { - nodeList.add((Node) p); - } + // Collecting nodes and ways from the selection + for (OsmPrimitive p : sel) { + if (p instanceof Way) { + wayList.add((Way) p); } - - if (wayList.size() == 1) { - // Starting improving the single selected way - startImproving(wayList.get(0)); - return; - } else if (nodeList.size() == 1) { - // Starting improving the only way of the single selected node - List r = nodeList.get(0).getReferrers(); - if (r.size() == 1 && (r.get(0) instanceof Way)) { - startImproving((Way) r.get(0)); - return; - } + if (p instanceof Node) { + nodeList.add((Node) p); } } - // Starting selecting by default - startSelecting(); + if (wayList.size() == 1) { + // Starting improving the single selected way + startImproving(wayList.get(0)); + return true; + } else if (nodeList.size() == 1) { + // Starting improving the only way of the single selected node + List r = nodeList.get(0).getReferrers(); + r.removeIf(osm -> !osm.isUsable()); + if (r.size() == 1 && (r.get(0) instanceof Way)) { + startImproving((Way) r.get(0)); + return true; + } + } + return false; } @Override diff --git a/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyHelper.java b/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyHelper.java index e12f985a371..51667a20db4 100644 --- a/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyHelper.java +++ b/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyHelper.java @@ -43,7 +43,7 @@ public static Way findWay(MapView mv, Point p) { Node node = mv.getNearestNode(p, OsmPrimitive::isSelectable); if (node != null) { - Optional candidate = node.referrers(Way.class).findFirst(); + Optional candidate = node.referrers(Way.class).filter(Way::isUsable).findFirst(); if (candidate.isPresent()) { return candidate.get(); } @@ -69,13 +69,12 @@ public static Node findCandidateNode(MapView mv, Way w, Point p) { EastNorth pEN = mv.getEastNorth(p.x, p.y); - Double bestDistance = Double.MAX_VALUE; - Double currentDistance; + double bestDistance = Double.MAX_VALUE; + double currentDistance; List> wpps = w.getNodePairs(false); Node result = null; - mainLoop: for (Node n : w.getNodes()) { EastNorth nEN = n.getEastNorth(); @@ -86,17 +85,7 @@ public static Node findCandidateNode(MapView mv, Way w, Point p) { currentDistance = pEN.distance(nEN); - if (currentDistance < bestDistance) { - // Making sure this candidate is not behind any segment. - for (Pair wpp : wpps) { - if (!wpp.a.equals(n) - && !wpp.b.equals(n) - && Geometry.getSegmentSegmentIntersection( - wpp.a.getEastNorth(), wpp.b.getEastNorth(), - pEN, nEN) != null) { - continue mainLoop; - } - } + if (currentDistance < bestDistance && ensureCandidateIsNotBehindSegments(wpps, n, pEN, nEN)) { result = n; bestDistance = currentDistance; } @@ -105,11 +94,34 @@ public static Node findCandidateNode(MapView mv, Way w, Point p) { return result; } + /** + * Check to see if a candidate node is underneath a way segment + * + * @param wpps The pairs of nodes to check for crossing way segments + * @param n The current node to check + * @param pEN The cursor east-north position + * @param nEN The node east-north position + * @return {@code true} if the candidate node is underneath a way segment + */ + private static boolean ensureCandidateIsNotBehindSegments(Iterable> wpps, Node n, EastNorth pEN, EastNorth nEN) { + // Making sure this candidate is not behind any segment. + for (Pair wpp : wpps) { + if (!wpp.a.equals(n) + && !wpp.b.equals(n) + && Geometry.getSegmentSegmentIntersection( + wpp.a.getEastNorth(), wpp.b.getEastNorth(), + pEN, nEN) != null) { + return false; + } + } + return true; + } + /** * Returns the nearest way segment to cursor. The distance to segment ab is * the length of altitude from p to ab (say, c) or the minimum distance from * p to a or b if c is out of ab. - * + *

* The priority is given to segments where c is in ab. Otherwise, a segment * with the largest angle apb is chosen. * @@ -125,10 +137,8 @@ public static WaySegment findCandidateSegment(MapView mv, Way w, Point p) { EastNorth pEN = mv.getEastNorth(p.x, p.y); - Double currentDistance; - Double currentAngle; - Double bestDistance = Double.MAX_VALUE; - Double bestAngle = 0.0; + double bestDistance = Double.MAX_VALUE; + double bestAngle = 0.0; int candidate = -1; @@ -143,8 +153,9 @@ public static WaySegment findCandidateSegment(MapView mv, Way w, Point p) { // Finding intersection of the segment with its altitude from p EastNorth altitudeIntersection = Geometry.closestPointToSegment(a, b, pEN); - currentDistance = pEN.distance(altitudeIntersection); + final double currentDistance = pEN.distance(altitudeIntersection); + final double currentAngle; if (!altitudeIntersection.equals(a) && !altitudeIntersection.equals(b)) { // If the segment intersects with the altitude from p, // make an angle too big to let this candidate win any others diff --git a/src/org/openstreetmap/josm/actions/mapmode/MapMode.java b/src/org/openstreetmap/josm/actions/mapmode/MapMode.java index 78d9b1ba7b1..911aae8dc08 100644 --- a/src/org/openstreetmap/josm/actions/mapmode/MapMode.java +++ b/src/org/openstreetmap/josm/actions/mapmode/MapMode.java @@ -185,19 +185,20 @@ protected void updateKeyModifiersEx(int modifiers) { * @return extended modifiers */ private static int mapOldModifiers(int modifiers) { - if ((modifiers & InputEvent.CTRL_MASK) != 0) { + if ((modifiers & ActionEvent.CTRL_MASK) != 0) { modifiers |= InputEvent.CTRL_DOWN_MASK; } - if ((modifiers & InputEvent.META_MASK) != 0) { + if ((modifiers & ActionEvent.META_MASK) != 0) { modifiers |= InputEvent.META_DOWN_MASK; } - if ((modifiers & InputEvent.ALT_MASK) != 0) { + if ((modifiers & ActionEvent.ALT_MASK) != 0) { modifiers |= InputEvent.ALT_DOWN_MASK; } + /* Old modifier in InputEvent is deprecated, but ActionEvent has no ALT_GRAPH */ if ((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) { modifiers |= InputEvent.ALT_GRAPH_DOWN_MASK; } - if ((modifiers & InputEvent.SHIFT_MASK) != 0) { + if ((modifiers & ActionEvent.SHIFT_MASK) != 0) { modifiers |= InputEvent.SHIFT_DOWN_MASK; } diff --git a/src/org/openstreetmap/josm/actions/mapmode/ParallelWayAction.java b/src/org/openstreetmap/josm/actions/mapmode/ParallelWayAction.java index 79d75a00964..1b9ec3986d1 100644 --- a/src/org/openstreetmap/josm/actions/mapmode/ParallelWayAction.java +++ b/src/org/openstreetmap/josm/actions/mapmode/ParallelWayAction.java @@ -224,7 +224,9 @@ public void modifiersExChanged(int modifiers) { } private boolean updateModifiersState(int modifiers) { - boolean oldAlt = alt, oldShift = shift, oldCtrl = ctrl; + boolean oldAlt = alt; + boolean oldShift = shift; + boolean oldCtrl = ctrl; updateKeyModifiersEx(modifiers); return oldAlt != alt || oldShift != shift || oldCtrl != ctrl; } @@ -615,7 +617,7 @@ public static Optional findWithShortCode(int charCode) { } } - private class ParallelWayLayer extends AbstractMapViewPaintable { + private final class ParallelWayLayer extends AbstractMapViewPaintable { @Override public void paint(Graphics2D g, MapView mv, Bounds bbox) { if (mode == Mode.DRAGGING) { diff --git a/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java b/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java index 90752143cf0..fcf5dab5ce0 100644 --- a/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java +++ b/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java @@ -43,7 +43,7 @@ import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.data.osm.WaySegment; import org.openstreetmap.josm.data.osm.visitor.AllNodesVisitor; -import org.openstreetmap.josm.data.osm.visitor.paint.WireframeMapRenderer; +import org.openstreetmap.josm.data.osm.visitor.paint.AbstractMapRenderer; import org.openstreetmap.josm.data.preferences.BooleanProperty; import org.openstreetmap.josm.data.preferences.CachingProperty; import org.openstreetmap.josm.gui.ExtendedDialog; @@ -205,7 +205,7 @@ public Cursor cursor() { public SelectAction(MapFrame mapFrame) { super(tr("Select mode"), "move/move", tr("Select, move, scale and rotate objects"), Shortcut.registerShortcut("mapmode:select", tr("Mode: {0}", tr("Select mode")), KeyEvent.VK_S, Shortcut.DIRECT), - ImageProvider.getCursor("normal", "selection")); + ImageProvider.getCursor(NORMAL, "selection")); mv = mapFrame.mapView; setHelpId(ht("/Action/Select")); selectionManager = new SelectionManager(this, false, mv); @@ -315,7 +315,7 @@ private boolean giveUserFeedback(MouseEvent e, int modifiers) { */ private Cursor getCursor(OsmPrimitive nearbyStuff) { String c = "rect"; - switch(mode) { + switch (mode) { case MOVE: if (virtualManager.hasVirtualNode()) { c = "virtual_node"; @@ -437,7 +437,7 @@ public void mousePressed(MouseEvent e) { determineMapMode(nearestPrimitive != null); - switch(mode) { + switch (mode) { case ROTATE: case SCALE: // if nothing was selected, select primitive under cursor for scaling or rotating @@ -518,7 +518,7 @@ public void mouseDragged(MouseEvent e) { if ((mode == Mode.MOVE) && (System.currentTimeMillis() - mouseDownTime < initialMoveDelay)) return; - if (mode != Mode.ROTATE && mode != Mode.SCALE && (e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == 0) { + if (mode != Mode.ROTATE && mode != Mode.SCALE && (e.getModifiersEx() & InputEvent.BUTTON1_DOWN_MASK) == 0) { // button is pressed in rotate mode return; } @@ -1115,7 +1115,7 @@ public void setLassoMode(boolean lassoMode) { private final transient CycleManager cycleManager = new CycleManager(); private final transient VirtualManager virtualManager = new VirtualManager(); - private class CycleManager { + private final class CycleManager { private Collection cycleList = Collections.emptyList(); private boolean cyclePrims; @@ -1254,7 +1254,7 @@ private Collection cyclePrims() { } } - private class VirtualManager { + private final class VirtualManager { private Node virtualNode; private Collection virtualWays = new LinkedList<>(); @@ -1293,7 +1293,7 @@ private boolean activateVirtualNodeNearPoint(Point p) { wnp.b = w.getNode(ws.getUpperIndex()); MapViewPoint p1 = mv.getState().getPointFor(wnp.a); MapViewPoint p2 = mv.getState().getPointFor(wnp.b); - if (WireframeMapRenderer.isLargeSegment(p1, p2, virtualSpace)) { + if (AbstractMapRenderer.isLargeSegment(p1, p2, virtualSpace)) { Point2D pc = new Point2D.Double((p1.getInViewX() + p2.getInViewX()) / 2, (p1.getInViewY() + p2.getInViewY()) / 2); if (p.distanceSq(pc) < virtualSnapDistSq2) { // Check that only segments on top of each other get added to the diff --git a/src/org/openstreetmap/josm/actions/search/SearchAction.java b/src/org/openstreetmap/josm/actions/search/SearchAction.java index d544f3698f6..1c397c1380a 100644 --- a/src/org/openstreetmap/josm/actions/search/SearchAction.java +++ b/src/org/openstreetmap/josm/actions/search/SearchAction.java @@ -83,7 +83,7 @@ public Collection getKeywords() { @Override public Match get(String keyword, boolean caseSensitive, boolean regexSearch, PushbackTokenizer tokenizer) throws SearchParseError { - switch(keyword) { + switch (keyword) { case "inview": return new InView(false); case "allinview": @@ -290,7 +290,7 @@ void receiveSearchResult(OsmData ds, Collection result, /** * Select the search result and display a status text for it. */ - private static class SelectSearchReceiver implements SearchReceiver { + private static final class SelectSearchReceiver implements SearchReceiver { @Override public void receiveSearchResult(OsmData ds, Collection result, @@ -397,7 +397,7 @@ protected void realRun() { if (setting.allElements) { all = ds.allPrimitives(); } else { - all = ds.getPrimitives(p -> p.isSelectable()); // Do not use method reference before Java 11! + all = ds.getPrimitives(IPrimitive::isSelectable); } final ProgressMonitor subMonitor = getProgressMonitor().createSubTaskMonitor(all.size(), false); subMonitor.beginTask(trn("Searching in {0} object", "Searching in {0} objects", all.size(), all.size())); diff --git a/src/org/openstreetmap/josm/actions/upload/ValidateUploadHook.java b/src/org/openstreetmap/josm/actions/upload/ValidateUploadHook.java index 63986dd0c6e..c10cc5638d1 100644 --- a/src/org/openstreetmap/josm/actions/upload/ValidateUploadHook.java +++ b/src/org/openstreetmap/josm/actions/upload/ValidateUploadHook.java @@ -6,6 +6,7 @@ import java.awt.Dimension; import java.awt.GridBagLayout; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -17,11 +18,9 @@ import org.openstreetmap.josm.data.validation.OsmValidator; import org.openstreetmap.josm.data.validation.TestError; import org.openstreetmap.josm.data.validation.ValidationTask; -import org.openstreetmap.josm.data.validation.util.AggregatePrimitivesVisitor; import org.openstreetmap.josm.gui.ExtendedDialog; import org.openstreetmap.josm.gui.MainApplication; import org.openstreetmap.josm.gui.dialogs.validator.ValidatorTreePanel; -import org.openstreetmap.josm.gui.layer.ValidatorLayer; import org.openstreetmap.josm.gui.util.GuiHelper; import org.openstreetmap.josm.gui.widgets.HtmlPanel; import org.openstreetmap.josm.tools.GBC; @@ -45,9 +44,9 @@ public class ValidateUploadHook implements UploadHook { @Override public boolean checkUpload(APIDataSet apiDataSet) { AtomicBoolean returnCode = new AtomicBoolean(); - AggregatePrimitivesVisitor v = new AggregatePrimitivesVisitor(); - v.visit(apiDataSet.getPrimitivesToAdd()); - Collection visited = v.visit(apiDataSet.getPrimitivesToUpdate()); + Collection toCheck = new HashSet<>(); + toCheck.addAll(apiDataSet.getPrimitivesToAdd()); + toCheck.addAll(apiDataSet.getPrimitivesToUpdate()); OsmValidator.initializeTests(); new ValidationTask(errors -> { if (errors.stream().allMatch(TestError::isIgnored)) { @@ -58,7 +57,7 @@ public boolean checkUpload(APIDataSet apiDataSet) { // of the progress monitor. GuiHelper.runInEDTAndWait(() -> returnCode.set(displayErrorScreen(errors))); } - }, null, OsmValidator.getEnabledTests(true), visited, null, true).run(); + }, null, OsmValidator.getEnabledTests(true), toCheck, null, true).run(); return returnCode.get(); } @@ -105,13 +104,6 @@ private static boolean displayErrorScreen(List errors) { int rc = ed.showDialog().getValue(); GuiHelper.destroyComponents(ed, false); ed.dispose(); - if (rc != 1) { - OsmValidator.initializeTests(); - OsmValidator.initializeErrorLayer(); - MainApplication.getMap().validatorDialog.unfurlDialog(); - MainApplication.getLayerManager().getLayersOfType(ValidatorLayer.class).forEach(ValidatorLayer::invalidate); - return false; - } - return true; + return rc == 1; } } diff --git a/src/org/openstreetmap/josm/command/AddCommand.java b/src/org/openstreetmap/josm/command/AddCommand.java index 64374a41350..5cd41f7e260 100644 --- a/src/org/openstreetmap/josm/command/AddCommand.java +++ b/src/org/openstreetmap/josm/command/AddCommand.java @@ -71,7 +71,7 @@ public void fillModifiedData(Collection modified, Collection modified, Collection entry = tags.entrySet().iterator().next(); if (Utils.isEmpty(entry.getValue())) { - switch(OsmPrimitiveType.from(primitive)) { + switch (OsmPrimitiveType.from(primitive)) { case NODE: msg = marktr("Remove \"{0}\" for node ''{1}''"); break; case WAY: msg = marktr("Remove \"{0}\" for way ''{1}''"); break; case RELATION: msg = marktr("Remove \"{0}\" for relation ''{1}''"); break; @@ -213,7 +213,7 @@ public String getDescriptionText() { } text = tr(msg, entry.getKey(), primitive.getDisplayName(DefaultNameFormatter.getInstance())); } else { - switch(OsmPrimitiveType.from(primitive)) { + switch (OsmPrimitiveType.from(primitive)) { case NODE: msg = marktr("Set {0}={1} for node ''{2}''"); break; case WAY: msg = marktr("Set {0}={1} for way ''{2}''"); break; case RELATION: msg = marktr("Set {0}={1} for relation ''{2}''"); break; diff --git a/src/org/openstreetmap/josm/command/Command.java b/src/org/openstreetmap/josm/command/Command.java index 64712378e05..e5f8267d4c2 100644 --- a/src/org/openstreetmap/josm/command/Command.java +++ b/src/org/openstreetmap/josm/command/Command.java @@ -227,9 +227,7 @@ public static int checkOutlyingOrIncompleteOperation( for (OsmPrimitive osm : primitives) { if (osm.isIncomplete()) { res |= IS_INCOMPLETE; - } else if ((res & IS_OUTSIDE) == 0 && (osm.isOutsideDownloadArea() - || (osm instanceof Node && !osm.isNew() && osm.getDataSet() != null && osm.getDataSet().getDataSourceBounds().isEmpty())) - && (ignore == null || !ignore.contains(osm))) { + } else if ((res & IS_OUTSIDE) == 0 && !osm.isReferrersDownloaded() && (ignore == null || !ignore.contains(osm))) { res |= IS_OUTSIDE; } } diff --git a/src/org/openstreetmap/josm/command/DeleteCommand.java b/src/org/openstreetmap/josm/command/DeleteCommand.java index a7117b0a9ef..d8eb3715db6 100644 --- a/src/org/openstreetmap/josm/command/DeleteCommand.java +++ b/src/org/openstreetmap/josm/command/DeleteCommand.java @@ -249,7 +249,7 @@ public String getDescriptionText() { if (toDelete.size() == 1) { OsmPrimitive primitive = toDelete.iterator().next(); String msg; - switch(OsmPrimitiveType.from(primitive)) { + switch (OsmPrimitiveType.from(primitive)) { case NODE: msg = marktr("Delete node {0}"); break; case WAY: msg = marktr("Delete way {0}"); break; case RELATION:msg = marktr("Delete relation {0}"); break; @@ -264,7 +264,7 @@ public String getDescriptionText() { msg = trn("Delete {0} object", "Delete {0} objects", toDelete.size(), toDelete.size()); } else { OsmPrimitiveType t = typesToDelete.iterator().next(); - switch(t) { + switch (t) { case NODE: msg = trn("Delete {0} node", "Delete {0} nodes", toDelete.size(), toDelete.size()); break; case WAY: msg = trn("Delete {0} way", "Delete {0} ways", toDelete.size(), toDelete.size()); break; case RELATION: msg = trn("Delete {0} relation", "Delete {0} relations", toDelete.size(), toDelete.size()); break; @@ -433,8 +433,7 @@ public static Command delete(Collection selection, boole primitivesToDelete.addAll(nodesToDelete); } - if (!silent && !callback.checkAndConfirmOutlyingDelete( - primitivesToDelete, Utils.filteredCollection(primitivesToDelete, Way.class))) + if (!silent && !callback.checkAndConfirmOutlyingDelete(primitivesToDelete, null)) return null; Collection waysToBeChanged = primitivesToDelete.stream() diff --git a/src/org/openstreetmap/josm/command/PurgeCommand.java b/src/org/openstreetmap/josm/command/PurgeCommand.java index 38df24c2f19..d0c6a12168e 100644 --- a/src/org/openstreetmap/josm/command/PurgeCommand.java +++ b/src/org/openstreetmap/josm/command/PurgeCommand.java @@ -95,7 +95,7 @@ public boolean executeCommand() { // but that would not free memory in case the // user clears undo/redo buffer after purge PrimitiveData empty; - switch(osm.getType()) { + switch (osm.getType()) { case NODE: empty = new NodeData(); break; case WAY: empty = new WayData(); break; case RELATION: empty = new RelationData(); break; diff --git a/src/org/openstreetmap/josm/command/conflict/ModifiedConflictResolveCommand.java b/src/org/openstreetmap/josm/command/conflict/ModifiedConflictResolveCommand.java index 638ac69c029..a5f06c3e22d 100644 --- a/src/org/openstreetmap/josm/command/conflict/ModifiedConflictResolveCommand.java +++ b/src/org/openstreetmap/josm/command/conflict/ModifiedConflictResolveCommand.java @@ -35,7 +35,7 @@ public ModifiedConflictResolveCommand(Conflict conflict) @Override public String getDescriptionText() { String msg; - switch(OsmPrimitiveType.from(conflict.getMy())) { + switch (OsmPrimitiveType.from(conflict.getMy())) { case NODE: msg = marktr("Set the ''modified'' flag for node {0}"); break; case WAY: msg = marktr("Set the ''modified'' flag for way {0}"); break; case RELATION: msg = marktr("Set the ''modified'' flag for relation {0}"); break; diff --git a/src/org/openstreetmap/josm/command/conflict/VersionConflictResolveCommand.java b/src/org/openstreetmap/josm/command/conflict/VersionConflictResolveCommand.java index a2b8f0b5df5..2920a4dade5 100644 --- a/src/org/openstreetmap/josm/command/conflict/VersionConflictResolveCommand.java +++ b/src/org/openstreetmap/josm/command/conflict/VersionConflictResolveCommand.java @@ -35,7 +35,7 @@ public VersionConflictResolveCommand(Conflict conflict) @Override public String getDescriptionText() { String msg; - switch(OsmPrimitiveType.from(conflict.getMy())) { + switch (OsmPrimitiveType.from(conflict.getMy())) { case NODE: msg = marktr("Resolve version conflict for node {0}"); break; case WAY: msg = marktr("Resolve version conflict for way {0}"); break; case RELATION: msg = marktr("Resolve version conflict for relation {0}"); break; diff --git a/src/org/openstreetmap/josm/data/ImageData.java b/src/org/openstreetmap/josm/data/ImageData.java index 7be3c4cb8f6..232ecc6588e 100644 --- a/src/org/openstreetmap/josm/data/ImageData.java +++ b/src/org/openstreetmap/josm/data/ImageData.java @@ -378,7 +378,7 @@ private void afterImageUpdated(ImageEntry img) { } /** - * Set the layer for use with {@link org.openstreetmap.josm.gui.layer.geoimage.ImageViewerDialog#displayImages(Layer, List)} + * Set the layer for use with {@link org.openstreetmap.josm.gui.layer.geoimage.ImageViewerDialog#displayImages(List)} * @param layer The layer to use for organization * @since 18591 */ diff --git a/src/org/openstreetmap/josm/data/Preferences.java b/src/org/openstreetmap/josm/data/Preferences.java index 1038eb1fe6b..71823140415 100644 --- a/src/org/openstreetmap/josm/data/Preferences.java +++ b/src/org/openstreetmap/josm/data/Preferences.java @@ -37,11 +37,13 @@ import org.openstreetmap.josm.data.preferences.ColorInfo; import org.openstreetmap.josm.data.preferences.JosmBaseDirectories; +import org.openstreetmap.josm.data.preferences.JosmUrls; import org.openstreetmap.josm.data.preferences.NamedColorProperty; import org.openstreetmap.josm.data.preferences.PreferencesReader; import org.openstreetmap.josm.data.preferences.PreferencesWriter; import org.openstreetmap.josm.gui.MainApplication; import org.openstreetmap.josm.io.NetworkManager; +import org.openstreetmap.josm.io.OsmApi; import org.openstreetmap.josm.spi.preferences.AbstractPreferences; import org.openstreetmap.josm.spi.preferences.Config; import org.openstreetmap.josm.spi.preferences.DefaultPreferenceChangeEvent; @@ -910,6 +912,12 @@ private void removeAndUpdateObsolete(int loadedVersion) { } modifiedDefault = false; } + // As of June 1st, 2024, the OSM.org instance no longer allows basic authentication. + if (JosmUrls.getInstance().getDefaultOsmApiUrl().equals(OsmApi.getOsmApi().getServerUrl()) && "basic".equals(OsmApi.getAuthMethod())) { + put("osm-server.auth-method", null); + put("osm-server.username", null); + put("osm-server.password", null); + } } /** @@ -917,6 +925,7 @@ private void removeAndUpdateObsolete(int loadedVersion) { * This should be removed sometime after 2024-06-01. */ private void updateMapPaintKnownDefaults() { + final String mapPaintStyleEntriesPrefEntry = "mappaint.style.entries"; final String url = "url"; final String active = "active"; final String potlatch2 = "resource://styles/standard/potlatch2.mapcss"; @@ -932,8 +941,13 @@ private void updateMapPaintKnownDefaults() { knownDefaults.removeIf("resource://styles/standard/elemstyles.xml"::equals); putList("mappaint.style.known-defaults", knownDefaults); + // If the user hasn't set the entries, don't go through the removal process for potlatch 2. There is an issue + // where it may clear all paintstyles (done when the user has never touched the style settings). + if (!this.settingsMap.containsKey(mapPaintStyleEntriesPrefEntry)) { + return; + } // Replace potlatch2 in the current style entries, but only if it is enabled. Otherwise, remove it. - final List> styleEntries = new ArrayList<>(getListOfMaps("mappaint.style.entries")); + final List> styleEntries = new ArrayList<>(getListOfMaps(mapPaintStyleEntriesPrefEntry)); final boolean potlatchEnabled = styleEntries.stream().filter(map -> potlatch2.equals(map.get(url))) .anyMatch(map -> Boolean.parseBoolean(map.get(active))); final boolean remotePotlatch2Present = styleEntries.stream().anyMatch(map -> remotePotlatch2.equals(map.get(url))); @@ -945,7 +959,7 @@ private void updateMapPaintKnownDefaults() { map.put(url, remotePotlatch2); } } - putListOfMaps("mappaint.style.entries", styleEntries.isEmpty() ? null : styleEntries); + putListOfMaps(mapPaintStyleEntriesPrefEntry, styleEntries); } /** diff --git a/src/org/openstreetmap/josm/data/StructUtils.java b/src/org/openstreetmap/josm/data/StructUtils.java index 438619b2ce7..d9e6e5f828f 100644 --- a/src/org/openstreetmap/josm/data/StructUtils.java +++ b/src/org/openstreetmap/josm/data/StructUtils.java @@ -266,13 +266,11 @@ private static Field[] getDeclaredFieldsInClassOrSuperTypes(Class clazz) return fields.toArray(new Field[] {}); } - @SuppressWarnings("rawtypes") - private static String mapToJson(Map map) { + private static String mapToJson(Map map) { StringWriter stringWriter = new StringWriter(); try (JsonWriter writer = Json.createWriter(stringWriter)) { JsonObjectBuilder object = Json.createObjectBuilder(); - for (Object o: map.entrySet()) { - Map.Entry e = (Map.Entry) o; + for (Map.Entry e: map.entrySet()) { Object evalue = e.getValue(); object.add(e.getKey().toString(), evalue.toString()); } @@ -281,12 +279,11 @@ private static String mapToJson(Map map) { return stringWriter.toString(); } - @SuppressWarnings({ "rawtypes", "unchecked" }) - private static Map mapFromJson(String s) { - Map ret; + private static Map mapFromJson(String s) { + Map ret; try (JsonReader reader = Json.createReader(new StringReader(s))) { JsonObject object = reader.readObject(); - ret = new HashMap(Utils.hashMapInitialCapacity(object.size())); + ret = new HashMap<>(Utils.hashMapInitialCapacity(object.size())); for (Map.Entry e: object.entrySet()) { JsonValue value = e.getValue(); if (value instanceof JsonString) { @@ -300,14 +297,12 @@ private static Map mapFromJson(String s) { return ret; } - @SuppressWarnings("rawtypes") - private static String multiMapToJson(MultiMap map) { + private static String multiMapToJson(MultiMap map) { StringWriter stringWriter = new StringWriter(); try (JsonWriter writer = Json.createWriter(stringWriter)) { JsonObjectBuilder object = Json.createObjectBuilder(); - for (Object o: map.entrySet()) { - Map.Entry e = (Map.Entry) o; - Set evalue = (Set) e.getValue(); + for (Map.Entry e : map.entrySet()) { + Set evalue = (Set) e.getValue(); JsonArrayBuilder a = Json.createArrayBuilder(); for (Object evo: evalue) { a.add(evo.toString()); @@ -319,12 +314,11 @@ private static String multiMapToJson(MultiMap map) { return stringWriter.toString(); } - @SuppressWarnings({ "rawtypes", "unchecked" }) - private static MultiMap multiMapFromJson(String s) { - MultiMap ret; + private static MultiMap multiMapFromJson(String s) { + MultiMap ret; try (JsonReader reader = Json.createReader(new StringReader(s))) { JsonObject object = reader.readObject(); - ret = new MultiMap(object.size()); + ret = new MultiMap<>(object.size()); for (Map.Entry e: object.entrySet()) { JsonValue value = e.getValue(); if (value instanceof JsonArray) { diff --git a/src/org/openstreetmap/josm/data/UndoRedoHandler.java b/src/org/openstreetmap/josm/data/UndoRedoHandler.java index fde615cc212..5e6a9300efa 100644 --- a/src/org/openstreetmap/josm/data/UndoRedoHandler.java +++ b/src/org/openstreetmap/josm/data/UndoRedoHandler.java @@ -39,7 +39,7 @@ public final class UndoRedoHandler { private final LinkedList listenerCommands = new LinkedList<>(); private final LinkedList preciseListenerCommands = new LinkedList<>(); - private static class InstanceHolder { + private static final class InstanceHolder { static final UndoRedoHandler INSTANCE = new UndoRedoHandler(); } diff --git a/src/org/openstreetmap/josm/data/UserIdentityManager.java b/src/org/openstreetmap/josm/data/UserIdentityManager.java index 36028e06fae..36b20a9ec03 100644 --- a/src/org/openstreetmap/josm/data/UserIdentityManager.java +++ b/src/org/openstreetmap/josm/data/UserIdentityManager.java @@ -214,7 +214,7 @@ public User asUser() { public void initFromPreferences() { String credentialsUserName = CredentialsManager.getInstance().getUsername(); if (isAnonymous()) { - if (!Utils.isBlank(credentialsUserName)) { + if (!Utils.isStripEmpty(credentialsUserName)) { setPartiallyIdentified(credentialsUserName); } } else { @@ -279,7 +279,7 @@ public void preferenceChanged(PreferenceChangeEvent evt) { if (evt.getNewValue() instanceof StringSetting) { newUserName = ((StringSetting) evt.getNewValue()).getValue(); } - if (Utils.isBlank(newUserName)) { + if (Utils.isStripEmpty(newUserName)) { setAnonymous(); } else if (!newUserName.equals(userName)) { setPartiallyIdentified(newUserName); @@ -290,7 +290,7 @@ public void preferenceChanged(PreferenceChangeEvent evt) { if (evt.getNewValue() instanceof StringSetting) { newUrl = ((StringSetting) evt.getNewValue()).getValue(); } - if (Utils.isBlank(newUrl)) { + if (Utils.isStripEmpty(newUrl)) { setAnonymous(); } else if (isFullyIdentified()) { setPartiallyIdentified(getUserName()); diff --git a/src/org/openstreetmap/josm/data/algorithms/Tarjan.java b/src/org/openstreetmap/josm/data/algorithms/Tarjan.java new file mode 100644 index 00000000000..4d9ba10a761 --- /dev/null +++ b/src/org/openstreetmap/josm/data/algorithms/Tarjan.java @@ -0,0 +1,168 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.data.algorithms; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.openstreetmap.josm.data.osm.Node; +import org.openstreetmap.josm.data.osm.NodeGraph; +import org.openstreetmap.josm.tools.Pair; +import org.openstreetmap.josm.tools.Utils; + +/** + * Tarjan's strongly connected components algorithm for JOSM. + * + * @author gaben + * @see + * Tarjan's strongly connected components algorithm + * @since 19062 + */ +public final class Tarjan { + + /** + * Used to remember visited nodes and its metadata. Key is used for storing + * the unique ID of the nodes instead of the full data to save space. + */ + private final Map registry; + + /** Used to store the graph data as a map. */ + private final Map> graphMap; + + /** Used to store strongly connected components. NOTE: single nodes are not stored to save memory. */ + private final Collection> scc = new ArrayList<>(); + + /** Used on algorithm runtime to keep track discovery progress. */ + private final Deque stack = new ArrayDeque<>(); + + /** Used on algorithm runtime to keep track discovery progress. */ + private int index; + + /** + * Initialize the Tarjan's algorithm. + * + * @param graph graph data in NodeGraph object format + */ + public Tarjan(NodeGraph graph) { + graphMap = graph.createMap(); + + this.registry = new HashMap<>(Utils.hashMapInitialCapacity(graph.getEdges().size())); + } + + /** + * Returns the strongly connected components in the current graph. Single nodes are ignored to save memory. + * + * @return the strongly connected components in the current graph + */ + public Collection> getSCC() { + for (Node node : graphMap.keySet()) { + if (!registry.containsKey(node.getUniqueId())) { + strongConnect(node); + } + } + return scc; + } + + /** + * Returns the graph data as a map. + * + * @return the graph data as a map + * @see NodeGraph#createMap() + */ + public Map> getGraphMap() { + return graphMap; + } + + /** + * Calculates strongly connected components available from the given node, in an iterative fashion. + * + * @param u0 the node to generate strongly connected components from + */ + private void strongConnect(final Node u0) { + final Deque> work = new ArrayDeque<>(); + work.push(new Pair<>(u0, 0)); + boolean recurse; + + while (!work.isEmpty()) { + Pair popped = work.remove(); + Node u = popped.a; + int j = popped.b; + + if (j == 0) { + index++; + registry.put(u.getUniqueId(), new TarjanHelper(index)); + stack.push(u); + } + + recurse = false; + List successors = getSuccessors(u); + + for (int i = j; i < successors.size(); i++) { + Node v = successors.get(i); + if (!registry.containsKey(v.getUniqueId())) { + work.push(new Pair<>(u, i + 1)); + work.push(new Pair<>(v, 0)); + recurse = true; + break; + } else if (stack.contains(v)) { + TarjanHelper uHelper = registry.get(u.getUniqueId()); + TarjanHelper vHelper = registry.get(v.getUniqueId()); + uHelper.lowlink = Math.min(uHelper.lowlink, vHelper.index); + } + } + + if (!recurse) { + TarjanHelper uHelper = registry.get(u.getUniqueId()); + if (uHelper.lowlink == uHelper.index) { + List currentSCC = new ArrayList<>(); + Node v; + do { + v = stack.remove(); + currentSCC.add(v); + } while (!v.equals(u)); + + // store the component only if it makes a cycle, otherwise it's a waste of memory + if (currentSCC.size() > 1) { + scc.add(currentSCC); + } + } + if (!work.isEmpty()) { + Node v = u; + Pair peeked = work.peek(); + u = peeked.a; + TarjanHelper vHelper = registry.get(v.getUniqueId()); + uHelper = registry.get(u.getUniqueId()); + uHelper.lowlink = Math.min(uHelper.lowlink, vHelper.lowlink); + } + } + } + } + + /** + * Returns the next direct successors from the graph of the given node. + * + * @param node a node to start search from + * @return direct successors of the node or an empty list, if it's a terminal node + */ + private List getSuccessors(Node node) { + return graphMap.getOrDefault(node, Collections.emptyList()); + } + + /** + * Helper class for storing the Tarjan algorithm runtime metadata. + */ + private static final class TarjanHelper { + private final int index; + private int lowlink; + + private TarjanHelper(int index) { + this.index = index; + this.lowlink = index; + } + } +} diff --git a/src/org/openstreetmap/josm/data/algorithms/package-info.java b/src/org/openstreetmap/josm/data/algorithms/package-info.java new file mode 100644 index 00000000000..dcf2b45585a --- /dev/null +++ b/src/org/openstreetmap/josm/data/algorithms/package-info.java @@ -0,0 +1,6 @@ +// License: GPL. For details, see LICENSE file. + +/** + * General purpose algorithm classes for OSM data validation. + */ +package org.openstreetmap.josm.data.algorithms; diff --git a/src/org/openstreetmap/josm/data/cache/JCSCacheManager.java b/src/org/openstreetmap/josm/data/cache/JCSCacheManager.java index ad6c50ae9a2..4edb193d4fc 100644 --- a/src/org/openstreetmap/josm/data/cache/JCSCacheManager.java +++ b/src/org/openstreetmap/josm/data/cache/JCSCacheManager.java @@ -173,7 +173,7 @@ private static boolean useBlockCache() { * @return cache access object */ public static CacheAccess getCache(String cacheName) { - return getCache(cacheName, DEFAULT_MAX_OBJECTS_IN_MEMORY.get().intValue(), 0, null); + return getCache(cacheName, DEFAULT_MAX_OBJECTS_IN_MEMORY.get(), 0, null); } /** diff --git a/src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java b/src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java index f1a4a307ff8..3ec5691b069 100644 --- a/src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java +++ b/src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java @@ -76,7 +76,7 @@ public abstract class JCSCachedTileLoaderJob implements 30, // keepalive for thread TimeUnit.SECONDS, // make queue of LIFO type - so recently requested tiles will be loaded first (assuming that these are which user is waiting to see) - new LinkedBlockingDeque(), + new LinkedBlockingDeque<>(), Utils.newThreadFactory("JCS-downloader-%d", Thread.NORM_PRIORITY) ); @@ -322,7 +322,7 @@ private boolean loadObjectFile(URL url) { file = new File(fileName.substring("file://".length() - 1)); } try (InputStream fileInputStream = Files.newInputStream(file.toPath())) { - cacheData = createCacheEntry(Utils.readBytesFromStream(fileInputStream)); + cacheData = createCacheEntry(fileInputStream.readAllBytes()); cache.put(getCacheKey(), cacheData, attributes); return true; } catch (IOException e) { @@ -393,7 +393,7 @@ private boolean loadObjectHttp() { attributes.setResponseCode(urlConn.getResponseCode()); byte[] raw; if (urlConn.getResponseCode() == HttpURLConnection.HTTP_OK) { - raw = Utils.readBytesFromStream(urlConn.getContent()); + raw = urlConn.getContent().readAllBytes(); } else { raw = new byte[]{}; try { @@ -473,7 +473,7 @@ public String detectErrorMessage(String data) { /** * Check if the object is loadable. This means, if the data will be parsed, and if this response * will finish as successful retrieve. - * + *

* This simple implementation doesn't load empty response, nor client (4xx) and server (5xx) errors * * @param headerFields headers sent by server @@ -543,10 +543,7 @@ private HttpClient getRequest(String requestMethod) throws IOException { urlConn.setHeaders(headers); } - final boolean noCache = force - // To remove when switching to Java 11 - // Workaround for https://bugs.openjdk.java.net/browse/JDK-8146450 - || (Utils.getJavaVersion() == 8 && Utils.isRunningJavaWebStart()); + final boolean noCache = force; urlConn.useCache(!noCache); return urlConn; diff --git a/src/org/openstreetmap/josm/data/coor/conversion/LatLonParser.java b/src/org/openstreetmap/josm/data/coor/conversion/LatLonParser.java index de14713b52b..eb46544c3ab 100644 --- a/src/org/openstreetmap/josm/data/coor/conversion/LatLonParser.java +++ b/src/org/openstreetmap/josm/data/coor/conversion/LatLonParser.java @@ -60,7 +60,7 @@ public final class LatLonParser { + "(?:[NE]|(?[SW]))?"; private static final Pattern P_DMS = Pattern.compile("^" + DMS + "$"); - private static class LatLonHolder { + private static final class LatLonHolder { private double lat = Double.NaN; private double lon = Double.NaN; } diff --git a/src/org/openstreetmap/josm/data/gpx/GpxData.java b/src/org/openstreetmap/josm/data/gpx/GpxData.java index 4bce174328e..fc30b394cfe 100644 --- a/src/org/openstreetmap/josm/data/gpx/GpxData.java +++ b/src/org/openstreetmap/josm/data/gpx/GpxData.java @@ -101,14 +101,16 @@ public GpxData(boolean initializing) { private final Map layerPrefs = new HashMap<>(); private final GpxTrackChangeListener proxy = e -> invalidate(); - private boolean modified, updating, initializing; + private boolean modified; + private boolean updating; + private boolean initializing; private boolean suppressedInvalidate; /** * Tracks. Access is discouraged, use {@link #getTracks()} to read. * @see #getTracks() */ - public final Collection tracks = new ListeningCollection(privateTracks, this::invalidate) { + public final Collection tracks = new ListeningCollection<>(privateTracks, this::invalidate) { @Override protected void removed(IGpxTrack cursor) { @@ -417,8 +419,8 @@ public synchronized List getOrderedTracks() { } else { OptionalLong i1 = getTrackFirstWaypointMin(t1); OptionalLong i2 = getTrackFirstWaypointMin(t2); - boolean i1absent = !i1.isPresent(); - boolean i2absent = !i2.isPresent(); + boolean i1absent = i1.isEmpty(); + boolean i2absent = i2.isEmpty(); if (i1absent && i2absent) { return 0; } else if (i1absent && !i2absent) { @@ -536,7 +538,7 @@ public synchronized void splitTrackSegmentsToTracks(String srcLayerName) { .flatMap(trk -> trk.getSegments().stream().map(seg -> { HashMap attrs = new HashMap<>(trk.getAttributes()); ensureUniqueName(attrs, counts, srcLayerName); - return new GpxTrack(Arrays.asList(seg), attrs); + return new GpxTrack(Collections.singletonList(seg), attrs); })) .collect(Collectors.toCollection(ArrayList::new)); @@ -547,7 +549,7 @@ public synchronized void splitTrackSegmentsToTracks(String srcLayerName) { /** * Split tracks into layers, the result is one layer for each track. * If this layer currently has only one GpxTrack this is a no-operation. - * + *

* The new GpxLayers are added to the LayerManager, the original GpxLayer * is untouched as to preserve potential route or wpt parts. * @@ -1257,7 +1259,8 @@ public void setModified(boolean value) { * @since 15496 */ public static class XMLNamespace { - private final String uri, prefix; + private final String uri; + private final String prefix; private String location; /** diff --git a/src/org/openstreetmap/josm/data/gpx/GpxExtension.java b/src/org/openstreetmap/josm/data/gpx/GpxExtension.java index cb5257bb248..ebc832546c1 100644 --- a/src/org/openstreetmap/josm/data/gpx/GpxExtension.java +++ b/src/org/openstreetmap/josm/data/gpx/GpxExtension.java @@ -174,7 +174,7 @@ public void remove() { parent.getExtensions().remove(this); if (parent instanceof GpxExtension) { GpxExtension gpx = ((GpxExtension) parent); - if (Utils.isBlank(gpx.getValue()) + if (Utils.isStripEmpty(gpx.getValue()) && Utils.isEmpty(gpx.getAttributes()) && Utils.isEmpty(gpx.getExtensions())) { gpx.remove(); @@ -190,7 +190,7 @@ public void hide() { visible = false; if (parent != null && parent instanceof GpxExtension) { GpxExtension gpx = (GpxExtension) parent; - if (Utils.isBlank(gpx.getValue()) + if (Utils.isStripEmpty(gpx.getValue()) && gpx.getAttributes().isEmpty() && !gpx.getExtensions().isVisible()) { gpx.hide(); diff --git a/src/org/openstreetmap/josm/data/gpx/GpxExtensionCollection.java b/src/org/openstreetmap/josm/data/gpx/GpxExtensionCollection.java index 37dc944fd70..93afaddac3f 100644 --- a/src/org/openstreetmap/josm/data/gpx/GpxExtensionCollection.java +++ b/src/org/openstreetmap/josm/data/gpx/GpxExtensionCollection.java @@ -228,7 +228,7 @@ public void findAndRemove(String prefix, String key) { public void remove(String prefix, String key) { stream(prefix, key) .collect(Collectors.toList()) //needs to be collected to avoid concurrent modification - .forEach(e -> super.remove(e)); + .forEach(super::remove); } /** @@ -239,7 +239,7 @@ public void removeAllWithPrefix(String prefix) { stream() .filter(e -> Objects.equals(prefix, e.getPrefix())) .collect(Collectors.toList()) //needs to be collected to avoid concurrent modification - .forEach(e -> super.remove(e)); + .forEach(super::remove); } /** diff --git a/src/org/openstreetmap/josm/data/imagery/AbstractWMSTileSource.java b/src/org/openstreetmap/josm/data/imagery/AbstractWMSTileSource.java index 985139d5288..c0ee96ab486 100644 --- a/src/org/openstreetmap/josm/data/imagery/AbstractWMSTileSource.java +++ b/src/org/openstreetmap/josm/data/imagery/AbstractWMSTileSource.java @@ -8,7 +8,6 @@ import java.util.Locale; import org.openstreetmap.gui.jmapviewer.Projected; -import org.openstreetmap.gui.jmapviewer.Tile; import org.openstreetmap.gui.jmapviewer.TileXY; import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate; import org.openstreetmap.gui.jmapviewer.interfaces.IProjected; @@ -103,16 +102,6 @@ public void initProjection(Projection proj) { } } - @Override - public ICoordinate tileXYToLatLon(Tile tile) { - return tileXYToLatLon(tile.getXtile(), tile.getYtile(), tile.getZoom()); - } - - @Override - public ICoordinate tileXYToLatLon(TileXY xy, int zoom) { - return tileXYToLatLon(xy.getXIndex(), xy.getYIndex(), zoom); - } - @Override public ICoordinate tileXYToLatLon(int x, int y, int zoom) { return CoordinateConversion.llToCoor(tileProjection.eastNorth2latlon(getTileEastNorth(x, y, zoom))); @@ -132,11 +121,6 @@ public TileXY latLonToTileXY(double lat, double lon, int zoom) { return eastNorthToTileXY(enPoint, zoom); } - @Override - public TileXY latLonToTileXY(ICoordinate point, int zoom) { - return latLonToTileXY(point.getLat(), point.getLon(), zoom); - } - @Override public int getTileXMax(int zoom) { return tileXMax[zoom]; @@ -167,16 +151,6 @@ public Point latLonToXY(double lat, double lon, int zoom) { ); } - @Override - public Point latLonToXY(ICoordinate point, int zoom) { - return latLonToXY(point.getLat(), point.getLon(), zoom); - } - - @Override - public ICoordinate xyToLatLon(Point point, int zoom) { - return xyToLatLon(point.x, point.y, zoom); - } - @Override public ICoordinate xyToLatLon(int x, int y, int zoom) { double scale = getDegreesPerTile(zoom) / getTileSize(); diff --git a/src/org/openstreetmap/josm/data/imagery/CachedAttributionBingAerialTileSource.java b/src/org/openstreetmap/josm/data/imagery/CachedAttributionBingAerialTileSource.java index fd283414111..ad76b03170f 100644 --- a/src/org/openstreetmap/josm/data/imagery/CachedAttributionBingAerialTileSource.java +++ b/src/org/openstreetmap/josm/data/imagery/CachedAttributionBingAerialTileSource.java @@ -33,7 +33,7 @@ public class CachedAttributionBingAerialTileSource extends BingAerialTileSource * @param info ImageryInfo description of this tile source */ public CachedAttributionBingAerialTileSource(ImageryInfo info) { - super(info); + this(info, null); } /** @@ -45,6 +45,12 @@ public CachedAttributionBingAerialTileSource(ImageryInfo info) { public CachedAttributionBingAerialTileSource(TileSourceInfo info, Runnable attributionDownloadedTask) { super(info); this.attributionDownloadedTask = attributionDownloadedTask; + // See #23227 and https://github.com/openstreetmap/iD/issues/9153#issuecomment-1781569820 + // Of specific note: + // > Due to increased usage of Bing Maps imagery APIs, we decided to separate the free usage of the API + // > (for OSM editors) from the paid usage of the API. + // We aren't paying for access, so we should solely use the AerialOSM layer. + super.setLayer("AerialOSM"); } class BingAttributionData extends CacheCustomContent { diff --git a/src/org/openstreetmap/josm/data/imagery/ImageryInfo.java b/src/org/openstreetmap/josm/data/imagery/ImageryInfo.java index ff86322eba1..defe8f0485c 100644 --- a/src/org/openstreetmap/josm/data/imagery/ImageryInfo.java +++ b/src/org/openstreetmap/josm/data/imagery/ImageryInfo.java @@ -19,9 +19,6 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import jakarta.json.Json; -import jakarta.json.JsonObject; -import jakarta.json.JsonReader; import javax.swing.ImageIcon; import org.openstreetmap.josm.data.StructUtils.StructEntry; @@ -39,6 +36,10 @@ import org.openstreetmap.josm.tools.StreamUtils; import org.openstreetmap.josm.tools.Utils; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonReader; + /** * Class that stores info about an image background layer. * @@ -78,7 +79,7 @@ public enum ImageryType implements ISourceType { * @since 6690 */ @Override - public final String getTypeString() { + public String getTypeString() { return typeString; } @@ -141,7 +142,7 @@ public enum ImageryCategory implements ISourceCategory { * @return the unique string identifying this category */ @Override - public final String getCategoryString() { + public String getCategoryString() { return category; } @@ -150,7 +151,7 @@ public final String getCategoryString() { * @return the description of this category */ @Override - public final String getDescription() { + public String getDescription() { return description; } @@ -161,7 +162,7 @@ public final String getDescription() { * @since 15049 */ @Override - public final ImageIcon getIcon(ImageSizes size) { + public ImageIcon getIcon(ImageSizes size) { return iconCache .computeIfAbsent(size, x -> Collections.synchronizedMap(new EnumMap<>(ImageryCategory.class))) .computeIfAbsent(this, x -> ImageProvider.get("data/imagery", x.category, size)); @@ -205,6 +206,8 @@ public ImageryBounds(String asString, String separator) { } } + private static final String[] EMPTY_STRING = new String[0]; + private double pixelPerDegree; /** maximum zoom level for TMS imagery */ private int defaultMaxZoom; @@ -566,7 +569,7 @@ public List getMirrors() { /** * Check if this object equals another ImageryInfo with respect to the properties * that get written to the preference file. - * + *

* The field {@link #pixelPerDegree} is ignored. * * @param other the ImageryInfo object to compare to @@ -963,6 +966,49 @@ public String getSourceName() { } } + /** + * Check to see if this info is valid (the XSD is overly permissive due to limitations of the XSD syntax). + * @return {@code true} if this info is valid + * @see ImageryInfo#getMissingFields() + * @since 18989 + */ + public boolean isValid() { + return this.getName() != null && + this.getId() != null && + this.getSourceType() != null && + this.getUrl() != null && + this.getImageryCategory() != null; + } + + /** + * Get the missing fields for this info + * @return The missing fields, or an empty array + * @see ImageryInfo#isValid() + * @since 18989 + */ + public String[] getMissingFields() { + if (this.isValid()) { + return EMPTY_STRING; + } + List missingFields = new ArrayList<>(); + if (this.getName() == null) { + missingFields.add("name"); + } + if (this.getId() == null) { + missingFields.add("id"); + } + if (this.getSourceType() == null) { + missingFields.add("type"); + } + if (this.getUrl() == null) { + missingFields.add("url"); + } + if (this.getImageryCategory() == null) { + missingFields.add("category"); + } + return missingFields.toArray(new String[0]); + } + /** * Return the sorted list of activated source IDs. * @return sorted list of activated source IDs diff --git a/src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java b/src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java index a8fa0341371..94cf26626a5 100644 --- a/src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java +++ b/src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java @@ -166,6 +166,8 @@ protected void loadSource(String source) { reader = new ImageryReader(source); reader.setFastFail(fastFail); Collection result = reader.parse(); + // See #23485 (remove invalid source entries) + result.removeIf(info -> !info.isValid()); newLayers.addAll(result); } catch (IOException ex) { loadError = true; diff --git a/src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoader.java b/src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoader.java index a6d2373c5d5..429182d2107 100644 --- a/src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoader.java +++ b/src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoader.java @@ -69,7 +69,7 @@ public TMSCachedTileLoader(TileLoaderListener listener, ICacheAccess tileMatrix = new TreeSet<>((o1, o2) -> -1 * Double.compare(o1.scaleDenominator, o2.scaleDenominator)); private String crs; @@ -193,7 +192,7 @@ public int getMaxZoom() { } } - private static class Dimension { + private static final class Dimension { private String identifier; private String defaultValue; private final List values = new ArrayList<>(); @@ -453,7 +452,7 @@ public static WMTSCapabilities getCapabilities(String url, Map h setMaxAge(Config.getPref().getLong("wmts.capabilities.cache.max_age", 7 * CachedFile.DAYS)). setCachingStrategy(CachedFile.CachingStrategy.IfModifiedSince). getInputStream()) { - byte[] data = Utils.readBytesFromStream(in); + byte[] data = in.readAllBytes(); if (data.length == 0) { cf.clear(); throw new IllegalArgumentException("Could not read data from: " + url); @@ -929,16 +928,6 @@ public double getDistance(double lat1, double lon1, double lat2, double lon2) { throw new UnsupportedOperationException("Not implemented"); } - @Override - public ICoordinate tileXYToLatLon(Tile tile) { - return tileXYToLatLon(tile.getXtile(), tile.getYtile(), tile.getZoom()); - } - - @Override - public ICoordinate tileXYToLatLon(TileXY xy, int zoom) { - return tileXYToLatLon(xy.getXIndex(), xy.getYIndex(), zoom); - } - @Override public ICoordinate tileXYToLatLon(int x, int y, int zoom) { TileMatrix matrix = getTileMatrix(zoom); @@ -965,11 +954,6 @@ public TileXY latLonToTileXY(double lat, double lon, int zoom) { ); } - @Override - public TileXY latLonToTileXY(ICoordinate point, int zoom) { - return latLonToTileXY(point.getLat(), point.getLon(), zoom); - } - @Override public int getTileXMax(int zoom) { return getTileXMax(zoom, tileProjection); @@ -994,16 +978,6 @@ public Point latLonToXY(double lat, double lon, int zoom) { ); } - @Override - public Point latLonToXY(ICoordinate point, int zoom) { - return latLonToXY(point.getLat(), point.getLon(), zoom); - } - - @Override - public Coordinate xyToLatLon(Point point, int zoom) { - return xyToLatLon(point.x, point.y, zoom); - } - @Override public Coordinate xyToLatLon(int x, int y, int zoom) { TileMatrix matrix = getTileMatrix(zoom); diff --git a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Layer.java b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Layer.java index 1eb0e5a4235..e4d33d4d3ba 100644 --- a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Layer.java +++ b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/Layer.java @@ -98,6 +98,7 @@ public T convertValue(ProtobufRecord protobufRecord) { * @param records The records to convert to a layer * @throws IOException - if an IO error occurs */ + @SuppressWarnings("PMD.CloseResource") // The resources _are_ closed after use; it just isn't detect with PMD 7.2.x. public Layer(Collection records) throws IOException { // Do the unique required fields first Map> sorted = new HashMap<>(records.size()); @@ -136,7 +137,7 @@ public Layer(Collection records) throws IOException { this.featureCollection.add(new Feature(this, protobufRecord)); } // Cleanup bytes (for memory) - for (ProtobufRecord protobufRecord : records) { + for (ProtobufRecord protobufRecord : records) { // NOSONAR -- this shouldn't be combined since this is a cleanup loop. protobufRecord.close(); } } diff --git a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MVTTile.java b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MVTTile.java index 0d75f790468..5a4c558886b 100644 --- a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MVTTile.java +++ b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MVTTile.java @@ -55,13 +55,11 @@ public void loadImage(final InputStream inputStream) throws IOException { this.layers = new ArrayList<>(protobufRecords.size()); for (ProtobufRecord protoBufRecord : protobufRecords) { if (protoBufRecord.getField() == Layer.LAYER_FIELD) { - try (ProtobufParser tParser = new ProtobufParser(protoBufRecord.getBytes())) { + try (protoBufRecord; // Cleanup bytes + ProtobufParser tParser = new ProtobufParser(protoBufRecord.getBytes())) { this.layers.add(new Layer(tParser.allRecords())); } catch (IOException e) { Logging.error(e); - } finally { - // Cleanup bytes - protoBufRecord.close(); } } } diff --git a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/style/Layers.java b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/style/Layers.java index 60cea226c0c..e182de3f2f8 100644 --- a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/style/Layers.java +++ b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/style/Layers.java @@ -58,6 +58,7 @@ enum Type { SKY } + private static final String DEFAULT_COLOR = "#000000"; private static final char SEMI_COLON = ';'; private static final Pattern CURLY_BRACES = Pattern.compile("(\\{(.*?)})"); private static final String PAINT = "paint"; @@ -185,7 +186,7 @@ private static String parsePaintLine(final JsonObject layoutObject, final JsonOb final StringBuilder sb = new StringBuilder(36); // line-blur, default 0 (px) // line-color, default #000000, disabled by line-pattern - final String color = paintObject.getString("line-color", "#000000"); + final String color = paintObject.getString("line-color", DEFAULT_COLOR); sb.append(StyleKeys.COLOR).append(':').append(color).append(SEMI_COLON); // line-opacity, default 1 (0-1) final JsonNumber opacity = paintObject.getJsonNumber("line-opacity"); @@ -209,10 +210,10 @@ private static String parsePaintLine(final JsonObject layoutObject, final JsonOb // line-dasharray, array of number >= 0, units in line widths, disabled by line-pattern if (paintObject.containsKey("line-dasharray")) { final JsonArray dashArray = paintObject.getJsonArray("line-dasharray"); - sb.append(StyleKeys.DASHES).append(':'); - sb.append(dashArray.stream().filter(JsonNumber.class::isInstance).map(JsonNumber.class::cast) - .map(JsonNumber::toString).collect(Collectors.joining(","))); - sb.append(SEMI_COLON); + sb.append(StyleKeys.DASHES).append(':') + .append(dashArray.stream().filter(JsonNumber.class::isInstance).map(JsonNumber.class::cast) + .map(JsonNumber::toString).collect(Collectors.joining(","))) + .append(SEMI_COLON); } // line-gap-width // line-gradient @@ -234,7 +235,7 @@ private static String parsePaintCircle(final JsonObject paintObject) { final StringBuilder sb = new StringBuilder(150).append("symbol-shape:circle;") // circle-blur // circle-color - .append("symbol-fill-color:").append(paintObject.getString("circle-color", "#000000")).append(SEMI_COLON); + .append("symbol-fill-color:").append(paintObject.getString("circle-color", DEFAULT_COLOR)).append(SEMI_COLON); // circle-opacity final JsonNumber fillOpacity = paintObject.getJsonNumber("circle-opacity"); sb.append("symbol-fill-opacity:").append(fillOpacity != null ? fillOpacity.numberValue().toString() : "1").append(SEMI_COLON); @@ -245,7 +246,7 @@ private static String parsePaintCircle(final JsonObject paintObject) { sb.append("symbol-size:").append(radius != null ? (2 * radius.numberValue().doubleValue()) : "10").append(SEMI_COLON) // circle-sort-key // circle-stroke-color - .append("symbol-stroke-color:").append(paintObject.getString("circle-stroke-color", "#000000")).append(SEMI_COLON); + .append("symbol-stroke-color:").append(paintObject.getString("circle-stroke-color", DEFAULT_COLOR)).append(SEMI_COLON); // circle-stroke-opacity final JsonNumber strokeOpacity = paintObject.getJsonNumber("circle-stroke-opacity"); sb.append("symbol-stroke-opacity:").append(strokeOpacity != null ? strokeOpacity.numberValue().toString() : "1").append(SEMI_COLON); @@ -272,11 +273,11 @@ private String parsePaintSymbol( boolean iconImage = false; if (layoutObject.containsKey("icon-image")) { sb.append(/* NO-ICON */"icon-image:concat("); - if (!Utils.isBlank(this.styleId)) { + if (!Utils.isStripEmpty(this.styleId)) { sb.append('"').append(this.styleId).append('/').append("\","); } Matcher matcher = CURLY_BRACES.matcher(layoutObject.getString("icon-image")); - StringBuffer stringBuffer = new StringBuffer(); + StringBuilder stringBuffer = new StringBuilder(); int previousMatch; if (matcher.lookingAt()) { matcher.appendReplacement(stringBuffer, "tag(\"$2\"),\""); @@ -298,15 +299,14 @@ private String parsePaintSymbol( } else if (!matcher.hitEnd()) { stringBuffer.append('"'); } - StringBuffer tail = new StringBuffer(); + StringBuilder tail = new StringBuilder(); matcher.appendTail(tail); if (tail.length() > 0) { String current = stringBuffer.toString(); if (!"\"".equals(current) && !current.endsWith(",\"")) { stringBuffer.append(",\""); } - stringBuffer.append(tail); - stringBuffer.append('"'); + stringBuffer.append(tail).append('"'); } sb.append(stringBuffer).append(')').append(SEMI_COLON); @@ -374,9 +374,9 @@ private String parsePaintSymbol( .orElseGet(() -> fontMatches.stream().filter(font -> font.getPSName().equals(fontString)).findAny() .orElseGet(() -> fontMatches.stream().filter(font -> font.getFamily().equals(fontString)).findAny().orElse(null)))); if (setFont != null) { - sb.append(StyleKeys.FONT_FAMILY).append(':').append('"').append(setFont.getFamily()).append('"').append(SEMI_COLON); - sb.append(StyleKeys.FONT_WEIGHT).append(':').append(setFont.isBold() ? "bold" : "normal").append(SEMI_COLON); - sb.append(StyleKeys.FONT_STYLE).append(':').append(setFont.isItalic() ? "italic" : "normal").append(SEMI_COLON); + sb.append(StyleKeys.FONT_FAMILY).append(':').append('"').append(setFont.getFamily()).append('"').append(SEMI_COLON) + .append(StyleKeys.FONT_WEIGHT).append(':').append(setFont.isBold() ? "bold" : "normal").append(SEMI_COLON) + .append(StyleKeys.FONT_STYLE).append(':').append(setFont.isItalic() ? "italic" : "normal").append(SEMI_COLON); break; } } @@ -438,13 +438,13 @@ private static String parsePaintFill(final JsonObject paintObject) { StringBuilder sb = new StringBuilder(50) // fill-antialias // fill-color - .append(StyleKeys.FILL_COLOR).append(':').append(paintObject.getString(StyleKeys.FILL_COLOR, "#000000")).append(SEMI_COLON); + .append(StyleKeys.FILL_COLOR).append(':').append(paintObject.getString(StyleKeys.FILL_COLOR, DEFAULT_COLOR)).append(SEMI_COLON); // fill-opacity final JsonNumber opacity = paintObject.getJsonNumber(StyleKeys.FILL_OPACITY); sb.append(StyleKeys.FILL_OPACITY).append(':').append(opacity != null ? opacity.numberValue().toString() : "1").append(SEMI_COLON) // fill-outline-color .append(StyleKeys.COLOR).append(':').append(paintObject.getString("fill-outline-color", - paintObject.getString("fill-color", "#000000"))).append(SEMI_COLON); + paintObject.getString("fill-color", DEFAULT_COLOR))).append(SEMI_COLON); // fill-pattern // fill-sort-key // fill-translate diff --git a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/style/MapboxVectorStyle.java b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/style/MapboxVectorStyle.java index d0e01e3af85..3c5cf78562a 100644 --- a/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/style/MapboxVectorStyle.java +++ b/src/org/openstreetmap/josm/data/imagery/vectortile/mapbox/style/MapboxVectorStyle.java @@ -142,7 +142,7 @@ public MapboxVectorStyle(JsonObject jsonObject) { MainApplication.worker.execute(() -> this.save((source == null ? data.hashCode() : source.getName()) + ".mapcss", style)); this.sources.put(source, new ElemStyles(Collections.singleton(style))); } - if (!Utils.isBlank(this.spriteUrl)) { + if (!Utils.isStripEmpty(this.spriteUrl)) { MainApplication.worker.execute(this::fetchSprites); } } else { @@ -250,7 +250,7 @@ private void save(String name, Object object) { } /** - * Get the generated layer->style mapping + * Get the generated layer → style mapping * @return The mapping (use to enable/disable a paint style) */ public Map getSources() { diff --git a/src/org/openstreetmap/josm/data/oauth/OAuth20Exception.java b/src/org/openstreetmap/josm/data/oauth/OAuth20Exception.java index 7cf14e0292a..3503282aa4f 100644 --- a/src/org/openstreetmap/josm/data/oauth/OAuth20Exception.java +++ b/src/org/openstreetmap/josm/data/oauth/OAuth20Exception.java @@ -54,7 +54,7 @@ public OAuth20Exception(String message) { ? serverMessage.getString("error_description", serverMessage.getString("error", "Unknown error")) : "Unknown error"); if (serverMessage != null && serverMessage.containsKey("error")) { - switch(serverMessage.getString("error")) { + switch (serverMessage.getString("error")) { case "invalid_request": case "invalid_client": case "invalid_grant": diff --git a/src/org/openstreetmap/josm/data/oauth/OAuth20Parameters.java b/src/org/openstreetmap/josm/data/oauth/OAuth20Parameters.java index e49ab769ae0..0214627dad9 100644 --- a/src/org/openstreetmap/josm/data/oauth/OAuth20Parameters.java +++ b/src/org/openstreetmap/josm/data/oauth/OAuth20Parameters.java @@ -147,4 +147,23 @@ public String toPreferencesString() { if (this.tokenUrl != null) builder.add(TOKEN_URL, this.tokenUrl); return builder.build().toString(); } + + @Override + public int hashCode() { + return Objects.hash(this.apiUrl, this.authorizeUrl, this.tokenUrl, this.clientId, this.clientSecret, this.redirectUri); + } + + @Override + public boolean equals(Object obj) { + if (obj != null && this.getClass() == obj.getClass()) { + OAuth20Parameters other = (OAuth20Parameters) obj; + return Objects.equals(this.clientSecret, other.clientSecret) && + Objects.equals(this.clientId, other.clientId) && + Objects.equals(this.redirectUri, other.redirectUri) && + Objects.equals(this.tokenUrl, other.tokenUrl) && + Objects.equals(this.authorizeUrl, other.authorizeUrl) && + Objects.equals(this.apiUrl, other.apiUrl); + } + return false; + } } diff --git a/src/org/openstreetmap/josm/data/oauth/OAuth20Token.java b/src/org/openstreetmap/josm/data/oauth/OAuth20Token.java index 77f292f2938..c27d35eab91 100644 --- a/src/org/openstreetmap/josm/data/oauth/OAuth20Token.java +++ b/src/org/openstreetmap/josm/data/oauth/OAuth20Token.java @@ -81,7 +81,7 @@ public OAuth20Token(IOAuthParameters oauthParameters, String json) throws OAuth2 @Override public void sign(HttpClient client) throws OAuthException { - if (!Utils.isBlank(this.oauthParameters.getApiUrl()) + if (!Utils.isStripEmpty(this.oauthParameters.getApiUrl()) && !this.oauthParameters.getApiUrl().contains(client.getURL().getHost())) { String host = URI.create(this.oauthParameters.getAccessTokenUrl()).getHost(); throw new IllegalArgumentException("Cannot sign URL with token for different host: Expected " + host diff --git a/src/org/openstreetmap/josm/data/oauth/OAuthAccessTokenHolder.java b/src/org/openstreetmap/josm/data/oauth/OAuthAccessTokenHolder.java index b5b7e7914cb..eb56590ec82 100644 --- a/src/org/openstreetmap/josm/data/oauth/OAuthAccessTokenHolder.java +++ b/src/org/openstreetmap/josm/data/oauth/OAuthAccessTokenHolder.java @@ -36,8 +36,6 @@ public static synchronized OAuthAccessTokenHolder getInstance() { } private boolean saveToPreferences; - private String accessTokenKey; - private String accessTokenSecret; private final Map> tokenMap = new HashMap<>(); @@ -52,10 +50,10 @@ public boolean isSaveToPreferences() { /** * Sets whether the current access token should be saved to the preferences file. - * + *

* If true, the access token is saved in clear text to the preferences file. The same * access token can therefore be used in multiple JOSM sessions. - * + *

* If false, the access token isn't saved to the preferences file. If JOSM is closed, * the access token is lost and new token has to be generated by the OSM server the * next time JOSM is used. @@ -66,52 +64,6 @@ public void setSaveToPreferences(boolean saveToPreferences) { this.saveToPreferences = saveToPreferences; } - /** - * Replies the access token key. null, if no access token key is currently set. - * - * @return the access token key - */ - public String getAccessTokenKey() { - return accessTokenKey; - } - - /** - * Sets the access token key. Pass in null to remove the current access token key. - * - * @param accessTokenKey the access token key - */ - public void setAccessTokenKey(String accessTokenKey) { - this.accessTokenKey = accessTokenKey; - } - - /** - * Replies the access token secret. null, if no access token secret is currently set. - * - * @return the access token secret - */ - public String getAccessTokenSecret() { - return accessTokenSecret; - } - - /** - * Sets the access token secret. Pass in null to remove the current access token secret. - * - * @param accessTokenSecret access token secret, or null - */ - public void setAccessTokenSecret(String accessTokenSecret) { - this.accessTokenSecret = accessTokenSecret; - } - - /** - * Replies the access token. - * @return the access token, can be {@code null} - */ - public OAuthToken getAccessToken() { - if (!containsAccessToken()) - return null; - return new OAuthToken(accessTokenKey, accessTokenSecret); - } - /** * Replies the access token. * @param api The api the token is for @@ -139,32 +91,6 @@ public IOAuthToken getAccessToken(String api, OAuthVersion version) { return null; } - /** - * Sets the access token hold by this holder. - * - * @param accessTokenKey the access token key - * @param accessTokenSecret the access token secret - */ - public void setAccessToken(String accessTokenKey, String accessTokenSecret) { - this.accessTokenKey = accessTokenKey; - this.accessTokenSecret = accessTokenSecret; - } - - /** - * Sets the access token hold by this holder. - * - * @param token the access token. Can be null to clear the content in this holder. - */ - public void setAccessToken(OAuthToken token) { - if (token == null) { - this.accessTokenKey = null; - this.accessTokenSecret = null; - } else { - this.accessTokenKey = token.getKey(); - this.accessTokenSecret = token.getSecret(); - } - } - /** * Sets the access token hold by this holder. * @@ -185,16 +111,6 @@ public void setAccessToken(String api, IOAuthToken token) { } } - /** - * Replies true if this holder contains an complete access token, consisting of an - * Access Token Key and an Access Token Secret. - * - * @return true if this holder contains an complete access token - */ - public boolean containsAccessToken() { - return accessTokenKey != null && accessTokenSecret != null; - } - /** * Initializes the content of this holder from the Access Token managed by the * credential manager. @@ -204,19 +120,7 @@ public boolean containsAccessToken() { */ public void init(CredentialsAgent cm) { CheckParameterUtil.ensureParameterNotNull(cm, "cm"); - OAuthToken token = null; - try { - token = cm.lookupOAuthAccessToken(); - } catch (CredentialsAgentException e) { - Logging.error(e); - Logging.warn(tr("Failed to retrieve OAuth Access Token from credential manager")); - Logging.warn(tr("Current credential manager is of type ''{0}''", cm.getClass().getName())); - } saveToPreferences = Config.getPref().getBoolean("oauth.access-token.save-to-preferences", true); - if (token != null) { - accessTokenKey = token.getKey(); - accessTokenSecret = token.getSecret(); - } } /** @@ -231,16 +135,10 @@ public void save(CredentialsAgent cm) { Config.getPref().putBoolean("oauth.access-token.save-to-preferences", saveToPreferences); try { if (!saveToPreferences) { - cm.storeOAuthAccessToken(null); for (String host : this.tokenMap.keySet()) { cm.storeOAuthAccessToken(host, null); } } else { - if (this.accessTokenKey != null && this.accessTokenSecret != null) { - cm.storeOAuthAccessToken(new OAuthToken(accessTokenKey, accessTokenSecret)); - } else { - cm.storeOAuthAccessToken(null); - } for (Map.Entry> entry : this.tokenMap.entrySet()) { if (entry.getValue().isEmpty()) { cm.storeOAuthAccessToken(entry.getKey(), null); @@ -264,7 +162,6 @@ public void save(CredentialsAgent cm) { * Clears the content of this holder */ public void clear() { - accessTokenKey = null; - accessTokenSecret = null; + this.tokenMap.clear(); } } diff --git a/src/org/openstreetmap/josm/data/oauth/OAuthParameters.java b/src/org/openstreetmap/josm/data/oauth/OAuthParameters.java index 71b176f473b..e58d032421b 100644 --- a/src/org/openstreetmap/josm/data/oauth/OAuthParameters.java +++ b/src/org/openstreetmap/josm/data/oauth/OAuthParameters.java @@ -3,70 +3,60 @@ import java.io.BufferedReader; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; -import java.util.Objects; - -import jakarta.json.Json; -import jakarta.json.JsonObject; -import jakarta.json.JsonReader; -import jakarta.json.JsonStructure; -import jakarta.json.JsonValue; +import java.util.HashMap; +import java.util.Map; +import org.openstreetmap.josm.io.NetworkManager; import org.openstreetmap.josm.io.OsmApi; import org.openstreetmap.josm.io.auth.CredentialsAgentException; import org.openstreetmap.josm.io.auth.CredentialsManager; import org.openstreetmap.josm.spi.preferences.Config; import org.openstreetmap.josm.spi.preferences.IUrls; -import org.openstreetmap.josm.tools.CheckParameterUtil; import org.openstreetmap.josm.tools.HttpClient; +import org.openstreetmap.josm.tools.JosmRuntimeException; import org.openstreetmap.josm.tools.Logging; import org.openstreetmap.josm.tools.Utils; -import oauth.signpost.OAuthConsumer; -import oauth.signpost.OAuthProvider; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonReader; +import jakarta.json.JsonStructure; +import jakarta.json.JsonValue; /** * This class manages an immutable set of OAuth parameters. - * @since 2747 + * @since 2747 (static factory class since 18991) */ -public class OAuthParameters implements IOAuthParameters { +public final class OAuthParameters { + private static final Map RFC8414_RESPONSES = new HashMap<>(1); + private static final String OSM_API_DEFAULT = "https://api.openstreetmap.org/api"; + private static final String OSM_API_DEV = "https://api06.dev.openstreetmap.org/api"; + private static final String OSM_API_MASTER = "https://master.apis.dev.openstreetmap.org/api"; - /** - * The default JOSM OAuth consumer key (created by user josmeditor). - */ - public static final String DEFAULT_JOSM_CONSUMER_KEY = "F7zPYlVCqE2BUH9Hr4SsWZSOnrKjpug1EgqkbsSb"; - /** - * The default JOSM OAuth consumer secret (created by user josmeditor). - */ - public static final String DEFAULT_JOSM_CONSUMER_SECRET = "rIkjpPcBNkMQxrqzcOvOC4RRuYupYr7k8mfP13H5"; + private OAuthParameters() { + // Hide constructor + } /** * Replies a set of default parameters for a consumer accessing the standard OSM server * at {@link IUrls#getDefaultOsmApiUrl}. - * + *

+ * Note that this may make network requests for RFC 8414 compliant endpoints. * @return a set of default parameters */ - public static OAuthParameters createDefault() { - return createDefault(null); - } - - /** - * Replies a set of default parameters for a consumer accessing an OSM server - * at the given API url. URL parameters are only set if the URL equals {@link IUrls#getDefaultOsmApiUrl} - * or references the domain "dev.openstreetmap.org", otherwise they may be null. - * - * @param apiUrl The API URL for which the OAuth default parameters are created. If null or empty, the default OSM API url is used. - * @return a set of default parameters for the given {@code apiUrl} - * @since 5422 - */ - public static OAuthParameters createDefault(String apiUrl) { - return (OAuthParameters) createDefault(apiUrl, OAuthVersion.OAuth10a); + public static IOAuthParameters createDefault() { + return createDefault(Config.getUrls().getDefaultOsmApiUrl(), OAuthVersion.OAuth20); } /** * Replies a set of default parameters for a consumer accessing an OSM server * at the given API url. URL parameters are only set if the URL equals {@link IUrls#getDefaultOsmApiUrl} * or references the domain "dev.openstreetmap.org", otherwise they may be null. + *

+ * Note that this may make network requests for RFC 8414 compliant endpoints. * * @param apiUrl The API URL for which the OAuth default parameters are created. If null or empty, the default OSM API url is used. * @param oAuthVersion The OAuth version to create default parameters for @@ -79,8 +69,6 @@ public static IOAuthParameters createDefault(String apiUrl, OAuthVersion oAuthVe } switch (oAuthVersion) { - case OAuth10a: - return getDefaultOAuth10Parameters(apiUrl); case OAuth20: case OAuth21: // For now, OAuth 2.1 (draft) is just OAuth 2.0 with mandatory extensions, which we implement. return getDefaultOAuth20Parameters(apiUrl); @@ -89,6 +77,33 @@ public static IOAuthParameters createDefault(String apiUrl, OAuthVersion oAuthVe } } + private static JsonObject getRFC8414Parameters(String apiUrl) { + HttpClient client = null; + try { + final URI apiURI = new URI(apiUrl); + final URL rfc8414URL = new URI(apiURI.getScheme(), apiURI.getHost(), + "/.well-known/oauth-authorization-server", null).toURL(); + client = HttpClient.create(rfc8414URL); + HttpClient.Response response = client.connect(); + if (response.getResponseCode() == 200) { + try (BufferedReader reader = response.getContentReader(); + JsonReader jsonReader = Json.createReader(reader)) { + JsonStructure structure = jsonReader.read(); + if (structure.getValueType() == JsonValue.ValueType.OBJECT) { + return structure.asJsonObject(); + } + } + } + } catch (URISyntaxException | IOException e) { + throw new JosmRuntimeException(e); + } finally { + if (client != null) { + client.disconnect(); + } + } + return Json.createObjectBuilder().build(); + } + /** * Get the default OAuth 2.0 parameters * @param apiUrl The API url @@ -97,41 +112,60 @@ public static IOAuthParameters createDefault(String apiUrl, OAuthVersion oAuthVe private static OAuth20Parameters getDefaultOAuth20Parameters(String apiUrl) { final String clientId; final String clientSecret; - final String redirectUri; + final String redirectUri = "http://127.0.0.1:8111/oauth_authorization"; final String baseUrl; - if (apiUrl != null && !Config.getUrls().getDefaultOsmApiUrl().equals(apiUrl) && !"http://invalid".equals(apiUrl)) { - clientId = ""; - clientSecret = ""; - baseUrl = apiUrl; - HttpClient client = null; - redirectUri = ""; - // Check if the server is RFC 8414 compliant - try { - client = HttpClient.create(new URL(apiUrl + (apiUrl.endsWith("/") ? "" : "/") + ".well-known/oauth-authorization-server")); - HttpClient.Response response = client.connect(); - if (response.getResponseCode() == 200) { - try (BufferedReader reader = response.getContentReader(); - JsonReader jsonReader = Json.createReader(reader)) { - JsonStructure structure = jsonReader.read(); - if (structure.getValueType() == JsonValue.ValueType.OBJECT) { - return parseAuthorizationServerMetadataResponse(clientId, clientSecret, apiUrl, - redirectUri, structure.asJsonObject()); - } - } + apiUrl = apiUrl == null ? OsmApi.getOsmApi().getServerUrl() : apiUrl; + switch (apiUrl) { + case OSM_API_DEV: + case OSM_API_MASTER: + // This clientId/clientSecret are provided by taylor.smock. Feel free to change if needed, but + // do let one of the maintainers with server access know so that they can update the test OAuth + // token. + clientId = "-QZt6n1btDfqrfJNGUIMZjzcyqTgIV6sy79_W4kmQLM"; + // Keep secret for dev apis, just in case we want to test something that needs it. + clientSecret = "SWnmRD4AdLO-2-ttHE5TR3eLF2McNf7dh0_Z2WNzJdI"; + break; + case OSM_API_DEFAULT: + clientId = "edPII614Lm0_0zEpc_QzEltA9BUll93-Y-ugRQUoHMI"; + // We don't actually use the client secret in our authorization flow. + clientSecret = null; + break; + case "https://www.openhistoricalmap.org/api": + // clientId provided by 1ec5 (Minh Nguyễn) + clientId = "Hl5yIhFS-Egj6aY7A35ouLOuZl0EHjj8JJQQ46IO96E"; + clientSecret = null; + break; + default: + clientId = ""; + clientSecret = null; + } + baseUrl = apiUrl; + // Check if the server is RFC 8414 compliant + try { + synchronized (RFC8414_RESPONSES) { + final JsonObject data; + if (NetworkManager.isOffline(apiUrl)) { + data = null; + } else { + data = RFC8414_RESPONSES.computeIfAbsent(apiUrl, OAuthParameters::getRFC8414Parameters); + } + if (data == null || data.isEmpty()) { + RFC8414_RESPONSES.remove(apiUrl); + } else { + return parseAuthorizationServerMetadataResponse(clientId, clientSecret, apiUrl, + redirectUri, data); } - } catch (IOException | OAuthException e) { + } + } catch (JosmRuntimeException e) { + if (e.getCause() instanceof URISyntaxException || e.getCause() instanceof IOException) { Logging.trace(e); - } finally { - if (client != null) client.disconnect(); + } else { + throw e; } - } else { - clientId = "edPII614Lm0_0zEpc_QzEltA9BUll93-Y-ugRQUoHMI"; - // We don't actually use the client secret in our authorization flow. - clientSecret = null; - baseUrl = "https://www.openstreetmap.org/oauth2"; - redirectUri = "http://127.0.0.1:8111/oauth_authorization"; - apiUrl = OsmApi.getOsmApi().getBaseUrl(); + } catch (OAuthException e) { + Logging.trace(e); } + // Fall back to guessing the parameters. return new OAuth20Parameters(clientId, clientSecret, baseUrl, apiUrl, redirectUri); } @@ -153,46 +187,6 @@ private static OAuth20Parameters parseAuthorizationServerMetadataResponse(String return new OAuth20Parameters(clientId, clientSecret, tokenEndpoint, authorizationEndpoint, apiUrl, redirectUri); } - /** - * Get the default OAuth 1.0a parameters - * @param apiUrl The api url - * @return The default parameters - */ - private static OAuthParameters getDefaultOAuth10Parameters(String apiUrl) { - final String consumerKey; - final String consumerSecret; - final String serverUrl; - - if (apiUrl != null && !Config.getUrls().getDefaultOsmApiUrl().equals(apiUrl)) { - consumerKey = ""; // a custom consumer key is required - consumerSecret = ""; // a custom consumer secret is requireds - serverUrl = apiUrl.replaceAll("/api$", ""); - } else { - consumerKey = DEFAULT_JOSM_CONSUMER_KEY; - consumerSecret = DEFAULT_JOSM_CONSUMER_SECRET; - serverUrl = Config.getUrls().getOSMWebsite(); - } - - return new OAuthParameters( - consumerKey, - consumerSecret, - serverUrl + "/oauth/request_token", - serverUrl + "/oauth/access_token", - serverUrl + "/oauth/authorize", - serverUrl + "/login", - serverUrl + "/logout"); - } - - /** - * Replies a set of parameters as defined in the preferences. - * - * @param apiUrl the API URL. Must not be null. - * @return the parameters - */ - public static OAuthParameters createFromApiUrl(String apiUrl) { - return (OAuthParameters) createFromApiUrl(apiUrl, OAuthVersion.OAuth10a); - } - /** * Replies a set of parameters as defined in the preferences. * @@ -202,214 +196,28 @@ public static OAuthParameters createFromApiUrl(String apiUrl) { * @since 18650 */ public static IOAuthParameters createFromApiUrl(String apiUrl, OAuthVersion oAuthVersion) { - IOAuthParameters parameters = createDefault(apiUrl, oAuthVersion); + // We actually need the host + if (apiUrl.startsWith("https://") || apiUrl.startsWith("http://")) { + try { + apiUrl = new URI(apiUrl).getHost(); + } catch (URISyntaxException syntaxException) { + Logging.trace(apiUrl); + } + } switch (oAuthVersion) { - case OAuth10a: - OAuthParameters oauth10aParameters = (OAuthParameters) parameters; - return new OAuthParameters( - Config.getPref().get("oauth.settings.consumer-key", oauth10aParameters.getConsumerKey()), - Config.getPref().get("oauth.settings.consumer-secret", oauth10aParameters.getConsumerSecret()), - Config.getPref().get("oauth.settings.request-token-url", oauth10aParameters.getRequestTokenUrl()), - Config.getPref().get("oauth.settings.access-token-url", oauth10aParameters.getAccessTokenUrl()), - Config.getPref().get("oauth.settings.authorise-url", oauth10aParameters.getAuthoriseUrl()), - Config.getPref().get("oauth.settings.osm-login-url", oauth10aParameters.getOsmLoginUrl()), - Config.getPref().get("oauth.settings.osm-logout-url", oauth10aParameters.getOsmLogoutUrl())); case OAuth20: case OAuth21: // Right now, OAuth 2.1 will work with our OAuth 2.0 implementation - OAuth20Parameters oAuth20Parameters = (OAuth20Parameters) parameters; try { IOAuthToken storedToken = CredentialsManager.getInstance().lookupOAuthAccessToken(apiUrl); - return storedToken != null ? storedToken.getParameters() : oAuth20Parameters; + if (storedToken != null) { + return storedToken.getParameters(); + } } catch (CredentialsAgentException e) { Logging.trace(e); } - return oAuth20Parameters; + return createDefault(apiUrl, oAuthVersion); default: throw new IllegalArgumentException("Unknown OAuth version: " + oAuthVersion); } } - - /** - * Remembers the current values in the preferences. - */ - @Override - public void rememberPreferences() { - Config.getPref().put("oauth.settings.consumer-key", getConsumerKey()); - Config.getPref().put("oauth.settings.consumer-secret", getConsumerSecret()); - Config.getPref().put("oauth.settings.request-token-url", getRequestTokenUrl()); - Config.getPref().put("oauth.settings.access-token-url", getAccessTokenUrl()); - Config.getPref().put("oauth.settings.authorise-url", getAuthoriseUrl()); - Config.getPref().put("oauth.settings.osm-login-url", getOsmLoginUrl()); - Config.getPref().put("oauth.settings.osm-logout-url", getOsmLogoutUrl()); - } - - private final String consumerKey; - private final String consumerSecret; - private final String requestTokenUrl; - private final String accessTokenUrl; - private final String authoriseUrl; - private final String osmLoginUrl; - private final String osmLogoutUrl; - - /** - * Constructs a new {@code OAuthParameters}. - * @param consumerKey consumer key - * @param consumerSecret consumer secret - * @param requestTokenUrl request token URL - * @param accessTokenUrl access token URL - * @param authoriseUrl authorise URL - * @param osmLoginUrl the OSM login URL (for automatic mode) - * @param osmLogoutUrl the OSM logout URL (for automatic mode) - * @see #createDefault - * @see #createFromApiUrl - * @since 9220 - */ - public OAuthParameters(String consumerKey, String consumerSecret, - String requestTokenUrl, String accessTokenUrl, String authoriseUrl, String osmLoginUrl, String osmLogoutUrl) { - this.consumerKey = consumerKey; - this.consumerSecret = consumerSecret; - this.requestTokenUrl = requestTokenUrl; - this.accessTokenUrl = accessTokenUrl; - this.authoriseUrl = authoriseUrl; - this.osmLoginUrl = osmLoginUrl; - this.osmLogoutUrl = osmLogoutUrl; - } - - /** - * Creates a clone of the parameters in other. - * - * @param other the other parameters. Must not be null. - * @throws IllegalArgumentException if other is null - */ - public OAuthParameters(OAuthParameters other) { - CheckParameterUtil.ensureParameterNotNull(other, "other"); - this.consumerKey = other.consumerKey; - this.consumerSecret = other.consumerSecret; - this.accessTokenUrl = other.accessTokenUrl; - this.requestTokenUrl = other.requestTokenUrl; - this.authoriseUrl = other.authoriseUrl; - this.osmLoginUrl = other.osmLoginUrl; - this.osmLogoutUrl = other.osmLogoutUrl; - } - - /** - * Gets the consumer key. - * @return The consumer key - */ - public String getConsumerKey() { - return consumerKey; - } - - /** - * Gets the consumer secret. - * @return The consumer secret - */ - public String getConsumerSecret() { - return consumerSecret; - } - - /** - * Gets the request token URL. - * @return The request token URL - */ - public String getRequestTokenUrl() { - return requestTokenUrl; - } - - /** - * Gets the access token URL. - * @return The access token URL - */ - @Override - public String getAccessTokenUrl() { - return accessTokenUrl; - } - - @Override - public String getAuthorizationUrl() { - return this.authoriseUrl; - } - - @Override - public OAuthVersion getOAuthVersion() { - return OAuthVersion.OAuth10a; - } - - @Override - public String getClientId() { - return this.consumerKey; - } - - @Override - public String getClientSecret() { - return this.consumerSecret; - } - - /** - * Gets the authorise URL. - * @return The authorise URL - */ - public String getAuthoriseUrl() { - return this.getAuthorizationUrl(); - } - - /** - * Gets the URL used to login users on the website (for automatic mode). - * @return The URL used to login users - */ - public String getOsmLoginUrl() { - return osmLoginUrl; - } - - /** - * Gets the URL used to logout users on the website (for automatic mode). - * @return The URL used to logout users - */ - public String getOsmLogoutUrl() { - return osmLogoutUrl; - } - - /** - * Builds an {@link OAuthConsumer} based on these parameters. - * - * @return the consumer - */ - public OAuthConsumer buildConsumer() { - return new SignpostAdapters.OAuthConsumer(consumerKey, consumerSecret); - } - - /** - * Builds an {@link OAuthProvider} based on these parameters and a OAuth consumer consumer. - * - * @param consumer the consumer. Must not be null. - * @return the provider - * @throws IllegalArgumentException if consumer is null - */ - public OAuthProvider buildProvider(OAuthConsumer consumer) { - CheckParameterUtil.ensureParameterNotNull(consumer, "consumer"); - return new SignpostAdapters.OAuthProvider( - requestTokenUrl, - accessTokenUrl, - authoriseUrl - ); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - OAuthParameters that = (OAuthParameters) o; - return Objects.equals(consumerKey, that.consumerKey) && - Objects.equals(consumerSecret, that.consumerSecret) && - Objects.equals(requestTokenUrl, that.requestTokenUrl) && - Objects.equals(accessTokenUrl, that.accessTokenUrl) && - Objects.equals(authoriseUrl, that.authoriseUrl) && - Objects.equals(osmLoginUrl, that.osmLoginUrl) && - Objects.equals(osmLogoutUrl, that.osmLogoutUrl); - } - - @Override - public int hashCode() { - return Objects.hash(consumerKey, consumerSecret, requestTokenUrl, accessTokenUrl, authoriseUrl, osmLoginUrl, osmLogoutUrl); - } } diff --git a/src/org/openstreetmap/josm/data/oauth/OAuthToken.java b/src/org/openstreetmap/josm/data/oauth/OAuthToken.java deleted file mode 100644 index 92e427d5f73..00000000000 --- a/src/org/openstreetmap/josm/data/oauth/OAuthToken.java +++ /dev/null @@ -1,82 +0,0 @@ -// License: GPL. For details, see LICENSE file. -package org.openstreetmap.josm.data.oauth; - -import java.util.Objects; - -import org.openstreetmap.josm.tools.CheckParameterUtil; - -import oauth.signpost.OAuthConsumer; - -/** - * An oauth token that has been obtained by JOSM and can be used to authenticate the user on the server. - */ -public class OAuthToken { - - /** - * Creates an OAuthToken from the token currently managed by the {@link OAuthConsumer}. - * - * @param consumer the consumer - * @return the token - */ - public static OAuthToken createToken(OAuthConsumer consumer) { - return new OAuthToken(consumer.getToken(), consumer.getTokenSecret()); - } - - private final String key; - private final String secret; - - /** - * Creates a new token - * - * @param key the token key - * @param secret the token secret - */ - public OAuthToken(String key, String secret) { - this.key = key; - this.secret = secret; - } - - /** - * Creates a clone of another token - * - * @param other the other token. Must not be null. - * @throws IllegalArgumentException if other is null - */ - public OAuthToken(OAuthToken other) { - CheckParameterUtil.ensureParameterNotNull(other, "other"); - this.key = other.key; - this.secret = other.secret; - } - - /** - * Replies the token key - * - * @return the token key - */ - public String getKey() { - return key; - } - - /** - * Replies the token secret - * - * @return the token secret - */ - public String getSecret() { - return secret; - } - - @Override - public int hashCode() { - return Objects.hash(key, secret); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null || getClass() != obj.getClass()) return false; - OAuthToken that = (OAuthToken) obj; - return Objects.equals(key, that.key) && - Objects.equals(secret, that.secret); - } -} diff --git a/src/org/openstreetmap/josm/data/oauth/OAuthVersion.java b/src/org/openstreetmap/josm/data/oauth/OAuthVersion.java index 91ed4d34b3a..faef4c28403 100644 --- a/src/org/openstreetmap/josm/data/oauth/OAuthVersion.java +++ b/src/org/openstreetmap/josm/data/oauth/OAuthVersion.java @@ -7,7 +7,11 @@ * @since 18650 */ public enum OAuthVersion { - /** OAuth 1.0a */ + /** + * OAuth 1.0a + * @deprecated The OSM API server has deprecated and will remove OAuth 1.0a support in June 2024. + */ + @Deprecated OAuth10a, /** OAuth 2.0 */ OAuth20, diff --git a/src/org/openstreetmap/josm/data/oauth/SignpostAdapters.java b/src/org/openstreetmap/josm/data/oauth/SignpostAdapters.java deleted file mode 100644 index 2b6c781bcbc..00000000000 --- a/src/org/openstreetmap/josm/data/oauth/SignpostAdapters.java +++ /dev/null @@ -1,162 +0,0 @@ -// License: GPL. For details, see LICENSE file. -package org.openstreetmap.josm.data.oauth; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Map; - -import org.openstreetmap.josm.tools.HttpClient; - -import oauth.signpost.AbstractOAuthConsumer; -import oauth.signpost.AbstractOAuthProvider; - -/** - * Adapters to make {@code oauth.signpost} work with {@link HttpClient}. - */ -public final class SignpostAdapters { - - private SignpostAdapters() { - // Hide constructor for utility classes - } - - /** - * OAuth provider. - */ - public static class OAuthProvider extends AbstractOAuthProvider { - - private static final long serialVersionUID = 1L; - - /** - * Constructs a new {@code OAuthProvider}. - * @param requestTokenEndpointUrl request token endpoint URL - * @param accessTokenEndpointUrl access token endpoint URL - * @param authorizationWebsiteUrl authorization website URL - */ - public OAuthProvider(String requestTokenEndpointUrl, String accessTokenEndpointUrl, String authorizationWebsiteUrl) { - super(requestTokenEndpointUrl, accessTokenEndpointUrl, authorizationWebsiteUrl); - } - - @Override - protected HttpRequest createRequest(String endpointUrl) throws Exception { - return new HttpRequest(HttpClient.create(new URL(endpointUrl))); - } - - @Override - protected HttpResponse sendRequest(oauth.signpost.http.HttpRequest request) throws Exception { - return new HttpResponse(((HttpRequest) request).request.connect()); - } - - @Override - protected void closeConnection(oauth.signpost.http.HttpRequest request, oauth.signpost.http.HttpResponse response) throws Exception { - if (response != null) { - ((HttpResponse) response).response.disconnect(); - } else if (request != null) { - ((HttpRequest) request).request.disconnect(); - } - } - } - - /** - * OAuth consumer. - */ - public static class OAuthConsumer extends AbstractOAuthConsumer { - - private static final long serialVersionUID = 1L; - - /** - * Constructs a new {@code OAuthConsumer}. - * @param consumerKey consumer key - * @param consumerSecret consumer secret - */ - public OAuthConsumer(String consumerKey, String consumerSecret) { - super(consumerKey, consumerSecret); - } - - @Override - protected HttpRequest wrap(Object request) { - return new HttpRequest((HttpClient) request); - } - } - - static final class HttpRequest implements oauth.signpost.http.HttpRequest { - final HttpClient request; - - HttpRequest(HttpClient request) { - this.request = request; - } - - @Override - public void setHeader(String name, String value) { - request.setHeader(name, value); - } - - @Override - public String getMethod() { - return request.getRequestMethod(); - } - - @Override - public String getRequestUrl() { - return request.getURL().toExternalForm(); - } - - @Override - public String getContentType() { - return request.getRequestHeader("Content-Type"); - } - - @Override - public String getHeader(String name) { - return request.getRequestHeader(name); - } - - @Override - public InputStream getMessagePayload() { - return null; - } - - @Override - public void setRequestUrl(String url) { - throw new IllegalStateException(); - } - - @Override - public Map getAllHeaders() { - throw new IllegalStateException(); - } - - @Override - public Object unwrap() { - throw new IllegalStateException(); - } - } - - static final class HttpResponse implements oauth.signpost.http.HttpResponse { - final HttpClient.Response response; - - HttpResponse(HttpClient.Response response) { - this.response = response; - } - - @Override - public int getStatusCode() { - return response.getResponseCode(); - } - - @Override - public String getReasonPhrase() { - return response.getResponseMessage(); - } - - @Override - public InputStream getContent() throws IOException { - return response.getContent(); - } - - @Override - public Object unwrap() { - throw new IllegalStateException(); - } - } -} diff --git a/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java b/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java index 329271d5b7e..0836638898b 100644 --- a/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java +++ b/src/org/openstreetmap/josm/data/osm/AbstractPrimitive.java @@ -130,6 +130,11 @@ public abstract class AbstractPrimitive implements IPrimitive, IFilterablePrimit */ protected static final short FLAG_PRESERVED = 1 << 13; + /** + * Determines if the primitive has all of its referrers + */ + protected static final short FLAG_ALL_REFERRERS_DOWNLOADED = 1 << 14; + /** * Put several boolean flags to one short int field to save memory. * Other bits of this field are used in subclasses. @@ -304,7 +309,7 @@ public void setChangesetId(int changesetId) { this.changesetId = changesetId; } - @Deprecated + @Deprecated(since = "17749") @Override public void setTimestamp(Date timestamp) { this.timestamp = (int) TimeUnit.MILLISECONDS.toSeconds(timestamp.getTime()); @@ -320,7 +325,7 @@ public void setRawTimestamp(int timestamp) { this.timestamp = timestamp; } - @Deprecated + @Deprecated(since = "17749") @Override public Date getTimestamp() { return Date.from(getInstant()); @@ -380,6 +385,11 @@ public boolean isDeleted() { return (flags & FLAG_DELETED) != 0; } + @Override + public void setReferrersDownloaded(boolean referrersDownloaded) { + this.updateFlags(FLAG_ALL_REFERRERS_DOWNLOADED, referrersDownloaded); + } + @Override public boolean isUndeleted() { return (flags & (FLAG_VISIBLE + FLAG_DELETED)) == 0; @@ -408,6 +418,51 @@ public void setDeleted(boolean deleted) { setModified(deleted ^ !isVisible()); } + @Override + public boolean hasDirectionKeys() { + return (flags & FLAG_HAS_DIRECTIONS) != 0; + } + + @Override + public boolean reversedDirection() { + return (flags & FLAG_DIRECTION_REVERSED) != 0; + } + + @Override + public boolean isTagged() { + return (flags & FLAG_TAGGED) != 0; + } + + @Override + public boolean isAnnotated() { + return (flags & FLAG_ANNOTATED) != 0; + } + + @Override + public boolean isHighlighted() { + return (flags & FLAG_HIGHLIGHTED) != 0; + } + + @Override + public boolean isDisabled() { + return (flags & FLAG_DISABLED) != 0; + } + + @Override + public boolean isDisabledAndHidden() { + return ((flags & FLAG_DISABLED) != 0) && ((flags & FLAG_HIDE_IF_DISABLED) != 0); + } + + @Override + public boolean isPreserved() { + return (flags & FLAG_PRESERVED) != 0; + } + + @Override + public boolean isReferrersDownloaded() { + return isNew() || (flags & FLAG_ALL_REFERRERS_DOWNLOADED) != 0; + } + /** * If set to true, this object is incomplete, which means only the id * and type is known (type is the objects instance class) @@ -592,7 +647,7 @@ public void put(String key, String value) { Map originalKeys = getKeys(); if (key == null || Utils.isStripEmpty(key)) return; - else if (value == null) { + if (value == null) { remove(key); } else if (keys == null) { keys = new String[] {key, value}; @@ -628,7 +683,7 @@ public void putAll(Map tags) { Map originalKeys = getKeys(); List> tagsToAdd = new ArrayList<>(tags.size()); for (Map.Entry tag : tags.entrySet()) { - if (!Utils.isBlank(tag.getKey())) { + if (!Utils.isStripEmpty(tag.getKey())) { int keyIndex = indexOfKey(newKeys, tag.getKey()); // Realistically, we will not hit the newKeys == null branch. If it is null, keyIndex is always < 0 if (keyIndex < 0 || newKeys == null) { @@ -752,6 +807,7 @@ public final int getNumKeys() { } @Override + @SuppressWarnings("PMD.UseArraysAsList") // See https://github.com/pmd/pmd/issues/5071 public final Collection keySet() { String[] tKeys = this.keys; if (tKeys == null) { diff --git a/src/org/openstreetmap/josm/data/osm/DataSetMerger.java b/src/org/openstreetmap/josm/data/osm/DataSetMerger.java index 0b30508fce4..7fad450b4a6 100644 --- a/src/org/openstreetmap/josm/data/osm/DataSetMerger.java +++ b/src/org/openstreetmap/josm/data/osm/DataSetMerger.java @@ -49,7 +49,7 @@ public class DataSetMerger { /** * constructor - * + *

* The visitor will merge sourceDataSet onto targetDataSet * * @param targetDataSet dataset with my primitives. Must not be null. @@ -68,11 +68,11 @@ public DataSetMerger(DataSet targetDataSet, DataSet sourceDataSet) { /** * Merges a primitive onto primitives dataset. - * + *

* If other.id != 0 it tries to merge it with an corresponding primitive from * my dataset with the same id. If this is not possible a conflict is remembered * in {@link #conflicts}. - * + *

* If other.id == 0 (new primitive) it tries to find a primitive in my dataset with id == 0 which * is semantically equal. If it finds one it merges its technical attributes onto * my primitive. @@ -114,7 +114,7 @@ protected void mergePrimitive(OsmPrimitive source, Collection(targetNode, sourceNode, true)); + addConflict(new Conflict<>(targetNode, sourceNode, true)); targetNode.setDeleted(false); } } else @@ -369,7 +369,11 @@ private boolean mergeById(OsmPrimitive source) { mergeFromSource = true; } if (mergeFromSource) { + boolean backupReferrersDownloadedStatus = target.isReferrersDownloaded() && haveSameVersion; target.mergeFrom(source); + if (backupReferrersDownloadedStatus && !target.isReferrersDownloaded()) { + target.setReferrersDownloaded(true); + } objectsWithChildrenToMerge.add(source.getPrimitiveId()); } return true; @@ -378,7 +382,7 @@ private boolean mergeById(OsmPrimitive source) { /** * Runs the merge operation. Successfully merged {@link OsmPrimitive}s are in * {@link #getTargetDataSet()}. - * + *

* See {@link #getConflicts()} for a map of conflicts after the merge operation. */ public void merge() { @@ -388,7 +392,7 @@ public void merge() { /** * Runs the merge operation. Successfully merged {@link OsmPrimitive}s are in * {@link #getTargetDataSet()}. - * + *

* See {@link #getConflicts()} for a map of conflicts after the merge operation. * @param progressMonitor The progress monitor */ @@ -399,7 +403,7 @@ public void merge(ProgressMonitor progressMonitor) { /** * Runs the merge operation. Successfully merged {@link OsmPrimitive}s are in * {@link #getTargetDataSet()}. - * + *

* See {@link #getConflicts()} for a map of conflicts after the merge operation. * @param progressMonitor The progress monitor * @param mergeBounds Whether or not to merge the bounds of the new DataSet to diff --git a/src/org/openstreetmap/josm/data/osm/DefaultNameFormatter.java b/src/org/openstreetmap/josm/data/osm/DefaultNameFormatter.java index 19bdfed4028..a671b258256 100644 --- a/src/org/openstreetmap/josm/data/osm/DefaultNameFormatter.java +++ b/src/org/openstreetmap/josm/data/osm/DefaultNameFormatter.java @@ -225,9 +225,9 @@ public String format(INode node) { preset.nameTemplate.appendText(name, (TemplateEngineDataProvider) node); } if (node.isLatLonKnown() && Boolean.TRUE.equals(PROPERTY_SHOW_COOR.get())) { - name.append(" \u200E("); - name.append(CoordinateFormatManager.getDefaultFormat().toString(node, ", ")); - name.append(")\u200C"); + name.append(" \u200E(") + .append(CoordinateFormatManager.getDefaultFormat().toString(node, ", ")) + .append(")\u200C"); } } decorateNameWithId(name, node); diff --git a/src/org/openstreetmap/josm/data/osm/Filter.java b/src/org/openstreetmap/josm/data/osm/Filter.java index 70a7455cc9f..55d9124ad1a 100644 --- a/src/org/openstreetmap/josm/data/osm/Filter.java +++ b/src/org/openstreetmap/josm/data/osm/Filter.java @@ -92,10 +92,15 @@ public Filter(FilterPreferenceEntry e) { inverted = e.inverted; } + /** + * The class for storing and retrieving a filter from a preference entry + */ public static class FilterPreferenceEntry { + /** See {@link Filter#version} */ @WriteExplicitly @StructEntry public String version = "1"; + /** See {@link Filter#text} */ @StructEntry public String text; /** @@ -110,10 +115,13 @@ public static class FilterPreferenceEntry { @WriteExplicitly @StructEntry public String mode = "add"; + /** See {@link Filter#caseSensitive} */ @StructEntry public boolean case_sensitive; + /** See {@link Filter#regexSearch} */ @StructEntry public boolean regex_search; + /** See {@link Filter#mapCSSSearch} */ @StructEntry public boolean mapCSS_search; /** diff --git a/src/org/openstreetmap/josm/data/osm/IPrimitive.java b/src/org/openstreetmap/josm/data/osm/IPrimitive.java index e59054f792c..fcefcb92c82 100644 --- a/src/org/openstreetmap/josm/data/osm/IPrimitive.java +++ b/src/org/openstreetmap/josm/data/osm/IPrimitive.java @@ -36,6 +36,15 @@ public interface IPrimitive extends IQuadBucketType, Tagged, PrimitiveId, Stylab */ void setModified(boolean modified); + /** + * Set the status of the referrers + * @param referrersDownloaded {@code true} if all referrers for this object have been downloaded + * @since xxx + */ + default void setReferrersDownloaded(boolean referrersDownloaded) { + throw new UnsupportedOperationException(this.getClass().getName() + " does not support referrers status"); + } + /** * Checks if object is known to the server. * Replies true if this primitive is either unknown to the server (i.e. its id @@ -75,6 +84,16 @@ public interface IPrimitive extends IQuadBucketType, Tagged, PrimitiveId, Stylab */ void setDeleted(boolean deleted); + /** + * Determines if this primitive is fully downloaded + * @return {@code true} if the primitive is fully downloaded and all parents and children should be available. + * {@code false} otherwise. + * @since xxx + */ + default boolean isReferrersDownloaded() { + return false; + } + /** * Determines if this primitive is incomplete. * @return {@code true} if this primitive is incomplete, {@code false} otherwise @@ -279,7 +298,7 @@ default PrimitiveId getPrimitiveId() { * @see #setTimestamp * @deprecated since 17749, use {@link #getInstant} instead */ - @Deprecated + @Deprecated(since = "17749") Date getTimestamp(); /** @@ -308,7 +327,7 @@ default PrimitiveId getPrimitiveId() { * @see #getTimestamp * @deprecated since 17749, use {@link #setInstant} instead */ - @Deprecated + @Deprecated(since = "17749") void setTimestamp(Date timestamp); /** @@ -380,11 +399,11 @@ default String getName() { * accessed from very specific (language variant) to more generic (default name). * * @return the name of this primitive, null if no name exists - * @see LanguageInfo#getLanguageCodes + * @see LanguageInfo#getOSMLocaleCodes */ default String getLocalName() { - for (String s : LanguageInfo.getLanguageCodes(null)) { - String val = get("name:" + s); + for (String s : LanguageInfo.getOSMLocaleCodes("name:")) { + String val = get(s); if (val != null) return val; } diff --git a/src/org/openstreetmap/josm/data/osm/IRelation.java b/src/org/openstreetmap/josm/data/osm/IRelation.java index 1cb29d99237..2902eddcb86 100644 --- a/src/org/openstreetmap/josm/data/osm/IRelation.java +++ b/src/org/openstreetmap/josm/data/osm/IRelation.java @@ -12,7 +12,7 @@ * @param Type of OSM relation member * @since 4098 */ -public interface IRelation> extends IPrimitive { +public interface IRelation> extends IPrimitive { /** * Returns the number of members. @@ -128,8 +128,8 @@ default List getChildren() { */ default Collection getIncompleteMembers() { return getMembers().stream() - .filter(rm -> rm.getMember().isIncomplete()) - .map(rm -> rm.getMember()) + .map(IRelationMember::getMember) + .filter(IPrimitive::isIncomplete) .collect(Collectors.toSet()); } diff --git a/src/org/openstreetmap/josm/data/osm/IWaySegment.java b/src/org/openstreetmap/josm/data/osm/IWaySegment.java index b3747ef828d..bb6eee2843b 100644 --- a/src/org/openstreetmap/josm/data/osm/IWaySegment.java +++ b/src/org/openstreetmap/josm/data/osm/IWaySegment.java @@ -17,6 +17,7 @@ * @since 17862 */ public class IWaySegment> implements Comparable> { + protected static final String NOT_A_SEGMENT = "Node pair is not a single segment of the way!"; private final W way; private final int lowerIndex; @@ -96,18 +97,20 @@ public N getSecondNode() { * @param first first node * @param second second node * @return way segment - * @throws IllegalArgumentException if the node pair is not part of way + * @throws IllegalArgumentException if the node pair is not a single segment of the way */ public static > IWaySegment forNodePair(W way, N first, N second) { int endIndex = way.getNodesCount() - 1; while (endIndex > 0) { final int indexOfFirst = way.getNodes().subList(0, endIndex).lastIndexOf(first); + if (indexOfFirst < 0) + break; if (second.equals(way.getNode(indexOfFirst + 1))) { return new IWaySegment<>(way, indexOfFirst); } endIndex--; } - throw new IllegalArgumentException("Node pair is not part of way!"); + throw new IllegalArgumentException(NOT_A_SEGMENT); } /** @@ -161,16 +164,18 @@ public int hashCode() { @Override public int compareTo(IWaySegment o) { + if (o == null) + return -1; final W thisWay; final IWay otherWay; try { thisWay = toWay(); - otherWay = o == null ? null : o.toWay(); + otherWay = o.toWay(); } catch (ReflectiveOperationException e) { Logging.error(e); return -1; } - return o == null ? -1 : (equals(o) ? 0 : thisWay.compareTo(otherWay)); + return equals(o) ? 0 : thisWay.compareTo(otherWay); } /** diff --git a/src/org/openstreetmap/josm/data/osm/MultipolygonBuilder.java b/src/org/openstreetmap/josm/data/osm/MultipolygonBuilder.java index aa8c17eaf6b..30fea704b59 100644 --- a/src/org/openstreetmap/josm/data/osm/MultipolygonBuilder.java +++ b/src/org/openstreetmap/josm/data/osm/MultipolygonBuilder.java @@ -70,7 +70,7 @@ public List getNodes() { for (int waypos = 0; waypos < this.ways.size(); waypos++) { Way way = this.ways.get(waypos); - if (!this.reversed.get(waypos)) { + if (Boolean.FALSE.equals(this.reversed.get(waypos))) { for (int pos = 0; pos < way.getNodesCount() - 1; pos++) { ringNodes.add(way.getNode(pos)); } @@ -211,7 +211,7 @@ public static List joinWays(Collection ways) { //add cur way to the list collectedWays.add(curWay); - collectedWaysReverse.add(Boolean.valueOf(curWayReverse)); + collectedWaysReverse.add(curWayReverse); if (nextNode == startNode) { //way finished diff --git a/src/org/openstreetmap/josm/data/osm/NodeGraph.java b/src/org/openstreetmap/josm/data/osm/NodeGraph.java index 0651c103cc2..c1ca1558f3d 100644 --- a/src/org/openstreetmap/josm/data/osm/NodeGraph.java +++ b/src/org/openstreetmap/josm/data/osm/NodeGraph.java @@ -21,9 +21,11 @@ import java.util.stream.Stream; import org.openstreetmap.josm.tools.Pair; +import org.openstreetmap.josm.tools.Utils; /** - * A directed or undirected graph of nodes. + * A directed or undirected graph of nodes. Nodes are connected via edges represented by NodePair instances. + * * @since 12463 (extracted from CombineWayAction) */ public class NodeGraph { @@ -32,12 +34,12 @@ public class NodeGraph { * Builds a list of pair of nodes from the given way. * @param way way * @param directed if {@code true} each pair of nodes will occur once, in the way nodes order. - * if {@code false} each pair of nodes will occur twice (the pair and its inversed copy) + * if {@code false} each pair of nodes will occur twice (the pair and its inverse copy) * @return a list of pair of nodes from the given way */ public static List buildNodePairs(Way way, boolean directed) { List pairs = new ArrayList<>(); - for (Pair pair: way.getNodePairs(false /* don't sort */)) { + for (Pair pair : way.getNodePairs(false)) { pairs.add(new NodePair(pair)); if (!directed) { pairs.add(new NodePair(pair).swap()); @@ -49,26 +51,26 @@ public static List buildNodePairs(Way way, boolean directed) { /** * Builds a list of pair of nodes from the given ways. * @param ways ways - * @param directed if {@code true} each pair of nodes will occur once, in the way nodes order. - * if {@code false} each pair of nodes will occur twice (the pair and its inversed copy) + * @param directed if {@code true} each pair of nodes will occur once, in the way nodes order.
+ * if {@code false} each pair of nodes will occur twice (the pair and its inverse copy) * @return a list of pair of nodes from the given ways */ public static List buildNodePairs(List ways, boolean directed) { List pairs = new ArrayList<>(); - for (Way w: ways) { + for (Way w : ways) { pairs.addAll(buildNodePairs(w, directed)); } return pairs; } /** - * Builds a new list of pair nodes without the duplicated pairs (including inversed copies). + * Builds a new list of pair nodes without the duplicated pairs (including inverse copies). * @param pairs existing list of pairs * @return a new list of pair nodes without the duplicated pairs */ public static List eliminateDuplicateNodePairs(List pairs) { List cleaned = new ArrayList<>(); - for (NodePair p: pairs) { + for (NodePair p : pairs) { if (!cleaned.contains(p) && !cleaned.contains(p.swap())) { cleaned.add(p); } @@ -76,18 +78,28 @@ public static List eliminateDuplicateNodePairs(List pairs) { return cleaned; } + /** + * Create a directed graph from the given node pairs. + * @param pairs Node pairs to build the graph from + * @return node graph structure + */ public static NodeGraph createDirectedGraphFromNodePairs(List pairs) { NodeGraph graph = new NodeGraph(); - for (NodePair pair: pairs) { + for (NodePair pair : pairs) { graph.add(pair); } return graph; } + /** + * Create a directed graph from the given ways. + * @param ways ways to build the graph from + * @return node graph structure + */ public static NodeGraph createDirectedGraphFromWays(Collection ways) { NodeGraph graph = new NodeGraph(); - for (Way w: ways) { - graph.add(buildNodePairs(w, true /* directed */)); + for (Way w : ways) { + graph.add(buildNodePairs(w, true)); } return graph; } @@ -99,7 +111,7 @@ public static NodeGraph createDirectedGraphFromWays(Collection ways) { */ public static NodeGraph createUndirectedGraphFromNodeList(List pairs) { NodeGraph graph = new NodeGraph(); - for (NodePair pair: pairs) { + for (NodePair pair : pairs) { graph.add(pair); graph.add(pair.swap()); } @@ -108,41 +120,68 @@ public static NodeGraph createUndirectedGraphFromNodeList(List pairs) /** * Create an undirected graph from the given ways, but prevent reversing of all - * non-new ways by fix one direction. + * non-new ways by fixing one direction. * @param ways Ways to build the graph from * @return node graph structure * @since 8181 */ public static NodeGraph createUndirectedGraphFromNodeWays(Collection ways) { NodeGraph graph = new NodeGraph(); - for (Way w: ways) { - graph.add(buildNodePairs(w, false /* undirected */)); + for (Way w : ways) { + graph.add(buildNodePairs(w, false)); } return graph; } + /** + * Create a nearly undirected graph from the given ways, but prevent reversing of all + * non-new ways by fixing one direction. + * The first new way gives the direction of the graph. + * @param ways Ways to build the graph from + * @return node graph structure + */ public static NodeGraph createNearlyUndirectedGraphFromNodeWays(Collection ways) { boolean dir = true; NodeGraph graph = new NodeGraph(); - for (Way w: ways) { + for (Way w : ways) { if (!w.isNew()) { /* let the first non-new way give the direction (see #5880) */ graph.add(buildNodePairs(w, dir)); dir = false; } else { - graph.add(buildNodePairs(w, false /* undirected */)); + graph.add(buildNodePairs(w, false)); } } return graph; } private final Set edges; - private int numUndirectedEges; - /** counts the number of edges that were added */ + private int numUndirectedEdges; + /** The number of edges that were added. */ private int addedEdges; private final Map> successors = new LinkedHashMap<>(); private final Map> predecessors = new LinkedHashMap<>(); + /** + * Constructs a lookup table from the existing edges in the graph to enable efficient querying. + * This method creates a map where each node is associated with a list of nodes that are directly connected to it. + * + * @return A map representing the graph structure, where nodes are keys, and values are their direct successors. + * @since 19062 + */ + public Map> createMap() { + final Map> result = new HashMap<>(Utils.hashMapInitialCapacity(edges.size())); + + for (NodePair edge : edges) { + result.computeIfAbsent(edge.getA(), k -> new ArrayList<>()).add(edge.getB()); + } + + return result; + } + + /** + * See {@link #prepare()} + */ protected void rememberSuccessor(NodePair pair) { List l = successors.computeIfAbsent(pair.getA(), k -> new ArrayList<>()); if (!l.contains(pair)) { @@ -150,6 +189,9 @@ protected void rememberSuccessor(NodePair pair) { } } + /** + * See {@link #prepare()} + */ protected void rememberPredecessors(NodePair pair) { List l = predecessors.computeIfAbsent(pair.getB(), k -> new ArrayList<>()); if (!l.contains(pair)) { @@ -157,6 +199,12 @@ protected void rememberPredecessors(NodePair pair) { } } + /** + * Replies true if {@code n} is a terminal node of the graph. Internal variables should be initialized first. + * @param n Node to check + * @return {@code true} if it is a terminal node + * @see #prepare() + */ protected boolean isTerminalNode(Node n) { if (successors.get(n) == null) return false; if (successors.get(n).size() != 1) return false; @@ -174,14 +222,14 @@ protected void prepare() { successors.clear(); predecessors.clear(); - for (NodePair pair: edges) { + for (NodePair pair : edges) { if (!undirectedEdges.contains(pair) && !undirectedEdges.contains(pair.swap())) { undirectedEdges.add(pair); } rememberSuccessor(pair); rememberPredecessors(pair); } - numUndirectedEges = undirectedEdges.size(); + numUndirectedEdges = undirectedEdges.size(); } /** @@ -202,14 +250,26 @@ public void add(NodePair pair) { /** * Add a list of node pairs. - * @param pairs list of node pairs + * @param pairs collection of node pairs */ - public void add(Collection pairs) { - for (NodePair pair: pairs) { + public void add(Iterable pairs) { + for (NodePair pair : pairs) { add(pair); } } + /** + * Return the edges containing the node pairs of the graph. + * @return the edges containing the node pairs of the graph + */ + public Collection getEdges() { + return Collections.unmodifiableSet(edges); + } + + /** + * Return the terminal nodes of the graph. + * @return the terminal nodes of the graph + */ protected Set getTerminalNodes() { return getNodes().stream().filter(this::isTerminalNode).collect(Collectors.toCollection(LinkedHashSet::new)); } @@ -229,9 +289,13 @@ protected List getOutboundPairs(Node node) { return Optional.ofNullable(successors.get(node)).orElseGet(Collections::emptyList); } - protected Set getNodes() { + /** + * Return the graph's nodes. + * @return the graph's nodes + */ + public Collection getNodes() { Set nodes = new LinkedHashSet<>(2 * edges.size()); - for (NodePair pair: edges) { + for (NodePair pair : edges) { nodes.add(pair.getA()); nodes.add(pair.getB()); } @@ -239,7 +303,7 @@ protected Set getNodes() { } protected boolean isSpanningWay(Collection way) { - return numUndirectedEges == way.size(); + return numUndirectedEdges == way.size(); } protected List buildPathFromNodePairs(Deque path) { @@ -248,8 +312,8 @@ protected List buildPathFromNodePairs(Deque path) { } /** - * Tries to find a spanning path starting from node startNode. - * + * Tries to find a spanning path starting from node {@code startNode}. + *

* Traverses the path in depth-first order. * * @param startNode the start node @@ -259,8 +323,7 @@ protected List buildSpanningPath(Node startNode) { if (startNode != null) { Deque path = new ArrayDeque<>(); Set dupCheck = new HashSet<>(); - Deque nextPairs = new ArrayDeque<>(); - nextPairs.addAll(getOutboundPairs(startNode)); + Deque nextPairs = new ArrayDeque<>(getOutboundPairs(startNode)); while (!nextPairs.isEmpty()) { NodePair cur = nextPairs.removeLast(); if (!dupCheck.contains(cur) && !dupCheck.contains(cur.swap())) { @@ -280,17 +343,17 @@ protected List buildSpanningPath(Node startNode) { /** * Tries to find a path through the graph which visits each edge (i.e. - * the segment of a way) exactly once. - *

Note that duplicated edges are removed first! + * the segment of a way) exactly once.

+ * Note that duplicated edges are removed first! * - * @return the path; null, if no path was found + * @return the path; {@code null}, if no path was found */ public List buildSpanningPath() { prepare(); - if (numUndirectedEges > 0 && isConnected()) { - // try to find a path from each "terminal node", i.e. from a - // node which is connected by exactly one undirected edges (or - // two directed edges in opposite direction) to the graph. A + if (numUndirectedEdges > 0 && isConnected()) { + // Try to find a path from each "terminal node", i.e. from a + // node which is connected by exactly one undirected edge (or + // two directed edges in the opposite direction) to the graph. A // graph built up from way segments is likely to include such // nodes, unless the edges build one or more closed rings. // We order the nodes to start with the best candidates, but @@ -324,10 +387,10 @@ public List buildSpanningPathNoRemove() { /** * Find out if the graph is connected. - * @return true if it is connected. + * @return {@code true} if it is connected */ private boolean isConnected() { - Set nodes = getNodes(); + Collection nodes = getNodes(); if (nodes.isEmpty()) return false; Deque toVisit = new ArrayDeque<>(); @@ -350,12 +413,12 @@ private boolean isConnected() { /** * Sort the nodes by number of appearances in the edges. - * @return set of nodes which can be start nodes in a spanning way. + * @return set of nodes which can be start nodes in a spanning way */ private Set getMostFrequentVisitedNodesFirst() { if (edges.isEmpty()) return Collections.emptySet(); - // count appearance of nodes in edges + // count the appearance of nodes in edges Map counters = new HashMap<>(); for (NodePair pair : edges) { Integer c = counters.get(pair.getA()); diff --git a/src/org/openstreetmap/josm/data/osm/OsmData.java b/src/org/openstreetmap/josm/data/osm/OsmData.java index a5be8be9a18..51083b73924 100644 --- a/src/org/openstreetmap/josm/data/osm/OsmData.java +++ b/src/org/openstreetmap/josm/data/osm/OsmData.java @@ -282,14 +282,14 @@ default Collection allPreservedPrimitives() { * clear all highlights of virtual nodes */ default void clearHighlightedVirtualNodes() { - setHighlightedVirtualNodes(new ArrayList()); + setHighlightedVirtualNodes(new ArrayList<>()); } /** * clear all highlights of way segments */ default void clearHighlightedWaySegments() { - setHighlightedWaySegments(new ArrayList()); + setHighlightedWaySegments(new ArrayList<>()); } /** @@ -326,7 +326,7 @@ default void clearHighlightedWaySegments() { /** * Replies an unmodifiable collection of primitives currently selected * in this dataset, except deleted ones. May be empty, but not null. - * + *

* When iterating through the set it is ordered by the order in which the primitives were added to the selection. * * @return unmodifiable collection of primitives @@ -338,7 +338,7 @@ default Collection getSelected() { /** * Replies an unmodifiable collection of primitives currently selected * in this dataset, including deleted ones. May be empty, but not null. - * + *

* When iterating through the set it is ordered by the order in which the primitives were added to the selection. * * @return unmodifiable collection of primitives diff --git a/src/org/openstreetmap/josm/data/osm/OsmDataManager.java b/src/org/openstreetmap/josm/data/osm/OsmDataManager.java index aaf07e4ef17..0b5f2a9812d 100644 --- a/src/org/openstreetmap/josm/data/osm/OsmDataManager.java +++ b/src/org/openstreetmap/josm/data/osm/OsmDataManager.java @@ -20,7 +20,7 @@ private OsmDataManager() { // hide constructor } - private static class InstanceHolder { + private static final class InstanceHolder { static final OsmDataManager INSTANCE = new OsmDataManager(); } diff --git a/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java b/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java index fabd39493c9..6c4bd1cf877 100644 --- a/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java +++ b/src/org/openstreetmap/josm/data/osm/OsmPrimitive.java @@ -32,9 +32,9 @@ /** * The base class for OSM objects ({@link Node}, {@link Way}, {@link Relation}). - * + *

* It can be created, deleted and uploaded to the OSM-Server. - * + *

* Although OsmPrimitive is designed as a base class, it is not to be meant to subclass * it by any other than from the package {@link org.openstreetmap.josm.data.osm}. The available primitives are a fixed * set that are given by the server environment and not an extendable data stuff. @@ -95,7 +95,7 @@ public static Set getReferrer(Collection p /** * Creates a new primitive for the given id. - * + *

* If allowNegativeId is set, provided id can be < 0 and will be set to primitive without any processing. * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or * positive number. @@ -123,11 +123,11 @@ else if (id == 0) { /** * Creates a new primitive for the given id and version. - * + *

* If allowNegativeId is set, provided id can be < 0 and will be set to primitive without any processing. * If allowNegativeId is not set, then id will have to be 0 (in that case new unique id will be generated) or * positive number. - * + *

* If id is not > 0 version is ignored and set to 0. * * @param id the id @@ -224,7 +224,7 @@ protected void writeUnlock(boolean locked) { /** * Sets the id and the version of this primitive if it is known to the OSM API. - * + *

* Since we know the id and its version it can't be incomplete anymore. incomplete * is set to false. * @@ -260,7 +260,7 @@ public void setOsmId(long id, int version) { * Clears the metadata, including id and version known to the OSM API. * The id is a new unique id. The version, changeset and timestamp are set to 0. * incomplete and deleted are set to false. It's preferred to use copy constructor with clearMetadata set to true instead - * + *

* Caution: Do not use this method on primitives which are already added to a {@link DataSet}. * * @throws DataIntegrityProblemException If primitive was already added to the dataset @@ -299,7 +299,7 @@ public void setChangesetId(int changesetId) { } } - @Deprecated + @Deprecated(since = "17749", forRemoval = true) @Override public void setTimestamp(Date timestamp) { checkDatasetNotReadOnly(); @@ -363,21 +363,6 @@ public void setPreserved(boolean isPreserved) { updateFlags(FLAG_PRESERVED, isPreserved); } - @Override - public boolean isDisabled() { - return (flags & FLAG_DISABLED) != 0; - } - - @Override - public boolean isDisabledAndHidden() { - return ((flags & FLAG_DISABLED) != 0) && ((flags & FLAG_HIDE_IF_DISABLED) != 0); - } - - @Override - public boolean isPreserved() { - return (flags & FLAG_PRESERVED) != 0; - } - @Override public boolean isSelectable() { // not synchronized -> check disabled twice just to be sure we did not have a race condition. @@ -492,11 +477,6 @@ public void setHighlighted(boolean highlighted) { } } - @Override - public boolean isHighlighted() { - return (flags & FLAG_HIGHLIGHTED) != 0; - } - /*--------------- * DIRECTION KEYS *---------------*/ @@ -526,16 +506,6 @@ private void updateAnnotated() { updateFlagsNoLock(FLAG_ANNOTATED, hasKeys() && getWorkInProgressKeys().stream().anyMatch(this::hasKey)); } - @Override - public boolean isTagged() { - return (flags & FLAG_TAGGED) != 0; - } - - @Override - public boolean isAnnotated() { - return (flags & FLAG_ANNOTATED) != 0; - } - protected void updateDirectionFlags() { boolean hasDirections = false; boolean directionReversed = false; @@ -551,16 +521,6 @@ protected void updateDirectionFlags() { updateFlagsNoLock(FLAG_HAS_DIRECTIONS, hasDirections); } - @Override - public boolean hasDirectionKeys() { - return (flags & FLAG_HAS_DIRECTIONS) != 0; - } - - @Override - public boolean reversedDirection() { - return (flags & FLAG_DIRECTION_REVERSED) != 0; - } - /*------------ * Keys handling ------------*/ @@ -752,19 +712,19 @@ public void visitReferrers(PrimitiveVisitor visitor) { } private void doVisitReferrers(Consumer visitor) { - if (this.referrers == null) - return; - else if (this.referrers instanceof OsmPrimitive) { - OsmPrimitive ref = (OsmPrimitive) this.referrers; - if (ref.dataSet == dataSet) { - visitor.accept(ref); - } - } else if (this.referrers instanceof OsmPrimitive[]) { - OsmPrimitive[] refs = (OsmPrimitive[]) this.referrers; - for (OsmPrimitive ref: refs) { + if (this.referrers != null) { + if (this.referrers instanceof OsmPrimitive) { + OsmPrimitive ref = (OsmPrimitive) this.referrers; if (ref.dataSet == dataSet) { visitor.accept(ref); } + } else if (this.referrers instanceof OsmPrimitive[]) { + OsmPrimitive[] refs = (OsmPrimitive[]) this.referrers; + for (OsmPrimitive ref : refs) { + if (ref.dataSet == dataSet) { + visitor.accept(ref); + } + } } } } @@ -830,7 +790,7 @@ protected void cloneFrom(OsmPrimitive other, boolean copyChildren) { /** * Merges the technical and semantic attributes from other onto this. - * + *

* Both this and other must be new, or both must be assigned an OSM ID. If both this and other * have an assigned OSM id, the IDs have to be the same. * @@ -851,12 +811,10 @@ public void mergeFrom(OsmPrimitive other) { throw new DataIntegrityProblemException( tr("Cannot merge primitives with different ids. This id is {0}, the other is {1}", id, other.getId())); + setIncomplete(other.isIncomplete()); + super.cloneFrom(other); setKeys(other.hasKeys() ? other.getKeys() : null); - timestamp = other.timestamp; version = other.version; - setIncomplete(other.isIncomplete()); - flags = other.flags; - user = other.user; changesetId = other.changesetId; } finally { writeUnlock(locked); diff --git a/src/org/openstreetmap/josm/data/osm/OsmPrimitiveType.java b/src/org/openstreetmap/josm/data/osm/OsmPrimitiveType.java index e5f46d9bb90..cfa8a515c8f 100644 --- a/src/org/openstreetmap/josm/data/osm/OsmPrimitiveType.java +++ b/src/org/openstreetmap/josm/data/osm/OsmPrimitiveType.java @@ -160,7 +160,7 @@ public OsmPrimitive newVersionedInstance(long id, int version) { * @return the unique identifier generator * @since 15820 */ - public final UniqueIdGenerator getIdGenerator() { + public UniqueIdGenerator getIdGenerator() { return idGenerator; } diff --git a/src/org/openstreetmap/josm/data/osm/OsmUtils.java b/src/org/openstreetmap/josm/data/osm/OsmUtils.java index c38082643e5..85634b4d44a 100644 --- a/src/org/openstreetmap/josm/data/osm/OsmUtils.java +++ b/src/org/openstreetmap/josm/data/osm/OsmUtils.java @@ -35,19 +35,19 @@ public final class OsmUtils { /** * Discouraged synonym for {@link #TRUE_VALUE} - * @deprecated since xxx, use {@link #TRUE_VALUE} instead. + * @deprecated since 18801, use {@link #TRUE_VALUE} instead. */ @Deprecated public static final String trueval = TRUE_VALUE; /** * Discouraged synonym for {@link #FALSE_VALUE} - * @deprecated since xxx, use {@link #FALSE_VALUE} instead. + * @deprecated since 18801, use {@link #FALSE_VALUE} instead. */ @Deprecated public static final String falseval = FALSE_VALUE; /** * Discouraged synonym for {@link #REVERSE_VALUE} - * @deprecated since xxx, use {@link #REVERSE_VALUE} instead. + * @deprecated since 18801, use {@link #REVERSE_VALUE} instead. */ @Deprecated public static final String reverseval = REVERSE_VALUE; diff --git a/src/org/openstreetmap/josm/data/osm/PrimitiveComparator.java b/src/org/openstreetmap/josm/data/osm/PrimitiveComparator.java index 9f10b5879be..97c9263bc4a 100644 --- a/src/org/openstreetmap/josm/data/osm/PrimitiveComparator.java +++ b/src/org/openstreetmap/josm/data/osm/PrimitiveComparator.java @@ -57,7 +57,7 @@ public static Comparator orderingNodesWaysRelations() { } static Comparator doOrderingNodesWaysRelations() { - return comparingInt(osm -> osm.getType().ordinal()); + return comparing(PrimitiveId::getType); } /** diff --git a/src/org/openstreetmap/josm/data/osm/PrimitiveData.java b/src/org/openstreetmap/josm/data/osm/PrimitiveData.java index 8f660579909..c5c2775a601 100644 --- a/src/org/openstreetmap/josm/data/osm/PrimitiveData.java +++ b/src/org/openstreetmap/josm/data/osm/PrimitiveData.java @@ -83,7 +83,6 @@ protected final void keysChangedImpl(Map originalKeys) { } private void writeObject(ObjectOutputStream oos) throws IOException { - // since super class is not Serializable oos.writeLong(id); oos.writeLong(user == null ? -1 : user.getId()); oos.writeInt(version); @@ -95,7 +94,6 @@ private void writeObject(ObjectOutputStream oos) throws IOException { } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { - // since super class is not Serializable id = ois.readLong(); final long userId = ois.readLong(); user = userId == -1 ? null : User.getById(userId); diff --git a/src/org/openstreetmap/josm/data/osm/Storage.java b/src/org/openstreetmap/josm/data/osm/Storage.java index 5e1e08af00a..8a2127dc5f6 100644 --- a/src/org/openstreetmap/josm/data/osm/Storage.java +++ b/src/org/openstreetmap/josm/data/osm/Storage.java @@ -17,7 +17,7 @@ * A Set-like class that allows looking up equivalent preexisting instance. * It is useful wherever one would use self-mapping construct like * Map<T,T>.put(t,t), that is, for caches, uniqueness filters or similar. - * + *

* The semantics of equivalency can be external to the object, using the * {@link Hash} interface. The set also supports querying for entries using * different key type, in case you can provide a Hash implementation @@ -399,7 +399,7 @@ private void ensureSpace() { * @return a hash implementation that just delegates to object's own hashCode and equals. */ public static Hash defaultHash() { - return new Hash() { + return new Hash<>() { @Override public int getHashCode(O t) { return Objects.hashCode(t); diff --git a/src/org/openstreetmap/josm/data/osm/TagMap.java b/src/org/openstreetmap/josm/data/osm/TagMap.java index a121f815d73..9716664eaa6 100644 --- a/src/org/openstreetmap/josm/data/osm/TagMap.java +++ b/src/org/openstreetmap/josm/data/osm/TagMap.java @@ -276,9 +276,9 @@ public String toString() { if (!first) { stringBuilder.append(','); } - stringBuilder.append(e.getKey()); - stringBuilder.append('='); - stringBuilder.append(e.getValue()); + stringBuilder.append(e.getKey()) + .append('=') + .append(e.getValue()); first = false; } stringBuilder.append(']'); diff --git a/src/org/openstreetmap/josm/data/osm/WaySegment.java b/src/org/openstreetmap/josm/data/osm/WaySegment.java index 48fe38c4a52..b1b7d583854 100644 --- a/src/org/openstreetmap/josm/data/osm/WaySegment.java +++ b/src/org/openstreetmap/josm/data/osm/WaySegment.java @@ -2,7 +2,7 @@ package org.openstreetmap.josm.data.osm; /** - * A segment consisting of 2 consecutive nodes out of a way. + * A segment consisting of two consecutive nodes out of a way. */ public final class WaySegment extends IWaySegment { @@ -25,18 +25,20 @@ public WaySegment(Way way, int i) { * @param first first node * @param second second node * @return way segment - * @throws IllegalArgumentException if the node pair is not part of way + * @throws IllegalArgumentException if the node pair is not single a segment of the way */ public static WaySegment forNodePair(Way way, Node first, Node second) { int endIndex = way.getNodesCount() - 1; while (endIndex > 0) { final int indexOfFirst = way.getNodes().subList(0, endIndex).lastIndexOf(first); + if (indexOfFirst < 0) + break; if (second.equals(way.getNode(indexOfFirst + 1))) { return new WaySegment(way, indexOfFirst); } endIndex--; } - throw new IllegalArgumentException("Node pair is not part of way!"); + throw new IllegalArgumentException(IWaySegment.NOT_A_SEGMENT); } /** diff --git a/src/org/openstreetmap/josm/data/osm/history/History.java b/src/org/openstreetmap/josm/data/osm/history/History.java index 97d7c10ae85..5a304c5007c 100644 --- a/src/org/openstreetmap/josm/data/osm/history/History.java +++ b/src/org/openstreetmap/josm/data/osm/history/History.java @@ -293,8 +293,11 @@ public OsmPrimitiveType getType() { @Override public String toString() { - StringBuilder result = new StringBuilder("History [" - + (type != null ? ("type=" + type + ", ") : "") + "id=" + id); + StringBuilder result = new StringBuilder("History ["); + if (type != null) { + result.append("type=").append(type).append(", "); + } + result.append("id=").append(id); if (versions != null) { result.append(", versions=\n"); for (HistoryOsmPrimitive v : versions) { diff --git a/src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java b/src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java index 4f059811c45..63c44141a28 100644 --- a/src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java +++ b/src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java @@ -159,7 +159,7 @@ public static class CoreSimpleMatchFactory implements SimpleMatchFactory { @Override public Match get(String keyword, boolean caseSensitive, boolean regexSearch, PushbackTokenizer tokenizer) throws SearchParseError { - switch(keyword) { + switch (keyword) { case MODIFIED: return new Modified(); case DELETED: @@ -1139,8 +1139,8 @@ public boolean match(Tagged osm) { value = Normalizer.normalize(value, Normalizer.Form.NFC); - Matcher keyMatcher = searchRegex.matcher(key); - Matcher valMatcher = searchRegex.matcher(value); + final Matcher keyMatcher = searchRegex.matcher(key); + final Matcher valMatcher = searchRegex.matcher(value); boolean keyMatchFound = keyMatcher.find(); boolean valMatchFound = valMatcher.find(); @@ -1622,7 +1622,7 @@ public boolean equals(Object obj) { /** * Matches objects that are new (i.e. have not been uploaded to the server) */ - private static class New extends Match { + private static final class New extends Match { @Override public boolean match(OsmPrimitive osm) { return osm.isNew(); @@ -1637,7 +1637,7 @@ public String toString() { /** * Matches all objects that have been modified, created, or undeleted */ - private static class Modified extends Match { + private static final class Modified extends Match { @Override public boolean match(OsmPrimitive osm) { return osm.isModified() || osm.isNewOrUndeleted(); @@ -1652,7 +1652,7 @@ public String toString() { /** * Matches all objects that have been deleted */ - private static class Deleted extends Match { + private static final class Deleted extends Match { @Override public boolean match(OsmPrimitive osm) { return osm.isDeleted(); @@ -1667,7 +1667,7 @@ public String toString() { /** * Matches all objects currently selected */ - private static class Selected extends Match { + private static final class Selected extends Match { @Override public boolean match(OsmPrimitive osm) { return osm.getDataSet().isSelected(osm); @@ -1684,7 +1684,7 @@ public String toString() { * Typically, some members of a relation are incomplete until they are * fetched from the server. */ - private static class Incomplete extends Match { + private static final class Incomplete extends Match { @Override public boolean match(OsmPrimitive osm) { return osm.isIncomplete() || (osm instanceof Relation && ((Relation) osm).hasIncompleteMembers()); @@ -1701,7 +1701,7 @@ public String toString() { * fixme, etc.). The complete list of uninteresting tags can be found here: * org.openstreetmap.josm.data.osm.OsmPrimitive.getUninterestingKeys() */ - private static class Untagged extends Match { + private static final class Untagged extends Match { @Override public boolean match(OsmPrimitive osm) { return !osm.isTagged() && !osm.isIncomplete(); @@ -1716,7 +1716,7 @@ public String toString() { /** * Matches ways which are closed (i.e. first and last node are the same) */ - private static class Closed extends Match { + private static final class Closed extends Match { @Override public boolean match(OsmPrimitive osm) { return osm instanceof Way && ((Way) osm).isClosed(); @@ -1817,7 +1817,7 @@ private static class WayLength extends RangeMatch { protected Long getNumber(OsmPrimitive osm) { if (!(osm instanceof Way)) return null; - Way way = (Way) osm; + final Way way = (Way) osm; return (long) way.getLength(); } diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/RenderBenchmarkCollector.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/RenderBenchmarkCollector.java index dc23b76343c..ced0d041519 100644 --- a/src/org/openstreetmap/josm/data/osm/visitor/paint/RenderBenchmarkCollector.java +++ b/src/org/openstreetmap/josm/data/osm/visitor/paint/RenderBenchmarkCollector.java @@ -112,7 +112,7 @@ public long getDrawTime() { } public static long getCurrentTimeMilliseconds() { - return System.nanoTime() / 1000000; // System.currentTimeMillis has low accuracy, sometimes multiples of 16ms + return System.nanoTime() / 1_000_000; // System.currentTimeMillis has low accuracy, sometimes multiples of 16ms } /** @@ -120,7 +120,7 @@ public static long getCurrentTimeMilliseconds() { * @author Michael Zangl */ public static class LoggingBenchmark extends RenderBenchmarkCollector.CapturingBenchmark { - private final PrintStream outStream = System.err; + private static final PrintStream outStream = System.err; private double circum; @Override diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java index c003bab88b8..f49eb1e3325 100644 --- a/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java +++ b/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java @@ -977,7 +977,7 @@ public void drawRestriction(IRelation r, MapImage icon, boolean disabled) { continue; } - switch(m.getRole()) { + switch (m.getRole()) { case "from": if (fromWay == null) { fromWay = w; @@ -1247,7 +1247,7 @@ private void forEachPolygon(IPrimitive osm, Consumer consumer) { for (PolyData pd : multipolygon.getCombinedPolygons()) { MapViewPath path = new MapViewPath(mapState); path.appendFromEastNorth(pd.get()); - path.setWindingRule(MapViewPath.WIND_EVEN_ODD); + path.setWindingRule(Path2D.WIND_EVEN_ODD); consumer.accept(path); } } diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/relations/Multipolygon.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/relations/Multipolygon.java index 804455a3964..39f68c28f63 100644 --- a/src/org/openstreetmap/josm/data/osm/visitor/paint/relations/Multipolygon.java +++ b/src/org/openstreetmap/josm/data/osm/visitor/paint/relations/Multipolygon.java @@ -67,7 +67,7 @@ public class Multipolygon { *

The decision is taken based on preference settings, see the four preference keys * above.

*/ - private static class MultipolygonRoleMatcher implements PreferenceChangedListener { + private static final class MultipolygonRoleMatcher implements PreferenceChangedListener { private final List outerExactRoles = new ArrayList<>(); private final List outerRolePrefixes = new ArrayList<>(); private final List innerExactRoles = new ArrayList<>(); diff --git a/src/org/openstreetmap/josm/data/preferences/JosmBaseDirectories.java b/src/org/openstreetmap/josm/data/preferences/JosmBaseDirectories.java index d3e7f6c6027..7650bf45e9d 100644 --- a/src/org/openstreetmap/josm/data/preferences/JosmBaseDirectories.java +++ b/src/org/openstreetmap/josm/data/preferences/JosmBaseDirectories.java @@ -25,7 +25,7 @@ private JosmBaseDirectories() { // hide constructor } - private static class InstanceHolder { + private static final class InstanceHolder { static final JosmBaseDirectories INSTANCE = new JosmBaseDirectories(); } diff --git a/src/org/openstreetmap/josm/data/preferences/JosmUrls.java b/src/org/openstreetmap/josm/data/preferences/JosmUrls.java index 7a7dc0ddcc6..cd8bac0730a 100644 --- a/src/org/openstreetmap/josm/data/preferences/JosmUrls.java +++ b/src/org/openstreetmap/josm/data/preferences/JosmUrls.java @@ -35,7 +35,7 @@ private JosmUrls() { // hide constructor } - private static class InstanceHolder { + private static final class InstanceHolder { static final JosmUrls INSTANCE = new JosmUrls(); } diff --git a/src/org/openstreetmap/josm/data/preferences/PreferencesReader.java b/src/org/openstreetmap/josm/data/preferences/PreferencesReader.java index 1398a60692b..e11538192a2 100644 --- a/src/org/openstreetmap/josm/data/preferences/PreferencesReader.java +++ b/src/org/openstreetmap/josm/data/preferences/PreferencesReader.java @@ -167,7 +167,7 @@ private void parseRoot() throws XMLStreamException { int event = parser.next(); if (event == XMLStreamConstants.START_ELEMENT) { String localName = parser.getLocalName(); - switch(localName) { + switch (localName) { case "tag": StringSetting setting; if (defaults && isNil()) { @@ -239,7 +239,7 @@ private void parseToplevelList() throws XMLStreamException { int event = parser.next(); if (event == XMLStreamConstants.START_ELEMENT) { String localName = parser.getLocalName(); - switch(localName) { + switch (localName) { case "entry": if (entries == null) { entries = new ArrayList<>(); diff --git a/src/org/openstreetmap/josm/data/preferences/StrokeProperty.java b/src/org/openstreetmap/josm/data/preferences/StrokeProperty.java index 42bd0fd1633..d8c3facb0ca 100644 --- a/src/org/openstreetmap/josm/data/preferences/StrokeProperty.java +++ b/src/org/openstreetmap/josm/data/preferences/StrokeProperty.java @@ -56,7 +56,7 @@ protected String toString(BasicStroke t) { /** * Return s new BasicStroke object with given thickness and style - * @param code = 3.5 -> thickness=3.5px; 3.5 10 5 -> thickness=3.5px, dashed: 10px filled + 5px empty + * @param code = 3.5 → thickness=3.5px; 3.5 10 5 → thickness=3.5px, dashed: 10px filled + 5px empty * @return stroke for drawing */ public static BasicStroke getFromString(String code) { diff --git a/src/org/openstreetmap/josm/data/preferences/sources/SourceEntry.java b/src/org/openstreetmap/josm/data/preferences/sources/SourceEntry.java index 1d0592aa32c..3ca5c675497 100644 --- a/src/org/openstreetmap/josm/data/preferences/sources/SourceEntry.java +++ b/src/org/openstreetmap/josm/data/preferences/sources/SourceEntry.java @@ -156,7 +156,7 @@ public String getDisplayString() { /** * Extracts file part from url, e.g.: - * http://www.test.com/file.xml?format=text --> file.xml + * http://www.test.com/file.xml?format=text → file.xml * @return The filename part of the URL */ public String getFileNamePart() { diff --git a/src/org/openstreetmap/josm/data/preferences/sources/ValidatorPrefHelper.java b/src/org/openstreetmap/josm/data/preferences/sources/ValidatorPrefHelper.java index a5fe3e30e70..edef195b30f 100644 --- a/src/org/openstreetmap/josm/data/preferences/sources/ValidatorPrefHelper.java +++ b/src/org/openstreetmap/josm/data/preferences/sources/ValidatorPrefHelper.java @@ -56,6 +56,27 @@ public class ValidatorPrefHelper extends SourcePrefHelper { */ public static final String PREF_FILTER_BY_SELECTION = PREFIX + ".selectionFilter"; + /** + * See #23397 + * The preferences key for the addition of parent objects for modified objects + */ + public static final BooleanProperty PREF_ADD_PARENTS = new BooleanProperty(PREFIX + ".partial.add.parents", true); + + /** + * See #23397 + * The preferences key for the deletion of results which do not belong to the selection + * or the parents of modified objects. + * + */ + public static final BooleanProperty PREF_REMOVE_IRRELEVANT = new BooleanProperty(PREFIX + ".partial.removeIrrelevant", true); + + /** + * See #23519 + * The preferences key for the automatic unfurl of the validation result window + * + */ + public static final BooleanProperty PREF_UNFURL = new BooleanProperty(PREFIX + ".force.unfurl.window", true); + /** * Constructs a new {@code PresetPrefHelper}. */ diff --git a/src/org/openstreetmap/josm/data/projection/CustomProjection.java b/src/org/openstreetmap/josm/data/projection/CustomProjection.java index e80a3425b8b..d84c45bf0cf 100644 --- a/src/org/openstreetmap/josm/data/projection/CustomProjection.java +++ b/src/org/openstreetmap/josm/data/projection/CustomProjection.java @@ -37,10 +37,11 @@ import org.openstreetmap.josm.tools.Logging; import org.openstreetmap.josm.tools.Utils; import org.openstreetmap.josm.tools.bugreport.BugReport; +import org.openstreetmap.josm.tools.bugreport.ReportedException; /** * Custom projection. - * + *

* Inspired by PROJ.4 and Proj4J. * @since 5072 */ @@ -58,7 +59,7 @@ public class CustomProjection extends AbstractProjection { /** * pref String that defines the projection - * + *

* null means fall back mode (Mercator) */ protected String pref; @@ -233,7 +234,9 @@ public CustomProjection(String name, String code, String pref) { try { update(null); } catch (ProjectionConfigurationException ex1) { - throw BugReport.intercept(ex1).put("name", name).put("code", code).put("pref", pref); + ReportedException reportedException = BugReport.intercept(ex1).put("name", name).put("code", code).put("pref", pref); + reportedException.addSuppressed(ex); + throw reportedException; } } } @@ -873,7 +876,7 @@ public ProjectionBounds getEastNorthBoundsBox(ProjectionBounds box, Projection b /** * Return true, if a geographic coordinate reference system is represented. - * + *

* I.e. if it returns latitude/longitude values rather than Cartesian * east/north coordinates on a flat surface. * @return true, if it is geographic diff --git a/src/org/openstreetmap/josm/data/projection/ShiftedProjecting.java b/src/org/openstreetmap/josm/data/projection/ShiftedProjecting.java index 6638263b1a4..7d4d5aeb88c 100644 --- a/src/org/openstreetmap/josm/data/projection/ShiftedProjecting.java +++ b/src/org/openstreetmap/josm/data/projection/ShiftedProjecting.java @@ -21,7 +21,7 @@ public class ShiftedProjecting implements Projecting { /** * Create a new {@link ShiftedProjecting} * @param base The base to use - * @param offset The offset to move base. Subtracted when converting lat/lon->east/north. + * @param offset The offset to move base. Subtracted when converting lat/lon → east/north. */ public ShiftedProjecting(Projecting base, EastNorth offset) { this.base = base; diff --git a/src/org/openstreetmap/josm/data/projection/datum/NTV2GridShiftFile.java b/src/org/openstreetmap/josm/data/projection/datum/NTV2GridShiftFile.java index df475736ea2..ee8dd327c8d 100644 --- a/src/org/openstreetmap/josm/data/projection/datum/NTV2GridShiftFile.java +++ b/src/org/openstreetmap/josm/data/projection/datum/NTV2GridShiftFile.java @@ -170,28 +170,28 @@ public void loadGridShiftFile(InputStream in, boolean loadAccuracy) throws IOExc private static NTV2SubGrid[] createSubGridTree(NTV2SubGrid... subGrid) { int topLevelCount = 0; Map> subGridMap = new HashMap<>(); - for (int i = 0; i < subGrid.length; i++) { - if ("NONE".equalsIgnoreCase(subGrid[i].getParentSubGridName())) { + for (NTV2SubGrid ntv2SubGrid : subGrid) { + if ("NONE".equalsIgnoreCase(ntv2SubGrid.getParentSubGridName())) { topLevelCount++; } - subGridMap.put(subGrid[i].getSubGridName(), new ArrayList()); + subGridMap.put(ntv2SubGrid.getSubGridName(), new ArrayList<>()); } NTV2SubGrid[] topLevelSubGrid = new NTV2SubGrid[topLevelCount]; topLevelCount = 0; - for (int i = 0; i < subGrid.length; i++) { - if ("NONE".equalsIgnoreCase(subGrid[i].getParentSubGridName())) { - topLevelSubGrid[topLevelCount++] = subGrid[i]; + for (NTV2SubGrid ntv2SubGrid : subGrid) { + if ("NONE".equalsIgnoreCase(ntv2SubGrid.getParentSubGridName())) { + topLevelSubGrid[topLevelCount++] = ntv2SubGrid; } else { - List parent = subGridMap.get(subGrid[i].getParentSubGridName()); - parent.add(subGrid[i]); + List parent = subGridMap.get(ntv2SubGrid.getParentSubGridName()); + parent.add(ntv2SubGrid); } } NTV2SubGrid[] nullArray = new NTV2SubGrid[0]; - for (int i = 0; i < subGrid.length; i++) { - List subSubGrids = subGridMap.get(subGrid[i].getSubGridName()); + for (NTV2SubGrid ntv2SubGrid : subGrid) { + List subSubGrids = subGridMap.get(ntv2SubGrid.getSubGridName()); if (!subSubGrids.isEmpty()) { NTV2SubGrid[] subGridArray = subSubGrids.toArray(nullArray); - subGrid[i].setSubGridArray(subGridArray); + ntv2SubGrid.setSubGridArray(subGridArray); } } return topLevelSubGrid; diff --git a/src/org/openstreetmap/josm/data/projection/datum/NTV2Proj4DirGridShiftFileSource.java b/src/org/openstreetmap/josm/data/projection/datum/NTV2Proj4DirGridShiftFileSource.java index 0fb32640cb8..f9d2c098a67 100644 --- a/src/org/openstreetmap/josm/data/projection/datum/NTV2Proj4DirGridShiftFileSource.java +++ b/src/org/openstreetmap/josm/data/projection/datum/NTV2Proj4DirGridShiftFileSource.java @@ -28,7 +28,7 @@ private NTV2Proj4DirGridShiftFileSource() { } // lazy initialization - private static class InstanceHolder { + private static final class InstanceHolder { static final NTV2Proj4DirGridShiftFileSource INSTANCE = new NTV2Proj4DirGridShiftFileSource(); } diff --git a/src/org/openstreetmap/josm/data/projection/proj/Proj.java b/src/org/openstreetmap/josm/data/projection/proj/Proj.java index 9f2a3ad08d3..adc05af0294 100644 --- a/src/org/openstreetmap/josm/data/projection/proj/Proj.java +++ b/src/org/openstreetmap/josm/data/projection/proj/Proj.java @@ -71,7 +71,7 @@ public interface Proj { * This is a fallback for when the projection bounds are not specified * explicitly. * - * In this area, the round trip lat/lon -> east/north -> lat/lon should + * In this area, the round trip lat/lon → east/north → lat/lon should * return the starting value with small error. In addition, regions with * extreme distortions should be excluded, if possible. * diff --git a/src/org/openstreetmap/josm/data/validation/OsmValidator.java b/src/org/openstreetmap/josm/data/validation/OsmValidator.java index 220f8ccaf40..81dbd623949 100644 --- a/src/org/openstreetmap/josm/data/validation/OsmValidator.java +++ b/src/org/openstreetmap/josm/data/validation/OsmValidator.java @@ -44,6 +44,7 @@ import org.openstreetmap.josm.data.validation.tests.ConditionalKeys; import org.openstreetmap.josm.data.validation.tests.ConnectivityRelations; import org.openstreetmap.josm.data.validation.tests.CrossingWays; +import org.openstreetmap.josm.data.validation.tests.CycleDetector; import org.openstreetmap.josm.data.validation.tests.DirectionNodes; import org.openstreetmap.josm.data.validation.tests.DuplicateNode; import org.openstreetmap.josm.data.validation.tests.DuplicateRelation; @@ -154,8 +155,9 @@ private OsmValidator() { // 3700 .. 3799 is automatically removed since it clashed with pt_assistant. SharpAngles.class, // 3800 .. 3899 ConnectivityRelations.class, // 3900 .. 3999 - DirectionNodes.class, // 4000-4099 + DirectionNodes.class, // 4000 .. 4099 RightAngleBuildingTest.class, // 4100 .. 4199 + CycleDetector.class, // 4200 .. 4299 }; /** diff --git a/src/org/openstreetmap/josm/data/validation/Test.java b/src/org/openstreetmap/josm/data/validation/Test.java index a4fe4ee6407..3b2a0052941 100644 --- a/src/org/openstreetmap/josm/data/validation/Test.java +++ b/src/org/openstreetmap/josm/data/validation/Test.java @@ -6,8 +6,10 @@ import java.awt.GridBagConstraints; import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -383,4 +385,22 @@ protected void setShowElements(boolean b) { public Object getSource() { return "Java: " + this.getClass().getName(); } + + /** + * Filter the list of errors, remove all which do not concern the given list of primitives + * @param given the list of primitives + * @since 18960 + */ + public void removeIrrelevantErrors(Collection given) { + if (errors == null || errors.isEmpty()) + return; + // filter errors for those which are needed, don't show errors for objects which were not in the selection + final Set relevant; + if (given instanceof Set) { + relevant = (Set) given; + } else { + relevant = new HashSet<>(given); + } + errors.removeIf(e -> !e.isConcerned(relevant)); + } } diff --git a/src/org/openstreetmap/josm/data/validation/TestError.java b/src/org/openstreetmap/josm/data/validation/TestError.java index 384af69472b..617236c4591 100644 --- a/src/org/openstreetmap/josm/data/validation/TestError.java +++ b/src/org/openstreetmap/josm/data/validation/TestError.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.TreeSet; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -37,7 +38,7 @@ public class TestError implements Comparable { /** * Used to switch users over to new ignore system, UNIQUE_CODE_MESSAGE_STATE - * 1_704_067_200L -> 2024-01-01 + * 1_704_067_200L → 2024-01-01 * We can probably remove this and the supporting code in 2025. */ private static boolean switchOver = Instant.now().isAfter(Instant.ofEpochMilli(1_704_067_200L)); @@ -62,6 +63,8 @@ public class TestError implements Comparable { private final int uniqueCode; /** If this error is selected */ private boolean selected; + /** If all relevant primitives are known*/ + private boolean incompletePrimitives; /** Supplying a command to fix the error */ private final Supplier fixingCommand; @@ -80,6 +83,7 @@ public static final class Builder { private Collection primitives; private Collection highlighted; private Supplier fixingCommand; + private boolean incompletePrimitives; Builder(Test tester, Severity severity, int code) { this.tester = tester; @@ -217,6 +221,16 @@ public Builder highlight(Area highlighted) { return this; } + /** + * Sets a flag that the list of primitives may be incomplete. See #23397 + * + * @return {@code this} + */ + public Builder imcompletePrimitives() { + this.incompletePrimitives = true; + return this; + } + /** * Sets a supplier to obtain a command to fix the error. * @@ -276,6 +290,7 @@ public static Builder builder(Test tester, Severity severity, int code) { this.code = builder.code; this.uniqueCode = builder.uniqueCode; this.fixingCommand = builder.fixingCommand; + this.incompletePrimitives = builder.incompletePrimitives; } /** @@ -666,4 +681,20 @@ public String toString() { ", code=" + code + ", message=" + message + ']'; } + /** + * Check if any of the primitives in this error occurs in the given set of primitives. + * @param given the set of primitives + * @return true if any of the primitives in this error occurs in the given set of primitives, else false + * @since 18960 + */ + public boolean isConcerned(Set given) { + if (incompletePrimitives) + return true; + for (OsmPrimitive p : getPrimitives()) { + if (given.contains(p)) { + return true; + } + } + return false; + } } diff --git a/src/org/openstreetmap/josm/data/validation/ValidationTask.java b/src/org/openstreetmap/josm/data/validation/ValidationTask.java index 78bbcbf43c9..7eb0f92db8d 100644 --- a/src/org/openstreetmap/josm/data/validation/ValidationTask.java +++ b/src/org/openstreetmap/josm/data/validation/ValidationTask.java @@ -6,19 +6,24 @@ import java.awt.GraphicsEnvironment; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Consumer; import javax.swing.JOptionPane; import org.openstreetmap.josm.data.osm.OsmPrimitive; +import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper; +import org.openstreetmap.josm.data.validation.util.AggregatePrimitivesVisitor; import org.openstreetmap.josm.gui.MainApplication; import org.openstreetmap.josm.gui.MapFrame; import org.openstreetmap.josm.gui.Notification; import org.openstreetmap.josm.gui.PleaseWaitRunnable; -import org.openstreetmap.josm.gui.layer.ValidatorLayer; +import org.openstreetmap.josm.gui.dialogs.ValidatorDialog; import org.openstreetmap.josm.gui.progress.ProgressMonitor; import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor; import org.openstreetmap.josm.gui.util.GuiHelper; @@ -30,7 +35,7 @@ public class ValidationTask extends PleaseWaitRunnable { private final Consumer> onFinish; private Collection tests; - private final Collection validatedPrimitives; + private final Collection initialPrimitives; private final Collection formerValidatedPrimitives; private final boolean beforeUpload; private boolean canceled; @@ -71,12 +76,42 @@ public ValidationTask(Consumer> onFinish, progressMonitor != null ? progressMonitor : new PleaseWaitProgressMonitor(tr("Validating")), false /*don't ignore exceptions */); this.onFinish = onFinish; - this.validatedPrimitives = validatedPrimitives; + this.initialPrimitives = validatedPrimitives; this.formerValidatedPrimitives = formerValidatedPrimitives; this.tests = tests; this.beforeUpload = beforeUpload; } + /** + * Find objects parent objects of given objects which should be checked for geometry problems + * or mismatches between child tags and parent tags. + * @param primitives the given objects + * @return the collection of relevant parent objects + */ + private static Set getRelevantParents(Collection primitives) { + Set addedWays = new HashSet<>(); + Set addedRelations = new HashSet<>(); + for (OsmPrimitive p : primitives) { + for (OsmPrimitive parent : p.getReferrers()) { + if (parent.isDeleted()) + continue; + if (parent instanceof Way) + addedWays.add(parent); + else + addedRelations.add(parent); + } + } + + // allow to find invalid multipolygon relations caused by moved nodes + OsmPrimitive.getParentRelations(addedWays).stream().filter(r -> r.isMultipolygon() && !r.isDeleted()) + .forEach(addedRelations::add); + HashSet extendedSet = new HashSet<>(); + extendedSet.addAll(addedWays); + extendedSet.addAll(addedRelations); + return extendedSet; + + } + protected ValidationTask(ProgressMonitor progressMonitor, Collection tests, Collection validatedPrimitives, @@ -100,14 +135,18 @@ protected void finish() { this.errors.removeIf(error -> error.getSeverity().getLevel() >= Severity.OTHER.getLevel()); } - if (!GraphicsEnvironment.isHeadless() && MainApplication.getMap() != null && (!beforeUpload || !errors.isEmpty())) { + if (!GraphicsEnvironment.isHeadless() && MainApplication.getMap() != null) { + MapFrame map = MainApplication.getMap(); // update GUI on Swing EDT GuiHelper.runInEDT(() -> { - MapFrame map = MainApplication.getMap(); - map.validatorDialog.unfurlDialog(); + // see #23440 why this is inside the EDT + if (!map.validatorDialog.isShowing() && errors.isEmpty() && beforeUpload) + return; + if (!map.validatorDialog.isShowing() || Boolean.TRUE.equals(ValidatorPrefHelper.PREF_UNFURL.get())) + map.validatorDialog.unfurlDialog(); map.validatorDialog.tree.setErrors(errors); //FIXME: nicer way to find / invalidate the corresponding error layer - MainApplication.getLayerManager().getLayersOfType(ValidatorLayer.class).forEach(ValidatorLayer::invalidate); + ValidatorDialog.invalidateValidatorLayers(); if (!errors.isEmpty()) { OsmValidator.initializeErrorLayer(); } @@ -122,8 +161,25 @@ protected void finish() { protected void realRun() { if (Utils.isEmpty(tests)) return; - getProgressMonitor().setTicksCount(tests.size() * validatedPrimitives.size()); int testCounter = 0; + final boolean isPartial = this.beforeUpload || formerValidatedPrimitives != null; + Set filter = null; + Collection validatedPrimitives = initialPrimitives; + if (isPartial) { + Set other = Collections.emptySet(); + if (Boolean.TRUE.equals(ValidatorPrefHelper.PREF_ADD_PARENTS.get())) { + other = getRelevantParents(initialPrimitives); + } + HashSet extendedSet = new HashSet<>(); + AggregatePrimitivesVisitor v = new AggregatePrimitivesVisitor(); + extendedSet.addAll(v.visit(initialPrimitives)); + extendedSet.addAll(other); + validatedPrimitives = extendedSet; + filter = new HashSet<>(initialPrimitives); + filter.addAll(other); + } + getProgressMonitor().setTicksCount(tests.size() * validatedPrimitives.size()); + for (Test test : tests) { if (canceled) return; @@ -131,10 +187,15 @@ protected void realRun() { getProgressMonitor().setCustomText(tr("Test {0}/{1}: Starting {2}", testCounter, tests.size(), test.getName())); test.setBeforeUpload(this.beforeUpload); // Pre-upload checks only run on a partial selection. - test.setPartialSelection(this.beforeUpload || formerValidatedPrimitives != null); + test.setPartialSelection(isPartial); test.startTest(getProgressMonitor().createSubTaskMonitor(validatedPrimitives.size(), false)); test.visit(validatedPrimitives); test.endTest(); + if (isPartial && Boolean.TRUE.equals(ValidatorPrefHelper.PREF_REMOVE_IRRELEVANT.get())) { + // #23397: remove errors for objects which were not in the initial list of primitives + test.removeIrrelevantErrors(filter); + } + errors.addAll(test.getErrors()); if (this.testConsumer != null) { this.testConsumer.accept(this, test); diff --git a/src/org/openstreetmap/josm/data/validation/ValidatorCLI.java b/src/org/openstreetmap/josm/data/validation/ValidatorCLI.java index f429e3dbb57..d04cb8b243b 100644 --- a/src/org/openstreetmap/josm/data/validation/ValidatorCLI.java +++ b/src/org/openstreetmap/josm/data/validation/ValidatorCLI.java @@ -26,8 +26,6 @@ import java.util.logging.Level; import java.util.stream.Collectors; -import jakarta.json.JsonObject; -import org.apache.commons.compress.utils.FileNameUtils; import org.openstreetmap.josm.actions.ExtensionFileFilter; import org.openstreetmap.josm.cli.CLIModule; import org.openstreetmap.josm.data.Preferences; @@ -64,6 +62,8 @@ import org.openstreetmap.josm.tools.Territories; import org.openstreetmap.josm.tools.Utils; +import jakarta.json.JsonObject; + /** * Add a validate command to the JOSM command line interface. * @author Taylor Smock @@ -77,9 +77,9 @@ public class ValidatorCLI implements CLIModule { /** The input file(s) */ private final List input = new ArrayList<>(); - /** The change files. input file -> list of change files */ + /** The change files. input file → list of change files */ private final Map> changeFiles = new HashMap<>(); - /** The output file(s). If {@code null}, use input filename as base (replace extension with geojson). input -> output */ + /** The output file(s). If {@code null}, use input filename as base (replace extension with geojson). input → output */ private final Map output = new HashMap<>(); private static final Supplier progressMonitorFactory = CLIProgressMonitor::new; @@ -263,24 +263,25 @@ private void processFile(final String inputFile) throws IllegalDataException, IO .findFirst().orElseThrow(() -> new JosmRuntimeException(tr("Could not find a layer for {0}", inputFile))); final DataSet dataSet = dataLayer.getDataSet(); if (this.changeFiles.containsKey(inputFile)) { - ProgressMonitor changeFilesMonitor = progressMonitorFactory.get(); + final ProgressMonitor changeFilesMonitor = progressMonitorFactory.get(); for (String changeFile : this.changeFiles.getOrDefault(inputFile, Collections.emptyList())) { try (InputStream changeStream = Compression.getUncompressedFileInputStream(Paths.get(changeFile))) { dataSet.mergeFrom(OsmChangeReader.parseDataSet(changeStream, changeFilesMonitor)); } } } - Path path = Paths.get(outputFile); + final Path path = Paths.get(outputFile); if (path.toFile().isFile() && !Files.deleteIfExists(path)) { Logging.error("Could not delete {0}, attempting to append", outputFile); } - GeoJSONMapRouletteWriter geoJSONMapRouletteWriter = new GeoJSONMapRouletteWriter(dataSet); + final GeoJSONMapRouletteWriter geoJSONMapRouletteWriter = new GeoJSONMapRouletteWriter(dataSet); OsmValidator.initializeTests(); try (OutputStream fileOutputStream = Files.newOutputStream(path)) { // The first writeErrors catches anything that was written, for whatever reason. This is probably never // going to be called. - ValidationTask validationTask = new ValidationTask(errors -> writeErrors(geoJSONMapRouletteWriter, fileOutputStream, errors), + final ValidationTask validationTask = + new ValidationTask(errors -> writeErrors(geoJSONMapRouletteWriter, fileOutputStream, errors), progressMonitorFactory.get(), OsmValidator.getEnabledTests(false), dataSet.allPrimitives(), Collections.emptyList(), false); // This avoids keeping errors in memory @@ -318,14 +319,35 @@ private void writeErrors(GeoJSONMapRouletteWriter geoJSONMapRouletteWriter, Outp * @return The default output name for the input file (extension stripped, ".geojson" added) */ private static String getDefaultOutputName(final String inputString) { - final String extension = FileNameUtils.getExtension(inputString); + final String[] parts = getFileParts(inputString); + final String extension = parts[1]; if (!Arrays.asList("zip", "bz", "xz", "geojson").contains(extension)) { - return FileNameUtils.getBaseName(inputString) + ".geojson"; + return parts[0] + ".geojson"; } else if ("geojson".equals(extension)) { // Account for geojson input files - return FileNameUtils.getBaseName(inputString) + ".validated.geojson"; + return parts[0] + ".validated.geojson"; + } + return parts[0] + ".geojson"; + } + + /** + * Split a string into a filename + extension. Example: + * "foo.bar.txt" → ["foo.bar", "txt"] + *

+ * Please note that future versions of Java may make this method redundant. It is not as of Java 21 (look for + * something like {@code Path#getExtension}, see JDK-8298318. + * That may be in Java 22. + * @param inputString The string to get the filename and extension from + * @return The filename and the (optional) extension + */ + private static String[] getFileParts(String inputString) { + final int split = inputString.lastIndexOf('.'); + final int path = inputString.lastIndexOf(File.separatorChar); + if (split == -1 || path > split) { + return new String[] {inputString, ""}; + } else { + return new String[]{inputString.substring(0, split), inputString.substring(split + 1)}; } - return FileNameUtils.getBaseName(FileNameUtils.getBaseName(inputString)) + ".geojson"; } /** @@ -355,9 +377,7 @@ void initialize() { Config.setUrlsProvider(JosmUrls.getInstance()); ProjectionRegistry.setProjection(Projections.getProjectionByCode("epsg:3857".toUpperCase(Locale.ROOT))); - if (Territories.getKnownIso3166Codes().isEmpty()) { - Territories.initializeInternalData(); - } + Territories.initializeInternalData(); // There is no current way to check to see if territories is already initialized OsmValidator.initialize(); MapPaintStyles.readFromPreferences(); } diff --git a/src/org/openstreetmap/josm/data/validation/routines/DomainValidator.java b/src/org/openstreetmap/josm/data/validation/routines/DomainValidator.java index 5d3e2ac98db..5b27fac584f 100644 --- a/src/org/openstreetmap/josm/data/validation/routines/DomainValidator.java +++ b/src/org/openstreetmap/josm/data/validation/routines/DomainValidator.java @@ -296,7 +296,7 @@ private static String chompLeadingDot(String str) { // WARNING: this array MUST be sorted, otherwise it cannot be searched reliably using binary search private static final String[] GENERIC_TLDS = { - // Taken from Version 2023101900, Last Updated Thu Oct 19 07:07:01 2023 UTC + // Taken from Version 2024040900, Last Updated Tue Apr 9 07:07:01 2024 UTC "aaa", // aaa American Automobile Association, Inc. "aarp", // aarp AARP "abb", // abb ABB Ltd @@ -368,14 +368,12 @@ private static String chompLeadingDot(String str) { "author", // author Amazon Registry Services, Inc. "auto", // auto Uniregistry, Corp. "autos", // autos DERAutos, LLC - "avianca", // avianca Aerovias del Continente Americano S.A. Avianca "aws", // aws Amazon Registry Services, Inc. "axa", // axa AXA SA "azure", // azure Microsoft Corporation "baby", // baby Johnson & Johnson Services, Inc. "baidu", // baidu Baidu, Inc. "banamex", // banamex Citigroup Inc. - "bananarepublic", // bananarepublic The Gap, Inc. "band", // band United TLD Holdco, Ltd "bank", // bank fTLD Registry Services, LLC "bar", // bar Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable @@ -510,7 +508,6 @@ private static String chompLeadingDot(String str) { "college", // college XYZ.COM LLC "cologne", // cologne NetCologne Gesellschaft für Telekommunikation mbH "com", // com VeriSign Global Registry Services - "comcast", // comcast Comcast IP Holdings I, LLC "commbank", // commbank COMMONWEALTH BANK OF AUSTRALIA "community", // community Fox Orchard, LLC "company", // company Silver Avenue, LLC @@ -718,7 +715,6 @@ private static String chompLeadingDot(String str) { "gripe", // gripe Corn Sunset, LLC "grocery", // grocery Wal-Mart Stores, Inc. "group", // group Romeo Town, LLC - "guardian", // guardian The Guardian Life Insurance Company of America "gucci", // gucci Guccio Gucci S.p.a. "guge", // guge Charleston Road Registry Inc. "guide", // guide Snow Moon, LLC @@ -945,7 +941,6 @@ private static String chompLeadingDot(String str) { "nab", // nab National Australia Bank Limited "nagoya", // nagoya GMO Registry, Inc. "name", // name VeriSign Information Services, Inc. - "natura", // natura NATURA COSMÉTICOS S.A. "navy", // navy United TLD Holdco Ltd. "nba", // nba NBA REGISTRY, LLC "nec", // nec NEC Corporation @@ -983,7 +978,6 @@ private static String chompLeadingDot(String str) { "okinawa", // okinawa BusinessRalliart inc. "olayan", // olayan Crescent Holding GmbH "olayangroup", // olayangroup Crescent Holding GmbH - "oldnavy", // oldnavy The Gap, Inc. "ollo", // ollo Dish DBS Corporation "omega", // omega The Swatch Group Ltd "one", // one One.com A/S @@ -1120,7 +1114,6 @@ private static String chompLeadingDot(String str) { "saxo", // saxo Saxo Bank A/S "sbi", // sbi STATE BANK OF INDIA "sbs", // sbs SPECIAL BROADCASTING SERVICE CORPORATION - "sca", // sca SVENSKA CELLULOSA AKTIEBOLAGET SCA (publ) "scb", // scb The Siam Commercial Bank Public Company Limited ("SCB") "schaeffler", // schaeffler Schaeffler Technologies AG & Co. KG "schmidt", // schmidt SALM S.A.S. @@ -1332,7 +1325,6 @@ private static String chompLeadingDot(String str) { "wtf", // wtf Hidden Way, LLC "xbox", // xbox Microsoft Corporation "xerox", // xerox Xerox DNHC LLC - "xfinity", // xfinity Comcast IP Holdings I, LLC "xihuan", // xihuan QIHOO 360 TECHNOLOGY CO. LTD. "xin", // xin Elegant Leader Limited "xn--11b4c3d", // कॉम VeriSign Sarl @@ -1859,7 +1851,7 @@ public static synchronized void updateTLDOverride(ArrayType table, String... tld .map(tld -> tld.toLowerCase(Locale.ENGLISH)) .toArray(String[]::new); Arrays.sort(copy); - switch(table) { + switch (table) { case COUNTRY_CODE_MINUS: countryCodeTLDsMinus = copy; break; @@ -1891,7 +1883,7 @@ public static synchronized void updateTLDOverride(ArrayType table, String... tld */ public static String[] getTLDEntries(ArrayType table) { final String[] array; - switch(table) { + switch (table) { case COUNTRY_CODE_MINUS: array = countryCodeTLDsMinus; break; @@ -1949,7 +1941,7 @@ public static String unicodeToASCII(String input) { // (ideographic full stop), U+FF0E (fullwidth full stop), U+FF61 // (halfwidth ideographic full stop). char lastChar = input.charAt(length-1); // fetch original last char - switch(lastChar) { + switch (lastChar) { case '.': // "." full stop, AKA U+002E case '\u3002': // ideographic full stop case '\uFF0E': // fullwidth full stop @@ -1964,7 +1956,7 @@ public static String unicodeToASCII(String input) { } } - private static class IdnBugHolder { + private static final class IdnBugHolder { private static boolean keepsTrailingDot() { final String input = "a."; // must be a valid name return input.equals(IDN.toASCII(input)); diff --git a/src/org/openstreetmap/josm/data/validation/tests/Addresses.java b/src/org/openstreetmap/josm/data/validation/tests/Addresses.java index 4f1fc1e50f7..e0a74cf68ce 100644 --- a/src/org/openstreetmap/josm/data/validation/tests/Addresses.java +++ b/src/org/openstreetmap/josm/data/validation/tests/Addresses.java @@ -19,6 +19,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.swing.JCheckBox; +import javax.swing.JPanel; + import org.openstreetmap.josm.command.Command; import org.openstreetmap.josm.command.DeleteCommand; import org.openstreetmap.josm.data.coor.EastNorth; @@ -30,10 +33,14 @@ import org.openstreetmap.josm.data.osm.RelationMember; import org.openstreetmap.josm.data.osm.TagMap; import org.openstreetmap.josm.data.osm.Way; +import org.openstreetmap.josm.data.preferences.BooleanProperty; import org.openstreetmap.josm.data.preferences.DoubleProperty; +import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper; import org.openstreetmap.josm.data.validation.Severity; import org.openstreetmap.josm.data.validation.Test; import org.openstreetmap.josm.data.validation.TestError; +import org.openstreetmap.josm.gui.progress.ProgressMonitor; +import org.openstreetmap.josm.tools.GBC; import org.openstreetmap.josm.tools.Geometry; import org.openstreetmap.josm.tools.Logging; import org.openstreetmap.josm.tools.Pair; @@ -70,8 +77,18 @@ public class Addresses extends Test { protected static final String ADDR_HOUSE_NAME = "addr:housename"; protected static final String ADDR_POSTCODE = "addr:postcode"; protected static final String ASSOCIATED_STREET = "associatedStreet"; + protected static final String NAME_TAG = "name"; + private static final String HOUSE = "house"; + private static final String STREET = "street"; // CHECKSTYLE.ON: SingleSpaceSeparator + private static final BooleanProperty PREF_INCLUDE_BLDG_POI = + new BooleanProperty(ValidatorPrefHelper.PREFIX + "." + OpeningHourTest.class.getSimpleName() + "." + "includebuildingpois", false); + private final JCheckBox checkboxIncludeBldgPOI = new JCheckBox( + /* I18n: Label text for checkbox choosing to validate addresses for all types of objects, not just plain addresses */ + tr("Include POIs like amenities, offices, and buildings in duplicate address detection")); + private boolean includeBldgAndPOI; + private Map> knownAddresses; private Set ignoredAddresses; @@ -79,6 +96,7 @@ public class Addresses extends Test { * Constructor */ public Addresses() { + /* I18n: Label text for checkbox choosing to validate addresses */ super(tr("Addresses"), tr("Checks for errors in addresses and associatedStreet relations.")); } @@ -141,7 +159,7 @@ static boolean hasAddress(OsmPrimitive p) { * @param p OsmPrimitive that has an address */ private void collectAddress(OsmPrimitive p) { - if (!isPOI(p)) { + if (includeBldgAndPOI || !isPOI(p)) { for (String simplifiedAddress : getSimplifiedAddresses(p)) { if (!ignoredAddresses.contains(simplifiedAddress)) { knownAddresses.computeIfAbsent(simplifiedAddress, x -> new ArrayList<>()).add(p); @@ -154,7 +172,7 @@ protected void initAddressMap(OsmPrimitive primitive) { knownAddresses = new HashMap<>(); ignoredAddresses = new HashSet<>(); for (OsmPrimitive p : primitive.getDataSet().allNonDeletedPrimitives()) { - if (p instanceof Node && p.hasKey(ADDR_UNIT, ADDR_FLATS)) { + if ((includeBldgAndPOI || p instanceof Node) && p.hasKey(ADDR_UNIT, ADDR_FLATS)) { for (OsmPrimitive r : p.getReferrers()) { if (hasAddress(r)) { // ignore addresses of buildings that are connected to addr:unit nodes @@ -175,6 +193,12 @@ protected void initAddressMap(OsmPrimitive primitive) { } } + @Override + public void startTest(ProgressMonitor progressMonitor) { + super.startTest(progressMonitor); + this.includeBldgAndPOI = PREF_INCLUDE_BLDG_POI.get(); + } + @Override public void endTest() { knownAddresses = null; @@ -186,7 +210,7 @@ protected List checkForDuplicate(OsmPrimitive p) { if (knownAddresses == null) { initAddressMap(p); } - if (!isPOI(p) && hasAddress(p)) { + if ((includeBldgAndPOI || !isPOI(p)) && hasAddress(p)) { List result = new ArrayList<>(); for (String simplifiedAddress : getSimplifiedAddresses(p)) { if (!ignoredAddresses.contains(simplifiedAddress) && knownAddresses.containsKey(simplifiedAddress)) { @@ -198,6 +222,8 @@ protected List checkForDuplicate(OsmPrimitive p) { Severity severityLevel; String city1 = p.get(ADDR_CITY); String city2 = p2.get(ADDR_CITY); + String name1 = p.get(NAME_TAG); + String name2 = p2.get(NAME_TAG); double distance = getDistance(p, p2); if (city1 != null && city2 != null) { if (city1.equals(city2)) { @@ -235,6 +261,10 @@ protected List checkForDuplicate(OsmPrimitive p) { } } } + if (severityLevel == Severity.WARNING && !Objects.equals(name1, name2)) { + // since multiple objects can exist at one address, a different name tag isn't very concerning + severityLevel = Severity.OTHER; + } result.add(TestError.builder(this, severityLevel, DUPLICATE_HOUSE_NUMBER) .message(tr("Duplicate house numbers"), marktr("''{0}'' ({1}m)"), simplifiedAddress, (int) distance) .primitives(Arrays.asList(p, p2)).build()); @@ -301,7 +331,7 @@ public void visit(Relation r) { for (RelationMember m : r.getMembers()) { String role = m.getRole(); OsmPrimitive p = m.getMember(); - if ("house".equals(role)) { + if (HOUSE.equals(role)) { houses.add(p); String number = p.get(ADDR_HOUSE_NUMBER); if (number != null) { @@ -315,7 +345,7 @@ public void visit(Relation r) { } wrongStreetNames.add(p); } - } else if ("street".equals(role)) { + } else if (STREET.equals(role)) { if (p instanceof Way) { street.add((Way) p); } @@ -456,12 +486,12 @@ private void checkIfObsolete(Relation r) { String role = m.getRole(); if ("".equals(role)) { if (m.isWay() && m.getMember().hasKey("highway")) { - role = "street"; + role = STREET; } else if (m.getMember().hasTag("building")) - role = "house"; + role = HOUSE; } switch (role) { - case "house": + case HOUSE: case "addr:houselink": case "address": if (!m.getMember().hasTag(ADDR_STREET) || !m.getMember().hasTag(ADDR_HOUSE_NUMBER)) @@ -471,7 +501,7 @@ private void checkIfObsolete(Relation r) { return; } break; - case "street": + case STREET: if (!m.getMember().hasTag("name") && r.hasTag("name")) return; break; @@ -522,4 +552,19 @@ public boolean isFixable(TestError testError) { return testError.getCode() == OBSOLETE_RELATION; } + @Override + public void addGui(JPanel testPanel) { + super.addGui(testPanel); + checkboxIncludeBldgPOI.setSelected(PREF_INCLUDE_BLDG_POI.get()); + testPanel.add(checkboxIncludeBldgPOI, GBC.eol().insets(20, 0, 0, 0)); + } + + @Override + public boolean ok() { + super.ok(); + PREF_INCLUDE_BLDG_POI.put(checkboxIncludeBldgPOI.isSelected()); + includeBldgAndPOI = PREF_INCLUDE_BLDG_POI.get(); + return false; + } + } diff --git a/src/org/openstreetmap/josm/data/validation/tests/BarriersEntrances.java b/src/org/openstreetmap/josm/data/validation/tests/BarriersEntrances.java index 442533074ed..2e53c3205fb 100644 --- a/src/org/openstreetmap/josm/data/validation/tests/BarriersEntrances.java +++ b/src/org/openstreetmap/josm/data/validation/tests/BarriersEntrances.java @@ -26,7 +26,7 @@ public BarriersEntrances() { @Override public void visit(Node n) { - if (n.hasTag("barrier", "entrance") && !n.isOutsideDownloadArea()) { + if (n.hasTag("barrier", "entrance") && n.isReferrersDownloaded()) { for (OsmPrimitive p : n.getReferrers()) { if (p.hasKey("barrier")) { return; diff --git a/src/org/openstreetmap/josm/data/validation/tests/CrossingWays.java b/src/org/openstreetmap/josm/data/validation/tests/CrossingWays.java index a9006841a09..f98b1ebb9eb 100644 --- a/src/org/openstreetmap/josm/data/validation/tests/CrossingWays.java +++ b/src/org/openstreetmap/josm/data/validation/tests/CrossingWays.java @@ -6,6 +6,7 @@ import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -16,6 +17,8 @@ import org.openstreetmap.josm.data.coor.EastNorth; import org.openstreetmap.josm.data.coor.ILatLon; +import org.openstreetmap.josm.data.osm.DataSet; +import org.openstreetmap.josm.data.osm.OsmDataManager; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.OsmUtils; import org.openstreetmap.josm.data.osm.Relation; @@ -81,6 +84,7 @@ else if (w.hasKey(CrossingWays.WATERWAY)) private final Map> cellSegments = new HashMap<>(1000); /** The already detected ways in error */ private final Map, List> seenWays = new HashMap<>(50); + private final Set waysToTest = new HashSet<>(); protected final int code; @@ -305,9 +309,49 @@ public void startTest(ProgressMonitor monitor) { @Override public void endTest() { - super.endTest(); + runTest(); + // free storage cellSegments.clear(); seenWays.clear(); + if (partialSelection) + removeIrrelevantErrors(waysToTest); + waysToTest.clear(); + super.endTest(); + } + + protected void runTest() { + final Collection selection; + if (this instanceof SelfCrossing || !partialSelection) { + selection = waysToTest; + } else { + selection = addNearbyObjects(); + } + for (Way w : selection) { + testWay(w); + } + + } + + private Collection addNearbyObjects() { + final Collection selection = new HashSet<>(); + DataSet ds = OsmDataManager.getInstance().getActiveDataSet(); + if (ds != null) { + for (Way wt : waysToTest) { + selection.addAll(ds.searchWays(wt.getBBox()).stream() + .filter(w -> !w.isDeleted() && isPrimitiveUsable(w)).collect(Collectors.toList())); + if (this instanceof CrossingWays.Boundaries) { + List relations = ds.searchRelations(wt.getBBox()).stream() + .filter(this::isPrimitiveUsable).collect(Collectors.toList()); + for (Relation r: relations) { + for (Way w : r.getMemberPrimitives(Way.class)) { + if (!w.isIncomplete()) + selection.add(w); + } + } + } + } + } + return selection; } static boolean isCoastline(OsmPrimitive w) { @@ -315,7 +359,7 @@ static boolean isCoastline(OsmPrimitive w) { } static boolean isWaterArea(OsmPrimitive w) { - return w.hasTag("natural", "water") || w.hasTag("waterway", "riverbank") || w.hasTag(LANDUSE, "reservoir"); + return w.hasTag("natural", "water") || w.hasTag(WATERWAY, "riverbank") || w.hasTag(LANDUSE, "reservoir"); } static boolean isHighway(OsmPrimitive w) { @@ -344,6 +388,10 @@ MessageHelper createMessage(Way w1, Way w2) { @Override public void visit(Way w) { + waysToTest.add(w); + } + + private void testWay(Way w) { boolean findSelfCrossingOnly = this instanceof SelfCrossing; if (findSelfCrossingOnly) { // free memory, we are not interested in previous ways @@ -482,6 +530,7 @@ public static boolean isSelfCrossing(Way way) { CheckParameterUtil.ensureParameterNotNull(way, "way"); SelfCrossing test = new SelfCrossing(); test.visit(way); + test.runTest(); return !test.getErrors().isEmpty(); } } diff --git a/src/org/openstreetmap/josm/data/validation/tests/CycleDetector.java b/src/org/openstreetmap/josm/data/validation/tests/CycleDetector.java new file mode 100644 index 00000000000..a868d38bd59 --- /dev/null +++ b/src/org/openstreetmap/josm/data/validation/tests/CycleDetector.java @@ -0,0 +1,247 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.data.validation.tests; + +import static org.openstreetmap.josm.tools.I18n.tr; +import static org.openstreetmap.josm.tools.I18n.trc; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.stream.Collectors; + +import org.openstreetmap.josm.data.algorithms.Tarjan; +import org.openstreetmap.josm.data.osm.BBox; +import org.openstreetmap.josm.data.osm.Node; +import org.openstreetmap.josm.data.osm.NodeGraph; +import org.openstreetmap.josm.data.osm.OsmPrimitive; +import org.openstreetmap.josm.data.osm.QuadBuckets; +import org.openstreetmap.josm.data.osm.Way; +import org.openstreetmap.josm.data.osm.WaySegment; +import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper; +import org.openstreetmap.josm.data.validation.Severity; +import org.openstreetmap.josm.data.validation.Test; +import org.openstreetmap.josm.data.validation.TestError; +import org.openstreetmap.josm.gui.progress.ProgressMonitor; +import org.openstreetmap.josm.spi.preferences.Config; +import org.openstreetmap.josm.tools.Pair; + +/** + * Test for detecting cycles in a directed graph, + * currently used for waterways only. The processed graph consists of ways labeled as waterway. + * + * @author gaben + * @since 19062 + */ +public class CycleDetector extends Test { + protected static final int CYCLE_DETECTED = 4200; + + /** All waterways for cycle detection */ + private final Set usableWaterways = new HashSet<>(); + + /** Already visited primitive unique IDs */ + private final Set visitedWays = new HashSet<>(); + + /** Currently used directional waterways from the OSM wiki */ + private List directionalWaterways; + + protected static final String PREFIX = ValidatorPrefHelper.PREFIX + "." + CycleDetector.class.getSimpleName(); + + /** + * Constructor + */ + public CycleDetector() { + super(tr("Cycle detector"), tr("Detects cycles in drainage systems.")); + } + + @Override + public boolean isPrimitiveUsable(OsmPrimitive p) { + return p.isUsable() && (p instanceof Way) && (((Way) p).getNodesCount() > 1) && p.hasTag("waterway", directionalWaterways); + } + + @Override + public void visit(Way w) { + if (isPrimitiveUsable(w)) + usableWaterways.add(w); + } + + @Override + public void startTest(ProgressMonitor progressMonitor) { + super.startTest(progressMonitor); + directionalWaterways = Config.getPref().getList(PREFIX + ".directionalWaterways", + Arrays.asList("river", "stream", "tidal_channel", "drain", "ditch", "fish_pass")); + } + + @Override + public void endTest() { + final QuadBuckets quadBuckets = new QuadBuckets<>(); + quadBuckets.addAll(usableWaterways); + + for (Collection graph : getGraphs()) { + NodeGraph nodeGraph = NodeGraph.createDirectedGraphFromWays(graph); + Tarjan tarjan = new Tarjan(nodeGraph); + Collection> scc = tarjan.getSCC(); + + // for partial selection, we need to manually add the rest of graph members to the lookup object + if (partialSelection) { + quadBuckets.addAll(graph); + } + + for (List possibleCycle : scc) { + // there is a cycle in the graph if a strongly connected component has more than one node + if (possibleCycle.size() > 1) { + // build bbox to locate the issue + BBox bBox = new BBox(); + possibleCycle.forEach(node -> bBox.addPrimitive(node, 0)); + // find ways within this bbox + List waysWithinErrorBbox = quadBuckets.search(bBox); + List toReport = waysWithinErrorBbox.stream() + .filter(w -> possibleCycle.stream().filter(w.getNodes()::contains).count() > 1) + .collect(Collectors.toList()); + + Map> graphMap = tarjan.getGraphMap(); + errors.add( + TestError.builder(this, Severity.ERROR, CYCLE_DETECTED) + .message(trc("graph theory", "Cycle in directional waterway network")) + .primitives(toReport) + .highlightWaySegments(createSegments(graphMap, possibleCycle)) + .build() + ); + } + } + } + + usableWaterways.clear(); + visitedWays.clear(); + super.endTest(); + } + + /** + * Creates WaySegments from Nodes for the error highlight function. + * + * @param graphMap the complete graph data + * @param nodes nodes to build the way segments from + * @return WaySegments from the Nodes + */ + private static Collection createSegments(Map> graphMap, Collection nodes) { + List> pairs = new ArrayList<>(); + + // build new graph exclusively from SCC nodes + for (Node node : nodes) { + for (Node successor : graphMap.get(node)) { + // check for outbound nodes + if (nodes.contains(successor)) { + pairs.add(new Pair<>(node, successor)); + } + } + } + + Collection segments = new ArrayList<>(); + + for (Pair pair : pairs) { + final Node n = pair.a; + final Node m = pair.b; + + if (n != null && m != null && !n.equals(m)) { + List intersect = new ArrayList<>(n.getParentWays()); + List mWays = m.getParentWays(); + intersect.retainAll(mWays); + + for (Way w : intersect) { + if (isConsecutive(w, n, m)) { + segments.add(WaySegment.forNodePair(w, n, m)); + } + } + } + } + + return segments; + } + + /** + * Determines if the given nodes are consecutive part of the parent way. + * + * @param w parent way + * @param n the first node to look up in the way direction + * @param m the second, possibly consecutive node + * @return {@code true} if the nodes are consecutive order in the way direction + */ + private static boolean isConsecutive(Way w, Node n, Node m) { + for (int i = 0; i < w.getNodesCount() - 1; i++) { + if (w.getNode(i).equals(n) && w.getNode(i + 1).equals(m)) { + return true; + } + } + + return false; + } + + /** + * Returns all directional waterways which connect to at least one other usable way. + * + * @return all directional waterways which connect to at least one other usable way + */ + private Collection> getGraphs() { + // HashSet doesn't make a difference here + Collection> graphs = new ArrayList<>(); + + for (Way waterway : usableWaterways) { + if (visitedWays.contains(waterway.getUniqueId())) { + continue; + } + Collection graph = buildGraph(waterway); + + if (!graph.isEmpty()) { + graphs.add(graph); + } + } + + return graphs; + } + + /** + * Returns a collection of ways, which belongs to the same graph. + * + * @param way starting way to extend the graph from + * @return a collection of ways which belongs to the same graph + */ + private Collection buildGraph(Way way) { + final Set graph = new HashSet<>(); + Queue queue = new ArrayDeque<>(); + queue.offer(way); + + while (!queue.isEmpty()) { + Way currentWay = queue.poll(); + visitedWays.add(currentWay.getUniqueId()); + + for (Node node : currentWay.getNodes()) { + Collection referrers = node.referrers(Way.class) + .filter(this::isPrimitiveUsable) + .filter(candidate -> candidate != currentWay) + .collect(Collectors.toList()); + + if (!referrers.isEmpty()) { + for (Way referrer : referrers) { + if (!visitedWays.contains(referrer.getUniqueId())) { + queue.offer(referrer); + visitedWays.add(referrer.getUniqueId()); + } + } + graph.addAll(referrers); + } + } + } + + // case for single, non-connected waterways + if (graph.isEmpty()) { + graph.add(way); + } + + return graph; + } +} diff --git a/src/org/openstreetmap/josm/data/validation/tests/DirectionNodes.java b/src/org/openstreetmap/josm/data/validation/tests/DirectionNodes.java index 5a5a725b839..128c650d4ce 100644 --- a/src/org/openstreetmap/josm/data/validation/tests/DirectionNodes.java +++ b/src/org/openstreetmap/josm/data/validation/tests/DirectionNodes.java @@ -44,7 +44,7 @@ public void visit(Node n) { for (Entry tag : n.getKeys().entrySet()) { if (("forward".equals(tag.getValue()) || "backward".equals(tag.getValue())) && ("direction".equals(tag.getKey()) || tag.getKey().endsWith(":direction"))) { - checkParents(n, tag.getKey() + "=forward|backbard"); + checkParents(n, tag.getKey() + "=forward|backward"); } } } diff --git a/src/org/openstreetmap/josm/data/validation/tests/DuplicateNode.java b/src/org/openstreetmap/josm/data/validation/tests/DuplicateNode.java index 946454f8401..45acc83dd28 100644 --- a/src/org/openstreetmap/josm/data/validation/tests/DuplicateNode.java +++ b/src/org/openstreetmap/josm/data/validation/tests/DuplicateNode.java @@ -79,7 +79,7 @@ protected LatLon getLatLon(Object o) { public boolean equals(Object k, Object t) { LatLon coorK = getLatLon(k); LatLon coorT = getLatLon(t); - return coorK == coorT || (coorK != null && coorT != null && coorK.equals(coorT)); + return Objects.equals(coorK, coorT); } @Override diff --git a/src/org/openstreetmap/josm/data/validation/tests/DuplicateRelation.java b/src/org/openstreetmap/josm/data/validation/tests/DuplicateRelation.java index 4b362169a94..704d4cd5348 100644 --- a/src/org/openstreetmap/josm/data/validation/tests/DuplicateRelation.java +++ b/src/org/openstreetmap/josm/data/validation/tests/DuplicateRelation.java @@ -1,11 +1,11 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.data.validation.tests; +import static org.openstreetmap.josm.tools.I18n.marktr; import static org.openstreetmap.josm.tools.I18n.tr; import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -19,6 +19,7 @@ import org.openstreetmap.josm.command.SequenceCommand; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.AbstractPrimitive; +import org.openstreetmap.josm.data.osm.DataSet; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.OsmPrimitiveType; @@ -36,6 +37,8 @@ */ public class DuplicateRelation extends Test { + private static final String DUPLICATED_RELATIONS = marktr("Duplicated relations"); + /** * Class to store one relation members and information about it */ @@ -110,13 +113,13 @@ public RelMember(RelationMember src) { */ private static class RelationMembers { /** Set of member objects of the relation */ - private final Set members; + private final List members; /** Store relation information * @param members The list of relation members */ RelationMembers(List members) { - this.members = new HashSet<>(members.size()); + this.members = new ArrayList<>(members.size()); for (RelationMember member : members) { this.members.add(new RelMember(member)); } @@ -175,68 +178,216 @@ public boolean equals(Object obj) { /** Code number of relation with same members error */ protected static final int SAME_RELATION = 1902; - /** MultiMap of all relations */ - private MultiMap relations; + /** Code number of relation with same members error */ + protected static final int IDENTICAL_MEMBERLIST = 1903; - /** MultiMap of all relations, regardless of keys */ - private MultiMap, OsmPrimitive> relationsNoKeys; - /** List of keys without useful information */ - private final Set ignoreKeys = new HashSet<>(AbstractPrimitive.getUninterestingKeys()); + /** List of all initially visited testable relations*/ + List visited; /** * Default constructor */ public DuplicateRelation() { - super(tr("Duplicated relations"), + super(tr(DUPLICATED_RELATIONS), tr("This test checks that there are no relations with same tags and same members with same roles.")); } @Override public void startTest(ProgressMonitor monitor) { super.startTest(monitor); - relations = new MultiMap<>(1000); - relationsNoKeys = new MultiMap<>(1000); + visited = new ArrayList<>(); + } @Override public void endTest() { - for (Set duplicated : relations.values()) { + if (!visited.isEmpty()) + performChecks(); + visited = null; + super.endTest(); + } + + private void performChecks() { + MultiMap relations = new MultiMap<>(1000); + // MultiMap of all relations, regardless of keys + MultiMap, Relation> sameMembers = new MultiMap<>(1000); + + for (Relation r : visited) { + final List rMembers = getSortedMembers(r); + sameMembers.put(rMembers, r); + addToRelations(relations, r, true); + } + + if (partialSelection) { + // add data for relations which were not in the initial selection when + // they can be duplicates + DataSet ds = visited.iterator().next().getDataSet(); + for (Relation r : ds.getRelations()) { + if (r.isDeleted() || r.getMembers().isEmpty() || visited.contains(r)) + continue; + final List rMembers = getSortedMembers(r); + if (sameMembers.containsKey(rMembers)) + sameMembers.put(rMembers, r); + + addToRelations(relations, r, false); + } + } + + for (Set duplicated : sameMembers.values()) { if (duplicated.size() > 1) { - TestError testError = TestError.builder(this, Severity.ERROR, DUPLICATE_RELATION) - .message(tr("Duplicated relations")) - .primitives(duplicated) - .build(); - errors.add(testError); + checkOrderAndTags(duplicated); } } - relations = null; - for (Set duplicated : relationsNoKeys.values()) { + + performGeometryTest(relations); + } + + private void performGeometryTest(MultiMap relations) { + // perform special test to find relations with different members but (possibly) same geometry + // this test is rather speculative and works only with complete members + for (Set duplicated : relations.values()) { if (duplicated.size() > 1) { - TestError testError = TestError.builder(this, Severity.OTHER, SAME_RELATION) - .message(tr("Relations with same members")) + TestError testError = TestError.builder(this, Severity.ERROR, DUPLICATE_RELATION) + .message(tr(DUPLICATED_RELATIONS)) .primitives(duplicated) .build(); - errors.add(testError); + if (errors.stream().noneMatch(e -> e.isSimilar(testError))) { + errors.add(testError); + } } } - relationsNoKeys = null; - super.endTest(); + } + + private static void addToRelations(MultiMap relations, Relation r, boolean forceAdd) { + if (r.isUsable() && !r.hasIncompleteMembers()) { + RelationPair rKey = new RelationPair(r.getMembers(), cleanedKeys(r)); + if (forceAdd || (!relations.isEmpty() && !relations.containsKey(rKey))) + relations.put(rKey, r); + } } @Override public void visit(Relation r) { - if (!r.isUsable() || r.hasIncompleteMembers() || "tmc".equals(r.get("type")) || "TMC".equals(r.get("type")) - || "destination_sign".equals(r.get("type")) || r.getMembers().isEmpty()) - return; - List rMembers = r.getMembers(); - Map rkeys = r.getKeys(); - for (String key : ignoreKeys) { - rkeys.remove(key); + if (!r.isDeleted() && r.getMembersCount() > 0) + visited.add(r); + } + + /** + * Check a list of relations which are guaranteed to have the same members, possibly in different order + * and possibly different tags. + * @param sameMembers the list of relations + */ + private void checkOrderAndTags(Set sameMembers) { + MultiMap, Relation> sameOrder = new MultiMap<>(); + MultiMap, Relation> sameKeys = new MultiMap<>(); + for (Relation r : sameMembers) { + sameOrder.put(r.getMembers(), r); + sameKeys.put(cleanedKeys(r), r); + } + for (Set duplicated : sameKeys.values()) { + if (duplicated.size() > 1) { + reportDuplicateKeys(duplicated); + } + } + for (Set duplicated : sameOrder.values()) { + if (duplicated.size() > 1) { + reportDuplicateMembers(duplicated); + } + } + List primitives = sameMembers.stream().filter(r -> !ignoredType(r)).collect(Collectors.toList()); + // report this collection if not already reported + if (primitives.size() > 1 && errors.stream().noneMatch(e -> e.getPrimitives().containsAll(primitives))) { + // same members, possibly different order + TestError testError = TestError.builder(this, Severity.OTHER, SAME_RELATION) + .message(tr("Relations with same members")) + .primitives(primitives) + .build(); + errors.add(testError); + } + } + + /** + * Check collection of relations with the same keys and members, possibly in different order + * @param duplicated collection of relations, caller must make sure that they have the same keys and members + */ + private void reportDuplicateKeys(Collection duplicated) { + Relation first = duplicated.iterator().next(); + if (memberOrderMatters(first)) { + List toCheck = new ArrayList<>(duplicated); + while (toCheck.size() > 1) { + Relation ref = toCheck.iterator().next(); + List same = toCheck.stream() + .filter(r -> r.getMembers().equals(ref.getMembers())).collect(Collectors.toList()); + if (same.size() > 1) { + // same members and keys, members in same order + TestError testError = TestError.builder(this, Severity.ERROR, DUPLICATE_RELATION) + .message(tr(DUPLICATED_RELATIONS)) + .primitives(same) + .build(); + errors.add(testError); + } + toCheck.removeAll(same); + } + } else { + // same members and keys, possibly different order + TestError testError = TestError.builder(this, Severity.ERROR, DUPLICATE_RELATION) + .message(tr(DUPLICATED_RELATIONS)) + .primitives(duplicated) + .build(); + errors.add(testError); + } + } + + /** + * Report relations with the same member(s) in the same order, keys may be different + * @param duplicated collection of relations with identical member list + */ + private void reportDuplicateMembers(Set duplicated) { + List primitives = duplicated.stream().filter(r -> !ignoredType(r)).collect(Collectors.toList()); + if (primitives.size() > 1 && errors.stream().noneMatch(e -> e.getPrimitives().containsAll(primitives))) { + TestError testError = TestError.builder(this, Severity.OTHER, IDENTICAL_MEMBERLIST) + .message(tr("Identical members")) + .primitives(primitives) + .build(); + errors.add(testError); } - RelationPair rKey = new RelationPair(rMembers, rkeys); - relations.put(rKey, r); - relationsNoKeys.put(rMembers, r); + + } + + private static boolean memberOrderMatters(Relation r) { + return r.hasTag("type", "route", "waterway"); // add more types? + } + + private static boolean ignoredType(Relation r) { + return r.hasTag("type", "tmc", "TMC", "destination_sign"); // see r11783 + } + + /** return tags of a primitive after removing all discardable tags + * + * @param p the primitive + * @return map with cleaned tags + */ + private static Map cleanedKeys(OsmPrimitive p) { + Map cleaned = p.getKeys(); + for (String key : AbstractPrimitive.getDiscardableKeys()) { + cleaned.remove(key); + } + return cleaned; + } + + /** + * Order members of given relation by type, unique id, and role. + * @param r the relation + * @return sorted list of members + */ + private static List getSortedMembers(Relation r) { + return r.getMembers().stream().sorted((m1, m2) -> { + int d = m1.getMember().compareTo(m2.getMember()); + if (d != 0) + return d; + return m1.getRole().compareTo(m2.getRole()); + }).collect(Collectors.toList()); } /** @@ -300,7 +451,7 @@ public Command fixError(TestError testError) { @Override public boolean isFixable(TestError testError) { if (!(testError.getTester() instanceof DuplicateRelation) - || testError.getCode() == SAME_RELATION) return false; + || testError.getCode() != DUPLICATE_RELATION) return false; // We fix it only if there is no more than one relation that is relation member. Set rels = testError.primitives(Relation.class) diff --git a/src/org/openstreetmap/josm/data/validation/tests/DuplicateWay.java b/src/org/openstreetmap/josm/data/validation/tests/DuplicateWay.java index 0b6d138f073..fd9b632d9a3 100644 --- a/src/org/openstreetmap/josm/data/validation/tests/DuplicateWay.java +++ b/src/org/openstreetmap/josm/data/validation/tests/DuplicateWay.java @@ -7,6 +7,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -103,6 +104,7 @@ public boolean equals(Object obj) { /** Set of known hashcodes for list of coordinates **/ private Set knownHashCodes; + private List waysToCheck; /** * Constructor @@ -115,6 +117,7 @@ public DuplicateWay() { @Override public void startTest(ProgressMonitor monitor) { super.startTest(monitor); + waysToCheck = new ArrayList<>(); ways = new MultiMap<>(1000); waysNoTags = new MultiMap<>(1000); knownHashCodes = new HashSet<>(1000); @@ -122,7 +125,22 @@ public void startTest(ProgressMonitor monitor) { @Override public void endTest() { - super.endTest(); + if (partialSelection && !waysToCheck.isEmpty()) { + // make sure that we have the error candidates even if not selected + Set extended = new LinkedHashSet<>(waysToCheck); + for (Way w : waysToCheck) { + // select a node, anyone can be used but a middle node is less likely to have many parent ways + final Node n = w.getNode(w.getNodesCount()/2); + // check the ways which might be in the same position + for (Way other : n.getParentWays()) { + if (other != w && !other.isDeleted() && other.isUsable() + && other.getNodesCount() == w.getNodesCount()) + extended.add(other); + } + } + extended.forEach(this::checkWay); + } + for (Set duplicated : ways.values()) { if (duplicated.size() > 1) { TestError testError = TestError.builder(this, Severity.ERROR, DUPLICATE_WAY) @@ -165,6 +183,8 @@ public void endTest() { ways = null; waysNoTags = null; knownHashCodes = null; + waysToCheck = null; + super.endTest(); } /** @@ -181,6 +201,13 @@ public void removeUninterestingKeys(Map wkeys) { public void visit(Way w) { if (!w.isUsable()) return; + if (partialSelection) + waysToCheck.add(w); + else + checkWay(w); + } + + private void checkWay(Way w) { List wLat = getOrderedNodes(w); // If this way has not direction-dependant keys, make sure the list is ordered the same for all ways (fix #8015) if (!w.hasDirectionKeys()) { diff --git a/src/org/openstreetmap/josm/data/validation/tests/Highways.java b/src/org/openstreetmap/josm/data/validation/tests/Highways.java index a772c10c2dd..bc769a86ae1 100644 --- a/src/org/openstreetmap/josm/data/validation/tests/Highways.java +++ b/src/org/openstreetmap/josm/data/validation/tests/Highways.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.stream.Collectors; @@ -38,6 +39,7 @@ public class Highways extends Test { protected static final int SOURCE_MAXSPEED_CONTEXT_MISMATCH_VS_MAXSPEED = 2705; protected static final int SOURCE_MAXSPEED_CONTEXT_MISMATCH_VS_HIGHWAY = 2706; protected static final int SOURCE_WRONG_LINK = 2707; + protected static final int DIFFERENT_LAYERS = 2708; protected static final String SOURCE_MAXSPEED = "source:maxspeed"; @@ -90,6 +92,9 @@ public void visit(Node n) { // as maxspeed is not set on highways here but on signs, speed cameras, etc. testSourceMaxspeed(n, false); } + if (n.isReferredByWays(2)) { + testDifferentLayers(n); + } } } @@ -298,4 +303,65 @@ private void testSourceMaxspeed(OsmPrimitive p, boolean testContextHighway) { } } } + + /** + * See #9304: Find Highways connected to bridges or tunnels at the wrong place. + * @param connection the connection node of two or more different ways + */ + private void testDifferentLayers(Node connection) { + if (connection.hasTag("highway", "elevator")) + return; + List ways = connection.getParentWays(); + ways.removeIf(w -> !w.hasTag("highway") || w.hasTag("highway", "steps") || isSpecialArea(w)); + if (ways.size() < 2 || ways.stream().noneMatch(w -> w.hasKey("layer"))) + return; + // check if connection has ways with different layers + Map> layerCount = new HashMap<>(); + for (Way w : ways) { + String layer = w.get("layer"); + if (layer == null) + layer = "0"; + layerCount.computeIfAbsent(layer, k-> new ArrayList<>()).add(w); + } + if (layerCount.size() == 1) + return; // all on the same layer + + for (Entry> entry : layerCount.entrySet()) { + if ("0".equals(entry.getKey())) + continue; + if (checkLayer(connection, entry.getValue())) { + errors.add(TestError.builder(this, Severity.WARNING, DIFFERENT_LAYERS) + .message(tr("Node connects highways on different layers")) + .primitives(connection) + .build()); + return; + } + } + } + + /** + * Check if way is an area on a layer above or below 0. + * @param w the way + * @return true if way is an area on a layer above or below 0 + */ + private static boolean isSpecialArea(Way w) { + return w.hasAreaTags() && OsmUtils.getLayer(w) != null; + } + + /** + * Check if there are at least two neighbouring nodes on the given ways. + * If so, the connection node can be considered to be at a specific layer, else it marks the end of such a layer + * @param connection the connection node + * @param ways the ways with the same layer attribute connected to that node + * @return true if the node can be considered to be at a specific layer + */ + private static boolean checkLayer(Node connection, List ways) { + int count = 0; + for (Way w : ways) { + if (!w.isFirstLastNode(connection)) + return true; // connection node has two neighbouring nodes + count++; + } + return count > 1; + } } diff --git a/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerRule.java b/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerRule.java index 94bc186ad78..060dc15808a 100644 --- a/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerRule.java +++ b/src/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerRule.java @@ -33,6 +33,7 @@ import org.openstreetmap.josm.gui.mappaint.Keyword; import org.openstreetmap.josm.gui.mappaint.MultiCascade; import org.openstreetmap.josm.gui.mappaint.mapcss.Condition; +import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.ClassCondition; import org.openstreetmap.josm.gui.mappaint.mapcss.Expression; import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction; import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSRule; @@ -377,6 +378,20 @@ List getErrorsForPrimitive(OsmPrimitive p, Selector matchingSelector, res.add(errorBuilder.primitives(p, (OsmPrimitive) c).build()); } } + } else if (env.parent != null) { + boolean imcompletePrimitives = false; + if (matchingSelector instanceof Selector.ChildOrParentSelector) { + Selector right = ((Selector.ChildOrParentSelector) matchingSelector).right; + if (right.getConditions().stream().anyMatch(ClassCondition.class::isInstance)) { + // see #23397 + // TODO: find a way to collect all and only those parent objects which triggered this error + imcompletePrimitives = true; + } + } + if (imcompletePrimitives) + res.add(errorBuilder.primitives(p).highlight(p).imcompletePrimitives().build()); + else + res.add(errorBuilder.primitives(p, (OsmPrimitive) env.parent).highlight(p).build()); } else { res.add(errorBuilder.primitives(p).build()); } diff --git a/src/org/openstreetmap/josm/data/validation/tests/MultipolygonTest.java b/src/org/openstreetmap/josm/data/validation/tests/MultipolygonTest.java index 99bd1a93dbe..16d5ea99286 100644 --- a/src/org/openstreetmap/josm/data/validation/tests/MultipolygonTest.java +++ b/src/org/openstreetmap/josm/data/validation/tests/MultipolygonTest.java @@ -46,6 +46,9 @@ */ public class MultipolygonTest extends Test { + private static final String OUTER = "outer"; + private static final String INNER = "inner"; + /** Non-Way in multipolygon */ public static final int WRONG_MEMBER_TYPE = 1601; /** No useful role for multipolygon member */ @@ -444,9 +447,18 @@ private void checkOrSetRoles(Relation r, List allPolygons, Map { + // outer first + int d = Integer.compare(r1.level % 2, r2.level % 2); + if (d != 0) + return d; + // ring with more members first + return Integer.compare(r2.outerWay.getWayIds().size(), r1.outerWay.getWayIds().size()); + }); List modMembers = new ArrayList<>(); for (PolygonLevel pol : list) { - final String calculatedRole = (pol.level % 2 == 0) ? "outer" : "inner"; + final String calculatedRole = (pol.level % 2 == 0) ? OUTER : INNER; for (long wayId : pol.outerWay.getWayIds()) { RelationMember member = wayMap.get(wayId); modMembers.add(new RelationMember(calculatedRole, member.getMember())); @@ -456,7 +468,7 @@ private void checkOrSetRoles(Relation r, List allPolygons, Map allPolygons, Map> findIntersectingWays(Relation r, List outerWays = r.getMembers().stream() - .filter(m -> m.getRole().isEmpty() || "outer".equals(m.getRole())) + .filter(m -> m.getRole().isEmpty() || OUTER.equals(m.getRole())) .map(RelationMember::getMember) .collect(Collectors.toSet()); for (int loop = 0; loop < 2; loop++) { @@ -643,9 +655,9 @@ private boolean checkMembersAndRoles(Relation r, List tmpErrors) { boolean hasUnexpectedWayRole = false; for (RelationMember rm : r.getMembers()) { if (rm.isWay()) { - if (rm.hasRole() && !rm.hasRole("inner", "outer")) + if (rm.hasRole() && !rm.hasRole(INNER, OUTER)) hasUnexpectedWayRole = true; - if (!rm.hasRole("inner", "outer") || !rm.hasRole()) { + if (!rm.hasRole(INNER, OUTER) || !rm.hasRole()) { tmpErrors.add(TestError.builder(this, Severity.ERROR, WRONG_MEMBER_ROLE) .message(tr("Role for multipolygon way member should be inner or outer")) .primitives(Arrays.asList(r, rm.getMember())) @@ -905,7 +917,7 @@ public Relation makeFromWays(Collection ways) { } createdRelation = null; // makes sure that repeatCheck is only set once } while (repeatCheck); - errors.removeIf(e->e.getSeverity() == Severity.OTHER); + errors.removeIf(e -> e.getSeverity() == Severity.OTHER); return r; } diff --git a/src/org/openstreetmap/josm/data/validation/tests/OverlappingWays.java b/src/org/openstreetmap/josm/data/validation/tests/OverlappingWays.java index ee9544f76a9..3f1ee414ffa 100644 --- a/src/org/openstreetmap/josm/data/validation/tests/OverlappingWays.java +++ b/src/org/openstreetmap/josm/data/validation/tests/OverlappingWays.java @@ -20,6 +20,7 @@ import java.util.function.Predicate; import java.util.stream.Collectors; +import org.openstreetmap.josm.data.osm.IWaySegment; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.OsmUtils; @@ -121,7 +122,7 @@ private void analyseOverlaps(Set duplicated, Map, Set currentWays = duplicated.stream().map(ws -> ws.getWay()).collect(Collectors.toList()); + List currentWays = duplicated.stream().map(IWaySegment::getWay).collect(Collectors.toList()); Collection highlight; if ((highlight = seenWays.get(currentWays)) != null) { /* this combination of ways was seen before, just add highlighted segment */ diff --git a/src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java b/src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java index ca4044d5b2f..0c1a60de398 100644 --- a/src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java +++ b/src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java @@ -108,13 +108,13 @@ public static synchronized void initializePresets() { return; } for (TaggingPreset p : TaggingPresets.getTaggingPresets()) { - if (p.data.stream().anyMatch(i -> i instanceof Roles)) { + if (p.data.stream().anyMatch(Roles.class::isInstance)) { relationpresets.add(p); } } } - private static class RoleInfo { + private static final class RoleInfo { private int total; } diff --git a/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java b/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java index 1839a40fa51..c9ab9ecc42d 100644 --- a/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java +++ b/src/org/openstreetmap/josm/data/validation/tests/TagChecker.java @@ -24,6 +24,7 @@ import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.OptionalInt; import java.util.Set; import java.util.regex.Pattern; @@ -37,8 +38,10 @@ import org.openstreetmap.josm.command.ChangePropertyKeyCommand; import org.openstreetmap.josm.command.Command; import org.openstreetmap.josm.command.SequenceCommand; +import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.AbstractPrimitive; import org.openstreetmap.josm.data.osm.DataSet; +import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.OsmUtils; import org.openstreetmap.josm.data.osm.Relation; @@ -62,13 +65,19 @@ import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; import org.openstreetmap.josm.gui.tagging.presets.items.Check; import org.openstreetmap.josm.gui.tagging.presets.items.CheckGroup; +import org.openstreetmap.josm.gui.tagging.presets.items.ComboMultiSelect; +import org.openstreetmap.josm.gui.tagging.presets.items.Key; import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem; +import org.openstreetmap.josm.gui.tagging.presets.items.PresetListEntry; +import org.openstreetmap.josm.gui.tagging.presets.items.RegionSpecific; import org.openstreetmap.josm.gui.widgets.EditableList; import org.openstreetmap.josm.io.CachedFile; import org.openstreetmap.josm.spi.preferences.Config; import org.openstreetmap.josm.tools.GBC; +import org.openstreetmap.josm.tools.JosmRuntimeException; import org.openstreetmap.josm.tools.Logging; import org.openstreetmap.josm.tools.MultiMap; +import org.openstreetmap.josm.tools.Territories; import org.openstreetmap.josm.tools.Utils; /** @@ -131,6 +140,7 @@ public class TagChecker extends TagTest implements TaggingPresetListener { * The preference key to check presets */ public static final String PREF_CHECK_PRESETS_TYPES = PREFIX + ".checkPresetsTypes"; + public static final String PREF_CHECK_REGIONS = PREFIX + ".checkPresetsRegions"; /** * The preference key for source files @@ -159,6 +169,7 @@ public class TagChecker extends TagTest implements TaggingPresetListener { * The preference key to search for presets - used before upload */ public static final String PREF_CHECK_PRESETS_TYPES_BEFORE_UPLOAD = PREF_CHECK_PRESETS_TYPES + BEFORE_UPLOAD; + public static final String PREF_CHECK_REGIONS_BEFORE_UPLOAD = PREF_CHECK_REGIONS + BEFORE_UPLOAD; /** * The preference key for the list of tag keys that are allowed to be the same on a multipolygon and an outer way @@ -175,18 +186,21 @@ public class TagChecker extends TagTest implements TaggingPresetListener { protected boolean checkComplex; protected boolean checkFixmes; protected boolean checkPresetsTypes; + protected boolean checkRegions; protected JCheckBox prefCheckKeys; protected JCheckBox prefCheckValues; protected JCheckBox prefCheckComplex; protected JCheckBox prefCheckFixmes; protected JCheckBox prefCheckPresetsTypes; + protected JCheckBox prefCheckRegions; protected JCheckBox prefCheckKeysBeforeUpload; protected JCheckBox prefCheckValuesBeforeUpload; protected JCheckBox prefCheckComplexBeforeUpload; protected JCheckBox prefCheckFixmesBeforeUpload; protected JCheckBox prefCheckPresetsTypesBeforeUpload; + protected JCheckBox prefCheckRegionsBeforeUpload; // CHECKSTYLE.OFF: SingleSpaceSeparator protected static final int EMPTY_VALUES = 1200; @@ -210,6 +224,7 @@ public class TagChecker extends TagTest implements TaggingPresetListener { protected static final int MULTIPOLYGON_INCOMPLETE = 1219; protected static final int MULTIPOLYGON_MAYBE_NO_AREA = 1220; protected static final int MULTIPOLYGON_SAME_TAG_ON_OUTER = 1221; + protected static final int INVALID_REGION = 1222; // CHECKSTYLE.ON: SingleSpaceSeparator protected EditableList sourcesList; @@ -559,7 +574,7 @@ private static Set getPresetValues(String key) { * @param key key * @return {@code true} if the given key is in internal presets * @since 9023 - * @deprecated Use {@link TaggingPresets#isKeyInPresets(String)} instead + * @deprecated since 18281 -- use {@link TaggingPresets#isKeyInPresets(String)} instead */ @Deprecated public static boolean isKeyInPresets(String key) { @@ -652,44 +667,194 @@ public void check(OsmPrimitive p) { } if (p instanceof Relation && p.hasTag("type", "multipolygon")) { - checkMultipolygonTags(p); + checkMultipolygonTags(p); } - if (checkPresetsTypes) { - TagMap tags = p.getKeys(); - TaggingPresetType presetType = TaggingPresetType.forPrimitive(p); - EnumSet presetTypes = EnumSet.of(presetType); - - Collection matchingPresets = presetIndex.entrySet().stream() + final Collection matchingPresets; + TagMap tags; + if (checkPresetsTypes || checkRegions) { + tags = p.getKeys(); + matchingPresets = presetIndex.entrySet().stream() .filter(e -> TaggingPresetItem.matches(e.getValue(), tags)) .map(Entry::getKey) .collect(Collectors.toCollection(LinkedHashSet::new)); - Collection matchingPresetsOK = matchingPresets.stream().filter( - tp -> tp.typeMatches(presetTypes)).collect(Collectors.toList()); - Collection matchingPresetsKO = matchingPresets.stream().filter( - tp -> !tp.typeMatches(presetTypes)).collect(Collectors.toList()); - - for (TaggingPreset tp : matchingPresetsKO) { - // Potential error, unless matching tags are all known by a supported preset - Map matchingTags = tp.data.stream() + } else { + matchingPresets = null; + tags = null; + } + + if (checkPresetsTypes) { + checkPresetsTypes(p, matchingPresets, tags); + } + + if (checkRegions) { + checkRegions(p, matchingPresets); + } + } + + /** + * Check that the primitive matches the preset types for the preset + * @param p The primitive to check + * @param matchingPresets The presets to go through + * @param tags Tags from the primitive to check + */ + private void checkPresetsTypes(OsmPrimitive p, Collection matchingPresets, Map tags) { + TaggingPresetType presetType = TaggingPresetType.forPrimitive(p); + EnumSet presetTypes = EnumSet.of(presetType); + + Collection matchingPresetsOK = matchingPresets.stream().filter( + tp -> tp.typeMatches(presetTypes)).collect(Collectors.toList()); + Collection matchingPresetsKO = matchingPresets.stream().filter( + tp -> !tp.typeMatches(presetTypes)).collect(Collectors.toList()); + + for (TaggingPreset tp : matchingPresetsKO) { + // Potential error, unless matching tags are all known by a supported preset + Map matchingTags = tp.data.stream() .filter(i -> Boolean.TRUE.equals(i.matches(tags))) .filter(i -> i instanceof KeyedItem).map(i -> ((KeyedItem) i).key) .collect(Collectors.toMap(k -> k, tags::get)); - if (matchingPresetsOK.stream().noneMatch( - tp2 -> matchingTags.entrySet().stream().allMatch( - e -> tp2.data.stream().anyMatch( - i -> i instanceof KeyedItem && ((KeyedItem) i).key.equals(e.getKey()))))) { - errors.add(TestError.builder(this, Severity.OTHER, INVALID_PRESETS_TYPE) - .message(tr("Object type not in preset"), - marktr("Object type {0} is not supported by tagging preset: {1}"), - tr(presetType.getName()), tp.getLocaleName()) + if (matchingPresetsOK.stream().noneMatch( + tp2 -> matchingTags.entrySet().stream().allMatch( + e -> tp2.data.stream().anyMatch( + i -> i instanceof KeyedItem && ((KeyedItem) i).key.equals(e.getKey()))))) { + errors.add(TestError.builder(this, Severity.OTHER, INVALID_PRESETS_TYPE) + .message(tr("Object type not in preset"), + marktr("Object type {0} is not supported by tagging preset: {1}"), + tr(presetType.getName()), tp.getLocaleName()) + .primitives(p) + .build()); + } + } + } + + /** + * Check that the preset is valid for the region the primitive is in + * @param p The primitive to check + * @param matchingPresets The presets to check against + */ + private void checkRegions(OsmPrimitive p, Collection matchingPresets) { + LatLon center; + if (p instanceof Node) { + center = ((Node) p).getCoor(); + } else { + center = p.getBBox().getCenter(); + } + for (TaggingPreset preset : matchingPresets) { + if (preset.regions() != null) { + boolean isInRegion = false; //true if the object is in an applicable region + for (String region : preset.regions()) { + if (Territories.isIso3166Code(region, center)) { //check if center of the object is in a region + isInRegion = true; + } + } + if (isInRegion == preset.exclude_regions()) { + errors.add(TestError.builder(this, Severity.WARNING, INVALID_REGION) + .message(tr("Preset is invalid in this region"), + marktr("Preset {0} should not be applied in this region"), + preset.getLocaleName()) .primitives(p) .build()); } } + // Check the tags + tagCheck(preset, p, center, preset.data); } } + /** + * Perform the checks against a given preset value + * @param preset The originating preset (used for error creation) + * @param p The originating primitive (used for error creation) + * @param center The center of the primitive or other location of the primitive to check + * @param tagInformation The sub items for the preset + */ + private void tagCheck(TaggingPreset preset, OsmPrimitive p, LatLon center, List tagInformation) { + for (TaggingPresetItem item : tagInformation) { + if (item instanceof CheckGroup) { + tagCheckReal(preset, p, center, ((CheckGroup) item).checks); + } else if (item instanceof ComboMultiSelect) { + tagCheckReal(preset, p, center, ((ComboMultiSelect) item).presetListEntries()); + } + if (item instanceof RegionSpecific && ((RegionSpecific) item).regions() != null) { + tagCheckReal(preset, p, center, (RegionSpecific) item); + } + } + } + + /** + * Perform the checks against a given preset value + * @param preset The originating preset (used for error creation) + * @param p The originating primitive (used for error creation) + * @param center The center of the primitive or other location of the primitive to check + * @param data The data for the region specific information + */ + private void tagCheckReal(TaggingPreset preset, OsmPrimitive p, LatLon center, List data) { + for (RegionSpecific regionSpecific : data) { + if (regionSpecific.regions() != null) { + tagCheckReal(preset, p, center, regionSpecific); + } + } + } + + /** + * Perform the checks against a given preset value + * @param preset The originating preset (used for error creation) + * @param p The originating primitive (used for error creation) + * @param center The center of the primitive or other location of the primitive to check + * @param data The data for the region specific information + */ + private void tagCheckReal(TaggingPreset preset, OsmPrimitive p, LatLon center, RegionSpecific data) { + // First, check if we aren't in the region for the tag + if (latLonInRegions(center, data.regions()) == data.exclude_regions()) { + final String key; + final String value; + if (data instanceof PresetListEntry) { + key = ((PresetListEntry) data).cms.key; + value = ((PresetListEntry) data).value; + } else if (data instanceof KeyedItem) { + key = ((KeyedItem) data).key; + if (data instanceof Key) { + value = ((Key) data).value; + } else { + value = null; + } + } else { + throw new JosmRuntimeException("Unknown implementor for RegionSpecific"); + } + if (p.hasTag(key) && (value == null || value.equals(p.get(key)))) { + final TestError.Builder builder = TestError.builder(this, Severity.WARNING, INVALID_REGION) + .primitives(p); + if (value == null) { + builder.message(tr("Key from a preset is invalid in this region"), + marktr("Preset {0} should not have the key {1}"), + preset.getLocaleName(), key); + } else { + builder.message(tr("Value from a preset is invalid in this region"), + marktr("Preset {0} should not have the tag {1}={2}"), + preset.getLocaleName(), key, value); + } + errors.add(builder.build()); + } + } + } + + /** + * Check if the specified latlon is inside any of the specified regions + * @param latLon The {@link LatLon} to check + * @param regions The regions to see if the {@link LatLon} is in + * @return {@code true} if the coordinate is inside any of the regions + */ + private static boolean latLonInRegions(LatLon latLon, Collection regions) { + if (regions != null) { + for (String region : regions) { + if (Territories.isIso3166Code(region, latLon)) { + return true; + } + } + } + return false; + } + private static final Collection NO_AREA_KEYS = Arrays.asList("name", "area", "ref", "access", "operator"); private void checkMultipolygonTags(OsmPrimitive p) { @@ -915,7 +1080,7 @@ private void spellCheckKey(MultiMap withErrors, OsmPrimiti fixedKey = prettifiedKey; } - if (!Utils.isEmpty(fixedKey) && !fixedKey.equals(key)) { + if (!Utils.isEmpty(fixedKey) && !Objects.equals(fixedKey, key)) { final String proposedKey = fixedKey; // misspelled preset key final TestError.Builder error = TestError.builder(this, Severity.WARNING, MISSPELLED_KEY) @@ -1075,6 +1240,7 @@ private static String harmonizeValue(String value) { @Override public void startTest(ProgressMonitor monitor) { super.startTest(monitor); + super.setShowElements(true); includeOtherSeverity = includeOtherSeverityChecks(); checkKeys = Config.getPref().getBoolean(PREF_CHECK_KEYS, true); if (isBeforeUpload) { @@ -1100,6 +1266,11 @@ public void startTest(ProgressMonitor monitor) { if (isBeforeUpload) { checkPresetsTypes = checkPresetsTypes && Config.getPref().getBoolean(PREF_CHECK_PRESETS_TYPES_BEFORE_UPLOAD, true); } + + checkRegions = Config.getPref().getBoolean(PREF_CHECK_REGIONS, true); + if (isBeforeUpload) { + checkRegions = checkRegions && Config.getPref().getBoolean(PREF_CHECK_REGIONS_BEFORE_UPLOAD, true); + } deprecatedChecker = OsmValidator.getTest(MapCSSTagChecker.class); ignoreForOuterMPSameTagCheck.addAll(Config.getPref().getList(PREF_KEYS_IGNORE_OUTER_MP_SAME_TAG, Collections.emptyList())); } @@ -1112,7 +1283,7 @@ public void endTest() { @Override public void visit(Collection selection) { - if (checkKeys || checkValues || checkComplex || checkFixmes || checkPresetsTypes) { + if (checkKeys || checkValues || checkComplex || checkFixmes || checkPresetsTypes || checkRegions) { super.visit(selection); } } @@ -1177,6 +1348,14 @@ public void addGui(JPanel testPanel) { prefCheckPresetsTypesBeforeUpload = new JCheckBox(); prefCheckPresetsTypesBeforeUpload.setSelected(Config.getPref().getBoolean(PREF_CHECK_PRESETS_TYPES_BEFORE_UPLOAD, true)); testPanel.add(prefCheckPresetsTypesBeforeUpload, a); + + prefCheckRegions = new JCheckBox(tr("Check for regions."), Config.getPref().getBoolean(PREF_CHECK_REGIONS, true)); + prefCheckRegions.setToolTipText(tr("Validate that objects are in the correct region.")); + testPanel.add(prefCheckRegions, GBC.std().insets(20, 0, 0, 0)); + + prefCheckRegionsBeforeUpload = new JCheckBox(); + prefCheckRegionsBeforeUpload.setSelected(Config.getPref().getBoolean(PREF_CHECK_REGIONS_BEFORE_UPLOAD, true)); + testPanel.add(prefCheckRegionsBeforeUpload, a); } /** @@ -1199,11 +1378,13 @@ public boolean ok() { Config.getPref().putBoolean(PREF_CHECK_KEYS, prefCheckKeys.isSelected()); Config.getPref().putBoolean(PREF_CHECK_FIXMES, prefCheckFixmes.isSelected()); Config.getPref().putBoolean(PREF_CHECK_PRESETS_TYPES, prefCheckPresetsTypes.isSelected()); + Config.getPref().putBoolean(PREF_CHECK_REGIONS, prefCheckRegions.isSelected()); Config.getPref().putBoolean(PREF_CHECK_VALUES_BEFORE_UPLOAD, prefCheckValuesBeforeUpload.isSelected()); Config.getPref().putBoolean(PREF_CHECK_COMPLEX_BEFORE_UPLOAD, prefCheckComplexBeforeUpload.isSelected()); Config.getPref().putBoolean(PREF_CHECK_KEYS_BEFORE_UPLOAD, prefCheckKeysBeforeUpload.isSelected()); Config.getPref().putBoolean(PREF_CHECK_FIXMES_BEFORE_UPLOAD, prefCheckFixmesBeforeUpload.isSelected()); Config.getPref().putBoolean(PREF_CHECK_PRESETS_TYPES_BEFORE_UPLOAD, prefCheckPresetsTypesBeforeUpload.isSelected()); + Config.getPref().putBoolean(PREF_CHECK_REGIONS_BEFORE_UPLOAD, prefCheckRegionsBeforeUpload.isSelected()); return Config.getPref().putList(PREF_SOURCES, sourcesList.getItems()); } @@ -1221,7 +1402,7 @@ public Command fixError(TestError testError) { for (Entry prop: tags.entrySet()) { String key = prop.getKey(); String value = prop.getValue(); - if (Utils.isBlank(value)) { + if (Utils.isStripEmpty(value)) { commands.add(new ChangePropertyCommand(p, key, null)); } else if (value.startsWith(" ") || value.endsWith(" ") || value.contains(" ")) { commands.add(new ChangePropertyCommand(p, key, Utils.removeWhiteSpaces(value))); diff --git a/src/org/openstreetmap/josm/data/validation/tests/WayConnectedToArea.java b/src/org/openstreetmap/josm/data/validation/tests/WayConnectedToArea.java index 668d98385dd..fe9ce3a5f46 100644 --- a/src/org/openstreetmap/josm/data/validation/tests/WayConnectedToArea.java +++ b/src/org/openstreetmap/josm/data/validation/tests/WayConnectedToArea.java @@ -51,16 +51,16 @@ public void visit(Way w) { } private void testForError(Way w, Node wayNode, OsmPrimitive p) { - if (wayNode.isOutsideDownloadArea() - || wayNode.getReferrers().stream().anyMatch(p1 -> p1.hasTag("route", "ferry"))) { - return; - } else if (isArea(p)) { - addPossibleError(w, wayNode, p, p); - } else { - p.referrers(Relation.class) - .filter(r -> r.isMultipolygon() && isArea(r)) - .findFirst() - .ifPresent(r -> addPossibleError(w, wayNode, p, r)); + if (!wayNode.isOutsideDownloadArea() + && wayNode.getReferrers().stream().noneMatch(p1 -> p1.hasTag("route", "ferry"))) { + if (isArea(p)) { + addPossibleError(w, wayNode, p, p); + } else { + p.referrers(Relation.class) + .filter(r -> r.isMultipolygon() && isArea(r)) + .findFirst() + .ifPresent(r -> addPossibleError(w, wayNode, p, r)); + } } } diff --git a/src/org/openstreetmap/josm/data/validation/tests/WronglyOrderedWays.java b/src/org/openstreetmap/josm/data/validation/tests/WronglyOrderedWays.java index 58d522453f0..69d0f892069 100644 --- a/src/org/openstreetmap/josm/data/validation/tests/WronglyOrderedWays.java +++ b/src/org/openstreetmap/josm/data/validation/tests/WronglyOrderedWays.java @@ -36,12 +36,12 @@ public void visit(Way w) { return; String natural = w.get("natural"); - if (natural == null) { - return; - } else if ("coastline".equals(natural) && Geometry.isClockwise(w)) { - reportError(w, tr("Reversed coastline: land not on left side"), WRONGLY_ORDERED_COAST); - } else if ("land".equals(natural) && Geometry.isClockwise(w)) { - reportError(w, tr("Reversed land: land not on left side"), WRONGLY_ORDERED_LAND); + if (natural != null) { + if ("coastline".equals(natural) && Geometry.isClockwise(w)) { + reportError(w, tr("Reversed coastline: land not on left side"), WRONGLY_ORDERED_COAST); + } else if ("land".equals(natural) && Geometry.isClockwise(w)) { + reportError(w, tr("Reversed land: land not on left side"), WRONGLY_ORDERED_LAND); + } } } diff --git a/src/org/openstreetmap/josm/data/validation/util/AggregatePrimitivesVisitor.java b/src/org/openstreetmap/josm/data/validation/util/AggregatePrimitivesVisitor.java index 34a35947a7f..862ca1d5666 100644 --- a/src/org/openstreetmap/josm/data/validation/util/AggregatePrimitivesVisitor.java +++ b/src/org/openstreetmap/josm/data/validation/util/AggregatePrimitivesVisitor.java @@ -14,7 +14,6 @@ /** * A visitor that aggregates all primitives it visits. *

- * The primitives are sorted according to their type: first nodes, then ways. * * @author frsantos */ @@ -24,7 +23,7 @@ public class AggregatePrimitivesVisitor implements OsmPrimitiveVisitor { /** * Visits a collection of primitives - * @param data The collection of primitives + * @param data The collection of primitives in no specific order. * @return The aggregated primitives */ public Collection visit(Collection data) { diff --git a/src/org/openstreetmap/josm/data/vector/VectorPrimitive.java b/src/org/openstreetmap/josm/data/vector/VectorPrimitive.java index 4fe1eb9ff21..86af9156b53 100644 --- a/src/org/openstreetmap/josm/data/vector/VectorPrimitive.java +++ b/src/org/openstreetmap/josm/data/vector/VectorPrimitive.java @@ -58,11 +58,6 @@ public void setHighlighted(boolean highlighted) { this.highlighted = highlighted; } - @Override - public boolean isTagged() { - return (flags & FLAG_TAGGED) != 0; - } - @Override public boolean isAnnotated() { return this.getInterestingTags().size() - this.getKeys().size() > 0; diff --git a/src/org/openstreetmap/josm/data/vector/VectorWay.java b/src/org/openstreetmap/josm/data/vector/VectorWay.java index ada7bbc0cd5..e4290566ad8 100644 --- a/src/org/openstreetmap/josm/data/vector/VectorWay.java +++ b/src/org/openstreetmap/josm/data/vector/VectorWay.java @@ -56,12 +56,12 @@ public BBox getBBox() { @Override public int getNodesCount() { - return this.getNodes().size(); + return this.nodes.size(); } @Override public VectorNode getNode(int index) { - return this.getNodes().get(index); + return this.nodes.get(index); } @Override @@ -82,12 +82,12 @@ public void setNodes(List nodes) { @Override public List getNodeIds() { - return this.getNodes().stream().map(VectorNode::getId).collect(Collectors.toList()); + return this.nodes.stream().map(VectorNode::getId).collect(Collectors.toList()); } @Override public long getNodeId(int idx) { - return this.getNodes().get(idx).getId(); + return this.nodes.get(idx).getId(); } @Override diff --git a/src/org/openstreetmap/josm/gui/ConditionalOptionPaneUtil.java b/src/org/openstreetmap/josm/gui/ConditionalOptionPaneUtil.java index e481c35060e..c3e66dd9c3b 100644 --- a/src/org/openstreetmap/josm/gui/ConditionalOptionPaneUtil.java +++ b/src/org/openstreetmap/josm/gui/ConditionalOptionPaneUtil.java @@ -4,7 +4,6 @@ import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.Component; -import java.awt.Dialog; import java.awt.GridBagLayout; import java.util.HashMap; import java.util.HashSet; @@ -15,9 +14,8 @@ import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JRadioButton; -import javax.swing.event.AncestorEvent; -import javax.swing.event.AncestorListener; +import org.openstreetmap.josm.gui.util.WindowOnTopListener; import org.openstreetmap.josm.gui.widgets.JMultilineLabel; import org.openstreetmap.josm.spi.preferences.Config; import org.openstreetmap.josm.tools.GBC; @@ -42,6 +40,8 @@ public final class ConditionalOptionPaneUtil { private static final Map immediateChoices = new HashMap<>(); /** a set indication that (preference key) is or may be stored for the currently active bulk operation */ private static final Set immediateActive = new HashSet<>(); + /** The prefix for config options */ + private static final String CONFIG_PREFIX = "message."; /** * this is a static utility class only @@ -60,7 +60,9 @@ private ConditionalOptionPaneUtil() { public static int getDialogReturnValue(String prefKey) { return Utils.firstNonNull(immediateChoices.get(prefKey), sessionChoices.get(prefKey), - !Config.getPref().getBoolean("message." + prefKey, true) ? Config.getPref().getInt("message." + prefKey + ".value", -1) : -1 + !Config.getPref().getBoolean(CONFIG_PREFIX + prefKey, true) + ? Config.getPref().getInt(CONFIG_PREFIX + prefKey + ".value", -1) + : -1 ); } @@ -227,8 +229,8 @@ void store(String prefKey, Integer value) { sessionChoices.put(prefKey, value); break; case PERMANENT: - Config.getPref().putBoolean("message." + prefKey, false); - Config.getPref().putInt("message." + prefKey + ".value", value); + Config.getPref().putBoolean(CONFIG_PREFIX + prefKey, false); + Config.getPref().putInt(CONFIG_PREFIX + prefKey + ".value", value); break; } } @@ -287,46 +289,23 @@ public static class MessagePanel extends JPanel { } add(cbStandard, GBC.eol()); - this.addAncestorListener(new AncestorListener() { - boolean wasAlwaysOnTop; - @Override - public void ancestorAdded(AncestorEvent event) { - if (event.getAncestor() instanceof Dialog) { - Dialog dialog = (Dialog) event.getAncestor(); - wasAlwaysOnTop = dialog.isAlwaysOnTop(); - if (dialog.isVisible() && dialog.isModal()) { - dialog.setAlwaysOnTop(true); - } - } - } - - @Override - public void ancestorRemoved(AncestorEvent event) { - if (event.getAncestor() instanceof Dialog) { - Dialog dialog = (Dialog) event.getAncestor(); - if (dialog.isVisible() && dialog.isModal()) { - dialog.setAlwaysOnTop(wasAlwaysOnTop); - } - } - } - - @Override - public void ancestorMoved(AncestorEvent event) { - // Do nothing - } - }); + this.addAncestorListener(new WindowOnTopListener()); } NotShowAgain getNotShowAgain() { - return cbStandard.isSelected() - ? NotShowAgain.NO - : cbShowImmediateDialog.isSelected() - ? NotShowAgain.OPERATION - : cbShowSessionDialog.isSelected() - ? NotShowAgain.SESSION - : cbShowPermanentDialog.isSelected() - ? NotShowAgain.PERMANENT - : null; + if (cbStandard.isSelected()) { + return NotShowAgain.NO; + } + if (cbShowImmediateDialog.isSelected()) { + return NotShowAgain.OPERATION; + } + if (cbShowSessionDialog.isSelected()) { + return NotShowAgain.SESSION; + } + if (cbShowPermanentDialog.isSelected()) { + return NotShowAgain.PERMANENT; + } + return null; } } } diff --git a/src/org/openstreetmap/josm/gui/ExtendedDialog.java b/src/org/openstreetmap/josm/gui/ExtendedDialog.java index eb10cdba473..06fbee7992d 100644 --- a/src/org/openstreetmap/josm/gui/ExtendedDialog.java +++ b/src/org/openstreetmap/josm/gui/ExtendedDialog.java @@ -30,11 +30,13 @@ import javax.swing.JScrollPane; import javax.swing.KeyStroke; import javax.swing.UIManager; +import javax.swing.WindowConstants; import org.openstreetmap.josm.gui.help.HelpBrowser; import org.openstreetmap.josm.gui.help.HelpUtil; import org.openstreetmap.josm.gui.util.GuiHelper; import org.openstreetmap.josm.gui.util.WindowGeometry; +import org.openstreetmap.josm.gui.util.WindowOnTopListener; import org.openstreetmap.josm.gui.widgets.JMultilineLabel; import org.openstreetmap.josm.io.NetworkManager; import org.openstreetmap.josm.io.OnlineResource; @@ -47,19 +49,19 @@ /** * General configurable dialog window. - * + *

* If dialog is modal, you can use {@link #getValue()} to retrieve the * button index. Note that the user can close the dialog * by other means. This is usually equivalent to cancel action. - * + *

* For non-modal dialogs, {@link #buttonAction(int, ActionEvent)} can be overridden. - * + *

* There are various options, see below. - * + *

* Note: The button indices are counted from 1 and upwards. * So for {@link #getValue()}, {@link #setDefaultButton(int)} and * {@link #setCancelButton} the first button has index 1. - * + *

* Simple example: *

  *  ExtendedDialog ed = new ExtendedDialog(
@@ -84,7 +86,7 @@ public class ExtendedDialog extends JDialog implements IExtendedDialog {
     private String togglePref = "";
     private int toggleValue = -1;
     private ConditionalOptionPaneUtil.MessagePanel togglePanel;
-    private final Component parent;
+    private final Component parentComponent;
     private Component content;
     private final String[] bTexts;
     private String[] bToolTipTexts;
@@ -93,7 +95,7 @@ public class ExtendedDialog extends JDialog implements IExtendedDialog {
     private int defaultButtonIdx = 1;
     protected JButton defaultButton;
     private transient Icon icon;
-    private final boolean modal;
+    private final boolean isModal;
     private boolean focusOnDefaultButton;
 
     /** true, if the dialog should include a help button */
@@ -157,11 +159,11 @@ public ExtendedDialog(Component parent, String title, String[] buttonTexts, bool
      */
     public ExtendedDialog(Component parent, String title, String[] buttonTexts, boolean modal, boolean disposeOnClose) {
         super(searchRealParent(parent), title, modal ? ModalityType.DOCUMENT_MODAL : ModalityType.MODELESS);
-        this.parent = parent;
-        this.modal = modal;
+        this.parentComponent = parent;
+        this.isModal = modal;
         bTexts = Utils.copyArray(buttonTexts);
         if (disposeOnClose) {
-            setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+            setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
         }
         this.disposeOnClose = disposeOnClose;
     }
@@ -371,7 +373,7 @@ public void setupDialog() {
         }
 
         setSize(d);
-        setLocationRelativeTo(parent);
+        setLocationRelativeTo(parentComponent);
     }
 
     protected Action createButtonAction(final int i) {
@@ -400,8 +402,8 @@ protected void buttonAction(int buttonIndex, ActionEvent evt) {
     protected Dimension findMaxDialogSize() {
         Dimension screenSize = GuiHelper.getScreenSize();
         Dimension x = new Dimension(screenSize.width*2/3, screenSize.height*2/3);
-        if (parent != null && parent.isVisible()) {
-            x = GuiHelper.getFrameForComponent(parent).getSize();
+        if (parentComponent != null && parentComponent.isVisible()) {
+            x = GuiHelper.getFrameForComponent(parentComponent).getSize();
         }
         return x;
     }
@@ -461,6 +463,7 @@ public void setVisible(boolean visible) {
         }
         if (visible && isModal()) {
             this.setAlwaysOnTop(true);
+            this.addWindowFocusListener(new WindowOnTopListener());
         }
         super.setVisible(visible);
 
@@ -478,7 +481,7 @@ public ExtendedDialog setRememberWindowGeometry(String pref, WindowGeometry wg)
 
     @Override
     public ExtendedDialog toggleEnable(String togglePref) {
-        if (!modal) {
+        if (!isModal) {
             throw new IllegalStateException();
         }
         this.toggleable = true;
@@ -494,7 +497,7 @@ public ExtendedDialog setDefaultButton(int defaultButtonIdx) {
 
     @Override
     public ExtendedDialog setCancelButton(Integer... cancelButtonIdx) {
-        this.cancelButtonIdx = new HashSet<>(Arrays.asList(cancelButtonIdx));
+        this.cancelButtonIdx = new HashSet<>(Arrays.asList(cancelButtonIdx));
         return this;
     }
 
diff --git a/src/org/openstreetmap/josm/gui/HelpAwareOptionPane.java b/src/org/openstreetmap/josm/gui/HelpAwareOptionPane.java
index 92ca48c1016..eaad135805f 100644
--- a/src/org/openstreetmap/josm/gui/HelpAwareOptionPane.java
+++ b/src/org/openstreetmap/josm/gui/HelpAwareOptionPane.java
@@ -27,6 +27,7 @@
 import org.openstreetmap.josm.gui.help.HelpUtil;
 import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.gui.util.WindowGeometry;
+import org.openstreetmap.josm.gui.util.WindowOnTopListener;
 import org.openstreetmap.josm.gui.widgets.HtmlPanel;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
@@ -352,6 +353,7 @@ public void actionPerformed(ActionEvent e) {
         }
         if (dialog.isModal()) {
             dialog.setAlwaysOnTop(true);
+            dialog.addWindowFocusListener(new WindowOnTopListener());
         }
         dialog.setVisible(true);
     }
diff --git a/src/org/openstreetmap/josm/gui/MainApplication.java b/src/org/openstreetmap/josm/gui/MainApplication.java
index 0cb04c2a236..bb2e4220508 100644
--- a/src/org/openstreetmap/josm/gui/MainApplication.java
+++ b/src/org/openstreetmap/josm/gui/MainApplication.java
@@ -2,11 +2,11 @@
 package org.openstreetmap.josm.gui;
 
 import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trc;
 import static org.openstreetmap.josm.tools.I18n.trn;
 import static org.openstreetmap.josm.tools.Utils.getSystemProperty;
 
 import java.awt.AWTError;
-import java.awt.Color;
 import java.awt.Container;
 import java.awt.Dimension;
 import java.awt.Font;
@@ -17,7 +17,6 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.lang.reflect.Field;
 import java.net.Authenticator;
 import java.net.Inet6Address;
 import java.net.InetAddress;
@@ -40,7 +39,6 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.ResourceBundle;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.ExecutorService;
@@ -54,9 +52,11 @@
 import javax.swing.Action;
 import javax.swing.InputMap;
 import javax.swing.JComponent;
+import javax.swing.JDialog;
 import javax.swing.JLabel;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
+import javax.swing.JTextPane;
 import javax.swing.KeyStroke;
 import javax.swing.LookAndFeel;
 import javax.swing.RepaintManager;
@@ -129,6 +129,7 @@
 import org.openstreetmap.josm.gui.util.GuiHelper;
 import org.openstreetmap.josm.gui.util.RedirectInputMap;
 import org.openstreetmap.josm.gui.util.WindowGeometry;
+import org.openstreetmap.josm.gui.widgets.TextContextualPopupMenu;
 import org.openstreetmap.josm.gui.widgets.UrlLabel;
 import org.openstreetmap.josm.io.CachedFile;
 import org.openstreetmap.josm.io.CertificateAmendment;
@@ -160,9 +161,7 @@
 import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.OsmUrlToBounds;
 import org.openstreetmap.josm.tools.PlatformHook.NativeOsCallback;
-import org.openstreetmap.josm.tools.PlatformHookWindows;
 import org.openstreetmap.josm.tools.PlatformManager;
-import org.openstreetmap.josm.tools.ReflectionUtils;
 import org.openstreetmap.josm.tools.Shortcut;
 import org.openstreetmap.josm.tools.Utils;
 import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler;
@@ -181,6 +180,10 @@ public class MainApplication {
      * Command-line arguments used to run the application.
      */
     private static volatile List commandLineArgs;
+    /**
+     * The preference key for the startup failure counter
+     */
+    private static final String PREF_STARTUP_FAILURE_COUNTER = "josm.startup.failure.count";
 
     /**
      * The main menu bar at top of screen.
@@ -388,21 +391,41 @@ public static void askUpdateJava(String updVersion, String url, String eolDate,
     }
 
     /**
-     * Asks user to migrate to OpenWebStart
-     * @param url download URL
-     * @since 17679
+     * Tells the user that a sanity check failed
+     * @param title The title of the message to show
+     * @param canContinue {@code true} if the failed sanity check(s) will not instantly kill JOSM when the user edits
+     * @param message The message parts to show the user (as a list)
      */
-    public static void askMigrateWebStart(String url) {
-        // CHECKSTYLE.OFF: LineLength
-        StringBuilder content = new StringBuilder(tr("You are running an Oracle implementation of Java WebStart."))
-                .append("

") - .append(tr("It was for years the recommended way to use JOSM. Oracle removed WebStart from Java 11,
but the open source community reimplemented the Java Web Start technology as a new product: OpenWebStart")) - .append("

") - .append(tr("OpenWebStart is now considered mature enough by JOSM developers to ask everyone to move away from an Oracle implementation,
allowing you to benefit from a recent version of Java, and allowing JOSM developers to move forward by planning the Java {0} migration.", "11")) - .append("

") - .append(tr("Would you like to download OpenWebStart now? (Please do!)")); - askUpdate(tr("Outdated Java WebStart version"), tr("Update to OpenWebStart"), "askUpdateWebStart", /* ICON */"presets/transport/rocket", content, url); - // CHECKSTYLE.ON: LineLength + public static void sanityCheckFailed(String title, boolean canContinue, String... message) { + final ExtendedDialog ed; + if (canContinue) { + ed = new ExtendedDialog(mainFrame, title, trc("dialog", "Stop"), tr("Continue")); + ed.setButtonIcons("cancel", "apply"); + } else { + ed = new ExtendedDialog(mainFrame, title, trc("dialog", "Stop")); + ed.setButtonIcons("cancel"); + } + ed.setDefaultButton(1).setCancelButton(1); + // Check if the dialog has not already been permanently hidden by user + ed.toggleEnable("sanityCheckFailed"); + final String content = Arrays.stream(message).collect(Collectors.joining("
  • ", + "
    • ", "
    ")); + final JTextPane textField = new JTextPane(); + textField.setContentType("text/html"); + textField.setText(content); + TextContextualPopupMenu.enableMenuFor(textField, true); + ed.setMinimumSize(new Dimension(480, 300)); + ed.setIcon(JOptionPane.WARNING_MESSAGE); + ed.setContent(textField); + ed.showDialog(); // This won't show the dialog if the user has previously saved their response + if (!canContinue || ed.getValue() <= 1) { // 0 == cancel (we want to stop) and 1 == stop + // Never store cancel/stop -- this would otherwise lead to the user never seeing the window again, and JOSM just stopping. + if (ConditionalOptionPaneUtil.getDialogReturnValue("sanityCheckFailed") != -1) { + Config.getPref().put("message.sanityCheckFailed", null); + Config.getPref().put("message.sanityCheckFailed.value", null); + } + Lifecycle.exitJosm(true, -1); + } } /** @@ -837,6 +860,24 @@ public PermissionCollection getPermissions(CodeSource codesource) { checkIPv6(); + // After IPv6 check since that may restart JOSM, must be after Preferences.main().init() + final int failures = prefs.getInt(PREF_STARTUP_FAILURE_COUNTER, 0); + // Always increment failures + prefs.putInt(PREF_STARTUP_FAILURE_COUNTER, failures + 1); + if (failures > 3) { + final int selection = JOptionPane.showOptionDialog(new JDialog(), + tr("JOSM has failed to start up {0} times. Reset JOSM?", failures), + tr("Reset JOSM?"), + JOptionPane.YES_NO_OPTION, + JOptionPane.ERROR_MESSAGE, + null, + null, + null); + if (selection == JOptionPane.YES_OPTION) { + Preferences.main().init(true); + } + } + processOffline(args); PlatformManager.getPlatform().afterPrefStartupHook(); @@ -969,6 +1010,7 @@ public void finish(Object status) { splash.dispose(); } mainFrame.setVisible(true); + Config.getPref().put(PREF_STARTUP_FAILURE_COUNTER, null); }); boolean maximized = Config.getPref().getBoolean("gui.maximized", false); @@ -981,11 +1023,11 @@ public void finish(Object status) { SwingUtilities.invokeLater(new GuiFinalizationWorker(args, proxySelector)); - if (RemoteControl.PROP_REMOTECONTROL_ENABLED.get()) { + if (Boolean.TRUE.equals(RemoteControl.PROP_REMOTECONTROL_ENABLED.get())) { RemoteControl.start(); } - if (MessageNotifier.PROP_NOTIFIER_ENABLED.get()) { + if (Boolean.TRUE.equals(MessageNotifier.PROP_NOTIFIER_ENABLED.get())) { MessageNotifier.start(); } @@ -1021,19 +1063,6 @@ private static void updateSystemProperties() { } Utils.updateSystemProperty("http.agent", Version.getInstance().getAgentString()); Utils.updateSystemProperty("user.language", Config.getPref().get("language")); - // Workaround to fix a Java bug. This ugly hack comes from Sun bug database: https://bugs.openjdk.java.net/browse/JDK-6292739 - // Force AWT toolkit to update its internal preferences (fix #6345). - // Does not work anymore with Java 9, to remove with Java 9 migration - if (Utils.getJavaVersion() < 9 && !GraphicsEnvironment.isHeadless()) { - try { - Field field = Toolkit.class.getDeclaredField("resources"); - ReflectionUtils.setObjectsAccessible(field); - field.set(null, ResourceBundle.getBundle("sun.awt.resources.awt")); - } catch (ReflectiveOperationException | RuntimeException e) { // NOPMD - // Catch RuntimeException in order to catch InaccessibleObjectException, new in Java 9 - Logging.log(Logging.LEVEL_WARN, null, e); - } - } // Possibility to disable SNI (not by default) in case of misconfigured https servers // See #9875 + http://stackoverflow.com/a/14884941/2257172 // then https://josm.openstreetmap.de/ticket/12152#comment:5 for details @@ -1068,44 +1097,6 @@ public static void setupNadGridSources() { */ static void applyLaFWorkarounds() { final String laf = UIManager.getLookAndFeel().getID(); - final int javaVersion = Utils.getJavaVersion(); - // Workaround for JDK-8180379: crash on Windows 10 1703 with Windows L&F and java < 8u141 / 9+172 - // To remove during Java 9 migration - if (getSystemProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows 10") && - PlatformManager.getPlatform().getDefaultStyle().equals(LafPreference.LAF.get())) { - try { - String build = PlatformHookWindows.getCurrentBuild(); - if (build != null) { - final int currentBuild = Integer.parseInt(build); - final int javaUpdate = Utils.getJavaUpdate(); - final int javaBuild = Utils.getJavaBuild(); - // See https://technet.microsoft.com/en-us/windows/release-info.aspx - if (currentBuild >= 15_063 && ((javaVersion == 8 && javaUpdate < 141) - || (javaVersion == 9 && javaUpdate == 0 && javaBuild < 173))) { - // Workaround from https://bugs.openjdk.java.net/browse/JDK-8179014 - UIManager.put("FileChooser.useSystemExtensionHiding", Boolean.FALSE); - } - } - } catch (NumberFormatException | ReflectiveOperationException | JosmRuntimeException e) { - Logging.error(e); - } catch (ExceptionInInitializerError e) { - Logging.log(Logging.LEVEL_ERROR, null, e); - } - } else if (PlatformManager.isPlatformOsx() && javaVersion < 17) { - // Workaround for JDK-8251377: JTabPanel active tab is unreadable in Big Sur, see #20075, see #20821 - // os.version will return 10.16, or 11.0 depending on environment variable - // https://twitter.com/BriceDutheil/status/1330926649269956612 - final String macOSVersion = getSystemProperty("os.version"); - if ((laf.contains("Mac") || laf.contains("Aqua")) - && (macOSVersion.startsWith("10.16") || macOSVersion.startsWith("11"))) { - UIManager.put("TabbedPane.foreground", Color.BLACK); - } - } - // Workaround for JDK-8262085 - if ("Metal".equals(laf) && javaVersion >= 11 && javaVersion < 17) { - UIManager.put("ToolTipUI", JosmMetalToolTipUI.class.getCanonicalName()); - } - // See #20850. The upstream bug (JDK-6396936) is unlikely to ever be fixed due to potential compatibility // issues. This affects Windows LaF only (includes Windows Classic, a sub-LaF of Windows LaF). if ("Windows".equals(laf) && "Monospaced".equals(UIManager.getFont("TextArea.font").getFamily())) { @@ -1223,7 +1214,6 @@ private static void setupTextAntiAliasing() { // On Linux and running on Java 9+, enable text anti aliasing // if not yet enabled and if neither running on Gnome or KDE desktop if (PlatformManager.isPlatformUnixoid() - && Utils.getJavaVersion() >= 9 && UIManager.getLookAndFeelDefaults().get(RenderingHints.KEY_TEXT_ANTIALIASING) == null && System.getProperty("awt.useSystemAAFontSettings") == null && Toolkit.getDefaultToolkit().getDesktopProperty("gnome.Xft/Antialias") == null @@ -1412,7 +1402,7 @@ public void run() { } private static void handleAutosave() { - if (AutosaveTask.PROP_AUTOSAVE_ENABLED.get()) { + if (Boolean.TRUE.equals(AutosaveTask.PROP_AUTOSAVE_ENABLED.get())) { AutosaveTask autosaveTask = new AutosaveTask(); List unsavedLayerFiles = autosaveTask.getUnsavedLayersFiles(); if (!unsavedLayerFiles.isEmpty()) { @@ -1493,7 +1483,7 @@ private static boolean handleNetworkErrors() { } } - private static class DefaultNativeOsCallback implements NativeOsCallback { + private static final class DefaultNativeOsCallback implements NativeOsCallback { @Override public void openFiles(List files) { Executors.newSingleThreadExecutor(Utils.newThreadFactory("openFiles-%d", Thread.NORM_PRIORITY)).submit( diff --git a/src/org/openstreetmap/josm/gui/MainFrame.java b/src/org/openstreetmap/josm/gui/MainFrame.java index a062bd60d73..91b3abf1782 100644 --- a/src/org/openstreetmap/josm/gui/MainFrame.java +++ b/src/org/openstreetmap/josm/gui/MainFrame.java @@ -6,6 +6,7 @@ import java.awt.BorderLayout; import java.awt.Component; import java.awt.ComponentOrientation; +import java.awt.Frame; import java.awt.Image; import java.awt.Rectangle; import java.awt.Toolkit; @@ -23,6 +24,7 @@ import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JPanel; +import javax.swing.WindowConstants; import org.openstreetmap.josm.data.UserIdentityManager; import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer; @@ -54,7 +56,7 @@ public class MainFrame extends JFrame { }; protected transient WindowGeometry geometry; - protected int windowState = JFrame.NORMAL; + protected int windowState = Frame.NORMAL; private final MainPanel panel; private MainMenu menu; @@ -110,7 +112,7 @@ public void initialize() { .collect(Collectors.toList()); setIconImages(l); addWindowListener(new ExitWindowAdapter()); - setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); // This listener is never removed, since the main frame exists forever. MainApplication.getLayerManager().addActiveLayerChangeListener(e -> refreshTitle()); @@ -130,7 +132,7 @@ public void storeState() { if (geometry != null) { geometry.remember(WindowGeometry.PREF_KEY_GUI_GEOMETRY); } - Config.getPref().putBoolean("gui.maximized", (windowState & JFrame.MAXIMIZED_BOTH) != 0); + Config.getPref().putBoolean("gui.maximized", (windowState & Frame.MAXIMIZED_BOTH) != 0); } /** @@ -161,8 +163,8 @@ public MainPanel getPanel() { */ public void setMaximized(boolean maximized) { if (maximized) { - if (Toolkit.getDefaultToolkit().isFrameStateSupported(JFrame.MAXIMIZED_BOTH)) { - windowState = JFrame.MAXIMIZED_BOTH; + if (Toolkit.getDefaultToolkit().isFrameStateSupported(Frame.MAXIMIZED_BOTH)) { + windowState = Frame.MAXIMIZED_BOTH; setExtendedState(windowState); } else { Logging.debug("Main window: maximizing not supported"); @@ -228,7 +230,7 @@ public void layerOrderChanged(LayerOrderChangeEvent e) { } } - private class WindowPositionSizeListener extends WindowAdapter implements ComponentListener { + private final class WindowPositionSizeListener extends WindowAdapter implements ComponentListener { @Override public void windowStateChanged(WindowEvent e) { windowState = e.getNewState(); @@ -257,7 +259,7 @@ public void componentShown(ComponentEvent e) { private void handleComponentEvent(ComponentEvent e) { Component c = e.getComponent(); if (c instanceof JFrame && c.isVisible()) { - if (windowState == JFrame.NORMAL) { + if (windowState == Frame.NORMAL) { geometry = new WindowGeometry((JFrame) c); } else { geometry.fixScreen((JFrame) c); diff --git a/src/org/openstreetmap/josm/gui/MainInitialization.java b/src/org/openstreetmap/josm/gui/MainInitialization.java index 023a362aeba..5ad096ea3fd 100644 --- a/src/org/openstreetmap/josm/gui/MainInitialization.java +++ b/src/org/openstreetmap/josm/gui/MainInitialization.java @@ -71,7 +71,7 @@ public List beforeInitializationTasks() { }), new InitializationTask(tr("Starting file watcher"), FileWatcher.getDefaultInstance()::start), new InitializationTask(tr("Executing platform startup hook"), - () -> PlatformManager.getPlatform().startupHook(MainApplication::askUpdateJava, MainApplication::askMigrateWebStart)), + () -> PlatformManager.getPlatform().startupHook(MainApplication::askUpdateJava, MainApplication::sanityCheckFailed)), new InitializationTask(tr("Building main menu"), application::initializeMainWindow), new InitializationTask(tr("Updating user interface"), () -> { UndoRedoHandler.getInstance().addCommandQueueListener(application.redoUndoListener); @@ -156,7 +156,7 @@ public List afterInitializationTasks() { ); } - private static class JosmSettingsAdapter implements SettingsAdapter { + private static final class JosmSettingsAdapter implements SettingsAdapter { @Override public String get(String key, String def) { diff --git a/src/org/openstreetmap/josm/gui/MapMover.java b/src/org/openstreetmap/josm/gui/MapMover.java index ed694864cc1..86cbaa8da5b 100644 --- a/src/org/openstreetmap/josm/gui/MapMover.java +++ b/src/org/openstreetmap/josm/gui/MapMover.java @@ -6,6 +6,7 @@ import java.awt.Cursor; import java.awt.Point; import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; @@ -87,7 +88,7 @@ public void actionPerformed(ActionEvent e) { } else { EastNorth center = nc.getCenter(); EastNorth newcenter = nc.getEastNorth(nc.getWidth()/2+nc.getWidth()/5, nc.getHeight()/2+nc.getHeight()/5); - switch(action) { + switch (action) { case "left": nc.zoomTo(new EastNorth(2*center.east()-newcenter.east(), center.north())); break; @@ -172,11 +173,11 @@ public boolean movementInProgress() { */ @Override public void mouseDragged(MouseEvent e) { - int offMask = MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK; - boolean allowMovement = (e.getModifiersEx() & (MouseEvent.BUTTON3_DOWN_MASK | offMask)) == MouseEvent.BUTTON3_DOWN_MASK; + int offMask = InputEvent.BUTTON1_DOWN_MASK | InputEvent.BUTTON2_DOWN_MASK; + boolean allowMovement = (e.getModifiersEx() & (InputEvent.BUTTON3_DOWN_MASK | offMask)) == InputEvent.BUTTON3_DOWN_MASK; if (PlatformManager.isPlatformOsx()) { MapFrame map = MainApplication.getMap(); - int macMouseMask = MouseEvent.CTRL_DOWN_MASK | MouseEvent.BUTTON1_DOWN_MASK; + int macMouseMask = InputEvent.CTRL_DOWN_MASK | InputEvent.BUTTON1_DOWN_MASK; boolean macMovement = e.getModifiersEx() == macMouseMask; boolean allowedMode = !map.mapModeSelect.equals(map.mapMode) || SelectAction.Mode.SELECT == map.mapModeSelect.getMode(); @@ -203,8 +204,8 @@ private void doMoveForDrag(MouseEvent e) { */ @Override public void mousePressed(MouseEvent e) { - int offMask = MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK; - int macMouseMask = MouseEvent.CTRL_DOWN_MASK | MouseEvent.BUTTON1_DOWN_MASK; + int offMask = InputEvent.BUTTON1_DOWN_MASK | InputEvent.BUTTON2_DOWN_MASK; + int macMouseMask = InputEvent.CTRL_DOWN_MASK | InputEvent.BUTTON1_DOWN_MASK; if ((e.getButton() == MouseEvent.BUTTON3 && (e.getModifiersEx() & offMask) == 0) || (PlatformManager.isPlatformOsx() && e.getModifiersEx() == macMouseMask)) { startMovement(e); @@ -252,7 +253,7 @@ private void endMovement() { */ @Override public void mouseWheelMoved(MouseWheelEvent e) { - int rotation = PROP_ZOOM_REVERSE_WHEEL.get() ? -e.getWheelRotation() : e.getWheelRotation(); + int rotation = Boolean.TRUE.equals(PROP_ZOOM_REVERSE_WHEEL.get()) ? -e.getWheelRotation() : e.getWheelRotation(); nc.zoomManyTimes(e.getX(), e.getY(), rotation); } @@ -267,7 +268,7 @@ public void mouseMoved(MouseEvent e) { // Mac OSX simulates with ctrl + mouse 1 the second mouse button hence no dragging events get fired. // Is only the selected mouse button pressed? if (PlatformManager.isPlatformOsx()) { - if (e.getModifiersEx() == MouseEvent.CTRL_DOWN_MASK) { + if (e.getModifiersEx() == InputEvent.CTRL_DOWN_MASK) { doMoveForDrag(e); } else { endMovement(); diff --git a/src/org/openstreetmap/josm/gui/MapStatus.java b/src/org/openstreetmap/josm/gui/MapStatus.java index 1034e61ff3f..ff088deefe8 100644 --- a/src/org/openstreetmap/josm/gui/MapStatus.java +++ b/src/org/openstreetmap/josm/gui/MapStatus.java @@ -13,6 +13,7 @@ import java.awt.EventQueue; import java.awt.Font; import java.awt.GraphicsEnvironment; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.MouseInfo; import java.awt.Point; @@ -55,6 +56,7 @@ import javax.swing.JSeparator; import javax.swing.Popup; import javax.swing.PopupFactory; +import javax.swing.SwingConstants; import javax.swing.UIManager; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; @@ -111,9 +113,9 @@ * A component that manages some status information display about the map. * It keeps a status line below the map up to date and displays some tooltip * information if the user hold the mouse long enough at some point. - * + *

    * All this is done in background to not disturb other processes. - * + *

    * The background thread does not alter any data of the map (read only thread). * Also it is rather fail safe. In case of some error in the data, it just does * nothing instead of whining and complaining. @@ -315,7 +317,7 @@ private CollectorWorker(MouseState ms) { @Override public void run() { // Freeze display when holding down CTRL - if ((ms.modifiers & MouseEvent.CTRL_DOWN_MASK) != 0) { + if ((ms.modifiers & InputEvent.CTRL_DOWN_MASK) != 0) { // update the information popup's labels though, because the selection might have changed from the outside popupUpdateLabels(); return; @@ -326,7 +328,7 @@ public void run() { // if the middle mouse button has been pressed in the first place boolean mouseNotMoved = oldMousePos != null && oldMousePos.equals(ms.mousePos); boolean isAtOldPosition = mouseNotMoved && popup != null; - boolean middleMouseDown = (ms.modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0; + boolean middleMouseDown = (ms.modifiers & InputEvent.BUTTON2_DOWN_MASK) != 0; DataSet ds = mv.getLayerManager().getActiveDataSet(); if (ds != null) { @@ -360,9 +362,9 @@ public void run() { ""+tr("Middle click again to cycle through.
    "+ "Hold CTRL to select directly from this list with the mouse.


    ")+"", null, - JLabel.HORIZONTAL + SwingConstants.CENTER ); - lbl.setHorizontalAlignment(JLabel.LEADING); + lbl.setHorizontalAlignment(SwingConstants.LEADING); c.add(lbl, GBC.eol().insets(2, 0, 2, 0)); // Only cycle if the mouse has not been moved and the middle mouse button has been pressed at least @@ -378,7 +380,7 @@ public void run() { for (final OsmPrimitive osm : osms) { JLabel l = popupBuildPrimitiveLabels(osm); lbls.add(l); - c.add(l, GBC.eol().fill(GBC.HORIZONTAL).insets(2, 0, 2, 2)); + c.add(l, GBC.eol().fill(GridBagConstraints.HORIZONTAL).insets(2, 0, 2, 2)); } popupShowPopup(popupCreatePopup(c, ms), lbls); @@ -536,7 +538,7 @@ private void popupCycleSelection(Collection osms, int mods) { // Clear previous selection if SHIFT (add to selection) is not // pressed. Cannot use "setSelected()" because it will cause a // fireSelectionChanged event which is unnecessary at this point. - if ((mods & MouseEvent.SHIFT_DOWN_MASK) == 0) { + if ((mods & InputEvent.SHIFT_DOWN_MASK) == 0) { ds.clearSelection(); } @@ -647,7 +649,7 @@ private JLabel popupBuildPrimitiveLabels(final OsmPrimitive osm) { final JLabel l = new JLabel( "" + text.toString() + "", ImageProvider.get(osm.getDisplayType()), - JLabel.HORIZONTAL + SwingConstants.HORIZONTAL ) { // This is necessary so the label updates its colors when the // selection is changed from the outside @@ -660,8 +662,8 @@ public void validate() { l.setOpaque(true); popupSetLabelColors(l, osm); l.setFont(l.getFont().deriveFont(Font.PLAIN)); - l.setVerticalTextPosition(JLabel.TOP); - l.setHorizontalAlignment(JLabel.LEADING); + l.setVerticalTextPosition(SwingConstants.TOP); + l.setHorizontalAlignment(SwingConstants.LEADING); l.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); l.addMouseListener(new MouseAdapter() { @Override @@ -901,7 +903,7 @@ public void mouseMoved(MouseEvent e) { if (mv.getCenter() == null) return; // Do not update the view if ctrl or right button is pressed. - if ((e.getModifiersEx() & (MouseEvent.CTRL_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK)) == 0) { + if ((e.getModifiersEx() & (InputEvent.CTRL_DOWN_MASK | InputEvent.BUTTON3_DOWN_MASK)) == 0) { updateLatLonText(e.getX(), e.getY()); } } @@ -950,7 +952,7 @@ public void mouseClicked(MouseEvent e) { helpText.setEditable(false); add(nameText, GBC.std().insets(3, 0, 0, 0)); - add(helpText, GBC.std().insets(3, 0, 0, 0).fill(GBC.HORIZONTAL)); + add(helpText, GBC.std().insets(3, 0, 0, 0).fill(GridBagConstraints.HORIZONTAL)); progressBar.setMaximum(PleaseWaitProgressMonitor.PROGRESS_BAR_MAX); progressBar.setVisible(false); @@ -1225,7 +1227,7 @@ private void refreshDistText(Collection newSelection) { return; } } - setDist(new SubclassFilteredCollection(newSelection, Way.class::isInstance)); + setDist(new SubclassFilteredCollection<>(newSelection, Way.class::isInstance)); } @Override diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index a9b2f1fcf56..6d9f17df793 100644 --- a/src/org/openstreetmap/josm/gui/MapView.java +++ b/src/org/openstreetmap/josm/gui/MapView.java @@ -1,6 +1,8 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui; +import static java.util.function.Predicate.not; + import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; @@ -121,10 +123,10 @@ public void mapPaintStyleEntryUpdated(int index) { * @author Michael Zangl * @since 10271 */ - private class LayerInvalidatedListener implements PaintableInvalidationListener { + private final class LayerInvalidatedListener implements PaintableInvalidationListener { private boolean ignoreRepaint; - private final Set invalidatedLayers = Collections.newSetFromMap(new IdentityHashMap()); + private final Set invalidatedLayers = Collections.newSetFromMap(new IdentityHashMap<>()); @Override public void paintableInvalidated(PaintableInvalidationEvent event) { @@ -161,7 +163,7 @@ public synchronized void removeFrom(MapViewPaintable p) { /** * Attempts to trace repaints that did not originate from this listener. Good to find missed {@link MapView#repaint()}s in code. */ - protected synchronized void traceRandomRepaint() { + private synchronized void traceRandomRepaint() { if (!ignoreRepaint) { Logging.trace("Repaint: {0} from {1}", Thread.currentThread().getStackTrace()[3], Thread.currentThread()); } @@ -172,8 +174,8 @@ protected synchronized void traceRandomRepaint() { * Retrieves a set of all layers that have been marked as invalid since the last call to this method. * @return The layers */ - protected synchronized Set collectInvalidatedLayers() { - Set layers = Collections.newSetFromMap(new IdentityHashMap()); + private synchronized Set collectInvalidatedLayers() { + Set layers = Collections.newSetFromMap(new IdentityHashMap<>()); layers.addAll(invalidatedLayers); invalidatedLayers.clear(); return layers; @@ -296,7 +298,7 @@ public void mousePressed(MouseEvent me) { } }); - setFocusTraversalKeysEnabled(!Shortcut.findShortcut(KeyEvent.VK_TAB, 0).isPresent()); + setFocusTraversalKeysEnabled(Shortcut.findShortcut(KeyEvent.VK_TAB, 0).isEmpty()); mapNavigationComponents = getMapNavigationComponents(this); for (JComponent c : mapNavigationComponents) { @@ -318,7 +320,7 @@ public static List getMapNavigationComponents(MapView forM Dimension size = zoomSlider.getPreferredSize(); zoomSlider.setSize(size); zoomSlider.setLocation(3, 0); - zoomSlider.setFocusTraversalKeysEnabled(!Shortcut.findShortcut(KeyEvent.VK_TAB, 0).isPresent()); + zoomSlider.setFocusTraversalKeysEnabled(Shortcut.findShortcut(KeyEvent.VK_TAB, 0).isEmpty()); MapScaler scaler = new MapScaler(forMapView); scaler.setPreferredLineLength(size.width - 10); @@ -826,8 +828,9 @@ public void destroy() { */ public String getLayerInformationForSourceTag() { return layerManager.getVisibleLayersInZOrder().stream() - .filter(layer -> !Utils.isBlank(layer.getChangesetSourceTag())) - .map(layer -> layer.getChangesetSourceTag().trim()) + .map(Layer::getChangesetSourceTag) + .filter(not(Utils::isStripEmpty)) + .map(String::trim) .distinct() .collect(Collectors.joining("; ")); } diff --git a/src/org/openstreetmap/josm/gui/MenuScroller.java b/src/org/openstreetmap/josm/gui/MenuScroller.java index 9e006cfb7d0..d70c9f359f0 100644 --- a/src/org/openstreetmap/josm/gui/MenuScroller.java +++ b/src/org/openstreetmap/josm/gui/MenuScroller.java @@ -349,7 +349,7 @@ private void refreshMenu() { } } - private class MenuScrollListener implements PopupMenuListener { + private final class MenuScrollListener implements PopupMenuListener { @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) { @@ -452,7 +452,7 @@ public int getIconHeight() { } } - private class MouseScrollListener implements MouseWheelListener { + private final class MouseScrollListener implements MouseWheelListener { @Override public void mouseWheelMoved(MouseWheelEvent mwe) { firstIndex += mwe.getWheelRotation(); diff --git a/src/org/openstreetmap/josm/gui/NavigatableComponent.java b/src/org/openstreetmap/josm/gui/NavigatableComponent.java index 228d1a9e8e4..515a238d5a7 100644 --- a/src/org/openstreetmap/josm/gui/NavigatableComponent.java +++ b/src/org/openstreetmap/josm/gui/NavigatableComponent.java @@ -1060,7 +1060,8 @@ private Map> getNearestNodesImpl(Point p, Predicate predicate, if (nlists.isEmpty()) return null; if (preferredRefs != null && preferredRefs.isEmpty()) preferredRefs = null; - Node ntsel = null, ntnew = null, ntref = null; + Node ntsel = null; + Node ntnew = null; + Node ntref = null; boolean useNtsel = useSelected; double minDistSq = nlists.keySet().iterator().next(); @@ -1787,7 +1790,7 @@ public double getMaxScale() { * Listener for mouse movement events. Used to detect when primitives are being hovered over with the mouse pointer * so that registered {@link PrimitiveHoverListener}s can be notified. */ - private class PrimitiveHoverMouseListener extends MouseAdapter { + private final class PrimitiveHoverMouseListener extends MouseAdapter { @Override public void mouseMoved(MouseEvent e) { OsmPrimitive hovered = getNearestNodeOrWay(e.getPoint(), isSelectablePredicate, true); diff --git a/src/org/openstreetmap/josm/gui/NoteInputDialog.java b/src/org/openstreetmap/josm/gui/NoteInputDialog.java index 13ffa599722..1e604d8dcb5 100644 --- a/src/org/openstreetmap/josm/gui/NoteInputDialog.java +++ b/src/org/openstreetmap/josm/gui/NoteInputDialog.java @@ -52,7 +52,7 @@ public void showNoteDialog(String message, Icon icon) { * @param message Translated message to display to the user as input prompt * @param icon Icon to display in the action button * @param text Default text of the note's comment - * @since xxx + * @since 18839 */ public void showNoteDialog(String message, Icon icon, String text) { textArea.setText(text); diff --git a/src/org/openstreetmap/josm/gui/NotificationManager.java b/src/org/openstreetmap/josm/gui/NotificationManager.java index 2d30cf605ea..da9681f3157 100644 --- a/src/org/openstreetmap/josm/gui/NotificationManager.java +++ b/src/org/openstreetmap/josm/gui/NotificationManager.java @@ -182,7 +182,7 @@ private void stopHideTimer() { pauseTimer.restart(); } - private class PauseFinishedEvent implements ActionListener { + private final class PauseFinishedEvent implements ActionListener { @Override public void actionPerformed(ActionEvent e) { @@ -193,7 +193,7 @@ public void actionPerformed(ActionEvent e) { } } - private class UnfreezeEvent implements ActionListener { + private final class UnfreezeEvent implements ActionListener { @Override public void actionPerformed(ActionEvent e) { diff --git a/src/org/openstreetmap/josm/gui/PleaseWaitDialog.java b/src/org/openstreetmap/josm/gui/PleaseWaitDialog.java index be198578395..a3dbfb62a85 100644 --- a/src/org/openstreetmap/josm/gui/PleaseWaitDialog.java +++ b/src/org/openstreetmap/josm/gui/PleaseWaitDialog.java @@ -106,7 +106,7 @@ protected void adjustLayout() { */ @Override public void setCustomText(String text) { - if (Utils.isBlank(text)) { + if (Utils.isStripEmpty(text)) { customText.setVisible(false); adjustLayout(); return; @@ -131,7 +131,7 @@ public void setCurrentAction(String text) { */ @Override public void appendLogMessage(String message) { - if (Utils.isBlank(message)) + if (Utils.isStripEmpty(message)) return; if (!spLog.isVisible()) { spLog.setVisible(true); diff --git a/src/org/openstreetmap/josm/gui/SelectionManager.java b/src/org/openstreetmap/josm/gui/SelectionManager.java index 0f9e4a0e924..6f10c5281b2 100644 --- a/src/org/openstreetmap/josm/gui/SelectionManager.java +++ b/src/org/openstreetmap/josm/gui/SelectionManager.java @@ -78,7 +78,7 @@ public interface SelectionEnded extends Action { * * @author Michael Zangl */ - private class SelectionHintLayer extends AbstractMapViewPaintable { + private final class SelectionHintLayer extends AbstractMapViewPaintable { @Override public void paint(Graphics2D g, MapView mv, Bounds bbox) { if (mousePos == null || mousePosStart == null || mousePos == mousePosStart) @@ -185,7 +185,7 @@ public void unregister(MapView eventSource) { public void mousePressed(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() > 1 && MainApplication.getLayerManager().getActiveDataSet() != null) { SelectByInternalPointAction.performSelection(MainApplication.getMap().mapView.getEastNorth(e.getX(), e.getY()), - (e.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) != 0, + (e.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) != 0, (e.getModifiersEx() & PlatformManager.getPlatform().getMenuShortcutKeyMaskEx()) != 0); } else if (e.getButton() == MouseEvent.BUTTON1) { mousePosStart = mousePos = e.getPoint(); @@ -200,7 +200,7 @@ public void mousePressed(MouseEvent e) { */ @Override public void mouseDragged(MouseEvent e) { - int buttonPressed = e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK); + int buttonPressed = e.getModifiersEx() & (InputEvent.BUTTON1_DOWN_MASK | InputEvent.BUTTON3_DOWN_MASK); if (buttonPressed != 0) { if (mousePosStart == null) { @@ -209,11 +209,11 @@ public void mouseDragged(MouseEvent e) { selectionAreaChanged(); } - if (buttonPressed == MouseEvent.BUTTON1_DOWN_MASK) { + if (buttonPressed == InputEvent.BUTTON1_DOWN_MASK) { mousePos = e.getPoint(); addLassoPoint(e.getPoint()); selectionAreaChanged(); - } else if (buttonPressed == (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK)) { + } else if (buttonPressed == (InputEvent.BUTTON1_DOWN_MASK | InputEvent.BUTTON3_DOWN_MASK)) { moveSelection(e.getX()-mousePos.x, e.getY()-mousePos.y); mousePos = e.getPoint(); selectionAreaChanged(); @@ -252,7 +252,7 @@ public void endSelecting(MouseEvent e) { } // Left mouse was released while right is still pressed. - boolean rightMouseStillPressed = (e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) != 0; + boolean rightMouseStillPressed = (e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0; if (!rightMouseStillPressed) { selectingDone(e); @@ -316,7 +316,7 @@ private Rectangle getSelectionRectangle() { */ @Override public void propertyChange(PropertyChangeEvent evt) { - if ("active".equals(evt.getPropertyName()) && !(Boolean) evt.getNewValue()) { + if ("active".equals(evt.getPropertyName()) && Boolean.FALSE.equals(evt.getNewValue())) { abortSelecting(); } } diff --git a/src/org/openstreetmap/josm/gui/animation/ChristmasExtension.java b/src/org/openstreetmap/josm/gui/animation/ChristmasExtension.java index 97886ce6c34..cf777e28e16 100644 --- a/src/org/openstreetmap/josm/gui/animation/ChristmasExtension.java +++ b/src/org/openstreetmap/josm/gui/animation/ChristmasExtension.java @@ -4,6 +4,7 @@ import java.awt.Graphics; import java.util.ArrayList; import java.util.List; +import java.util.Random; /** * Christmas animation extension. Copied from Icedtea-Web. @@ -13,26 +14,28 @@ */ public class ChristmasExtension implements AnimationExtension { - private final List stars = new ArrayList<>(50); + private static final Random seed = new Random(); + private final List objs = new ArrayList<>(50); @Override public void paint(Graphics g) { - stars.forEach(s -> s.paint(g)); + objs.forEach(o -> o.paint(g)); } @Override public void animate() { - stars.forEach(Star::animate); + objs.forEach(IAnimObject::animate); } @Override public final void adjustForSize(int w, int h) { int count = w / (2 * (Star.averageStarWidth + 1)); - while (stars.size() > count) { - stars.remove(stars.size() - 1); + while (objs.size() > count) { + objs.remove(objs.size() - 1); } - while (stars.size() < count) { - stars.add(new Star(w, h)); + objs.forEach(o -> o.setExtend(w, h)); + while (objs.size() < count) { + objs.add(seed.nextInt(5) > 0 ? new Star(w, h) : new DropImage(w, h)); } } } diff --git a/src/org/openstreetmap/josm/gui/animation/DropImage.java b/src/org/openstreetmap/josm/gui/animation/DropImage.java new file mode 100644 index 00000000000..c8447336309 --- /dev/null +++ b/src/org/openstreetmap/josm/gui/animation/DropImage.java @@ -0,0 +1,109 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.gui.animation; + +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Image; +import java.awt.Point; +import java.io.File; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.Random; + +import org.openstreetmap.josm.tools.ImageProvider; +import org.openstreetmap.josm.tools.Logging; + +/** + * A random image displayed when {@link ChristmasExtension} is active. + * @since 18929 + */ +class DropImage implements IAnimObject { + private static final Random seed = new Random(); + + static final int averageFallSpeed = 4; // 2-6 + + private int w; + private int h; + + private final Point edge = new Point(); + private final int fallSpeed; + private Image image; + + DropImage(int w, int h) { + this.w = w; + this.h = h; + edge.x = seed.nextInt(w - 1); + edge.y = seed.nextInt(h + 1); + fallSpeed = averageFallSpeed / 2 + seed.nextInt(averageFallSpeed / 2); + image = getImage(); + } + + @Override + public void paint(Graphics g) { + g.drawImage(image, edge.x, edge.y, null); + } + + @Override + public void setExtend(int w, int h) { + this.w = w; + this.h = h; + } + + @Override + public void animate() { + edge.y += fallSpeed; + if (edge.x > w - 1 || edge.y > h) { + edge.x = seed.nextInt(w - 1); + edge.y = -image.getWidth(null) * 2; + image = getImage(); + } + } + + private Image getImage() { + int size = 15 + seed.nextInt(5); + String name = "logo"; + try { + ArrayList result = new ArrayList<>(); + String path = "images/presets/"; + URL url = DropImage.class.getClassLoader().getResource(path); + if (url != null && "file".equals(url.getProtocol())) { + ArrayList dirs = new ArrayList<>(); + dirs.add(new File(url.toURI())); + do { + File[] files = dirs.remove(0).listFiles(); + if (files != null) { + for (File f : files) { + if (f.isFile()) { + result.add(f.getPath()); + } else { + dirs.add(f); + } + } + } + } while (!dirs.isEmpty()); + name = result.get(seed.nextInt(result.size())); + } else if (url != null && "jar".equals(url.getProtocol())) { + String jarPath = url.getPath().substring(5, url.getPath().indexOf('!')); + try (JarFile jar = new JarFile(URLDecoder.decode(jarPath, StandardCharsets.UTF_8))) { + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + String fileName = entries.nextElement().getName(); + if (fileName.startsWith(path) && !fileName.endsWith("/")) { + result.add(fileName.substring(7)); + } + } + } + name = result.get(seed.nextInt(result.size())); + } + return new ImageProvider(name).setMaxSize(new Dimension(size, size)).get().getImage(); + } catch (Exception ex) { + Logging.log(Logging.LEVEL_DEBUG, ex); + } + return new ImageProvider("logo").setMaxSize(new Dimension(size, size)).get().getImage(); + } +} diff --git a/src/org/openstreetmap/josm/gui/animation/IAnimObject.java b/src/org/openstreetmap/josm/gui/animation/IAnimObject.java new file mode 100644 index 00000000000..feda1b1ebd3 --- /dev/null +++ b/src/org/openstreetmap/josm/gui/animation/IAnimObject.java @@ -0,0 +1,26 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.gui.animation; + +import java.awt.Graphics; + +/** + * An animated object + * @since 18929 + */ +public interface IAnimObject { + + /** Paint the object + * @param g the graphics object to paint to + */ + void paint(Graphics g); + + /** Set the extend when window size changed + * @param w window width + * @param h window height + */ + void setExtend(int w, int h); + + /** Animate the object - Cause next step of animation + */ + void animate(); +} diff --git a/src/org/openstreetmap/josm/gui/animation/Star.java b/src/org/openstreetmap/josm/gui/animation/Star.java index 066b2abe3dd..1863858c005 100644 --- a/src/org/openstreetmap/josm/gui/animation/Star.java +++ b/src/org/openstreetmap/josm/gui/animation/Star.java @@ -13,7 +13,7 @@ * @see Initial commit * @since 14581 */ -class Star { +class Star implements IAnimObject { private static final Random seed = new Random(); static final int averageStarWidth = 10; // stars will be 5-15 @@ -22,8 +22,8 @@ class Star { private static final Color WATER_LIVE_COLOR = new Color(80, 131, 160); - private final int w; - private final int h; + private int w; + private int h; private int radiusX; private int radiusY; @@ -58,7 +58,8 @@ class Star { } } - void paint(Graphics g) { + @Override + public void paint(Graphics g) { Color c = g.getColor(); g.setColor(new Color(color[0], color[1], color[2])); Polygon p = createPolygon(); @@ -71,7 +72,14 @@ void paint(Graphics g) { g.setColor(c); } - void animate() { + @Override + public void setExtend(int w, int h) { + this.w = w; + this.h = h; + } + + @Override + public void animate() { center.y += fallSpeed; if (orientation) { radiusX += direction; @@ -96,7 +104,7 @@ void animate() { } interpolateColors(radiusY, maxRadiusY); } - if (center.y > h + radiusX * 2 || center.y > h + radiusY * 2) { + if (center.y > w + 1 || center.y > h + radiusY * 2) { createRadiuses(); center.x = seed.nextInt(w + 1); center.y = -radiusY * 2; diff --git a/src/org/openstreetmap/josm/gui/bbox/SlippyMapController.java b/src/org/openstreetmap/josm/gui/bbox/SlippyMapController.java index 21d69d95a36..10e50cc1fda 100644 --- a/src/org/openstreetmap/josm/gui/bbox/SlippyMapController.java +++ b/src/org/openstreetmap/josm/gui/bbox/SlippyMapController.java @@ -44,7 +44,7 @@ public class SlippyMapController extends MouseAdapter { /** The speed increase per timer interval when a cursor button is clicked */ private static final double ACCELERATION = 0.10; - private static final int MAC_MOUSE_BUTTON3_MASK = MouseEvent.CTRL_DOWN_MASK | MouseEvent.BUTTON1_DOWN_MASK; + private static final int MAC_MOUSE_BUTTON3_MASK = InputEvent.CTRL_DOWN_MASK | InputEvent.BUTTON1_DOWN_MASK; private static final String[] N = { ",", ".", "up", "right", "down", "left"}; @@ -72,7 +72,7 @@ public SlippyMapController(SlippyMapBBoxChooser navComp, JPanel contentPane) { if (contentPane != null) { for (int i = 0; i < N.length; ++i) { contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( - KeyStroke.getKeyStroke(K[i], KeyEvent.CTRL_DOWN_MASK), "MapMover.Zoomer." + N[i]); + KeyStroke.getKeyStroke(K[i], InputEvent.CTRL_DOWN_MASK), "MapMover.Zoomer." + N[i]); } } isSelecting = false; @@ -129,7 +129,7 @@ public void mousePressed(MouseEvent e) { @Override public void mouseDragged(MouseEvent e) { - if (iStartSelectionPoint != null && (e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == MouseEvent.BUTTON1_DOWN_MASK + if (iStartSelectionPoint != null && (e.getModifiersEx() & InputEvent.BUTTON1_DOWN_MASK) == InputEvent.BUTTON1_DOWN_MASK && !(PlatformManager.isPlatformOsx() && e.getModifiersEx() == MAC_MOUSE_BUTTON3_MASK)) { iEndSelectionPoint = e.getPoint(); iSlippyMapChooser.setSelection(iStartSelectionPoint, iEndSelectionPoint); @@ -194,7 +194,7 @@ public void actionPerformed(ActionEvent e) { } /** Moves the map depending on which cursor keys are pressed (or not) */ - private class MoveTask extends TimerTask { + private final class MoveTask extends TimerTask { /** The current x speed (pixels per timer interval) */ private double speedX = 1; @@ -211,14 +211,14 @@ private class MoveTask extends TimerTask { * Indicated if moveTask is currently enabled (periodically * executed via timer) or disabled */ - protected boolean scheduled; + boolean scheduled; - protected void setDirectionX(int directionX) { + void setDirectionX(int directionX) { this.directionX = directionX; updateScheduleStatus(); } - protected void setDirectionY(int directionY) { + void setDirectionY(int directionY) { this.directionY = directionY; updateScheduleStatus(); } @@ -300,7 +300,7 @@ public void run() { } } - private class ZoomInAction extends AbstractAction { + private final class ZoomInAction extends AbstractAction { @Override public void actionPerformed(ActionEvent e) { @@ -308,7 +308,7 @@ public void actionPerformed(ActionEvent e) { } } - private class ZoomOutAction extends AbstractAction { + private final class ZoomOutAction extends AbstractAction { @Override public void actionPerformed(ActionEvent e) { diff --git a/src/org/openstreetmap/josm/gui/conflict/pair/AbstractListMergeModel.java b/src/org/openstreetmap/josm/gui/conflict/pair/AbstractListMergeModel.java index 99b1da558ab..07e820e800d 100644 --- a/src/org/openstreetmap/josm/gui/conflict/pair/AbstractListMergeModel.java +++ b/src/org/openstreetmap/josm/gui/conflict/pair/AbstractListMergeModel.java @@ -64,7 +64,7 @@ * A ListMergeModel can be ''frozen''. If it's frozen, it doesn't accept additional merge * decisions. {@link PropertyChangeListener}s can register for property value changes of * {@link #FROZEN_PROP}. - * + *

    * ListMergeModel is an abstract class. Three methods have to be implemented by subclasses: *

      *
    • {@link AbstractListMergeModel#cloneEntryForMergedList} - clones an entry of type T
    • @@ -202,7 +202,7 @@ public int getTheirEntriesSize() { protected AbstractListMergeModel() { entries = new EnumMap<>(ListRole.class); for (ListRole role : ListRole.values()) { - entries.put(role, new ArrayList()); + entries.put(role, new ArrayList<>()); } buildMyEntriesTableModel(); @@ -355,6 +355,9 @@ public void copyTheirToEnd(int... rows) { copyToEnd(THEIR_ENTRIES, rows); } + /** + * Clear the merged list. + */ public void clearMerged() { getMergedEntries().clear(); fireModelDataChanged(); @@ -375,14 +378,13 @@ protected void alertCopyFailedForDeletedPrimitives(List deletedIds) if (deletedIds.size() > MAX_DELETED_PRIMITIVE_IN_DIALOG) { items.add(tr("{0} more...", deletedIds.size() - MAX_DELETED_PRIMITIVE_IN_DIALOG)); } - StringBuilder sb = new StringBuilder(); - sb.append("") - .append(tr("The following objects could not be copied to the target object
      because they are deleted in the target dataset:")) - .append(Utils.joinAsHtmlUnorderedList(items)) - .append(""); + String sb = "" + + tr("The following objects could not be copied to the target object
      because they are deleted in the target dataset:") + + Utils.joinAsHtmlUnorderedList(items) + + ""; HelpAwareOptionPane.showOptionDialog( MainApplication.getMainFrame(), - sb.toString(), + sb, tr("Merging deleted objects failed"), JOptionPane.WARNING_MESSAGE, HelpUtil.ht("/Dialog/Conflict#MergingDeletedPrimitivesFailed") @@ -581,7 +583,7 @@ protected boolean myAndTheirEntriesEqual() { /** * This an adapter between a {@link JTable} and one of the three entry lists * in the role {@link ListRole} managed by the {@link AbstractListMergeModel}. - * + *

      * From the point of view of the {@link JTable} it is a {@link TableModel}. * * @see AbstractListMergeModel#getMyTableModel() @@ -592,7 +594,7 @@ public class EntriesTableModel extends DefaultTableModel implements OsmPrimitive private final ListRole role; /** - * + * Create a new {@link EntriesTableModel} * @param role the role */ public EntriesTableModel(ListRole role) { @@ -730,7 +732,7 @@ public OsmPrimitive getReferredPrimitive(int idx) { /** * This is the selection model to be used in a {@link JTable} which displays * an entry list managed by {@link AbstractListMergeModel}. - * + *

      * The model ensures that only rows displaying an entry in the entry list * can be selected. "Empty" rows can't be selected. * @@ -831,6 +833,9 @@ public ComparePairListModel getComparePairListModel() { return this.comparePairListModel; } + /** + * A model for {@link ComparePairType} with the enums added as options. + */ public class ComparePairListModel extends JosmComboBoxModel { private int selectedIdx; diff --git a/src/org/openstreetmap/josm/gui/conflict/pair/nodes/NodeListTableCellRenderer.java b/src/org/openstreetmap/josm/gui/conflict/pair/nodes/NodeListTableCellRenderer.java index 0bf7f5409cf..8000d7dbc1f 100644 --- a/src/org/openstreetmap/josm/gui/conflict/pair/nodes/NodeListTableCellRenderer.java +++ b/src/org/openstreetmap/josm/gui/conflict/pair/nodes/NodeListTableCellRenderer.java @@ -110,7 +110,7 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole if (node == null) { renderEmptyRow(); } else { - switch(column) { + switch (column) { case 0: renderRowId(getModel(table), row); break; diff --git a/src/org/openstreetmap/josm/gui/conflict/pair/properties/PropertiesMergeModel.java b/src/org/openstreetmap/josm/gui/conflict/pair/properties/PropertiesMergeModel.java index e01c6e48e14..32f549a6e07 100644 --- a/src/org/openstreetmap/josm/gui/conflict/pair/properties/PropertiesMergeModel.java +++ b/src/org/openstreetmap/josm/gui/conflict/pair/properties/PropertiesMergeModel.java @@ -179,7 +179,7 @@ public LatLon getTheirCoords() { * have no coordinates or if the conflict is yet {@link MergeDecisionType#UNDECIDED} */ public LatLon getMergedCoords() { - switch(coordMergeDecision) { + switch (coordMergeDecision) { case KEEP_MINE: return myCoords; case KEEP_THEIR: return theirCoords; case UNDECIDED: return null; @@ -220,7 +220,7 @@ public Boolean getTheirDeletedState() { * @return The state of deleted flag */ public Boolean getMergedDeletedState() { - switch(deletedMergeDecision) { + switch (deletedMergeDecision) { case KEEP_MINE: return myDeletedState; case KEEP_THEIR: return theirDeletedState; case UNDECIDED: return null; diff --git a/src/org/openstreetmap/josm/gui/conflict/pair/relation/RelationMemberTableCellRenderer.java b/src/org/openstreetmap/josm/gui/conflict/pair/relation/RelationMemberTableCellRenderer.java index 99d7808341b..05619df3705 100644 --- a/src/org/openstreetmap/josm/gui/conflict/pair/relation/RelationMemberTableCellRenderer.java +++ b/src/org/openstreetmap/josm/gui/conflict/pair/relation/RelationMemberTableCellRenderer.java @@ -124,7 +124,7 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole } else { renderBackground(getModel(table), member, row, column, isSelected); renderForeground(getModel(table), member, row, column, isSelected); - switch(column) { + switch (column) { case 0: renderRowId(row); break; diff --git a/src/org/openstreetmap/josm/gui/conflict/pair/tags/TagMergeTableCellRenderer.java b/src/org/openstreetmap/josm/gui/conflict/pair/tags/TagMergeTableCellRenderer.java index d6c933941b8..4f83bbc682d 100644 --- a/src/org/openstreetmap/josm/gui/conflict/pair/tags/TagMergeTableCellRenderer.java +++ b/src/org/openstreetmap/josm/gui/conflict/pair/tags/TagMergeTableCellRenderer.java @@ -34,7 +34,7 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole return this; TagMergeItem item = (TagMergeItem) value; - switch(col) { + switch (col) { case 0: renderKey(item, isSelected); break; diff --git a/src/org/openstreetmap/josm/gui/conflict/tags/CombinePrimitiveResolverDialog.java b/src/org/openstreetmap/josm/gui/conflict/tags/CombinePrimitiveResolverDialog.java index 11e4cb1e879..c46f080f4e1 100644 --- a/src/org/openstreetmap/josm/gui/conflict/tags/CombinePrimitiveResolverDialog.java +++ b/src/org/openstreetmap/josm/gui/conflict/tags/CombinePrimitiveResolverDialog.java @@ -589,7 +589,7 @@ protected static void informAboutRelationMembershipConflicts( DefaultNameFormatter.getInstance().formatAsHtmlUnorderedList(parentRelations, 20)); if (!ConditionalOptionPaneUtil.showConfirmationDialog( - "combine_tags", + "combine_relation_member", MainApplication.getMainFrame(), "" + msg + "", tr("Combine confirmation"), diff --git a/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueCellEditor.java b/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueCellEditor.java index 4b744ba99ba..619ecdaa30d 100644 --- a/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueCellEditor.java +++ b/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueCellEditor.java @@ -27,11 +27,11 @@ * This is a table cell editor for selecting a possible tag value from a list of * proposed tag values. The editor also allows to select all proposed valued or * to remove the tag. - * + *

      * The editor responds intercepts some keys and interprets them as navigation keys. It * forwards navigation events to {@link NavigationListener}s registered with this editor. * You should register the parent table using this editor as {@link NavigationListener}. - * + *

      * {@link KeyEvent#VK_ENTER} and {@link KeyEvent#VK_TAB} trigger a {@link NavigationListener#gotoNextDecision()}. */ public class MultiValueCellEditor extends AbstractCellEditor implements TableCellEditor { @@ -87,7 +87,7 @@ protected void fireGotoPreviousDecision() { */ public MultiValueCellEditor() { editorModel = new JosmComboBoxModel<>(); - editor = new JosmComboBox(editorModel) { + editor = new JosmComboBox<>(editorModel) { @Override public void processKeyEvent(KeyEvent e) { if (e.getID() == KeyEvent.KEY_PRESSED) { @@ -149,7 +149,7 @@ protected void initEditor(MultiValueResolutionDecision decision) { if (decision.canKeepAll()) { editorModel.addElement(MultiValueDecisionType.KEEP_ALL); } - switch(decision.getDecisionType()) { + switch (decision.getDecisionType()) { case UNDECIDED: editor.setSelectedItem(MultiValueDecisionType.UNDECIDED); break; @@ -215,26 +215,27 @@ protected void renderColors(boolean selected) { * @param value {@link String} or {@link MultiValueDecisionType} */ protected void renderValue(Object value) { - setFont(UIManager.getFont("ComboBox.font")); + final String comboBoxFont = "ComboBox.font"; + setFont(UIManager.getFont(comboBoxFont)); if (value instanceof String) { setText((String) value); } else if (value instanceof MultiValueDecisionType) { - switch((MultiValueDecisionType) value) { + switch ((MultiValueDecisionType) value) { case UNDECIDED: setText(tr("Choose a value")); - setFont(UIManager.getFont("ComboBox.font").deriveFont(Font.ITALIC + Font.BOLD)); + setFont(UIManager.getFont(comboBoxFont).deriveFont(Font.ITALIC + Font.BOLD)); break; case KEEP_NONE: setText(tr("none")); - setFont(UIManager.getFont("ComboBox.font").deriveFont(Font.ITALIC + Font.BOLD)); + setFont(UIManager.getFont(comboBoxFont).deriveFont(Font.ITALIC + Font.BOLD)); break; case KEEP_ALL: setText(tr("all")); - setFont(UIManager.getFont("ComboBox.font").deriveFont(Font.ITALIC + Font.BOLD)); + setFont(UIManager.getFont(comboBoxFont).deriveFont(Font.ITALIC + Font.BOLD)); break; case SUM_ALL_NUMERIC: setText(tr("sum")); - setFont(UIManager.getFont("ComboBox.font").deriveFont(Font.ITALIC + Font.BOLD)); + setFont(UIManager.getFont(comboBoxFont).deriveFont(Font.ITALIC + Font.BOLD)); break; default: // don't display other values diff --git a/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueCellRenderer.java b/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueCellRenderer.java index 13263f30b69..dddd94ed259 100644 --- a/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueCellRenderer.java +++ b/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueCellRenderer.java @@ -154,7 +154,7 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole boolean conflict = tagModel.getKeysWithConflicts().contains(tagModel.getKey(row)); renderColors(decision, isSelected, conflict); renderToolTipText(decision); - switch(column) { + switch (column) { case 0: if (decision.isDecided()) { setIcon(iconDecided); diff --git a/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueResolutionDecision.java b/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueResolutionDecision.java index 499ae080fa0..69cb4c2c483 100644 --- a/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueResolutionDecision.java +++ b/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueResolutionDecision.java @@ -142,7 +142,7 @@ public void undecide() { * @throws IllegalStateException if this resolution is not yet decided */ public String getChosenValue() { - switch(type) { + switch (type) { case UNDECIDED: throw new IllegalStateException(tr("Not decided yet")); case KEEP_ONE: return value; case SUM_ALL_NUMERIC: return tags.getSummedValues(getKey()); @@ -293,7 +293,7 @@ public Command buildChangeCommand(Collection primitives) * @return a tag representing the current resolution. Null, if this resolution is not resolved yet */ public Tag getResolution() { - switch(type) { + switch (type) { case SUM_ALL_NUMERIC: return new Tag(getKey(), tags.getSummedValues(getKey())); case KEEP_ALL: return new Tag(getKey(), tags.getJoinedValues(getKey())); case KEEP_ONE: return new Tag(getKey(), value); diff --git a/src/org/openstreetmap/josm/gui/conflict/tags/PasteTagsConflictResolverDialog.java b/src/org/openstreetmap/josm/gui/conflict/tags/PasteTagsConflictResolverDialog.java index 7c457419358..067e79b444a 100644 --- a/src/org/openstreetmap/josm/gui/conflict/tags/PasteTagsConflictResolverDialog.java +++ b/src/org/openstreetmap/josm/gui/conflict/tags/PasteTagsConflictResolverDialog.java @@ -428,7 +428,7 @@ private void renderStatistics(Map stat) { continue; } String msg; - switch(type) { + switch (type) { case NODE: msg = trn("{0} node", "{0} nodes", numPrimitives, numPrimitives); break; case WAY: msg = trn("{0} way", "{0} ways", numPrimitives, numPrimitives); break; case RELATION: msg = trn("{0} relation", "{0} relations", numPrimitives, numPrimitives); break; @@ -460,7 +460,7 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole } else { StatisticsInfo info = (StatisticsInfo) value; - switch(column) { + switch (column) { case 0: renderNumTags(info); break; case 1: renderFrom(info); break; case 2: renderTo(info); break; diff --git a/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictResolverModel.java b/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictResolverModel.java index 27a0294967f..362c76f364d 100644 --- a/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictResolverModel.java +++ b/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictResolverModel.java @@ -105,13 +105,13 @@ public Object getValueAt(int row, int column) { if (decisions == null) return null; RelationMemberConflictDecision d = decisions.get(row); - switch(column) { + switch (column) { case 0: /* relation */ return d.getRelation(); case 1: /* pos */ return Integer.toString(d.getPos() + 1); // position in "user space" starting at 1 case 2: /* role */ return d.getRole(); case 3: /* original */ return d.getOriginalPrimitive(); - case 4: /* decision keep */ return RelationMemberConflictDecisionType.KEEP.equals(d.getDecision()); - case 5: /* decision remove */ return RelationMemberConflictDecisionType.REMOVE.equals(d.getDecision()); + case 4: /* decision keep */ return RelationMemberConflictDecisionType.KEEP == d.getDecision(); + case 5: /* decision remove */ return RelationMemberConflictDecisionType.REMOVE == d.getDecision(); } return null; } @@ -119,7 +119,7 @@ public Object getValueAt(int row, int column) { @Override public void setValueAt(Object value, int row, int column) { RelationMemberConflictDecision d = decisions.get(row); - switch(column) { + switch (column) { case 2: /* role */ d.setRole((String) value); break; @@ -199,13 +199,13 @@ public void populate(Collection references) { references = references == null ? new LinkedList<>() : references; decisions.clear(); this.relations = new HashSet<>(references.size()); - final Collection primitives = new HashSet<>(); + final Collection newPrimitives = new HashSet<>(); for (RelationToChildReference reference: references) { decisions.add(new RelationMemberConflictDecision(reference.getParent(), reference.getPosition())); relations.add(reference.getParent()); - primitives.add(reference.getChild()); + newPrimitives.add(reference.getChild()); } - this.primitives = primitives; + this.primitives = newPrimitives; refresh(); } @@ -248,7 +248,7 @@ void prepareDefaultRelationDecisions(boolean fireEvent) { if (decision.getRelation() == relation) { final OsmPrimitive primitive = decision.getOriginalPrimitive(); if (!decisionsByPrimitive.containsKey(primitive)) { - decisionsByPrimitive.put(primitive, new ArrayList()); + decisionsByPrimitive.put(primitive, new ArrayList<>()); } decisionsByPrimitive.get(primitive).add(decision); } @@ -262,12 +262,12 @@ void prepareDefaultRelationDecisions(boolean fireEvent) { .map(List::iterator) .collect(Collectors.toList()); while (iterators.stream().allMatch(Iterator::hasNext)) { - final List decisions = new ArrayList<>(); + final List conflictDecisions = new ArrayList<>(); final Collection roles = new HashSet<>(); final Collection indices = new TreeSet<>(); for (Iterator it : iterators) { final RelationMemberConflictDecision decision = it.next(); - decisions.add(decision); + conflictDecisions.add(decision); roles.add(decision.getRole()); indices.add(decision.getPos()); } @@ -275,8 +275,8 @@ void prepareDefaultRelationDecisions(boolean fireEvent) { // roles do not match or not consecutive members in relation, leave undecided continue; } - decisions.get(0).decide(RelationMemberConflictDecisionType.KEEP); - for (RelationMemberConflictDecision decision : decisions.subList(1, decisions.size())) { + conflictDecisions.get(0).decide(RelationMemberConflictDecisionType.KEEP); + for (RelationMemberConflictDecision decision : conflictDecisions.subList(1, conflictDecisions.size())) { decision.decide(RelationMemberConflictDecisionType.REMOVE); } } @@ -371,7 +371,7 @@ protected Command buildResolveCommand(Relation relation, OsmPrimitive newPrimiti if (decision == null) { modifiedMemberList.add(member); } else { - switch(decision.getDecision()) { + switch (decision.getDecision()) { case KEEP: final RelationMember newMember = new RelationMember(decision.getRole(), newPrimitive); modifiedMemberList.add(newMember); @@ -409,7 +409,7 @@ protected boolean isChanged(Relation relation, OsmPrimitive newPrimitive) { if (decision == null) { continue; } - switch(decision.getDecision()) { + switch (decision.getDecision()) { case REMOVE: return true; case KEEP: if (!relation.getMember(i).getRole().equals(decision.getRole())) diff --git a/src/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolverModel.java b/src/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolverModel.java index 004ca0d31a0..fe3cf226df5 100644 --- a/src/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolverModel.java +++ b/src/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolverModel.java @@ -175,7 +175,7 @@ public void setValueAt(Object value, int row, int column) { decision.keepOne((String) value); } else if (value instanceof MultiValueDecisionType) { MultiValueDecisionType type = (MultiValueDecisionType) value; - switch(type) { + switch (type) { case KEEP_NONE: decision.keepNone(); break; @@ -275,7 +275,7 @@ public void prepareDefaultTagDecisions() { /** * Prepare the default decisions for the current model * @param fireEvent {@code true} to call {@code fireTableDataChanged} (can be a slow operation) - * @since 11626 + * @since 11627 */ void prepareDefaultTagDecisions(boolean fireEvent) { for (MultiValueResolutionDecision decision: decisions.values()) { diff --git a/src/org/openstreetmap/josm/gui/datatransfer/TagTransferable.java b/src/org/openstreetmap/josm/gui/datatransfer/TagTransferable.java index f1f3255c329..4a46f3818a9 100644 --- a/src/org/openstreetmap/josm/gui/datatransfer/TagTransferable.java +++ b/src/org/openstreetmap/josm/gui/datatransfer/TagTransferable.java @@ -53,9 +53,9 @@ private String getStringData() { if (string.length() > 0) { string.append('\n'); } - string.append(e.getKey()); - string.append('='); - string.append(e.getValue()); + string.append(e.getKey()) + .append('=') + .append(e.getValue()); } return string.toString(); } diff --git a/src/org/openstreetmap/josm/gui/datatransfer/importers/PrimitiveDataPaster.java b/src/org/openstreetmap/josm/gui/datatransfer/importers/PrimitiveDataPaster.java index 344a150ea47..c05363c8724 100644 --- a/src/org/openstreetmap/josm/gui/datatransfer/importers/PrimitiveDataPaster.java +++ b/src/org/openstreetmap/josm/gui/datatransfer/importers/PrimitiveDataPaster.java @@ -91,9 +91,9 @@ private static AddPrimitivesCommand createNewPrimitives(PrimitiveTransferData pa private static EnumMap> generateNewPrimitives(PrimitiveTransferData pasteBuffer, List bufferCopy, List toSelect) { EnumMap> newIds = new EnumMap<>(OsmPrimitiveType.class); - newIds.put(OsmPrimitiveType.NODE, new HashMap()); - newIds.put(OsmPrimitiveType.WAY, new HashMap()); - newIds.put(OsmPrimitiveType.RELATION, new HashMap()); + newIds.put(OsmPrimitiveType.NODE, new HashMap<>()); + newIds.put(OsmPrimitiveType.WAY, new HashMap<>()); + newIds.put(OsmPrimitiveType.RELATION, new HashMap<>()); for (PrimitiveData data : pasteBuffer.getAll()) { if (!data.isUsable()) { diff --git a/src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java b/src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java index d11a969f686..ce3ec4b06d9 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java +++ b/src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java @@ -5,6 +5,7 @@ import java.awt.Component; import java.awt.Dimension; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; @@ -119,10 +120,10 @@ public CommandStackDialog() { treesPanel.add(spacer, GBC.eol()); spacer.setVisible(false); - treesPanel.add(undoTree, GBC.eol().fill(GBC.HORIZONTAL)); + treesPanel.add(undoTree, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); separator.setVisible(false); - treesPanel.add(separator, GBC.eol().fill(GBC.HORIZONTAL)); - treesPanel.add(redoTree, GBC.eol().fill(GBC.HORIZONTAL)); + treesPanel.add(separator, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); + treesPanel.add(redoTree, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); treesPanel.add(Box.createRigidArea(new Dimension(0, 0)), GBC.std().weight(0, 1)); treesPanel.setBackground(redoTree.getBackground()); @@ -145,7 +146,7 @@ public CommandStackDialog() { InputMapUtils.addEnterAction(redoTree, selectAndZoomAction); } - private static class CommandCellRenderer extends DefaultTreeCellRenderer { + private static final class CommandCellRenderer extends DefaultTreeCellRenderer { @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { diff --git a/src/org/openstreetmap/josm/gui/dialogs/ConflictResolutionDialog.java b/src/org/openstreetmap/josm/gui/dialogs/ConflictResolutionDialog.java index 2be1e5f94a3..7812c9c86f4 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/ConflictResolutionDialog.java +++ b/src/org/openstreetmap/josm/gui/dialogs/ConflictResolutionDialog.java @@ -15,6 +15,7 @@ import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; +import javax.swing.SwingConstants; import org.openstreetmap.josm.data.UndoRedoHandler; import org.openstreetmap.josm.data.osm.DefaultNameFormatter; @@ -33,7 +34,7 @@ public class ConflictResolutionDialog extends ExtendedDialog implements PropertyChangeListener { /** the conflict resolver component */ private final ConflictResolver resolver = new ConflictResolver(); - private final JLabel titleLabel = new JLabel("", null, JLabel.CENTER); + private final JLabel titleLabel = new JLabel("", null, SwingConstants.CENTER); private final ApplyResolutionAction applyResolutionAction = new ApplyResolutionAction(); @@ -195,7 +196,7 @@ public void actionPerformed(ActionEvent evt) { options, options[1] ); - switch(ret) { + switch (ret) { case JOptionPane.YES_OPTION: buttonAction(1, evt); break; diff --git a/src/org/openstreetmap/josm/gui/dialogs/DeleteFromRelationConfirmationDialog.java b/src/org/openstreetmap/josm/gui/dialogs/DeleteFromRelationConfirmationDialog.java index 549b175a0f8..2c982d89398 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/DeleteFromRelationConfirmationDialog.java +++ b/src/org/openstreetmap/josm/gui/dialogs/DeleteFromRelationConfirmationDialog.java @@ -214,7 +214,7 @@ public void tableChanged(TableModelEvent e) { * The table model which manages the list of relation-to-child references */ public static class RelationMemberTableModel extends DefaultTableModel { - private static class RelationToChildReferenceComparator implements Comparator, Serializable { + private static final class RelationToChildReferenceComparator implements Comparator, Serializable { private static final long serialVersionUID = 1L; @Override public int compare(RelationToChildReference o1, RelationToChildReference o2) { @@ -291,7 +291,7 @@ public int getNumParentRelations() { public Object getValueAt(int rowIndex, int columnIndex) { if (data == null) return null; RelationToChildReference ref = data.get(rowIndex); - switch(columnIndex) { + switch (columnIndex) { case 0: return ref.getChild(); case 1: return ref.getParent(); case 2: return ref.getPosition()+1; @@ -422,7 +422,7 @@ public Object getValueAt(int rowIndex, int columnIndex) { return null; } Pair ref = this.data.get(rowIndex); - switch(columnIndex) { + switch (columnIndex) { case 0: return ref.a; case 1: return ref.b; default: diff --git a/src/org/openstreetmap/josm/gui/dialogs/InspectPrimitiveDataText.java b/src/org/openstreetmap/josm/gui/dialogs/InspectPrimitiveDataText.java index 94f57d22c35..44150754cd5 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/InspectPrimitiveDataText.java +++ b/src/org/openstreetmap/josm/gui/dialogs/InspectPrimitiveDataText.java @@ -129,6 +129,11 @@ void addState(IPrimitive o) { if (!o.isVisible()) { sb.append(tr("deleted-on-server")).append(INDENT); } + if (o.isReferrersDownloaded()) { + sb.append(tr("all-referrers-downloaded")).append(INDENT); + } else { + sb.append(tr("referrers-not-all-downloaded")).append(INDENT); + } if (o.isModified()) { sb.append(tr("modified")).append(INDENT); } @@ -217,8 +222,8 @@ void addRelationMembers(IRelation r) { for (IRelationMember m : r.getMembers()) { s.append(INDENT).append(INDENT); addHeadline(m.getMember()); - s.append(tr(" as \"{0}\"", m.getRole())); - s.append(NL); + s.append(tr(" as \"{0}\"", m.getRole())) + .append(NL); } } diff --git a/src/org/openstreetmap/josm/gui/dialogs/LatLonDialog.java b/src/org/openstreetmap/josm/gui/dialogs/LatLonDialog.java index 3c084ba6898..cc20cd8fbc3 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/LatLonDialog.java +++ b/src/org/openstreetmap/josm/gui/dialogs/LatLonDialog.java @@ -246,7 +246,7 @@ public void validate() { latLon = null; } if (latLon == null) { - feedbackInvalid(tr("Please enter a GPS coordinates")); + feedbackInvalid(tr("Please enter GPS coordinates")); latLonCoordinates = null; setOkEnabled(false); } else { diff --git a/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java b/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java index a3be6a64e44..58db48a58e7 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java +++ b/src/org/openstreetmap/josm/gui/dialogs/LayerListDialog.java @@ -694,9 +694,9 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole } } - private class LayerNameCellRenderer extends DefaultTableCellRenderer { + private final class LayerNameCellRenderer extends DefaultTableCellRenderer { - protected boolean isActiveLayer(Layer layer) { + boolean isActiveLayer(Layer layer) { return getLayerManager().getActiveLayer() == layer; } diff --git a/src/org/openstreetmap/josm/gui/dialogs/MapPaintDialog.java b/src/org/openstreetmap/josm/gui/dialogs/MapPaintDialog.java index 6c8d7e74567..8a5a212d8e3 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/MapPaintDialog.java +++ b/src/org/openstreetmap/josm/gui/dialogs/MapPaintDialog.java @@ -7,6 +7,7 @@ import java.awt.Component; import java.awt.Dimension; import java.awt.Font; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; @@ -121,7 +122,7 @@ protected void build() { model = new StylesModel(); cbWireframe = new JCheckBox(); - JLabel wfLabel = new JLabel(tr("Wireframe View"), ImageProvider.get("dialogs/mappaint", "wireframe_small"), JLabel.HORIZONTAL); + JLabel wfLabel = new JLabel(tr("Wireframe View"), ImageProvider.get("dialogs/mappaint", "wireframe_small"), SwingConstants.HORIZONTAL); wfLabel.setFont(wfLabel.getFont().deriveFont(Font.PLAIN)); wfLabel.setLabelFor(cbWireframe); @@ -295,7 +296,7 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole } } - private class StyleSourceRenderer extends DefaultTableCellRenderer { + private final class StyleSourceRenderer extends DefaultTableCellRenderer { @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (value == null) @@ -574,7 +575,7 @@ private JPanel addErrorOrWarningTab(final JTabbedPane tabs, JLabel lblInfo, tabs.setTabComponentAt(pos, lblErrors); tabs.setEnabledAt(pos, false); } else { - JLabel lblErrors = new JLabel(tr(title), icon, JLabel.HORIZONTAL); + JLabel lblErrors = new JLabel(tr(title), icon, SwingConstants.HORIZONTAL); lblErrors.setLabelFor(pErrors); tabs.setTabComponentAt(pos, lblErrors); } @@ -600,7 +601,7 @@ private JPanel buildInfoPanel(StyleSource s) { } text.append(tableRow(tr("Style is currently active?"), s.active ? tr("Yes") : tr("No"))) .append(""); - p.add(new JScrollPane(new HtmlPanel(text.toString())), GBC.eol().fill(GBC.BOTH)); + p.add(new JScrollPane(new HtmlPanel(text.toString())), GBC.eol().fill(GridBagConstraints.BOTH)); return p; } diff --git a/src/org/openstreetmap/josm/gui/dialogs/MenuItemSearchDialog.java b/src/org/openstreetmap/josm/gui/dialogs/MenuItemSearchDialog.java index b91cc241331..a98d25e5a37 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/MenuItemSearchDialog.java +++ b/src/org/openstreetmap/josm/gui/dialogs/MenuItemSearchDialog.java @@ -92,7 +92,7 @@ protected void filterItems() { } } - private static class CellRenderer implements ListCellRenderer { + private static final class CellRenderer implements ListCellRenderer { @Override public Component getListCellRendererComponent(JList list, JMenuItem value, int index, diff --git a/src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java b/src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java index 80576c07a94..e34bab04a31 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java +++ b/src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java @@ -1,6 +1,7 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.dialogs; +import static org.openstreetmap.josm.tools.I18n.marktr; import static org.openstreetmap.josm.tools.I18n.tr; import static org.openstreetmap.josm.tools.I18n.trn; @@ -93,6 +94,8 @@ * @since 8 */ public class SelectionListDialog extends ToggleDialog { + private static final String SELECTION_CASING = marktr("Selection"); + private static final String SELECTION = "selection"; private JList lstPrimitives; private final DefaultListSelectionModel selectionModel = new DefaultListSelectionModel(); private final SelectionListModel model = new SelectionListModel(selectionModel); @@ -146,9 +149,9 @@ public void destroy() { * Constructs a new {@code SelectionListDialog}. */ public SelectionListDialog() { - super(tr("Selection"), "selectionlist", tr("Open a selection list window."), + super(tr(SELECTION_CASING), "selectionlist", tr("Open a selection list window."), Shortcut.registerShortcut("subwindow:selection", tr("Windows: {0}", - tr("Selection")), KeyEvent.VK_T, Shortcut.ALT_SHIFT), + tr(SELECTION_CASING)), KeyEvent.VK_T, Shortcut.ALT_SHIFT), 150, // default height true // default is "show dialog" ); @@ -394,7 +397,7 @@ class ZoomToJOSMSelectionAction extends AbstractAction implements ListDataListen ZoomToJOSMSelectionAction() { putValue(NAME, tr("Zoom to selection")); putValue(SHORT_DESCRIPTION, tr("Zoom to selection")); - new ImageProvider("dialogs/autoscale", "selection").getResource().attachImageIcon(this, true); + new ImageProvider("dialogs/autoscale", SELECTION).getResource().attachImageIcon(this, true); updateEnabledState(); } @@ -433,7 +436,7 @@ class ZoomToListSelection extends AbstractAction implements ListSelectionListene * Constructs a new {@code ZoomToListSelection}. */ ZoomToListSelection() { - new ImageProvider("dialogs/autoscale", "selection").getResource().attachImageIcon(this, true); + new ImageProvider("dialogs/autoscale", SELECTION).getResource().attachImageIcon(this, true); updateEnabledState(); } @@ -463,7 +466,7 @@ public void valueChanged(ListSelectionEvent e) { /** * The list model for the list of OSM primitives in the current JOSM selection. - * + *

      * The model also maintains a history of the last {@link SelectionListModel#SELECTION_HISTORY_SIZE} * JOSM selection. * @@ -492,12 +495,12 @@ static class SelectionListModel extends AbstractListModel * @return a summary of the current JOSM selection */ public synchronized String getJOSMSelectionSummary() { - if (selection.isEmpty()) return tr("Selection"); + if (selection.isEmpty()) return tr(SELECTION_CASING); int numNodes = 0; int numWays = 0; int numRelations = 0; for (OsmPrimitive p: selection) { - switch(p.getType()) { + switch (p.getType()) { case NODE: numNodes++; break; case WAY: numWays++; break; case RELATION: numRelations++; break; @@ -525,7 +528,7 @@ public void remember(Collection selection) { IntStream.range(1, history.size()) .filter(i -> history.get(i).equals(selection)) .findFirst() - .ifPresent(i -> history.remove(i)); + .ifPresent(history::remove); int maxsize = Config.getPref().getInt("select.history-size", SELECTION_HISTORY_SIZE); while (history.size() > maxsize) { history.removeLast(); @@ -649,7 +652,7 @@ public synchronized void sort() { ? OsmPrimitiveComparator.comparingUniqueId() : OsmPrimitiveComparator.comparingNames())); } catch (IllegalArgumentException e) { - throw BugReport.intercept(e).put("size", size).put("quick", quick).put("selection", selection); + throw BugReport.intercept(e).put("size", size).put("quick", quick).put(SELECTION, selection); } } } diff --git a/src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java b/src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java index 5c1b5fa3e7c..2ecea9d1d28 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java +++ b/src/org/openstreetmap/josm/gui/dialogs/ToggleDialog.java @@ -10,6 +10,7 @@ import java.awt.FlowLayout; import java.awt.Graphics; import java.awt.GraphicsEnvironment; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Rectangle; @@ -45,6 +46,7 @@ import javax.swing.JScrollPane; import javax.swing.JToggleButton; import javax.swing.Scrollable; +import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import org.openstreetmap.josm.actions.JosmAction; @@ -99,25 +101,26 @@ public enum ButtonHidingType { * @since 6752 */ public static final BooleanProperty PROP_DYNAMIC_BUTTONS = new BooleanProperty("dialog.dynamic.buttons", false); + private static final String SELECTED = "selected"; private final transient ParametrizedEnumProperty propButtonHiding = - new ParametrizedEnumProperty(ButtonHidingType.class, ButtonHidingType.DYNAMIC) { - @Override - protected String getKey(String... params) { - return preferencePrefix + ".buttonhiding"; - } + new ParametrizedEnumProperty<>(ButtonHidingType.class, ButtonHidingType.DYNAMIC) { + @Override + protected String getKey(String... params) { + return preferencePrefix + ".buttonhiding"; + } - @Override - protected ButtonHidingType parse(String s) { - try { - return super.parse(s); - } catch (IllegalArgumentException e) { - // Legacy settings - Logging.trace(e); - return Boolean.parseBoolean(s) ? ButtonHidingType.DYNAMIC : ButtonHidingType.ALWAYS_SHOWN; - } - } - }; + @Override + protected ButtonHidingType parse(String s) { + try { + return super.parse(s); + } catch (IllegalArgumentException e) { + // Legacy settings + Logging.trace(e); + return Boolean.parseBoolean(s) ? ButtonHidingType.DYNAMIC : ButtonHidingType.ALWAYS_SHOWN; + } + } + }; /** The action to toggle this dialog */ protected final ToggleDialogAction toggleAction; @@ -353,8 +356,8 @@ public void showDialog() { if (windowMenuItem != null) { windowMenuItem.setState(true); } - toggleAction.putValue("selected", Boolean.FALSE); - toggleAction.putValue("selected", Boolean.TRUE); + toggleAction.putValue(SELECTED, Boolean.FALSE); + toggleAction.putValue(SELECTED, Boolean.TRUE); } /** @@ -381,7 +384,7 @@ public void unfurlDialog() { @Override public void buttonHidden() { - if (Boolean.TRUE.equals(toggleAction.getValue("selected"))) { + if (Boolean.TRUE.equals(toggleAction.getValue(SELECTED))) { toggleAction.actionPerformed(null); } } @@ -401,7 +404,7 @@ public void hideDialog() { windowMenuItem.setState(false); } setIsShowing(false); - toggleAction.putValue("selected", Boolean.FALSE); + toggleAction.putValue(SELECTED, Boolean.FALSE); } /** @@ -550,7 +553,7 @@ public TitleBar(String toggleDialogName, String iconName) { // scale down the dialog icon ImageIcon icon = ImageProvider.get("dialogs", iconName, ImageProvider.ImageSizes.SMALLICON); - lblTitle = new JLabel("", icon, JLabel.TRAILING); + lblTitle = new JLabel("", icon, SwingConstants.TRAILING); lblTitle.setIconTextGap(8); JPanel conceal = new JPanel(); @@ -568,7 +571,7 @@ public void paintComponent(Graphics g) { }; lblTitleWeak.setPreferredSize(new Dimension(Integer.MAX_VALUE, 20)); lblTitleWeak.setMinimumSize(new Dimension(0, 20)); - add(lblTitleWeak, GBC.std().fill(GBC.HORIZONTAL)); + add(lblTitleWeak, GBC.std().fill(GridBagConstraints.HORIZONTAL)); buttonsHide = new JButton(ImageProvider.get("misc", buttonHiding != ButtonHidingType.ALWAYS_SHOWN ? /* ICON(misc/)*/ "buttonhide" : /* ICON(misc/)*/ "buttonshow")); diff --git a/src/org/openstreetmap/josm/gui/dialogs/UserListDialog.java b/src/org/openstreetmap/josm/gui/dialogs/UserListDialog.java index c8c971f3636..476a05d76e6 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/UserListDialog.java +++ b/src/org/openstreetmap/josm/gui/dialogs/UserListDialog.java @@ -350,7 +350,7 @@ public int getRowCount() { @Override public Object getValueAt(int row, int column) { UserInfo info = data.get(row); - switch(column) { + switch (column) { case 0: /* author */ return info.getName() == null ? "" : info.getName(); case 1: /* count */ return info.count; case 2: /* percent */ return NumberFormat.getPercentInstance().format(info.percent); diff --git a/src/org/openstreetmap/josm/gui/dialogs/ValidatorDialog.java b/src/org/openstreetmap/josm/gui/dialogs/ValidatorDialog.java index a35a308e81f..4186cdc5708 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/ValidatorDialog.java +++ b/src/org/openstreetmap/josm/gui/dialogs/ValidatorDialog.java @@ -68,6 +68,7 @@ import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; import org.openstreetmap.josm.io.OsmTransferException; import org.openstreetmap.josm.spi.preferences.Config; +import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.InputMapUtils; import org.openstreetmap.josm.tools.JosmRuntimeException; @@ -174,7 +175,7 @@ public void actionPerformed(ActionEvent e) { fixAction.setEnabled(false); buttons.add(new SideButton(fixAction)); - if (ValidatorPrefHelper.PREF_USE_IGNORE.get()) { + if (Boolean.TRUE.equals(ValidatorPrefHelper.PREF_USE_IGNORE.get())) { ignoreAction = new AbstractAction() { { putValue(NAME, tr("Ignore")); @@ -428,7 +429,7 @@ private boolean setSelection(Collection sel, boolean addSelected) public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { OsmDataLayer editLayer = e.getSource().getEditLayer(); if (editLayer == null) { - tree.setErrorList(new ArrayList()); + tree.setErrorList(new ArrayList<>()); } else { tree.setErrorList(editLayer.validationErrors); } @@ -587,12 +588,16 @@ public void visit(TestError error) { * @param newSelection The new selection */ public void updateSelection(Collection newSelection) { - if (!Config.getPref().getBoolean(ValidatorPrefHelper.PREF_FILTER_BY_SELECTION, false)) + if (!Config.getPref().getBoolean(ValidatorPrefHelper.PREF_FILTER_BY_SELECTION, false)) { + if (tree.getFilter() != null) + tree.setFilter(null); return; - if (newSelection.isEmpty()) { - tree.setFilter(null); } - tree.setFilter(new HashSet<>(newSelection)); + + if (newSelection.isEmpty()) + tree.setFilter(null); + else + tree.setFilter(new HashSet<>(newSelection)); } @Override @@ -696,7 +701,10 @@ private void tryUndo() { } } - private static void invalidateValidatorLayers() { + /** + * Invalidate the error layer + */ + public static void invalidateValidatorLayers() { MainApplication.getLayerManager().getLayersOfType(ValidatorLayer.class).forEach(ValidatorLayer::invalidate); } @@ -730,4 +738,17 @@ public void destroy() { ignoreForNowAction.destroy(); } } + + @Override + public void preferenceChanged(PreferenceChangeEvent e) { + super.preferenceChanged(e); + // see #23430: update selection so that filters are applied + if (ValidatorPrefHelper.PREF_FILTER_BY_SELECTION.equals(e.getKey())) { + DataSet ds = MainApplication.getLayerManager().getActiveDataSet(); + if (ds != null) { + updateSelection(ds.getAllSelected()); + } + } + } + } diff --git a/src/org/openstreetmap/josm/gui/dialogs/changeset/AbstractCellRenderer.java b/src/org/openstreetmap/josm/gui/dialogs/changeset/AbstractCellRenderer.java index 9c4f94ba723..402adf77846 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/changeset/AbstractCellRenderer.java +++ b/src/org/openstreetmap/josm/gui/dialogs/changeset/AbstractCellRenderer.java @@ -56,7 +56,7 @@ protected void renderId(long id) { } protected void renderUser(User user) { - if (user == null || Utils.isBlank(user.getName())) { + if (user == null || Utils.isStripEmpty(user.getName())) { setFont(UIManager.getFont("Table.font").deriveFont(Font.ITALIC)); setText(tr("anonymous")); } else { diff --git a/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheTableCellRenderer.java b/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheTableCellRenderer.java index bf64aef9a4e..2997577fd08 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheTableCellRenderer.java +++ b/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheTableCellRenderer.java @@ -56,7 +56,7 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole reset(); renderColors(isSelected); Changeset cs = (Changeset) value; - switch(column) { + switch (column) { case 0: /* id */ renderId(cs.getId()); break; case 1: /* upload comment */ renderUploadComment(cs); break; case 2: /* open/closed */ renderOpen(cs); break; diff --git a/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableCellRenderer.java b/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableCellRenderer.java index 6ab5952410b..881e3adf748 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableCellRenderer.java +++ b/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableCellRenderer.java @@ -21,7 +21,7 @@ public class ChangesetContentTableCellRenderer extends AbstractCellRenderer { * @param type modification type */ protected void renderModificationType(ChangesetModificationType type) { - switch(type) { + switch (type) { case CREATED: setText(tr("Created")); break; case UPDATED: setText(tr("Updated")); break; case DELETED: setText(tr("Deleted")); break; @@ -36,7 +36,7 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole return this; reset(); renderColors(isSelected); - switch(column) { + switch (column) { case 0: if (value instanceof ChangesetModificationType) { renderModificationType((ChangesetModificationType) value); diff --git a/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableModel.java b/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableModel.java index 4e021b04ba2..d3f70017c67 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableModel.java +++ b/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetContentTableModel.java @@ -113,7 +113,7 @@ public int getRowCount() { @Override public Object getValueAt(int row, int col) { - switch(col) { + switch (col) { case 0: return data.get(row).getModificationType(); default: return data.get(row).getPrimitive(); } diff --git a/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDiscussionTableCellRenderer.java b/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDiscussionTableCellRenderer.java index 7415d4fc209..1a506778703 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDiscussionTableCellRenderer.java +++ b/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetDiscussionTableCellRenderer.java @@ -24,7 +24,7 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole JComponent comp = this; reset(comp, true); renderColors(comp, isSelected); - switch(column) { + switch (column) { case 0: renderInstant((Instant) value); break; diff --git a/src/org/openstreetmap/josm/gui/dialogs/changeset/query/BasicChangesetQueryPanel.java b/src/org/openstreetmap/josm/gui/dialogs/changeset/query/BasicChangesetQueryPanel.java index b9fe397bba2..9d123d8e6bc 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/changeset/query/BasicChangesetQueryPanel.java +++ b/src/org/openstreetmap/josm/gui/dialogs/changeset/query/BasicChangesetQueryPanel.java @@ -164,7 +164,7 @@ public void restoreFromPreferences() { q = BasicQuery.MOST_RECENT_CHANGESETS; } else { try { - q = BasicQuery.valueOf(BasicQuery.class, value); + q = BasicQuery.valueOf(value); } catch (IllegalArgumentException e) { Logging.log(Logging.LEVEL_WARN, tr("Unexpected value for preference ''{0}'', got ''{1}''. Resetting to default query.", "changeset-query.basic.query", value), e); @@ -193,7 +193,7 @@ public ChangesetQuery buildChangesetQuery() { UserIdentityManager im = UserIdentityManager.getInstance(); if (q == null) return query; - switch(q) { + switch (q) { case MOST_RECENT_CHANGESETS: break; case MY_OPEN_CHANGESETS: diff --git a/src/org/openstreetmap/josm/gui/dialogs/changeset/query/ChangesetQueryDialog.java b/src/org/openstreetmap/josm/gui/dialogs/changeset/query/ChangesetQueryDialog.java index 0bd66985bc6..5c63b8841cd 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/changeset/query/ChangesetQueryDialog.java +++ b/src/org/openstreetmap/josm/gui/dialogs/changeset/query/ChangesetQueryDialog.java @@ -125,7 +125,7 @@ protected void setCanceled(boolean canceled) { public ChangesetQuery getChangesetQuery() { if (isCanceled()) return null; - switch(tpQueryPanels.getSelectedIndex()) { + switch (tpQueryPanels.getSelectedIndex()) { case 0: return pnlBasicChangesetQueries.buildChangesetQuery(); case 1: @@ -185,7 +185,7 @@ protected void alertInvalidChangesetQuery() { @Override public void actionPerformed(ActionEvent arg0) { try { - switch(tpQueryPanels.getSelectedIndex()) { + switch (tpQueryPanels.getSelectedIndex()) { case 0: // currently, query specifications can't be invalid in the basic query panel. // We select from a couple of predefined queries and there is always a query diff --git a/src/org/openstreetmap/josm/gui/dialogs/changeset/query/UidInputFieldValidator.java b/src/org/openstreetmap/josm/gui/dialogs/changeset/query/UidInputFieldValidator.java index 3ecdf5909c2..bd7b090adba 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/changeset/query/UidInputFieldValidator.java +++ b/src/org/openstreetmap/josm/gui/dialogs/changeset/query/UidInputFieldValidator.java @@ -39,7 +39,7 @@ public boolean isValid() { @Override public void validate() { String value = getComponent().getText(); - if (Utils.isBlank(value)) { + if (Utils.isStripEmpty(value)) { feedbackInvalid(""); return; } @@ -62,7 +62,7 @@ public void validate() { */ public int getUid() { String value = getComponent().getText(); - if (Utils.isBlank(value)) return 0; + if (Utils.isStripEmpty(value)) return 0; try { int uid = Integer.parseInt(value.trim()); return Math.max(uid, 0); diff --git a/src/org/openstreetmap/josm/gui/dialogs/layer/MergeGpxLayerDialog.java b/src/org/openstreetmap/josm/gui/dialogs/layer/MergeGpxLayerDialog.java index 4bcc39810ff..cdb5d60e8cb 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/layer/MergeGpxLayerDialog.java +++ b/src/org/openstreetmap/josm/gui/dialogs/layer/MergeGpxLayerDialog.java @@ -4,6 +4,7 @@ import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.Component; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -57,11 +58,11 @@ public MergeGpxLayerDialog(Component parent, List layers) { JPanel p = new JPanel(new GridBagLayout()); p.add(new JLabel("" + tr("Please select the order of the selected layers:
      Tracks will be cut, when timestamps of higher layers are overlapping.") + - ""), GBC.std(0, 0).fill(GBC.HORIZONTAL).span(2)); + ""), GBC.std(0, 0).fill(GridBagConstraints.HORIZONTAL).span(2)); c = new JCheckBox(tr("Connect overlapping tracks on cuts")); c.setSelected(Config.getPref().getBoolean("mergelayer.gpx.connect", true)); - p.add(c, GBC.std(0, 1).fill(GBC.HORIZONTAL).span(2)); + p.add(c, GBC.std(0, 1).fill(GridBagConstraints.HORIZONTAL).span(2)); model = new GpxLayersTableModel(layers); t = new JTable(model); @@ -84,8 +85,8 @@ public MergeGpxLayerDialog(Component parent, List layers) { btnDown = new JButton(tr("Move layer down")); btnDown.setIcon(ImageProvider.get("dialogs", "down", ImageSizes.SMALLICON)); - p.add(btnUp, GBC.std(0, 3).fill(GBC.HORIZONTAL)); - p.add(btnDown, GBC.std(1, 3).fill(GBC.HORIZONTAL)); + p.add(btnUp, GBC.std(0, 3).fill(GridBagConstraints.HORIZONTAL)); + p.add(btnDown, GBC.std(1, 3).fill(GridBagConstraints.HORIZONTAL)); btnUp.addActionListener(new MoveLayersActionListener(true)); btnDown.addActionListener(new MoveLayersActionListener(false)); @@ -151,7 +152,7 @@ public void actionPerformed(ActionEvent e) { } } - private class RowSelectionChangedListener implements ListSelectionListener { + private final class RowSelectionChangedListener implements ListSelectionListener { @Override public void valueChanged(ListSelectionEvent e) { diff --git a/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java b/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java index 922413e4673..0783d66f815 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java +++ b/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java @@ -7,6 +7,7 @@ import java.awt.Component; import java.awt.Container; import java.awt.Font; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Point; import java.awt.event.ActionEvent; @@ -100,9 +101,9 @@ import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor; import org.openstreetmap.josm.gui.dialogs.relation.RelationPopupMenus; import org.openstreetmap.josm.gui.help.HelpUtil; +import org.openstreetmap.josm.gui.layer.Layer; import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent; import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; -import org.openstreetmap.josm.gui.layer.Layer; import org.openstreetmap.josm.gui.layer.OsmDataLayer; import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler; @@ -292,18 +293,18 @@ public PropertiesDialog() { boolean top = Config.getPref().getBoolean("properties.presets.top", true); boolean presetsVisible = Config.getPref().getBoolean("properties.presets.visible", true); if (presetsVisible && top) { - bothTables.add(presets, GBC.std().fill(GBC.HORIZONTAL).insets(5, 2, 5, 2).anchor(GBC.NORTHWEST)); + bothTables.add(presets, GBC.std().fill(GridBagConstraints.HORIZONTAL).insets(5, 2, 5, 2).anchor(GridBagConstraints.NORTHWEST)); double epsilon = Double.MIN_VALUE; // need to set a weight or else anchor value is ignored - bothTables.add(pluginHook, GBC.eol().insets(0, 1, 1, 1).anchor(GBC.NORTHEAST).weight(epsilon, epsilon)); + bothTables.add(pluginHook, GBC.eol().insets(0, 1, 1, 1).anchor(GridBagConstraints.NORTHEAST).weight(epsilon, epsilon)); } bothTables.add(selectSth, GBC.eol().fill().insets(10, 10, 10, 10)); - bothTables.add(tagTableFilter, GBC.eol().fill(GBC.HORIZONTAL)); - bothTables.add(tagTable.getTableHeader(), GBC.eol().fill(GBC.HORIZONTAL)); - bothTables.add(tagTable, GBC.eol().fill(GBC.BOTH)); - bothTables.add(membershipTable.getTableHeader(), GBC.eol().fill(GBC.HORIZONTAL)); - bothTables.add(membershipTable, GBC.eol().fill(GBC.BOTH)); + bothTables.add(tagTableFilter, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); + bothTables.add(tagTable.getTableHeader(), GBC.eol().fill(GridBagConstraints.HORIZONTAL)); + bothTables.add(tagTable, GBC.eol().fill(GridBagConstraints.BOTH)); + bothTables.add(membershipTable.getTableHeader(), GBC.eol().fill(GridBagConstraints.HORIZONTAL)); + bothTables.add(membershipTable, GBC.eol().fill(GridBagConstraints.BOTH)); if (presetsVisible && !top) { - bothTables.add(presets, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 2, 5, 2)); + bothTables.add(presets, GBC.eol().fill(GridBagConstraints.HORIZONTAL).insets(5, 2, 5, 2)); } setupBlankSpaceMenu(); @@ -1503,7 +1504,7 @@ static SearchSetting createSearchSetting(String key, Collection { + private final class HoverPreviewPropListener implements ValueChangeListener { @Override public void valueChanged(ValueChangeEvent e) { if (Boolean.TRUE.equals(e.getProperty().get()) && isDialogShowing()) { @@ -1541,7 +1542,7 @@ public void valueChanged(ValueChangeEvent e) { * Ensure HoverListener is re-added when selection priority is disabled while something is selected. * Otherwise user would need to change selection to see the preference change take effect. */ - private class HoverPreviewPreferSelectionPropListener implements ValueChangeListener { + private final class HoverPreviewPreferSelectionPropListener implements ValueChangeListener { @Override public void valueChanged(ValueChangeEvent e) { if (Boolean.FALSE.equals(e.getProperty().get()) && diff --git a/src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java b/src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java index 242f934e936..b38e729cf4f 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java +++ b/src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java @@ -1,6 +1,7 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.dialogs.properties; +import static org.openstreetmap.josm.tools.I18n.marktr; import static org.openstreetmap.josm.tools.I18n.tr; import static org.openstreetmap.josm.tools.I18n.trn; @@ -123,6 +124,10 @@ public class TagEditHelper { static final Comparator DEFAULT_AC_ITEM_COMPARATOR = (o1, o2) -> String.CASE_INSENSITIVE_ORDER.compare(o1.getValue(), o2.getValue()); + private static final String CANCEL_TR = marktr("Cancel"); + private static final String CANCEL = "cancel"; + private static final String HTML = ""; + private static final String DUMMY = "dummy"; /** Default number of recent tags */ public static final int DEFAULT_LRU_TAGS_NUMBER = 5; /** Maximum number of recent tags */ @@ -140,7 +145,7 @@ public class TagEditHelper { DEFAULT_LRU_TAGS_NUMBER); /** The preference storage of recent tags */ public static final ListProperty PROPERTY_RECENT_TAGS = new ListProperty("properties.recent-tags", - Collections.emptyList()); + Collections.emptyList()); /** The preference list of tags which should not be remembered, since r9940 */ public static final StringProperty PROPERTY_TAGS_TO_IGNORE = new StringProperty("properties.recent-tags.ignore", new SearchSetting().writeToString()); @@ -180,7 +185,7 @@ private enum RefreshRecent { /** * Copy of recently added tags in sorted from newest to oldest order. - * + *

      * We store the maximum number of recent tags to allow dynamic change of number of tags shown in the preferences. * Used to cache initial status. */ @@ -277,27 +282,24 @@ public void addTag() { DataSet activeDataSet = OsmDataManager.getInstance().getActiveDataSet(); if (activeDataSet == null) return; - try { - activeDataSet.beginUpdate(); - Collection selection = OsmDataManager.getInstance().getInProgressSelection(); - this.sel = selection; - if (Utils.isEmpty(selection)) - return; + final Collection selection = updateSelection(); + + if (Utils.isEmpty(selection)) + return; - final AddTagsDialog addDialog = getAddTagsDialog(); + final AddTagsDialog addDialog = getAddTagsDialog(); - addDialog.showDialog(); + addDialog.showDialog(); - addDialog.destroyActions(); + addDialog.destroyActions(); + activeDataSet.update(() -> { // Remote control can cause the selection to change, see #23191. - if (addDialog.getValue() == 1 && (selection == sel || warnSelectionChanged())) { + if (addDialog.getValue() == 1 && (selection.equals(updateSelection()) || warnSelectionChanged())) { addDialog.performTagAdding(selection); } else { addDialog.undoAllTagsAdding(); } - } finally { - activeDataSet.endUpdate(); - } + }); } /** @@ -316,7 +318,7 @@ protected AddTagsDialog getAddTagsDialog() { */ public void editTag(final int row, boolean focusOnKey) { changedKey = null; - sel = OsmDataManager.getInstance().getInProgressSelection(); + updateSelection(); if (Utils.isEmpty(sel)) return; @@ -359,6 +361,21 @@ public void resetChangedKey() { changedKey = null; } + /** + * Update the current selection for this editor + */ + private Collection updateSelection() { + final DataSet activeDataSet = OsmDataManager.getInstance().getActiveDataSet(); + activeDataSet.getReadLock().lock(); + try { + Collection selection = new ArrayList<>(OsmDataManager.getInstance().getInProgressSelection()); + this.sel = selection; + return selection; + } finally { + activeDataSet.getReadLock().unlock(); + } + } + /** * For a given key k, return a list of keys which are used as keys for * auto-completing values to increase the search space. @@ -369,7 +386,7 @@ private static List getAutocompletionKeys(String key) { if ("name".equals(key) || "addr:street".equals(key)) return Arrays.asList("addr:street", "name"); else - return Arrays.asList(key); + return Collections.singletonList(key); } /** @@ -477,8 +494,8 @@ private static boolean warnOverwriteKey(String action, String togglePref) { return new ExtendedDialog( MainApplication.getMainFrame(), tr("Overwrite tag"), - tr("Overwrite"), tr("Cancel")) - .setButtonIcons("ok", "cancel") + tr("Overwrite"), tr(CANCEL_TR)) + .setButtonIcons("ok", CANCEL) .setContent(action) .setCancelButton(2) .toggleEnable(togglePref) @@ -492,8 +509,8 @@ protected class EditTagDialog extends AbstractTagsDialog implements IEditTagDial private final transient AutoCompletionManager autocomplete; protected EditTagDialog(String key, Map map, boolean initialFocusOnKey) { - super(MainApplication.getMainFrame(), trn("Change value?", "Change values?", map.size()), tr("OK"), tr("Cancel")); - setButtonIcons("ok", "cancel"); + super(MainApplication.getMainFrame(), trn("Change value?", "Change values?", map.size()), tr("OK"), tr(CANCEL_TR)); + setButtonIcons("ok", CANCEL); setCancelButton(2); configureContextsensitiveHelp("/Dialog/EditValue", true /* show help button */); this.key = key; @@ -513,7 +530,7 @@ else if (c1) JPanel mainPanel = new JPanel(new BorderLayout()); - String msg = ""+trn("This will change {0} object.", + String msg = HTML+trn("This will change {0} object.", "This will change up to {0} objects.", sel.size(), sel.size()) +"

      ("+tr("An empty value deletes the tag.", key)+")"; @@ -544,14 +561,14 @@ public void applyComponentOrientation(ComponentOrientation o) { keys = new AutoCompComboBox<>(); keys.getModel().setComparator(Comparator.naturalOrder()); // according to Comparable keys.setEditable(true); - keys.setPrototypeDisplayValue(new AutoCompletionItem("dummy")); + keys.setPrototypeDisplayValue(new AutoCompletionItem(DUMMY)); keys.getModel().addAllElements(keyList); keys.setSelectedItemText(key); p.add(Box.createVerticalStrut(5), GBC.eol()); p.add(new JLabel(tr("Key")), GBC.std()); p.add(Box.createHorizontalStrut(10), GBC.std()); - p.add(keys, GBC.eol().fill(GBC.HORIZONTAL)); + p.add(keys, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); List valueList = autocomplete.getTagValues(getAutocompletionKeys(key), usedValuesAwareComparator); @@ -561,14 +578,14 @@ public void applyComponentOrientation(ComponentOrientation o) { values.getModel().setComparator(Comparator.naturalOrder()); values.setRenderer(new TEHListCellRenderer(values, values.getRenderer(), valueCount.get(key))); values.setEditable(true); - values.setPrototypeDisplayValue(new AutoCompletionItem("dummy")); + values.setPrototypeDisplayValue(new AutoCompletionItem(DUMMY)); values.getModel().addAllElements(valueList); values.setSelectedItemText(selection); p.add(Box.createVerticalStrut(5), GBC.eol()); p.add(new JLabel(tr("Value")), GBC.std()); p.add(Box.createHorizontalStrut(10), GBC.std()); - p.add(values, GBC.eol().fill(GBC.HORIZONTAL)); + p.add(values, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); p.add(Box.createVerticalStrut(2), GBC.eol()); p.applyComponentOrientation(OrientationAction.getDefaultComponentOrientation()); @@ -605,6 +622,8 @@ public void performTagEdit() { } if (key.equals(newkey) && KeyedItem.DIFFERENT_I18N.equals(value)) return; + if (value != null && key.equals(getEditItem(keys)) && m.size() == 1 && m.containsKey(getEditItem(values))) + return; // see #22814: avoid to create a command that wouldn't change anything if (key.equals(newkey) || value == null) { UndoRedoHandler.getInstance().add(new ChangePropertyCommand(sel, newkey, value)); if (value != null) { @@ -802,8 +821,8 @@ protected class AddTagsDialog extends AbstractTagsDialog { private final transient AutoCompletionManager autocomplete; protected AddTagsDialog() { - super(MainApplication.getMainFrame(), tr("Add tag"), tr("OK"), tr("Cancel")); - setButtonIcons("ok", "cancel"); + super(MainApplication.getMainFrame(), tr("Add tag"), tr("OK"), tr(CANCEL_TR)); + setButtonIcons("ok", CANCEL); setCancelButton(2); configureContextsensitiveHelp("/Dialog/AddValue", true /* show help button */); @@ -824,26 +843,26 @@ public void applyComponentOrientation(ComponentOrientation o) { setComponentOrientation(o); } }; - mainPanel.add(new JLabel(""+trn("This will change up to {0} object.", + mainPanel.add(new JLabel(HTML+trn("This will change up to {0} object.", "This will change up to {0} objects.", sel.size(), sel.size()) - +"

      "+tr("Please select a key")), GBC.eol().fill(GBC.HORIZONTAL)); + +"

      "+tr("Please select a key")), GBC.eol().fill(GridBagConstraints.HORIZONTAL)); keys = new AutoCompComboBox<>(); - keys.setPrototypeDisplayValue(new AutoCompletionItem("dummy")); + keys.setPrototypeDisplayValue(new AutoCompletionItem(DUMMY)); keys.setEditable(true); keys.getModel().setComparator(Comparator.naturalOrder()); // according to Comparable keys.setAutocompleteEnabled(AUTOCOMPLETE_KEYS.get()); - mainPanel.add(keys, GBC.eop().fill(GBC.HORIZONTAL)); + mainPanel.add(keys, GBC.eop().fill(GridBagConstraints.HORIZONTAL)); mainPanel.add(new JLabel(tr("Choose a value")), GBC.eol()); values = new AutoCompComboBox<>(); - values.setPrototypeDisplayValue(new AutoCompletionItem("dummy")); + values.setPrototypeDisplayValue(new AutoCompletionItem(DUMMY)); values.setEditable(true); values.getModel().setComparator(Comparator.naturalOrder()); values.setAutocompleteEnabled(AUTOCOMPLETE_VALUES.get()); - mainPanel.add(values, GBC.eop().fill(GBC.HORIZONTAL)); + mainPanel.add(values, GBC.eop().fill(GridBagConstraints.HORIZONTAL)); cacheRecentTags(); autocomplete = AutoCompletionManager.of(OsmDataManager.getInstance().getActiveDataSet()); @@ -975,12 +994,12 @@ public void setContentPane(Container contentPane) { Shortcut.findShortcut(KeyEvent.VK_1, commandDownMask).ifPresent(sc -> lines.add(sc.getKeyText() + ' ' + tr("to apply first suggestion")) ); - lines.add(Shortcut.getKeyText(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.SHIFT_DOWN_MASK)) + ' ' + lines.add(Shortcut.getKeyText(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.SHIFT_DOWN_MASK)) + ' ' +tr("to add without closing the dialog")); - Shortcut.findShortcut(KeyEvent.VK_1, commandDownMask | KeyEvent.SHIFT_DOWN_MASK).ifPresent(sc -> + Shortcut.findShortcut(KeyEvent.VK_1, commandDownMask | InputEvent.SHIFT_DOWN_MASK).ifPresent(sc -> lines.add(sc.getKeyText() + ' ' + tr("to add first suggestion without closing the dialog")) ); - final JLabel helpLabel = new JLabel("" + String.join("
      ", lines) + ""); + final JLabel helpLabel = new JLabel(HTML + String.join("
      ", lines) + ""); helpLabel.setFont(helpLabel.getFont().deriveFont(Font.PLAIN)); contentPane.add(helpLabel, GBC.eol().fill(GridBagConstraints.HORIZONTAL).insets(5, 5, 5, 5)); super.setContentPane(contentPane); @@ -1010,7 +1029,7 @@ protected void suggestRecentlyAddedTags() { if (recentTagsPanel == null) { recentTagsPanel = new JPanel(new GridBagLayout()); buildRecentTagsPanel(); - mainPanel.add(recentTagsPanel, GBC.eol().fill(GBC.HORIZONTAL)); + mainPanel.add(recentTagsPanel, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); } else { Dimension panelOldSize = recentTagsPanel.getPreferredSize(); recentTagsPanel.removeAll(); @@ -1082,7 +1101,7 @@ public void actionPerformed(ActionEvent e) { recentTagsPanel.add(new JLabel(action.isEnabled() ? icon : GuiHelper.getDisabledIcon(icon)), gbc); // Create tag label final String color = action.isEnabled() ? "" : "; color:gray"; - final JLabel tagLabel = new JLabel("" + final JLabel tagLabel = new JLabel(HTML + "" + "" + "" @@ -1129,7 +1148,7 @@ public void mouseClicked(MouseEvent e) { // Finally add label to the resulting panel JPanel tagPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); tagPanel.add(tagLabel); - recentTagsPanel.add(tagPanel, GBC.eol().fill(GBC.HORIZONTAL)); + recentTagsPanel.add(tagPanel, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); } // Clear label if no tags were added if (count == 0) { @@ -1223,7 +1242,7 @@ private void performTagAdding(Collection selection) { String val = osm.get(key); if (val != null && !val.equals(value)) { String valueHtmlString = Utils.joinAsHtmlUnorderedList(Arrays.asList("" + val + "", value)); - if (!warnOverwriteKey("" + if (!warnOverwriteKey(HTML + tr("You changed the value of ''{0}'': {1}", key, valueHtmlString) + tr("Overwrite?"), "overwriteAddKey")) return; diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java b/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java index 01e46ce3621..2fb49413e44 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java +++ b/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java @@ -32,6 +32,7 @@ import java.util.stream.Collectors; import javax.swing.AbstractAction; +import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.InputMap; import javax.swing.JButton; @@ -47,6 +48,7 @@ import javax.swing.JTable; import javax.swing.JToolBar; import javax.swing.KeyStroke; +import javax.swing.SwingConstants; import javax.swing.event.TableModelListener; import org.openstreetmap.josm.actions.JosmAction; @@ -55,7 +57,6 @@ import org.openstreetmap.josm.data.UndoRedoHandler; import org.openstreetmap.josm.data.UndoRedoHandler.CommandQueueListener; import org.openstreetmap.josm.data.osm.DefaultNameFormatter; -import org.openstreetmap.josm.data.osm.IRelation; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.Relation; import org.openstreetmap.josm.data.osm.RelationMember; @@ -405,6 +406,7 @@ protected static JToolBar buildToolBar(AbstractRelationEditorAction... actions) /** * builds the panel with the OK and the Cancel button * @param okAction OK action + * @param deleteAction Delete action * @param cancelAction Cancel action * * @return the panel with the OK and the Cancel button @@ -421,7 +423,7 @@ protected final JPanel buildOkCancelButtonPanel(OKAction okAction, DeleteCurrent pnl.add(new JButton(new ContextSensitiveHelpAction(ht("/Dialog/RelationEditor")))); // Keep users from saving invalid relations -- a relation MUST have at least a tag with the key "type" // AND must contain at least one other OSM object. - final TableModelListener listener = l -> updateOkPanel(this.actionAccess.getChangedRelation(), okButton, deleteButton); + final TableModelListener listener = l -> updateOkPanel(okButton, deleteButton); listener.tableChanged(null); this.memberTableModel.addTableModelListener(listener); this.tagEditorPanel.getModel().addTableModelListener(listener); @@ -429,15 +431,15 @@ protected final JPanel buildOkCancelButtonPanel(OKAction okAction, DeleteCurrent } /** - * Update the OK panel area - * @param newRelation What the new relation would "look" like if it were to be saved now + * Update the OK panel area with a temporary relation that looks if it were to be saved now. * @param okButton The OK button * @param deleteButton The delete button */ - private void updateOkPanel(IRelation newRelation, JButton okButton, JButton deleteButton) { - okButton.setVisible(newRelation.isUseful() || this.getRelationSnapshot() == null); - deleteButton.setVisible(!newRelation.isUseful() && this.getRelationSnapshot() != null); - if (this.getRelationSnapshot() == null && !newRelation.isUseful()) { + private void updateOkPanel(JButton okButton, JButton deleteButton) { + boolean useful = this.actionAccess.wouldRelationBeUseful(); + okButton.setVisible(useful || this.getRelationSnapshot() == null); + deleteButton.setVisible(!useful && this.getRelationSnapshot() != null); + if (this.getRelationSnapshot() == null && !useful) { okButton.setText(tr("Delete")); } else { okButton.setText(tr("OK")); @@ -669,7 +671,7 @@ static class LeftButtonToolbar extends JToolBar { * @param editorAccess relation editor */ LeftButtonToolbar(IRelationEditorActionAccess editorAccess) { - setOrientation(JToolBar.VERTICAL); + setOrientation(SwingConstants.VERTICAL); setFloatable(false); List groups = new ArrayList<>(); @@ -702,15 +704,15 @@ static class LeftButtonToolbar extends JToolBar { IRelationEditorActionGroup.fillToolbar(this, groups, editorAccess); - InputMap inputMap = editorAccess.getMemberTable().getInputMap(MemberTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + InputMap inputMap = editorAccess.getMemberTable().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); inputMap.put((KeyStroke) new RemoveAction(editorAccess, "removeSelected") - .getValue(AbstractAction.ACCELERATOR_KEY), "removeSelected"); + .getValue(Action.ACCELERATOR_KEY), "removeSelected"); inputMap.put((KeyStroke) new MoveUpAction(editorAccess, "moveUp") - .getValue(AbstractAction.ACCELERATOR_KEY), "moveUp"); + .getValue(Action.ACCELERATOR_KEY), "moveUp"); inputMap.put((KeyStroke) new MoveDownAction(editorAccess, "moveDown") - .getValue(AbstractAction.ACCELERATOR_KEY), "moveDown"); + .getValue(Action.ACCELERATOR_KEY), "moveDown"); inputMap.put((KeyStroke) new DownloadIncompleteMembersAction( - editorAccess, "downloadIncomplete").getValue(AbstractAction.ACCELERATOR_KEY), "downloadIncomplete"); + editorAccess, "downloadIncomplete").getValue(Action.ACCELERATOR_KEY), "downloadIncomplete"); } } @@ -721,7 +723,7 @@ static class LeftButtonToolbar extends JToolBar { * @return control buttons panel for selection/members */ protected static JToolBar buildSelectionControlButtonToolbar(IRelationEditorActionAccess editorAccess) { - JToolBar tb = new JToolBar(JToolBar.VERTICAL); + JToolBar tb = new JToolBar(SwingConstants.VERTICAL); tb.setFloatable(false); List groups = new ArrayList<>(); @@ -848,7 +850,7 @@ protected static void cleanSelfReferences(MemberTableModel memberTableModel, Rel new String[]{tr("Remove them, clean up relation"), tr("Ignore them, leave relation as is")}, tr("Remove them, clean up relation") ); - switch(ret) { + switch (ret) { case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION: case JOptionPane.CLOSED_OPTION: case JOptionPane.NO_OPTION: @@ -927,7 +929,7 @@ public static boolean confirmAddingPrimitive(OsmPrimitive primitive) throws AddA null, null ); - switch(ret) { + switch (ret) { case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION: case JOptionPane.YES_OPTION: return true; @@ -1035,7 +1037,7 @@ public void mouseClicked(MouseEvent e) { } } - private class RelationEditorActionAccess implements IRelationEditorActionAccess { + private final class RelationEditorActionAccess implements IRelationEditorActionAccess { @Override public MemberTable getMemberTable() { diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTransferHandler.java b/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTransferHandler.java index 929029902f6..fd001154c2b 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTransferHandler.java +++ b/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTransferHandler.java @@ -100,7 +100,7 @@ protected void importRelationMemberData(TransferSupport support, final MemberTab throws UnsupportedFlavorException, IOException { final RelationMemberTransferable.Data memberData = (RelationMemberTransferable.Data) support.getTransferable().getTransferData(RelationMemberTransferable.RELATION_MEMBER_DATA); - importData(destination, insertRow, memberData.getRelationMemberData(), new AbstractRelationMemberConverter() { + importData(destination, insertRow, memberData.getRelationMemberData(), new AbstractRelationMemberConverter<>() { @Override protected RelationMember getMember(MemberTable destination, RelationMemberData data, OsmPrimitive p) { return new RelationMember(data.getRole(), p); @@ -112,7 +112,7 @@ protected void importPrimitiveData(TransferSupport support, final MemberTable de throws UnsupportedFlavorException, IOException { final PrimitiveTransferData data = (PrimitiveTransferData) support.getTransferable().getTransferData(PrimitiveTransferData.DATA_FLAVOR); - importData(destination, insertRow, data.getDirectlyAdded(), new AbstractRelationMemberConverter() { + importData(destination, insertRow, data.getDirectlyAdded(), new AbstractRelationMemberConverter<>() { @Override protected RelationMember getMember(MemberTable destination, PrimitiveData data, OsmPrimitive p) { return destination.getMemberTableModel().getRelationMemberForPrimitive(p); diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/ApplyAction.java b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/ApplyAction.java index 0b65d27cb54..4ef30cd6228 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/ApplyAction.java +++ b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/ApplyAction.java @@ -35,6 +35,6 @@ public void actionPerformed(ActionEvent e) { @Override public void updateEnabledState() { - setEnabled(this.editorAccess.getChangedRelation().isUseful() && isEditorDirty()); + setEnabled(this.editorAccess.wouldRelationBeUseful() && isEditorDirty()); } } diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/CancelAction.java b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/CancelAction.java index a51be408345..d78dc8cf9fc 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/CancelAction.java +++ b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/CancelAction.java @@ -8,7 +8,6 @@ import javax.swing.JOptionPane; import javax.swing.RootPaneContainer; -import org.openstreetmap.josm.data.osm.IRelation; import org.openstreetmap.josm.data.osm.Relation; import org.openstreetmap.josm.gui.HelpAwareOptionPane; import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec; @@ -48,7 +47,7 @@ public void actionPerformed(ActionEvent e) { if ((!getMemberTableModel().hasSameMembersAs(snapshot) || getTagModel().isDirty()) && !(snapshot == null && getTagModel().getTags().isEmpty())) { //give the user a chance to save the changes - int ret = confirmClosingByCancel(this.editorAccess.getChangedRelation()); + int ret = confirmClosingByCancel(this.editorAccess.wouldRelationBeUseful()); if (ret == 0) { //Yes, save the changes //copied from OKAction.run() Config.getPref().put("relation.editor.generic.lastrole", Utils.strip(tfRole.getText())); @@ -61,7 +60,7 @@ public void actionPerformed(ActionEvent e) { hideEditor(); } - protected int confirmClosingByCancel(final IRelation newRelation) { + protected int confirmClosingByCancel(boolean isUseful) { ButtonSpec[] options = { new ButtonSpec( tr("Yes, save the changes and close"), @@ -85,7 +84,7 @@ protected int confirmClosingByCancel(final IRelation newRelation) { // Keep users from saving invalid relations -- a relation MUST have at least a tag with the key "type" // AND must contain at least one other OSM object. - options[0].setEnabled(newRelation.isUseful()); + options[0].setEnabled(isUseful); return HelpAwareOptionPane.showOptionDialog( MainApplication.getMainFrame(), diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/DeleteCurrentRelationAction.java b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/DeleteCurrentRelationAction.java index d8f8481ebc7..c195ca335f0 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/DeleteCurrentRelationAction.java +++ b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/DeleteCurrentRelationAction.java @@ -8,7 +8,10 @@ import org.openstreetmap.josm.actions.mapmode.DeleteAction; import org.openstreetmap.josm.data.osm.Relation; +import org.openstreetmap.josm.gui.MainApplication; import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor; +import org.openstreetmap.josm.gui.dialogs.relation.RelationDialogManager; +import org.openstreetmap.josm.gui.layer.OsmDataLayer; import org.openstreetmap.josm.tools.ImageProvider; /** @@ -35,6 +38,14 @@ public void actionPerformed(ActionEvent e) { Relation toDelete = getEditor().getRelation(); if (toDelete == null) return; + if (toDelete.isDeleted()) { + // see #23447 + OsmDataLayer layer = MainApplication.getLayerManager().getEditLayer(); + if (layer != null) { + RelationDialogManager.getRelationDialogManager().close(layer, toDelete); + } + return; + } DeleteAction.deleteRelation(getLayer(), toDelete); } diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/IRelationEditorActionAccess.java b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/IRelationEditorActionAccess.java index d5a70ea7bd1..b59d0a69a2a 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/IRelationEditorActionAccess.java +++ b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/IRelationEditorActionAccess.java @@ -71,7 +71,9 @@ default void addMemberTableAction(String actionMapKey, Action action) { /** * Get the changed relation - * @return The changed relation (note: will not be part of a dataset). This should never be {@code null}. + * @return The changed relation (note: will not be part of a dataset) or the + * value returned by {@code getEditor().getRelation()}. This should never be {@code null}. + * If called for a temporary use of the relation instance, make sure to cleanup a copy to avoid memory leaks, see #23527 * @since 18413 */ default IRelation getChangedRelation() { @@ -96,6 +98,15 @@ default IRelation getChangedRelation() { return newRelation; } + /** + * Check if the changed relation would be useful. + * @return true if the saved relation has at least one tag and one member + * @since 19014 + */ + default boolean wouldRelationBeUseful() { + return (getTagModel().getRowCount() > 0 && getMemberTableModel().getRowCount() > 0); + } + /** * Get the text field that is used to edit the role. * @return The role text field. diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SetRoleAction.java b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SetRoleAction.java index 80d4ee6c421..fda4baae4a8 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SetRoleAction.java +++ b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SetRoleAction.java @@ -44,7 +44,7 @@ protected void updateEnabledState() { } protected boolean isEmptyRole() { - return Utils.isBlank(tfRole.getText()); + return Utils.isStripEmpty(tfRole.getText()); } protected boolean confirmSettingEmptyRole(int onNumMembers) { @@ -69,7 +69,7 @@ protected boolean confirmSettingEmptyRole(int onNumMembers) { options, options[0] ); - switch(ret) { + switch (ret) { case JOptionPane.YES_OPTION: case ConditionalOptionPaneUtil.DIALOG_DISABLED_OPTION: return true; diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/sort/RelationSorter.java b/src/org/openstreetmap/josm/gui/dialogs/relation/sort/RelationSorter.java index 5752800b297..5da5ce861fe 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/relation/sort/RelationSorter.java +++ b/src/org/openstreetmap/josm/gui/dialogs/relation/sort/RelationSorter.java @@ -47,7 +47,7 @@ private interface AdditionalSorter { * Class that sorts the {@code street} members of * {@code type=associatedStreet} and {@code type=street} relations. */ - private static class AssociatedStreetRoleStreetSorter implements AdditionalSorter { + private static final class AssociatedStreetRoleStreetSorter implements AdditionalSorter { @Override public boolean acceptsMember(List relationMembers, RelationMember m) { @@ -64,7 +64,7 @@ public List sortMembers(List list) { * Class that sorts the {@code address} and {@code house} members of * {@code type=associatedStreet} and {@code type=street} relations. */ - private static class AssociatedStreetRoleAddressHouseSorter implements AdditionalSorter { + private static final class AssociatedStreetRoleAddressHouseSorter implements AdditionalSorter { @Override public boolean acceptsMember(List relationMembers, RelationMember m) { @@ -92,7 +92,7 @@ public List sortMembers(List list) { * Class that sorts the {@code platform} and {@code stop} members of * {@code type=public_transport} relations. */ - private static class PublicTransportRoleStopPlatformSorter implements AdditionalSorter { + private static final class PublicTransportRoleStopPlatformSorter implements AdditionalSorter { @Override public boolean acceptsMember(List relationMembers, RelationMember m) { @@ -139,7 +139,7 @@ public List sortMembers(List list) { * Class that sorts the {@code from}, {@code via} and {@code to} members of * {@code type=restriction} relations. */ - private static class FromViaToSorter implements AdditionalSorter { + private static final class FromViaToSorter implements AdditionalSorter { private static final List ROLES = Arrays.asList("from", "via", "to"); diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/sort/WayConnectionType.java b/src/org/openstreetmap/josm/gui/dialogs/relation/sort/WayConnectionType.java index 5fd0886d7a8..e243c912ff4 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/relation/sort/WayConnectionType.java +++ b/src/org/openstreetmap/josm/gui/dialogs/relation/sort/WayConnectionType.java @@ -32,6 +32,9 @@ public class WayConnectionType { */ public Direction direction; + /** + * The direction of the way connection + */ public enum Direction { FORWARD, BACKWARD, ROUNDABOUT_LEFT, ROUNDABOUT_RIGHT, NONE; diff --git a/src/org/openstreetmap/josm/gui/download/BookmarkList.java b/src/org/openstreetmap/josm/gui/download/BookmarkList.java index 708d8145da0..26267234491 100644 --- a/src/org/openstreetmap/josm/gui/download/BookmarkList.java +++ b/src/org/openstreetmap/josm/gui/download/BookmarkList.java @@ -241,7 +241,7 @@ public ChangesetBookmark(Changeset cs) { * Creates a bookmark list as well as the Buttons add and remove. */ public BookmarkList() { - setModel(new DefaultListModel()); + setModel(new DefaultListModel<>()); load(); setVisibleRowCount(7); setCellRenderer(new BookmarkCellRenderer()); diff --git a/src/org/openstreetmap/josm/gui/download/OSMDownloadSource.java b/src/org/openstreetmap/josm/gui/download/OSMDownloadSource.java index c076de55983..da912c7e218 100644 --- a/src/org/openstreetmap/josm/gui/download/OSMDownloadSource.java +++ b/src/org/openstreetmap/josm/gui/download/OSMDownloadSource.java @@ -8,6 +8,7 @@ import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Font; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; @@ -108,7 +109,10 @@ public void doDownload(List data, DownloadSettings settings } else if (b != null) { bounds.extend(b); } - } catch (InterruptedException | ExecutionException ex) { + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + Logging.warn(ex); + } catch (ExecutionException ex) { Logging.warn(ex); } } @@ -213,17 +217,17 @@ public OSMDownloadSourcePanel(OSMDownloadSource ds, DownloadDialog dialog) { // size check depends on selected data source checkboxChangeListener = e -> { rememberSettings(); - dialog.getSelectedDownloadArea().ifPresent(OSMDownloadSourcePanel.this::boundingBoxChanged); + dialog.getSelectedDownloadArea().ifPresent(this::boundingBoxChanged); }; downloadSourcesPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); - add(downloadSourcesPanel, GBC.eol().fill(GBC.HORIZONTAL)); + add(downloadSourcesPanel, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); updateSources(); sizeCheck.setFont(sizeCheck.getFont().deriveFont(Font.PLAIN)); JPanel sizeCheckPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); sizeCheckPanel.add(sizeCheck); - add(sizeCheckPanel, GBC.eol().fill(GBC.HORIZONTAL)); + add(sizeCheckPanel, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); setMinimumSize(new Dimension(450, 115)); } @@ -271,7 +275,7 @@ public boolean checkDownload(DownloadSettings settings) { /* * It is mandatory to specify the area to download from OSM. */ - if (!settings.getDownloadBounds().isPresent()) { + if (settings.getDownloadBounds().isEmpty()) { JOptionPane.showMessageDialog( this.getParent(), tr("Please select a download area first."), @@ -353,7 +357,7 @@ private void displaySizeCheckResult(boolean isAreaTooLarge) { } } - private static class OsmDataDownloadType implements IDownloadSourceType { + private static final class OsmDataDownloadType implements IDownloadSourceType { static final BooleanProperty IS_ENABLED = new BooleanProperty("download.osm.data", true); JCheckBox cbDownloadOsmData; @@ -393,7 +397,7 @@ public boolean isDownloadAreaTooLarge(Bounds bound) { } } - private static class GpsDataDownloadType implements IDownloadSourceType { + private static final class GpsDataDownloadType implements IDownloadSourceType { static final BooleanProperty IS_ENABLED = new BooleanProperty("download.osm.gps", false); private JCheckBox cbDownloadGpxData; @@ -431,7 +435,7 @@ public boolean isDownloadAreaTooLarge(Bounds bound) { } } - private static class NotesDataDownloadType implements IDownloadSourceType { + private static final class NotesDataDownloadType implements IDownloadSourceType { static final BooleanProperty IS_ENABLED = new BooleanProperty("download.osm.notes", false); private JCheckBox cbDownloadNotes; diff --git a/src/org/openstreetmap/josm/gui/download/PlaceSelection.java b/src/org/openstreetmap/josm/gui/download/PlaceSelection.java index 6fe1ad17c78..ded3e06352d 100644 --- a/src/org/openstreetmap/josm/gui/download/PlaceSelection.java +++ b/src/org/openstreetmap/josm/gui/download/PlaceSelection.java @@ -6,6 +6,7 @@ import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; @@ -105,7 +106,7 @@ protected JPanel buildSearchPanel() { JPanel panel = new JPanel(new GridBagLayout()); lpanel.add(new JLabel(tr("Choose the server for searching:")), GBC.std(0, 0).weight(0, 0).insets(0, 0, 5, 0)); - lpanel.add(serverComboBox, GBC.std(1, 0).fill(GBC.HORIZONTAL)); + lpanel.add(serverComboBox, GBC.std(1, 0).fill(GridBagConstraints.HORIZONTAL)); String s = Config.getPref().get("namefinder.server", SERVERS[0].name); for (int i = 0; i < SERVERS.length; ++i) { if (SERVERS[i].name.equals(s)) { @@ -117,9 +118,9 @@ protected JPanel buildSearchPanel() { cbSearchExpression = new HistoryComboBox(); cbSearchExpression.setToolTipText(tr("Enter a place name to search for")); cbSearchExpression.getModel().prefs().load(HISTORY_KEY); - lpanel.add(cbSearchExpression, GBC.std(1, 1).fill(GBC.HORIZONTAL)); + lpanel.add(cbSearchExpression, GBC.std(1, 1).fill(GridBagConstraints.HORIZONTAL)); - panel.add(lpanel, GBC.std().fill(GBC.HORIZONTAL).insets(5, 5, 0, 5)); + panel.add(lpanel, GBC.std().fill(GridBagConstraints.HORIZONTAL).insets(5, 5, 0, 5)); SearchAction searchAction = new SearchAction(); JButton btnSearch = new JButton(searchAction); cbSearchExpression.getEditorComponent().getDocument().addDocumentListener(searchAction); @@ -494,7 +495,7 @@ public Component getTableCellRendererComponent(JTable table, Object value, if (value == null) return this; SearchResult sr = (SearchResult) value; - switch(column) { + switch (column) { case 0: setText(sr.getName()); break; diff --git a/src/org/openstreetmap/josm/gui/help/HelpUtil.java b/src/org/openstreetmap/josm/gui/help/HelpUtil.java index 2fe039138e7..0a65c5c240a 100644 --- a/src/org/openstreetmap/josm/gui/help/HelpUtil.java +++ b/src/org/openstreetmap/josm/gui/help/HelpUtil.java @@ -149,7 +149,7 @@ private static String getHelpTopicPrefix(LocaleType type) { */ public static String buildAbsoluteHelpTopic(String topic, LocaleType type) { String prefix = getHelpTopicPrefix(type); - if (prefix == null || Utils.isBlank(topic) || "/".equals(topic.trim())) + if (prefix == null || Utils.isStripEmpty(topic) || "/".equals(topic.trim())) return prefix; prefix += '/' + topic; return prefix.replaceAll("\\/+", "\\/"); // collapse sequences of // diff --git a/src/org/openstreetmap/josm/gui/history/HistoryBrowser.java b/src/org/openstreetmap/josm/gui/history/HistoryBrowser.java index 7409755082d..3c747f7fe18 100644 --- a/src/org/openstreetmap/josm/gui/history/HistoryBrowser.java +++ b/src/org/openstreetmap/josm/gui/history/HistoryBrowser.java @@ -11,6 +11,8 @@ import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.OsmPrimitiveType; @@ -22,7 +24,7 @@ * * @since 1709 */ -public class HistoryBrowser extends JPanel implements Destroyable { +public class HistoryBrowser extends JPanel implements Destroyable, ChangeListener { /** the model */ private transient HistoryBrowserModel model; @@ -115,6 +117,7 @@ protected void build() { public void populate(History history) { boolean samePrimitive = model.isSamePrimitive(history); // needs to be before setHistory model.setHistory(history); + model.addChangeListener(this); if (samePrimitive) { // no need to rebuild the UI return; @@ -159,6 +162,7 @@ public HistoryBrowserModel getModel() { public void destroy() { if (model != null) { model.unlinkAsListener(); + model.removeChangeListener(this); model = null; } Stream.of(tagInfoViewer, nodeListViewer, relationMemberListViewer, coordinateInfoViewer) @@ -168,4 +172,9 @@ public void destroy() { relationMemberListViewer = null; coordinateInfoViewer = null; } + + @Override + public void stateChanged(ChangeEvent e) { + tagInfoViewer.adjustWidths(); + } } diff --git a/src/org/openstreetmap/josm/gui/history/HistoryBrowserDialogManager.java b/src/org/openstreetmap/josm/gui/history/HistoryBrowserDialogManager.java index 121490c41ec..309ba01475e 100644 --- a/src/org/openstreetmap/josm/gui/history/HistoryBrowserDialogManager.java +++ b/src/org/openstreetmap/josm/gui/history/HistoryBrowserDialogManager.java @@ -18,6 +18,7 @@ import javax.swing.JOptionPane; import javax.swing.SwingUtilities; +import javax.swing.event.ChangeEvent; import org.openstreetmap.josm.data.osm.PrimitiveId; import org.openstreetmap.josm.data.osm.history.History; @@ -147,6 +148,7 @@ public void show(History h) { HistoryBrowserDialog dialog = new HistoryBrowserDialog(h); placeOnScreen(dialog); dialog.setVisible(true); + dialog.getHistoryBrowser().stateChanged(new ChangeEvent(this)); dialogs.put(h.getId(), dialog); } } diff --git a/src/org/openstreetmap/josm/gui/history/HistoryLoadTask.java b/src/org/openstreetmap/josm/gui/history/HistoryLoadTask.java index b46b3409f48..defdd142e71 100644 --- a/src/org/openstreetmap/josm/gui/history/HistoryLoadTask.java +++ b/src/org/openstreetmap/josm/gui/history/HistoryLoadTask.java @@ -9,12 +9,14 @@ import java.net.HttpURLConnection; import java.util.ArrayList; import java.util.Collection; +import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.Set; import org.openstreetmap.josm.data.osm.Changeset; +import org.openstreetmap.josm.data.osm.ChangesetCache; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.PrimitiveId; import org.openstreetmap.josm.data.osm.history.History; @@ -229,15 +231,27 @@ protected static HistoryDataSet loadHistory(OsmServerHistoryReader reader, Progr // load corresponding changesets (mostly for changeset comment) OsmServerChangesetReader changesetReader = new OsmServerChangesetReader(); List changesetIds = new ArrayList<>(ds.getChangesetIds()); + Iterator iter = changesetIds.iterator(); + while (iter.hasNext()) { + long id = iter.next(); + Changeset cs = ChangesetCache.getInstance().get((int) id); + if (cs != null && !cs.isOpen()) { + ds.putChangeset(cs); + iter.remove(); + } + } // query changesets 100 by 100 (OSM API limit) int n = ChangesetQuery.MAX_CHANGESETS_NUMBER; for (int i = 0; i < changesetIds.size(); i += n) { + List downloadedCS = new ArrayList<>(changesetIds.size()); for (Changeset c : changesetReader.queryChangesets( new ChangesetQuery().forChangesetIds(changesetIds.subList(i, Math.min(i + n, changesetIds.size()))), progressMonitor.createSubTaskMonitor(1, false))) { ds.putChangeset(c); + downloadedCS.add(c); } + ChangesetCache.getInstance().update(downloadedCS); } } return ds; diff --git a/src/org/openstreetmap/josm/gui/history/RelationMemberListTableCellRenderer.java b/src/org/openstreetmap/josm/gui/history/RelationMemberListTableCellRenderer.java index 4f9c41430ec..f1f7e4aadd3 100644 --- a/src/org/openstreetmap/josm/gui/history/RelationMemberListTableCellRenderer.java +++ b/src/org/openstreetmap/josm/gui/history/RelationMemberListTableCellRenderer.java @@ -56,7 +56,7 @@ protected void renderPrimitive(Item diffItem) { String text = ""; RelationMemberData member = (RelationMemberData) diffItem.value; if (member != null) { - switch(member.getMemberType()) { + switch (member.getMemberType()) { case NODE: text = tr("Node {0}", member.getMemberId()); break; case WAY: text = tr("Way {0}", member.getMemberId()); break; case RELATION: text = tr("Relation {0}", member.getMemberId()); break; @@ -77,7 +77,7 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole if (value == null) return this; Item member = (TwoColumnDiff.Item) value; Item.DiffItemType type = member.state; - switch(column) { + switch (column) { case RelationMemberTableColumnModel.INDEX_COLUMN: type = Item.DiffItemType.EMPTY; renderIndex(((DiffTableModel) table.getModel()), row); diff --git a/src/org/openstreetmap/josm/gui/history/TagInfoViewer.java b/src/org/openstreetmap/josm/gui/history/TagInfoViewer.java index 888334bf101..354bafcb528 100644 --- a/src/org/openstreetmap/josm/gui/history/TagInfoViewer.java +++ b/src/org/openstreetmap/josm/gui/history/TagInfoViewer.java @@ -35,6 +35,8 @@ * @since 1709 */ public class TagInfoViewer extends HistoryViewerPanel { + private JTable reference; + private JTable current; private static final class RepaintOnFocusChange implements FocusListener { @Override public void focusLost(FocusEvent e) { @@ -62,12 +64,14 @@ public TagInfoViewer(HistoryBrowserModel model) { @Override protected JTable buildReferenceTable() { - return buildTable(PointInTimeType.REFERENCE_POINT_IN_TIME); + reference = buildTable(PointInTimeType.REFERENCE_POINT_IN_TIME); + return reference; } @Override protected JTable buildCurrentTable() { - return buildTable(PointInTimeType.CURRENT_POINT_IN_TIME); + current = buildTable(PointInTimeType.CURRENT_POINT_IN_TIME); + return current; } private JTable buildTable(PointInTimeType pointInTime) { @@ -105,4 +109,23 @@ private JTable buildTable(PointInTimeType pointInTime) { table.addMouseListener(new PopupMenuLauncher(tagMenu)); return table; } + + /** + * Use current data to adjust preferredWidth for both tables. + * @since 19013 + */ + public void adjustWidths() { + // We have two tables with 3 columns each. no column should get more than 1/4 of the size + int maxWidth = this.getWidth() / 4; + if (maxWidth == 0) + maxWidth = Integer.MAX_VALUE; + adjustWidths(reference, maxWidth); + adjustWidths(current, maxWidth); + } + + private static void adjustWidths(JTable table, int maxWidth) { + for (int column = 0; column < table.getColumnCount(); column++) { + TableHelper.adjustColumnWidth(table, column, maxWidth); + } + } } diff --git a/src/org/openstreetmap/josm/gui/history/TagTableCellRenderer.java b/src/org/openstreetmap/josm/gui/history/TagTableCellRenderer.java index 5750a7c8975..6502e223375 100644 --- a/src/org/openstreetmap/josm/gui/history/TagTableCellRenderer.java +++ b/src/org/openstreetmap/josm/gui/history/TagTableCellRenderer.java @@ -1,6 +1,8 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.history; +import static org.openstreetmap.josm.tools.I18n.tr; + import java.awt.Color; import java.awt.Component; @@ -13,8 +15,6 @@ import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive; import org.openstreetmap.josm.gui.util.GuiHelper; -import static org.openstreetmap.josm.tools.I18n.tr; - /** * The {@link TableCellRenderer} for a list of tags in {@link HistoryBrowser} * @@ -50,7 +50,7 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole String tooltip = null; setBorder(null); if (model.hasTag(key)) { - switch(column) { + switch (column) { case TagTableColumnModel.COLUMN_KEY: // the name column text = key; diff --git a/src/org/openstreetmap/josm/gui/history/TagTableColumnModel.java b/src/org/openstreetmap/josm/gui/history/TagTableColumnModel.java index ae17ee1f8d8..1eb328ef14b 100644 --- a/src/org/openstreetmap/josm/gui/history/TagTableColumnModel.java +++ b/src/org/openstreetmap/josm/gui/history/TagTableColumnModel.java @@ -3,6 +3,7 @@ import static org.openstreetmap.josm.tools.I18n.tr; +import javax.swing.JLabel; import javax.swing.table.DefaultTableColumnModel; import javax.swing.table.TableColumn; @@ -41,6 +42,7 @@ protected void createColumns() { col.setHeaderValue(tr("Since")); col.setCellRenderer(renderer); col.setPreferredWidth(10); + col.setMaxWidth(new JLabel("v" + Long.MAX_VALUE).getMinimumSize().width); // See #23482 addColumn(col); } } diff --git a/src/org/openstreetmap/josm/gui/history/VersionInfoPanel.java b/src/org/openstreetmap/josm/gui/history/VersionInfoPanel.java index 6b98f64187b..6bd44bf705a 100644 --- a/src/org/openstreetmap/josm/gui/history/VersionInfoPanel.java +++ b/src/org/openstreetmap/josm/gui/history/VersionInfoPanel.java @@ -293,21 +293,17 @@ public void update(final Changeset cs, final boolean isLatest, final Instant tim } final Changeset oppCs = model != null ? model.getPointInTime(pointInTimeType.opposite()).getChangeset() : null; - updateText(cs, "comment", texChangesetComment, null, oppCs, texChangesetComment); - updateText(cs, "source", texChangesetSource, lblSource, oppCs, pnlChangesetSource); - updateText(cs, "imagery_used", texChangesetImageryUsed, lblImageryUsed, oppCs, pnlChangesetImageryUsed); + updateText(cs, "comment", texChangesetComment, oppCs, texChangesetComment); + updateText(cs, "source", texChangesetSource, oppCs, pnlChangesetSource); + updateText(cs, "imagery_used", texChangesetImageryUsed, oppCs, pnlChangesetImageryUsed); } private static String insertWbr(String s) { return Utils.escapeReservedCharactersHTML(s).replace("_", "_"); } - protected static void updateText(Changeset cs, String attr, JTextArea textArea, JLabel label, Changeset oppCs, JComponent container) { + protected static void updateText(Changeset cs, String attr, JTextArea textArea, Changeset oppCs, JComponent container) { final String text = cs != null ? cs.get(attr) : null; - // Update text, hide prefixing label if empty - if (label != null) { - label.setVisible(text != null && !Utils.isStripEmpty(text)); - } textArea.setText(text); // Hide container if values of both versions are empty container.setVisible(text != null || (oppCs != null && oppCs.get(attr) != null)); diff --git a/src/org/openstreetmap/josm/gui/io/AbstractUploadTask.java b/src/org/openstreetmap/josm/gui/io/AbstractUploadTask.java index 6c40f476f9c..597a64be16c 100644 --- a/src/org/openstreetmap/josm/gui/io/AbstractUploadTask.java +++ b/src/org/openstreetmap/josm/gui/io/AbstractUploadTask.java @@ -2,6 +2,7 @@ package org.openstreetmap.josm.gui.io; import static org.openstreetmap.josm.gui.help.HelpUtil.ht; +import static org.openstreetmap.josm.tools.I18n.marktr; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.event.ActionEvent; @@ -9,7 +10,6 @@ import java.time.Instant; import java.time.format.FormatStyle; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.regex.Matcher; @@ -40,10 +40,13 @@ /** * Abstract base class for the task of uploading primitives via OSM API. - * + *

      * Mainly handles conflicts and certain error situations. */ public abstract class AbstractUploadTask extends PleaseWaitRunnable { + private static final String CANCEL_TR = marktr("Cancel"); + private static final String CANCEL = "cancel"; + private static final String UPDATE_DATA = "updatedata"; /** * Constructs a new {@code AbstractUploadTask}. @@ -97,7 +100,7 @@ protected void synchronizePrimitive(final OsmPrimitiveType type, final long id) /** * Synchronizes the local state of the dataset with the state on the server. - * + *

      * Reuses the functionality of {@link UpdateDataAction}. * * @see UpdateDataAction#actionPerformed(ActionEvent) @@ -120,7 +123,7 @@ protected void synchronizeDataSet() { protected void handleUploadConflictForKnownConflict(final OsmPrimitiveType primitiveType, final long id, String serverVersion, String myVersion) { String lbl; - switch(primitiveType) { + switch (primitiveType) { // CHECKSTYLE.OFF: SingleSpaceSeparator case NODE: lbl = tr("Synchronize node {0} only", id); break; case WAY: lbl = tr("Synchronize way {0} only", id); break; @@ -131,15 +134,15 @@ protected void handleUploadConflictForKnownConflict(final OsmPrimitiveType primi ButtonSpec[] spec = { new ButtonSpec( lbl, - new ImageProvider("updatedata"), + new ImageProvider(UPDATE_DATA), null, null), new ButtonSpec( tr("Synchronize entire dataset"), - new ImageProvider("updatedata"), + new ImageProvider(UPDATE_DATA), null, null), new ButtonSpec( - tr("Cancel"), - new ImageProvider("cancel"), + tr(CANCEL_TR), + new ImageProvider(CANCEL), null, null) }; String msg = tr("Uploading failed because the server has a newer version of one
      " @@ -163,10 +166,10 @@ protected void handleUploadConflictForKnownConflict(final OsmPrimitiveType primi spec[0], "/Concepts/Conflict" ); - switch(ret) { + switch (ret) { case 0: synchronizePrimitive(primitiveType, id); break; case 1: synchronizeDataSet(); break; - default: return; + default: // Do nothing (just return) } } @@ -179,11 +182,11 @@ protected void handleUploadConflictForUnknownConflict() { ButtonSpec[] spec = { new ButtonSpec( tr("Synchronize entire dataset"), - new ImageProvider("updatedata"), + new ImageProvider(UPDATE_DATA), null, null), new ButtonSpec( - tr("Cancel"), - new ImageProvider("cancel"), + tr(CANCEL_TR), + new ImageProvider(CANCEL), null, null) }; String msg = tr("Uploading failed because the server has a newer version of one
      " @@ -243,8 +246,8 @@ protected void handleUploadPreconditionFailedConflict(OsmApiException e, Pair * It is displayed as one of the configuration panels in the {@link UploadDialog}. - * + *

      * ChangesetManagementPanel is a source for {@link java.beans.PropertyChangeEvent}s. Clients can listen to *

        *
      • {@link #SELECTED_CHANGESET_PROP} - the new value in the property change event is @@ -49,6 +50,7 @@ public class ChangesetManagementPanel extends JPanel implements ItemListener, ChangesetCacheListener { static final String SELECTED_CHANGESET_PROP = ChangesetManagementPanel.class.getName() + ".selectedChangeset"; static final String CLOSE_CHANGESET_AFTER_UPLOAD = ChangesetManagementPanel.class.getName() + ".closeChangesetAfterUpload"; + private static final String UPLOAD_CHANGESET_CLOSE = "upload.changeset.close"; private JosmComboBox cbOpenChangesets; private JosmComboBoxModel model; @@ -141,7 +143,7 @@ protected void build() { cbOpenChangesets.addItemListener(this); cbOpenChangesets.addItemListener(closeChangesetAction); - cbCloseAfterUpload.setSelected(Config.getPref().getBoolean("upload.changeset.close", true)); + cbCloseAfterUpload.setSelected(Config.getPref().getBoolean(UPLOAD_CHANGESET_CLOSE, true)); cbCloseAfterUpload.addItemListener(new CloseAfterUploadItemStateListener()); ChangesetCache.getInstance().addChangesetCacheListener(this); @@ -194,14 +196,14 @@ class CloseAfterUploadItemStateListener implements ItemListener { public void itemStateChanged(ItemEvent e) { if (e.getItemSelectable() != cbCloseAfterUpload) return; - switch(e.getStateChange()) { + switch (e.getStateChange()) { case ItemEvent.SELECTED: firePropertyChange(CLOSE_CHANGESET_AFTER_UPLOAD, false, true); - Config.getPref().putBoolean("upload.changeset.close", true); + Config.getPref().putBoolean(UPLOAD_CHANGESET_CLOSE, true); break; case ItemEvent.DESELECTED: firePropertyChange(CLOSE_CHANGESET_AFTER_UPLOAD, true, false); - Config.getPref().putBoolean("upload.changeset.close", false); + Config.getPref().putBoolean(UPLOAD_CHANGESET_CLOSE, false); break; default: // Do nothing } @@ -261,7 +263,7 @@ protected void refreshChangesets() { try { ChangesetCache.getInstance().refreshChangesetsFromServer(); } catch (OsmTransferException e) { - return; + Logging.trace(e); } } diff --git a/src/org/openstreetmap/josm/gui/io/CredentialDialog.java b/src/org/openstreetmap/josm/gui/io/CredentialDialog.java index 8939d1431d2..311a567cb45 100644 --- a/src/org/openstreetmap/josm/gui/io/CredentialDialog.java +++ b/src/org/openstreetmap/josm/gui/io/CredentialDialog.java @@ -48,7 +48,7 @@ /** * Dialog box to request username and password from the user. - * + *

        * The credentials can be for the OSM API (basic authentication), a different * host or an HTTP proxy. */ @@ -369,7 +369,7 @@ protected void build() { } } - private static class SelectAllOnFocusHandler extends FocusAdapter { + private static final class SelectAllOnFocusHandler extends FocusAdapter { @Override public void focusGained(FocusEvent e) { if (e.getSource() instanceof JTextField) { diff --git a/src/org/openstreetmap/josm/gui/io/CustomConfigurator.java b/src/org/openstreetmap/josm/gui/io/CustomConfigurator.java index b84ddfd543d..dad7d11f7ac 100644 --- a/src/org/openstreetmap/josm/gui/io/CustomConfigurator.java +++ b/src/org/openstreetmap/josm/gui/io/CustomConfigurator.java @@ -58,6 +58,8 @@ import org.w3c.dom.NodeList; import org.xml.sax.SAXException; +import jakarta.annotation.Nullable; + /** * Class to process configuration changes stored in XML * can be used to modify preferences, store/delete files in .josm folders etc @@ -175,6 +177,12 @@ public static int askForOption(String text, String opts) { } } + /** + * Ask the user for text + * @param text The message for the user + * @return The text the user entered + */ + @Nullable public static String askForText(String text) { String s = JOptionPane.showInputDialog(MainApplication.getMainFrame(), text, tr("Enter text"), JOptionPane.QUESTION_MESSAGE); return s != null ? s.trim() : null; @@ -260,6 +268,11 @@ public static void exportPreferencesKeysToFile(String filename, boolean append, } } + /** + * Delete a file + * @param path The path to delete inside the base directory + * @param base The base directory for the path + */ public static void deleteFile(String path, String base) { String dir = getDirectoryByAbbr(base); if (dir == null) { @@ -276,6 +289,10 @@ public static void deleteFile(String path, String base) { } } + /** + * Delete a file or a directory + * @param f The file or directory to delete + */ public static void deleteFileOrDirectory(File f) { if (f.isDirectory()) { File[] files = f.listFiles(); @@ -292,6 +309,12 @@ public static void deleteFileOrDirectory(File f) { private static boolean busy; + /** + * Perform install, uninstall, and deletion operations on plugins + * @param install The {@code ;} delimited list of plugins to install + * @param uninstall The {@code ;} delimited list of plugins to uninstall + * @param delete The {@code ;} delimited list of plugins to delete + */ public static void pluginOperation(String install, String uninstall, String delete) { final List installList = new ArrayList<>(); final List removeList = new ArrayList<>(); @@ -375,6 +398,9 @@ private static String getDirectoryByAbbr(String base) { return dir; } + /** + * Read preferences from xml files + */ public static class XMLCommandProcessor { private final Preferences mainPrefs; @@ -383,6 +409,10 @@ public static class XMLCommandProcessor { private boolean lastV; // last If condition result + /** + * Read preferences from an XML file + * @param file The file to read custom preferences from + */ public void openAndReadXML(File file) { PreferencesUtils.log("-- Reading custom preferences from " + file.getAbsolutePath() + " --"); try { @@ -396,6 +426,10 @@ public void openAndReadXML(File file) { } } + /** + * Read custom preferences from an XML {@link InputStream} + * @param is The {@link InputStream} to read from + */ public void openAndReadXML(InputStream is) { try { Document document = XmlUtils.parseSafeDOM(is); @@ -408,6 +442,10 @@ public void openAndReadXML(InputStream is) { PreferencesUtils.log("-- Reading complete --"); } + /** + * Create a new {@link XMLCommandProcessor} + * @param mainPrefs The preferences to modify with custom preferences + */ public XMLCommandProcessor(Preferences mainPrefs) { this.mainPrefs = mainPrefs; PreferencesUtils.resetLog(); @@ -428,7 +466,7 @@ private void processXmlFragment(Element root) { String elementName = item.getNodeName(); Element elem = (Element) item; - switch(elementName) { + switch (elementName) { case "var": setVar(elem.getAttribute("name"), evalVars(elem.getAttribute("value"))); break; @@ -547,20 +585,25 @@ private void processAskElement(Element elem) { String text = evalVars(elem.getAttribute("text")); String locText = evalVars(elem.getAttribute(LanguageInfo.getJOSMLocaleCode()+".text")); if (!locText.isEmpty()) text = locText; - String var = elem.getAttribute("var"); - if (var.isEmpty()) var = "result"; + String varAttribute = elem.getAttribute("var"); + if (varAttribute.isEmpty()) varAttribute = "result"; String input = evalVars(elem.getAttribute("input")); if ("true".equals(input)) { - setVar(var, askForText(text)); + setVar(varAttribute, askForText(text)); } else { String opts = evalVars(elem.getAttribute("options")); String locOpts = evalVars(elem.getAttribute(LanguageInfo.getJOSMLocaleCode()+".options")); if (!locOpts.isEmpty()) opts = locOpts; - setVar(var, String.valueOf(askForOption(text, opts))); + setVar(varAttribute, String.valueOf(askForOption(text, opts))); } } + /** + * Set a variable in the environment + * @param name The name of the environment variable + * @param value The value for the environment variable + */ public void setVar(String name, String value) { environment.put(name, value); } @@ -603,7 +646,7 @@ private boolean processRunTaskElement(Element elem) { * @return evaluation result */ private String evalVars(String s) { - Matcher mr = Pattern.compile("\\$\\{(?[^\\}]*)\\}").matcher(s); + Matcher mr = Pattern.compile("\\$\\{(?[^}]*)}").matcher(s); StringBuffer sb = new StringBuffer(); while (mr.find()) { String identifier = mr.group("identifier"); diff --git a/src/org/openstreetmap/josm/gui/io/LayerNameAndFilePathTableCell.java b/src/org/openstreetmap/josm/gui/io/LayerNameAndFilePathTableCell.java index 3d10e43097a..387184f7458 100644 --- a/src/org/openstreetmap/josm/gui/io/LayerNameAndFilePathTableCell.java +++ b/src/org/openstreetmap/josm/gui/io/LayerNameAndFilePathTableCell.java @@ -229,7 +229,7 @@ public boolean shouldSelectCell(EventObject anEvent) { @Override public boolean stopCellEditing() { - if (Utils.isBlank(tfFilename.getText())) { + if (Utils.isStripEmpty(tfFilename.getText())) { value = null; } else { value = new File(tfFilename.getText()); diff --git a/src/org/openstreetmap/josm/gui/io/OnlineResourceMenu.java b/src/org/openstreetmap/josm/gui/io/OnlineResourceMenu.java index f18ecd0d332..d5d1d7e3d38 100644 --- a/src/org/openstreetmap/josm/gui/io/OnlineResourceMenu.java +++ b/src/org/openstreetmap/josm/gui/io/OnlineResourceMenu.java @@ -76,7 +76,7 @@ protected boolean listenToSelectionChange() { } } - private class ToggleMenuListener implements MenuListener { + private final class ToggleMenuListener implements MenuListener { @Override public void menuSelected(MenuEvent e) { for (Component component : getMenuComponents()) { diff --git a/src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java b/src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java index dcd750dc505..ae2d73f60c2 100644 --- a/src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java +++ b/src/org/openstreetmap/josm/gui/io/SaveLayersDialog.java @@ -36,6 +36,7 @@ import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.ListCellRenderer; +import javax.swing.SwingConstants; import javax.swing.WindowConstants; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; @@ -121,10 +122,9 @@ public static boolean saveUnsavedModifications(Iterable selecte continue; } AbstractModifiableLayer odl = (AbstractModifiableLayer) l; - if (odl.isModified() && - ((!odl.isSavable() && !odl.isUploadable()) || - odl.requiresSaveToFile() || - odl.requiresUploadToServer())) { + if (odl.isModified() && ( + (odl.isSavable() && odl.requiresSaveToFile()) || + (odl.isUploadable() && odl.requiresUploadToServer() && !odl.isUploadDiscouraged()))) { layersWithUnsavedChanges.add(odl); } } @@ -132,7 +132,7 @@ public static boolean saveUnsavedModifications(Iterable selecte if (!layersWithUnsavedChanges.isEmpty()) { dialog.getModel().populate(layersWithUnsavedChanges); dialog.setVisible(true); - switch(dialog.getUserAction()) { + switch (dialog.getUserAction()) { case PROCEED: return true; case CANCEL: default: return false; @@ -182,14 +182,14 @@ protected JPanel buildButtonRow() { JPanel pnl = new JPanel(new GridBagLayout()); model.addPropertyChangeListener(saveAndProceedAction); - pnl.add(saveAndProceedActionButton, GBC.std(0, 0).insets(5, 5, 0, 0).fill(GBC.HORIZONTAL)); + pnl.add(saveAndProceedActionButton, GBC.std(0, 0).insets(5, 5, 0, 0).fill(GridBagConstraints.HORIZONTAL)); - pnl.add(new JButton(saveSessionAction), GBC.std(1, 0).insets(5, 5, 5, 0).fill(GBC.HORIZONTAL)); + pnl.add(new JButton(saveSessionAction), GBC.std(1, 0).insets(5, 5, 5, 0).fill(GridBagConstraints.HORIZONTAL)); model.addPropertyChangeListener(discardAndProceedAction); - pnl.add(new JButton(discardAndProceedAction), GBC.std(0, 1).insets(5, 5, 0, 5).fill(GBC.HORIZONTAL)); + pnl.add(new JButton(discardAndProceedAction), GBC.std(0, 1).insets(5, 5, 0, 5).fill(GridBagConstraints.HORIZONTAL)); - pnl.add(new JButton(cancelAction), GBC.std(1, 1).insets(5, 5, 5, 5).fill(GBC.HORIZONTAL)); + pnl.add(new JButton(cancelAction), GBC.std(1, 1).insets(5, 5, 5, 5).fill(GridBagConstraints.HORIZONTAL)); JPanel pnl2 = new JPanel(new BorderLayout()); pnl2.add(pnlUploadLayers, BorderLayout.CENTER); @@ -267,7 +267,7 @@ protected void build() { gc.weightx = 1.0; gc.weighty = 0.0; add(lblMessage, gc); - lblMessage.setHorizontalAlignment(JLabel.LEADING); + lblMessage.setHorizontalAlignment(SwingConstants.LEADING); lstLayers.setCellRenderer(new LayerCellRenderer()); gc.gridx = 0; gc.gridy = 1; @@ -377,7 +377,7 @@ protected void cancelWhenInEditingModel() { } public void cancel() { - switch(model.getMode()) { + switch (model.getMode()) { case EDITING_DATA: cancelWhenInEditingModel(); break; case UPLOADING_AND_SAVING: cancelSafeAndUploadTask(); @@ -426,7 +426,7 @@ public void actionPerformed(ActionEvent e) { public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) { Mode mode = (Mode) evt.getNewValue(); - switch(mode) { + switch (mode) { case EDITING_DATA: setEnabled(true); break; case UPLOADING_AND_SAVING: setEnabled(false); @@ -523,7 +523,7 @@ public void actionPerformed(ActionEvent e) { public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) { SaveLayersModel.Mode mode = (SaveLayersModel.Mode) evt.getNewValue(); - switch(mode) { + switch (mode) { case EDITING_DATA: setEnabled(true); break; case UPLOADING_AND_SAVING: setEnabled(false); diff --git a/src/org/openstreetmap/josm/gui/io/SaveLayersModel.java b/src/org/openstreetmap/josm/gui/io/SaveLayersModel.java index 9f01c640362..41ec75d1df4 100644 --- a/src/org/openstreetmap/josm/gui/io/SaveLayersModel.java +++ b/src/org/openstreetmap/josm/gui/io/SaveLayersModel.java @@ -21,6 +21,9 @@ public class SaveLayersModel extends DefaultTableModel { public static final String MODE_PROP = SaveLayerInfo.class.getName() + ".mode"; + /** + * The status of the editor + */ public enum Mode { EDITING_DATA, UPLOADING_AND_SAVING @@ -99,7 +102,7 @@ public boolean isCellEditable(int row, int column) { @Override public void setValueAt(Object value, int row, int column) { final SaveLayerInfo info = this.layerInfo.get(row); - switch(column) { + switch (column) { case columnFilename: info.setFile((File) value); if (info.isSavable()) { diff --git a/src/org/openstreetmap/josm/gui/io/SaveLayersTable.java b/src/org/openstreetmap/josm/gui/io/SaveLayersTable.java index 4c0c3b15c01..82285eb4e07 100644 --- a/src/org/openstreetmap/josm/gui/io/SaveLayersTable.java +++ b/src/org/openstreetmap/josm/gui/io/SaveLayersTable.java @@ -25,7 +25,7 @@ class SaveLayersTable extends JTable implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(SaveLayersModel.MODE_PROP)) { Mode mode = (Mode) evt.getNewValue(); - switch(mode) { + switch (mode) { case EDITING_DATA: setEnabled(true); break; case UPLOADING_AND_SAVING: setEnabled(false); diff --git a/src/org/openstreetmap/josm/gui/io/UploadPrimitivesTask.java b/src/org/openstreetmap/josm/gui/io/UploadPrimitivesTask.java index b23018d65cf..42a59f8a603 100644 --- a/src/org/openstreetmap/josm/gui/io/UploadPrimitivesTask.java +++ b/src/org/openstreetmap/josm/gui/io/UploadPrimitivesTask.java @@ -140,7 +140,7 @@ protected MaxChangesetSizeExceededPolicy promptUserForPolicy() { specs[0], ht("/Action/Upload#ChangesetFull") ); - switch(ret) { + switch (ret) { case 0: return MaxChangesetSizeExceededPolicy.AUTOMATICALLY_OPEN_NEW_CHANGESETS; case 1: return MaxChangesetSizeExceededPolicy.FILL_ONE_CHANGESET_AND_RETURN_TO_UPLOAD_DIALOG; case 2: @@ -167,9 +167,9 @@ protected boolean handleChangesetFullResponse() throws OsmTransferException { if (strategy.getPolicy() == null || strategy.getPolicy() == MaxChangesetSizeExceededPolicy.ABORT) { strategy.setPolicy(promptUserForPolicy()); } - switch(strategy.getPolicy()) { + switch (strategy.getPolicy()) { case AUTOMATICALLY_OPEN_NEW_CHANGESETS: - Changeset newChangeSet = new Changeset(); + final Changeset newChangeSet = new Changeset(); newChangeSet.setKeys(changeset.getKeys()); closeChangeset(); this.changeset = newChangeSet; @@ -279,7 +279,7 @@ protected void realRun() { if (writer != null) { processedPrimitives.addAll(writer.getProcessedPrimitives()); // OsmPrimitive in => OsmPrimitive out } - switch(e.getSource()) { + switch (e.getSource()) { case UPLOAD_DATA: // Most likely the changeset is full. Try to recover and continue // with a new changeset, but let the user decide first. @@ -318,7 +318,7 @@ protected void realRun() { lastException = e; } } finally { - if (MessageNotifier.PROP_NOTIFIER_ENABLED.get()) { + if (Boolean.TRUE.equals(MessageNotifier.PROP_NOTIFIER_ENABLED.get())) { MessageNotifier.start(); } } @@ -371,7 +371,7 @@ protected void finish() { } if (uploadCanceled) return; if (lastException == null) { - HtmlPanel panel = new HtmlPanel( + final HtmlPanel panel = new HtmlPanel( "

        " + tr("Upload successful!") + "

        "); panel.enableClickableHyperlinks(); @@ -392,7 +392,7 @@ protected void finish() { /* do nothing if unknown policy */ return; if (e.getSource() == ChangesetClosedException.Source.UPLOAD_DATA) { - switch(strategy.getPolicy()) { + switch (strategy.getPolicy()) { case ABORT: break; /* do nothing - we return to map editing */ case AUTOMATICALLY_OPEN_NEW_CHANGESETS: diff --git a/src/org/openstreetmap/josm/gui/io/UploadSelectionDialog.java b/src/org/openstreetmap/josm/gui/io/UploadSelectionDialog.java index 9e4fb1edb26..973e25348b0 100644 --- a/src/org/openstreetmap/josm/gui/io/UploadSelectionDialog.java +++ b/src/org/openstreetmap/josm/gui/io/UploadSelectionDialog.java @@ -253,7 +253,7 @@ public List getPrimitives(int... indices) { if (indices == null || indices.length == 0) return Collections.emptyList(); return Arrays.stream(indices).filter(i -> i >= 0) - .mapToObj(i -> data.get(i)) + .mapToObj(data::get) .collect(Collectors.toList()); } } diff --git a/src/org/openstreetmap/josm/gui/io/UploadStrategySelectionPanel.java b/src/org/openstreetmap/josm/gui/io/UploadStrategySelectionPanel.java index 2936192705c..96d95e94457 100644 --- a/src/org/openstreetmap/josm/gui/io/UploadStrategySelectionPanel.java +++ b/src/org/openstreetmap/josm/gui/io/UploadStrategySelectionPanel.java @@ -211,7 +211,7 @@ public UploadStrategySpecification getUploadStrategySpecification() { UploadStrategy strategy = getUploadStrategy(); UploadStrategySpecification spec = new UploadStrategySpecification(); if (strategy != null) { - switch(strategy) { + switch (strategy) { case CHUNKED_DATASET_STRATEGY: spec.setStrategy(strategy).setChunkSize(getChunkSize()); break; @@ -388,7 +388,7 @@ public void itemStateChanged(ItemEvent e) { UploadStrategy strategy = getUploadStrategy(); if (strategy == null) return; - switch(strategy) { + switch (strategy) { case CHUNKED_DATASET_STRATEGY: tfChunkSize.setEnabled(true); tfChunkSize.requestFocusInWindow(); diff --git a/src/org/openstreetmap/josm/gui/io/importexport/FileExporter.java b/src/org/openstreetmap/josm/gui/io/importexport/FileExporter.java index 430e24b0586..1ff20853a4e 100644 --- a/src/org/openstreetmap/josm/gui/io/importexport/FileExporter.java +++ b/src/org/openstreetmap/josm/gui/io/importexport/FileExporter.java @@ -67,7 +67,7 @@ public void exportDataQuiet(File file, Layer layer) throws IOException { } /** - * Returns the enabled state of this {@code FileExporter}. When enabled, it is listed and usable in "File->Save" dialogs. + * Returns the enabled state of this {@code FileExporter}. When enabled, it is listed and usable in "File → Save" dialogs. * @return true if this {@code FileExporter} is enabled * @since 5459 */ @@ -76,7 +76,7 @@ public final boolean isEnabled() { } /** - * Sets the enabled state of the {@code FileExporter}. When enabled, it is listed and usable in "File->Save" dialogs. + * Sets the enabled state of the {@code FileExporter}. When enabled, it is listed and usable in "File → Save" dialogs. * @param enabled true to enable this {@code FileExporter}, false to disable it * @since 5459 */ diff --git a/src/org/openstreetmap/josm/gui/io/importexport/FileImporter.java b/src/org/openstreetmap/josm/gui/io/importexport/FileImporter.java index d334493ab7a..4eb065313ec 100644 --- a/src/org/openstreetmap/josm/gui/io/importexport/FileImporter.java +++ b/src/org/openstreetmap/josm/gui/io/importexport/FileImporter.java @@ -179,7 +179,7 @@ public int compareTo(FileImporter other) { } /** - * Returns the enabled state of this {@code FileImporter}. When enabled, it is listed and usable in "File->Open" dialog. + * Returns the enabled state of this {@code FileImporter}. When enabled, it is listed and usable in "File → Open" dialog. * @return true if this {@code FileImporter} is enabled * @since 5459 */ @@ -188,7 +188,7 @@ public final boolean isEnabled() { } /** - * Sets the enabled state of the {@code FileImporter}. When enabled, it is listed and usable in "File->Open" dialog. + * Sets the enabled state of the {@code FileImporter}. When enabled, it is listed and usable in "File → Open" dialog. * @param enabled true to enable this {@code FileImporter}, false to disable it * @since 5459 */ diff --git a/src/org/openstreetmap/josm/gui/io/importexport/GpxExporter.java b/src/org/openstreetmap/josm/gui/io/importexport/GpxExporter.java index 60b71d4816e..9a8b28bdffd 100644 --- a/src/org/openstreetmap/josm/gui/io/importexport/GpxExporter.java +++ b/src/org/openstreetmap/josm/gui/io/importexport/GpxExporter.java @@ -3,6 +3,7 @@ import static org.openstreetmap.josm.tools.I18n.tr; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; @@ -54,20 +55,21 @@ public class GpxExporter extends FileExporter implements GpxConstants { private static final String GPL_WARNING = "" + tr("Note: GPL is not compatible with the OSM license. Do not upload GPL licensed tracks.") + ""; + private static final String PUBLIC_DOMAIN = "public domain"; private static final String[] LICENSES = { "Creative Commons By-SA", "Open Database License (ODbL)", - "public domain", + PUBLIC_DOMAIN, "GNU Lesser Public License (LGPL)", "BSD License (MIT/X11)"}; private static final String[] URLS = { "https://creativecommons.org/licenses/by-sa/3.0", - "http://opendatacommons.org/licenses/odbl/1.0", - "public domain", + "https://opendatacommons.org/licenses/odbl/1-0/", + PUBLIC_DOMAIN, "https://www.gnu.org/copyleft/lesser.html", - "http://www.opensource.org/licenses/bsd-license.php"}; + "https://opensource.org/license/BSD-2-Clause"}; /** * Constructs a new {@code GpxExporter}. @@ -107,10 +109,9 @@ private void exportData(File file, Layer layer, boolean quiet) throws IOExceptio GpxData gpxData; if (quiet) { gpxData = getGpxData(layer, file); - try (OutputStream fo = Compression.getCompressedFileOutputStream(file)) { - GpxWriter w = new GpxWriter(fo); + try (OutputStream fo = Compression.getCompressedFileOutputStream(file); + GpxWriter w = new GpxWriter(fo)) { w.write(gpxData); - w.close(); fo.flush(); } return; @@ -134,7 +135,7 @@ private void exportData(File file, Layer layer, boolean quiet) throws IOExceptio desc.setWrapStyleWord(true); desc.setLineWrap(true); desc.setText(gpxData.getString(META_DESC)); - p.add(new JScrollPane(desc), GBC.eop().fill(GBC.BOTH)); + p.add(new JScrollPane(desc), GBC.eop().fill(GridBagConstraints.BOTH)); JCheckBox author = new JCheckBox(tr("Add author information"), Config.getPref().getBoolean("lastAddAuthor", true)); p.add(author, GBC.eol()); @@ -142,19 +143,19 @@ private void exportData(File file, Layer layer, boolean quiet) throws IOExceptio JLabel nameLabel = new JLabel(tr("Real name")); p.add(nameLabel, GBC.std().insets(10, 0, 5, 0)); JosmTextField authorName = new JosmTextField(); - p.add(authorName, GBC.eol().fill(GBC.HORIZONTAL)); + p.add(authorName, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); nameLabel.setLabelFor(authorName); JLabel emailLabel = new JLabel(tr("E-Mail")); p.add(emailLabel, GBC.std().insets(10, 0, 5, 0)); JosmTextField email = new JosmTextField(); - p.add(email, GBC.eol().fill(GBC.HORIZONTAL)); + p.add(email, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); emailLabel.setLabelFor(email); JLabel copyrightLabel = new JLabel(tr("Copyright (URL)")); p.add(copyrightLabel, GBC.std().insets(10, 0, 5, 0)); JosmTextField copyright = new JosmTextField(); - p.add(copyright, GBC.std().fill(GBC.HORIZONTAL)); + p.add(copyright, GBC.std().fill(GridBagConstraints.HORIZONTAL)); copyrightLabel.setLabelFor(copyright); JButton predefined = new JButton(tr("Predefined")); @@ -163,32 +164,32 @@ private void exportData(File file, Layer layer, boolean quiet) throws IOExceptio JLabel copyrightYearLabel = new JLabel(tr("Copyright year")); p.add(copyrightYearLabel, GBC.std().insets(10, 0, 5, 5)); JosmTextField copyrightYear = new JosmTextField(""); - p.add(copyrightYear, GBC.eol().fill(GBC.HORIZONTAL)); + p.add(copyrightYear, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); copyrightYearLabel.setLabelFor(copyrightYear); JLabel warning = new JLabel("  { - garmin.setEnabled(colors.isSelected()); - }); + colors.addActionListener(l -> garmin.setEnabled(colors.isSelected())); garmin.addActionListener(l -> { if (garmin.isSelected() && !ConditionalOptionPaneUtil.showConfirmationDialog( @@ -267,10 +266,9 @@ private void exportData(File file, Layer layer, boolean quiet) throws IOExceptio gpxData.put(META_KEYWORDS, keywords.getText()); } - try (OutputStream fo = Compression.getCompressedFileOutputStream(file)) { - GpxWriter w = new GpxWriter(fo); + try (OutputStream fo = Compression.getCompressedFileOutputStream(file); + GpxWriter w = new GpxWriter(fo)) { w.write(gpxData, cFormat, layerPrefs.isSelected()); - w.close(); fo.flush(); } } @@ -408,7 +406,7 @@ private static void addDependencies( StringBuilder license = new StringBuilder(); for (int i : l.getSelectedIndices()) { if (i == 2) { - license = new StringBuilder("public domain"); + license = new StringBuilder(PUBLIC_DOMAIN); break; } if (license.length() > 0) { diff --git a/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java b/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java index 89d48a0f6ee..e7bf4751791 100644 --- a/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java +++ b/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java @@ -1,6 +1,7 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.layer; +import static org.openstreetmap.josm.tools.I18n.marktr; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.Color; @@ -9,6 +10,7 @@ import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Image; import java.awt.Shape; @@ -178,6 +180,7 @@ public abstract class AbstractTileSourceLayer e public static final IntegerProperty ZOOM_OFFSET = new IntegerProperty(PREFERENCE_PREFIX + ".zoom_offset", 0); private static final BooleanProperty POPUP_MENU_ENABLED = new BooleanProperty(PREFERENCE_PREFIX + ".popupmenu", true); + private static final String ERROR_STRING = marktr("Error"); /* * use MemoryTileCache instead of tileLoader JCS cache, as tileLoader caches only content (byte[] of image) @@ -342,7 +345,7 @@ public Object getInfoComponent() { for (List entry: content) { panel.add(new JLabel(entry.get(0) + ':'), GBC.std()); panel.add(GBC.glue(5, 0), GBC.std()); - panel.add(createTextField(entry.get(1)), GBC.eol().fill(GBC.HORIZONTAL)); + panel.add(createTextField(entry.get(1)), GBC.eol().fill(GridBagConstraints.HORIZONTAL)); } return panel; } @@ -468,11 +471,11 @@ public void actionPerformed(ActionEvent ae) { content.add(Arrays.asList(tr("Status"), tr(tile.getStatus()))); content.add(Arrays.asList(tr("Loaded"), tr(Boolean.toString(tile.isLoaded())))); content.add(Arrays.asList(tr("Loading"), tr(Boolean.toString(tile.isLoading())))); - content.add(Arrays.asList(tr("Error"), tr(Boolean.toString(tile.hasError())))); + content.add(Arrays.asList(tr(ERROR_STRING), tr(Boolean.toString(tile.hasError())))); for (List entry: content) { panel.add(new JLabel(entry.get(0) + ':'), GBC.std()); panel.add(GBC.glue(5, 0), GBC.std()); - panel.add(layer.createTextField(entry.get(1)), GBC.eol().fill(GBC.HORIZONTAL)); + panel.add(layer.createTextField(entry.get(1)), GBC.eol().fill(GridBagConstraints.HORIZONTAL)); } for (Entry e: tile.getMetadata().entrySet()) { @@ -482,7 +485,7 @@ public void actionPerformed(ActionEvent ae) { if ("lastModification".equals(e.getKey()) || "expirationTime".equals(e.getKey())) { value = Instant.ofEpochMilli(Long.parseLong(value)).toString(); } - panel.add(layer.createTextField(value), GBC.eol().fill(GBC.HORIZONTAL)); + panel.add(layer.createTextField(value), GBC.eol().fill(GridBagConstraints.HORIZONTAL)); } ed.setIcon(JOptionPane.INFORMATION_MESSAGE); @@ -558,7 +561,7 @@ public LayerPainter attachToMapView(MapViewEvent event) { initializeIfRequired(); event.getMapView().addMouseListener(adapter); - MapView.addZoomChangeListener(this); + NavigatableComponent.addZoomChangeListener(this); if (this instanceof NativeScaleLayer && Boolean.TRUE.equals(NavigatableComponent.PROP_ZOOM_SCALE_FOLLOW_NATIVE_RES_AT_LOAD.get())) { event.getMapView().setNativeScaleLayer((NativeScaleLayer) this); @@ -660,13 +663,13 @@ protected int estimateTileCacheSize() { * The value should be sum(2^x for x in (-5 to 2)) - 1 * -1 to exclude current zoom level *

        + * Add +2 to maxYtiles / maxXtiles to add space in cache for extra tiles in current zoom level that are + * download by overloadTiles(). This is not added in computation of visibleTiles as this unnecessarily grow the cache size + *

        * Check call to tryLoadFromDifferentZoom * @see #tryLoadFromDifferentZoom(Graphics2D, int, List, int) * @see #drawInViewArea(Graphics2D, MapView, ProjectionBounds) - * - * Add +2 to maxYtiles / maxXtiles to add space in cache for extra tiles in current zoom level that are - * download by overloadTiles(). This is not added in computation of visibleTiles as this unnecessarily grow the cache size - * @see TileSet#overloadTiles() + * @see AbstractTileSourceLayer.TileSet#overloadTiles() */ private static int calculateRealTiles(int visibleTiles, int maxXtiles, int maxYtiles) { return (int) Math.ceil( @@ -1205,8 +1208,8 @@ private void paintTileText(Tile tile, Graphics2D g) { } catch (IllegalArgumentException e) { Logging.debug(e); } - if (!errorMessage.startsWith("Error") && !errorMessage.startsWith(tr("Error"))) { - errorMessage = tr("Error") + ": " + errorMessage; + if (!errorMessage.startsWith(ERROR_STRING) && !errorMessage.startsWith(tr(ERROR_STRING))) { + errorMessage = tr(ERROR_STRING) + ": " + errorMessage; } myDrawString(g, errorMessage, x + 2, texty); } @@ -1263,7 +1266,12 @@ private boolean tooSmall() { } private boolean tooLarge() { - return tileCache == null || size() > tileCache.getCacheSize(); + try { + return tileCache == null || size() > tileCache.getCacheSize(); + } catch (ArithmeticException arithmeticException) { + Logging.trace(arithmeticException); + return true; + } } /** @@ -1423,7 +1431,12 @@ private TileSetInfo getTileSetInfo() { if (info == null) { List allTiles = this.allExistingTiles(); TileSetInfo newInfo = new TileSetInfo(); - newInfo.hasLoadingTiles = allTiles.size() < this.size(); + try { + newInfo.hasLoadingTiles = allTiles.size() < this.size(); + } catch (ArithmeticException arithmeticException) { + Logging.trace(arithmeticException); + newInfo.hasLoadingTiles = false; + } newInfo.hasAllLoadedTiles = true; for (Tile t : allTiles) { if ("no-tile".equals(t.getValue("tile-info"))) { @@ -1449,14 +1462,25 @@ private TileSetInfo getTileSetInfo() { @Override public String toString() { - return getClass().getName() + ": zoom: " + zoom + " X(" + minX + ", " + maxX + ") Y(" + minY + ", " + maxY + ") size: " + size(); + int size; + try { + size = size(); + } catch (ArithmeticException arithmeticException) { + Logging.trace(arithmeticException); + size = Integer.MIN_VALUE; + } + return getClass().getName() + + ": zoom: " + zoom + + " X(" + minX + ", " + maxX + + ") Y(" + minY + ", " + maxY + + ") size: " + (size >= 0 ? size : "Integer Overflow"); } } /** * Data container to hold information about a {@code TileSet} class. */ - private static class TileSetInfo { + private static final class TileSetInfo { boolean hasVisibleTiles; boolean hasOverzoomedTiles; boolean hasLoadingTiles; @@ -1472,7 +1496,8 @@ private static class TileSetInfo { protected TileSet getTileSet(ProjectionBounds bounds, int zoom) { if (zoom == 0) return new TileSet(); - TileXY t1, t2; + TileXY t1; + TileXY t2; IProjected topLeftUnshifted = coordinateConverter.shiftDisplayToServer(bounds.getMin()); IProjected botRightUnshifted = coordinateConverter.shiftDisplayToServer(bounds.getMax()); if (coordinateConverter.requiresReprojection()) { @@ -1495,7 +1520,8 @@ protected TileSet getTileSet(ProjectionBounds bounds, int zoom) { private class DeepTileSet { private final ProjectionBounds bounds; - private final int minZoom, maxZoom; + private final int minZoom; + private final int maxZoom; private final TileSet[] tileSets; @SuppressWarnings("unchecked") @@ -1992,14 +2018,14 @@ public Tile createTile(T source, int x, int y, int zoom) { @Override public synchronized void destroy() { super.destroy(); - MapView.removeZoomChangeListener(this); + NavigatableComponent.removeZoomChangeListener(this); adjustAction.destroy(); if (tileLoader instanceof TMSCachedTileLoader) { ((TMSCachedTileLoader) tileLoader).shutdown(); } } - private class TileSourcePainter extends CompatibilityModeLayerPainter { + private final class TileSourcePainter extends CompatibilityModeLayerPainter { /** The memory handle that will hold our tile source. */ private MemoryHandle memory; @@ -2045,14 +2071,14 @@ private void allocateCacheMemory() { } } - protected long getEstimatedCacheSize() { + private long getEstimatedCacheSize() { return 4L * tileSource.getTileSize() * tileSource.getTileSize() * estimateTileCacheSize(); } @Override public void detachFromMapView(MapViewEvent event) { event.getMapView().removeMouseListener(adapter); - MapView.removeZoomChangeListener(AbstractTileSourceLayer.this); + NavigatableComponent.removeZoomChangeListener(AbstractTileSourceLayer.this); super.detachFromMapView(event); if (memory != null) { memory.free(); diff --git a/src/org/openstreetmap/josm/gui/layer/GpxLayer.java b/src/org/openstreetmap/josm/gui/layer/GpxLayer.java index 43c772a06b3..26d9829c08c 100644 --- a/src/org/openstreetmap/josm/gui/layer/GpxLayer.java +++ b/src/org/openstreetmap/josm/gui/layer/GpxLayer.java @@ -78,9 +78,9 @@ public class GpxLayer extends AbstractModifiableLayer implements GpxDataContaine /** * used by {@link ChooseTrackVisibilityAction} to determine which tracks to show/hide - * + *

        * Call {@link #invalidate()} after each change! - * + *

        * TODO: Make it private, make it respond to track changes. */ public boolean[] trackVisibility = new boolean[0]; @@ -145,7 +145,7 @@ public GpxLayer(GpxData d, String name, boolean isLocal) { public Color getColor() { if (data == null) return null; - Color[] c = data.getTracks().stream().map(t -> t.getColor()).distinct().toArray(Color[]::new); + Color[] c = data.getTracks().stream().map(IGpxTrack::getColor).distinct().toArray(Color[]::new); return c.length == 1 ? c[0] : null; //only return if exactly one distinct color present } @@ -209,30 +209,31 @@ private void fillDataInfoComponent(StringBuilder info) { } if (!data.getTracks().isEmpty()) { + String tdSep = "

      " + count + "."; info.append(""); for (IGpxTrack trk : data.getTracks()) { - info.append("
      ") .append(trn("{0} track, {1} track segments", "{0} tracks, {1} track segments", data.getTrackCount(), data.getTrackCount(), data.getTrackSegsCount(), data.getTrackSegsCount())) .append("
      ").append(tr("Name")) - .append("").append(tr("Description")) - .append("").append(tr("Timespan")) - .append("").append(tr("Length")) - .append("").append(tr("Number of
      Segments")) - .append("
      ").append(tr("URL")) + .append(tdSep).append(tr("Description")) + .append(tdSep).append(tr("Timespan")) + .append(tdSep).append(tr("Length")) + .append(tdSep).append(tr("Number of
      Segments")) + .append(tdSep).append(tr("URL")) .append("
      "); - info.append(trk.getAttributes().getOrDefault(GpxConstants.GPX_NAME, "")); - info.append(""); - info.append(trk.getAttributes().getOrDefault(GpxConstants.GPX_DESC, "")); - info.append(""); - info.append(getTimespanForTrack(trk)); - info.append(""); - info.append(SystemOfMeasurement.getSystemOfMeasurement().getDistText(trk.length())); - info.append(""); - info.append(trk.getSegments().size()); - info.append(""); + info.append("
      ") + .append(trk.getAttributes().getOrDefault(GpxConstants.GPX_NAME, "")) + .append(tdSep) + .append(trk.getAttributes().getOrDefault(GpxConstants.GPX_DESC, "")) + .append(tdSep) + .append(getTimespanForTrack(trk)) + .append(tdSep) + .append(SystemOfMeasurement.getSystemOfMeasurement().getDistText(trk.length())) + .append(tdSep) + .append(trk.getSegments().size()) + .append(tdSep); if (trk.getAttributes().containsKey("url")) { info.append(trk.get("url")); } @@ -286,7 +287,7 @@ public Action[] getMenuEntries() { if (isExpertMode && expert.stream().anyMatch(Action::isEnabled)) { entries.add(SeparatorLayerAction.INSTANCE); - expert.stream().filter(Action::isEnabled).forEach(entries::add); + entries.addAll(expert.stream().filter(Action::isEnabled).collect(Collectors.toList())); } entries.add(SeparatorLayerAction.INSTANCE); @@ -620,18 +621,16 @@ public void jumpToPreviousMarker() { } private void jumpToNext(List segments) { - if (segments.isEmpty()) { - return; - } else if (currentSegment == null) { + if (!segments.isEmpty() && currentSegment == null) { currentSegment = segments.get(0); MainApplication.getMap().mapView.zoomTo(currentSegment.getBounds()); - } else { + } else if (!segments.isEmpty()) { try { int index = segments.indexOf(currentSegment); currentSegment = segments.listIterator(index + 1).next(); MainApplication.getMap().mapView.zoomTo(currentSegment.getBounds()); - } catch (IndexOutOfBoundsException | NoSuchElementException ignore) { - Logging.trace(ignore); + } catch (IndexOutOfBoundsException | NoSuchElementException exception) { + Logging.trace(exception); } } } diff --git a/src/org/openstreetmap/josm/gui/layer/ImageryLayer.java b/src/org/openstreetmap/josm/gui/layer/ImageryLayer.java index 933933da39c..db7b37e2182 100644 --- a/src/org/openstreetmap/josm/gui/layer/ImageryLayer.java +++ b/src/org/openstreetmap/josm/gui/layer/ImageryLayer.java @@ -5,6 +5,7 @@ import static org.openstreetmap.josm.tools.I18n.trc; import java.awt.Component; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.image.BufferedImage; @@ -47,7 +48,7 @@ /** * Abstract base class for background imagery layers ({@link WMSLayer}, {@link TMSLayer}, {@link WMTSLayer}). - * + *

      * Handles some common tasks, like image filters, image processors, etc. */ public abstract class ImageryLayer extends Layer { @@ -137,7 +138,7 @@ public Object getInfoComponent() { for (List entry: content) { panel.add(new JLabel(entry.get(0) + ':'), GBC.std()); panel.add(GBC.glue(5, 0), GBC.std()); - panel.add(createTextField(entry.get(1)), GBC.eol().fill(GBC.HORIZONTAL)); + panel.add(createTextField(entry.get(1)), GBC.eol().fill(GridBagConstraints.HORIZONTAL)); } } return panel; @@ -159,7 +160,7 @@ protected JComponent createTextField(String text) { * @return The created layer */ public static ImageryLayer create(ImageryInfo info) { - switch(info.getImageryType()) { + switch (info.getImageryType()) { case WMS: case WMS_ENDPOINT: return new WMSLayer(info); @@ -192,6 +193,9 @@ public void actionPerformed(ActionEvent ev) { } } + /** + * Create an offset for an imagery layer + */ public class OffsetAction extends AbstractAction implements LayerAction { @Override public void actionPerformed(ActionEvent e) { @@ -222,9 +226,9 @@ public JMenuItem getOffsetMenuItem() { /** * Create the submenu or the menu item to set the offset of the layer. - * + *

      * If only one menu item for this layer exists, it is returned by this method. - * + *

      * If there are multiple, this method appends them to the subMenu and then returns the reference to the subMenu. * @param subMenu The subMenu to use * @return A single menu item to adjust the layer or the passed subMenu to which the menu items were appended. diff --git a/src/org/openstreetmap/josm/gui/layer/JumpToMarkerActions.java b/src/org/openstreetmap/josm/gui/layer/JumpToMarkerActions.java index 5713ed11135..2e049783f1c 100644 --- a/src/org/openstreetmap/josm/gui/layer/JumpToMarkerActions.java +++ b/src/org/openstreetmap/josm/gui/layer/JumpToMarkerActions.java @@ -123,8 +123,15 @@ public MultikeyInfo getLastMultikeyAction() { } } + /** + * Go to the next marker in a layer + */ public static final class JumpToNextMarker extends JumpToMarker { + /** + * Create a new {@link JumpToNextMarker} action + * @param layer The layer to use when jumping to the next marker + */ public JumpToNextMarker(JumpToMarkerLayer layer) { super(layer, Shortcut.registerShortcut("core_multikey:nextMarker", tr("Multikey: {0}", tr("Next marker")), KeyEvent.VK_J, Shortcut.ALT_CTRL)); @@ -139,8 +146,15 @@ protected void execute(JumpToMarkerLayer l) { } } + /** + * Go to the previous marker in a layer + */ public static final class JumpToPreviousMarker extends JumpToMarker { + /** + * Create a new {@link JumpToPreviousMarker} action + * @param layer The layer to use when jumping to the previous marker + */ public JumpToPreviousMarker(JumpToMarkerLayer layer) { super(layer, Shortcut.registerShortcut("core_multikey:previousMarker", tr("Multikey: {0}", tr("Previous marker")), KeyEvent.VK_P, Shortcut.ALT_CTRL)); diff --git a/src/org/openstreetmap/josm/gui/layer/Layer.java b/src/org/openstreetmap/josm/gui/layer/Layer.java index f0a4bbec9a4..879d4177b07 100644 --- a/src/org/openstreetmap/josm/gui/layer/Layer.java +++ b/src/org/openstreetmap/josm/gui/layer/Layer.java @@ -608,7 +608,7 @@ public void onPostLoadFromFile() { } /** - * Replies the savable state of this layer (i.e., if it can be saved through a "File->Save" dialog). + * Replies the savable state of this layer (i.e., if it can be saved through a "File → Save" dialog). * @return true if this layer can be saved to a file * @since 5459 */ diff --git a/src/org/openstreetmap/josm/gui/layer/LayerManager.java b/src/org/openstreetmap/josm/gui/layer/LayerManager.java index 096b8814d09..9a8cb0f2abc 100644 --- a/src/org/openstreetmap/josm/gui/layer/LayerManager.java +++ b/src/org/openstreetmap/josm/gui/layer/LayerManager.java @@ -23,7 +23,7 @@ * This manager handles a list of layers with the first layer being the front layer. *

      Threading

      * Synchronization of the layer manager is done by synchronizing all read/write access. All changes are internally done in the EDT thread. - * + *

      * Methods of this manager may be called from any thread in any order. * Listeners are called while this layer manager is locked, so they should not block on other threads. * @@ -188,10 +188,13 @@ public String toString() { } } + private static final String LISTENER = "listener"; + private static final String EVENT = "event"; + /** * This is the list of layers we manage. The list is unmodifiable. That way, read access does * not need to be synchronized. - * + *

      * It is only changed in the EDT. * @see LayerManager#updateLayers(Consumer) */ @@ -254,7 +257,7 @@ public void removeLayer(final Layer layer) { */ protected synchronized void realRemoveLayer(Layer layer) { GuiHelper.assertCallFromEdt(); - Set toRemove = Collections.newSetFromMap(new IdentityHashMap()); + Set toRemove = Collections.newSetFromMap(new IdentityHashMap<>()); toRemove.add(layer); while (!toRemove.isEmpty()) { @@ -360,7 +363,7 @@ public List getLayers() { /** * Replies an unmodifiable list of layers of a certain type. - * + *

      * Example: *

            *     List<WMSLayer> wmsLayers = getLayersOfType(WMSLayer.class);
      @@ -458,7 +461,7 @@ private void fireLayerAdded(Layer layer, boolean initialZoom) {
                   try {
                       l.layerAdded(e);
                   } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException t) {
      -                throw BugReport.intercept(t).put("listener", l).put("event", e);
      +                throw BugReport.intercept(t).put(LISTENER, l).put(EVENT, e);
                   }
               }
           }
      @@ -475,7 +478,7 @@ private Collection fireLayerRemoving(Layer layer) {
                   try {
                       l.layerRemoving(e);
                   } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException t) {
      -                throw BugReport.intercept(t).put("listener", l).put("event", e).put("layer", layer);
      +                throw BugReport.intercept(t).put(LISTENER, l).put(EVENT, e).put("layer", layer);
                   }
               }
               if (layer instanceof OsmDataLayer) {
      @@ -491,7 +494,7 @@ private void fireLayerOrderChanged() {
                   try {
                       l.layerOrderChanged(e);
                   } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException t) {
      -                throw BugReport.intercept(t).put("listener", l).put("event", e);
      +                throw BugReport.intercept(t).put(LISTENER, l).put(EVENT, e);
                   }
               }
           }
      diff --git a/src/org/openstreetmap/josm/gui/layer/NoteLayer.java b/src/org/openstreetmap/josm/gui/layer/NoteLayer.java
      index dda1fec72bc..f4ac06dffcd 100644
      --- a/src/org/openstreetmap/josm/gui/layer/NoteLayer.java
      +++ b/src/org/openstreetmap/josm/gui/layer/NoteLayer.java
      @@ -311,10 +311,10 @@ public static String getNoteToolTip(Note note) {
               for (NoteComment comment : note.getComments()) {
                   String commentText = comment.getText();
                   //closing a note creates an empty comment that we don't want to show
      -            if (!Utils.isBlank(commentText)) {
      +            if (!Utils.isStripEmpty(commentText)) {
                       sb.append("
      "); String userName = XmlWriter.encode(comment.getUser().getName()); - if (Utils.isBlank(userName)) { + if (Utils.isStripEmpty(userName)) { userName = "<Anonymous>"; } sb.append(userName) diff --git a/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java b/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java index 96530bd9ba4..14f71ddc872 100644 --- a/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java +++ b/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java @@ -1195,6 +1195,7 @@ public final void setUploadDiscouraged(boolean uploadDiscouraged) { if (data.getUploadPolicy() != UploadPolicy.BLOCKED && (uploadDiscouraged ^ isUploadDiscouraged())) { data.setUploadPolicy(uploadDiscouraged ? UploadPolicy.DISCOURAGED : UploadPolicy.NORMAL); + setRequiresSaveToFile(true); for (LayerStateChangeListener l : layerStateChangeListeners) { l.uploadDiscouragedChanged(this, uploadDiscouraged); } diff --git a/src/org/openstreetmap/josm/gui/layer/SaveToFile.java b/src/org/openstreetmap/josm/gui/layer/SaveToFile.java index 135d29d1e9d..d7f50ae101e 100644 --- a/src/org/openstreetmap/josm/gui/layer/SaveToFile.java +++ b/src/org/openstreetmap/josm/gui/layer/SaveToFile.java @@ -9,7 +9,7 @@ public interface SaveToFile { /** * Replies the savable state of the layer (i.e. if it can be saved through - * a "File->Save" dialog). A layer that implements the + * a "File → Save" dialog). A layer that implements the * {@code SaveToFile} interface must return {@code true}. * * @return {@code true} if the layer can be saved to a file; {@code false}, otherwise diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java b/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java index 4cb612e1911..c2b7c4bda6a 100644 --- a/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java +++ b/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java @@ -260,7 +260,7 @@ public String toString() { * This class is called when the user doesn't find the GPX file he needs in the files that have * been loaded yet. It displays a FileChooser dialog to select the GPX file to be loaded. */ - private class LoadGpxDataActionListener implements ActionListener { + private final class LoadGpxDataActionListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { @@ -283,7 +283,7 @@ public void actionPerformed(ActionEvent e) { } } - private class UseSupportLayerActionListener implements ActionListener { + private final class UseSupportLayerActionListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { @@ -295,7 +295,7 @@ public void actionPerformed(ActionEvent e) { } } - private class AdvancedSettingsActionListener implements ActionListener { + private final class AdvancedSettingsActionListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { @@ -316,7 +316,7 @@ public void actionPerformed(ActionEvent e) { * Then values of timezone and delta are set. * @author chris */ - private class SetOffsetActionListener implements ActionListener { + private final class SetOffsetActionListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { @@ -353,7 +353,7 @@ public void actionPerformed(ActionEvent e) { } } - private static class GpxLayerAddedListener implements LayerChangeListener { + private static final class GpxLayerAddedListener implements LayerChangeListener { @Override public void layerAdded(LayerAddEvent e) { Layer layer = e.getAddedLayer(); @@ -542,7 +542,7 @@ public void actionPerformed(ActionEvent ae) { gbc.gridy = y++; panelTf.add(panelCb, gbc); - gbc = GBC.eol().fill(GBC.HORIZONTAL).insets(0, 0, 0, 12); + gbc = GBC.eol().fill(GridBagConstraints.HORIZONTAL).insets(0, 0, 0, 12); gbc.gridx = 0; gbc.gridy = y++; panelTf.add(new JSeparator(SwingConstants.HORIZONTAL), gbc); @@ -552,7 +552,7 @@ public void actionPerformed(ActionEvent ae) { gbc.gridy = y; panelTf.add(new JLabel(tr("Timezone: ")), gbc); - gbc = GBC.std().fill(GBC.HORIZONTAL); + gbc = GBC.std().fill(GridBagConstraints.HORIZONTAL); gbc.gridx = 1; gbc.gridy = y++; gbc.weightx = 1.; @@ -563,7 +563,7 @@ public void actionPerformed(ActionEvent ae) { gbc.gridy = y; panelTf.add(new JLabel(tr("Offset:")), gbc); - gbc = GBC.std().fill(GBC.HORIZONTAL); + gbc = GBC.std().fill(GridBagConstraints.HORIZONTAL); gbc.gridx = 1; gbc.gridy = y++; gbc.weightx = 1.; @@ -578,7 +578,7 @@ public void actionPerformed(ActionEvent ae) { gbc.weightx = 0.5; panelTf.add(buttonViewGpsPhoto, gbc); - gbc = GBC.std().fill(GBC.BOTH).insets(5, 5, 5, 5); + gbc = GBC.std().fill(GridBagConstraints.BOTH).insets(5, 5, 5, 5); gbc.gridx = 1; gbc.gridy = y++; gbc.weightx = 0.5; @@ -590,7 +590,7 @@ public void actionPerformed(ActionEvent ae) { gbc.gridx = 3; panelTf.add(buttonAdjust, gbc); - gbc = GBC.eol().fill(GBC.HORIZONTAL).insets(0, 12, 0, 0); + gbc = GBC.eol().fill(GridBagConstraints.HORIZONTAL).insets(0, 12, 0, 0); gbc.gridx = 0; gbc.gridy = y++; panelTf.add(new JSeparator(SwingConstants.HORIZONTAL), gbc); @@ -615,7 +615,7 @@ public void actionPerformed(ActionEvent ae) { gbc.gridy = y; panelTf.add(cbShowThumbs, gbc); - gbc = GBC.eol().fill(GBC.HORIZONTAL).insets(0, 12, 0, 0); + gbc = GBC.eol().fill(GridBagConstraints.HORIZONTAL).insets(0, 12, 0, 0); sepDirectionPosition = new JSeparator(SwingConstants.HORIZONTAL); panelTf.add(sepDirectionPosition, gbc); @@ -807,7 +807,7 @@ public void focusLost(FocusEvent e) { /** * Presents dialog with sliders for manual adjust. */ - private class AdjustActionListener implements ActionListener { + private final class AdjustActionListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { @@ -894,7 +894,7 @@ static Pair autoGuess(List imgs, GpxData return GpxTimeOffset.milliseconds(firstExifDate - firstGPXDate).splitOutTimezone(); } - private class AutoGuessActionListener implements ActionListener { + private final class AutoGuessActionListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { @@ -942,7 +942,7 @@ private List getSortedImgList() { return yLayer.getSortedImgList(cbExifImg.isSelected(), cbTaggedImg.isSelected()); } - private GpxDataWrapper selectedGPX(boolean complain) { + private static GpxDataWrapper selectedGPX(boolean complain) { Object item = gpxModel.getSelectedItem(); if (item == null || ((GpxDataWrapper) item).data == null) { @@ -960,7 +960,7 @@ public void destroy() { ExpertToggleAction.removeExpertModeChangeListener(this); if (cbGpx != null) { // Force the JCombobox to remove its eventListener from the static GpxDataWrapper - cbGpx.setModel(new DefaultComboBoxModel()); + cbGpx.setModel(new DefaultComboBoxModel<>()); cbGpx = null; } diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java b/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java index da9414f0b6e..57b2ec54c60 100644 --- a/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java +++ b/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java @@ -185,7 +185,7 @@ public GeoImageLayer(final List data, GpxLayer gpxLayer, final Strin * @since 18078 */ public GeoImageLayer(final List data, GpxData gpxData, final String name, boolean useThumbs) { - super(!Utils.isBlank(name) ? name : tr("Geotagged Images")); + super(!Utils.isStripEmpty(name) ? name : tr("Geotagged Images")); this.data = new ImageData(data); this.gpxData = gpxData; this.useThumbs = useThumbs; @@ -743,12 +743,11 @@ public void mouseDragged(MouseEvent evt) { mapModeListener = (oldMapMode, newMapMode) -> { MapView mapView = MainApplication.getMap().mapView; + mapView.removeMouseListener(mouseAdapter); + mapView.removeMouseMotionListener(mouseMotionAdapter); if (newMapMode == null || isSupportedMapMode(newMapMode)) { mapView.addMouseListener(mouseAdapter); mapView.addMouseMotionListener(mouseMotionAdapter); - } else { - mapView.removeMouseListener(mouseAdapter); - mapView.removeMouseMotionListener(mouseMotionAdapter); } }; diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java index 7f3a1d40776..fb764e6a37b 100644 --- a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java +++ b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java @@ -116,7 +116,9 @@ public class ImageDisplay extends JComponent implements Destroyable, PreferenceC private UpdateImageThread updateImageThreadInstance; - private class UpdateImageThread extends Thread { + private boolean destroyed; + + private final class UpdateImageThread extends Thread { private boolean restart; @SuppressWarnings("DoNotCall") // we are calling `run` from the thread we want it to be running on (aka recursive) @@ -143,7 +145,7 @@ public void restart() { public void preferenceChanged(PreferenceChangeEvent e) { if (e == null || e.getKey().equals(AGPIFO_STYLE.getKey())) { - dragButton = AGPIFO_STYLE.get() ? 1 : 3; + dragButton = Boolean.TRUE.equals(AGPIFO_STYLE.get()) ? 1 : 3; zoomButton = dragButton == 1 ? 3 : 1; } } @@ -317,7 +319,7 @@ public void run() { } } - private class ImgDisplayMouseListener extends MouseAdapter { + private final class ImgDisplayMouseListener extends MouseAdapter { private MouseEvent lastMouseEvent; private Point mousePointInImg; @@ -431,7 +433,7 @@ public void mouseClicked(MouseEvent e) { if (currentImage == null) return; - if (ZOOM_ON_CLICK.get()) { + if (Boolean.TRUE.equals(ZOOM_ON_CLICK.get())) { // click notions are less coherent than wheel, refresh mousePointInImg on each click lastMouseEvent = null; @@ -619,13 +621,16 @@ public ImageDisplay(ImageProcessor imageProcessor) { @Override public void destroy() { - removeMouseListener(imgMouseListener); - removeMouseWheelListener(imgMouseListener); - removeMouseMotionListener(imgMouseListener); - Config.getPref().removePreferenceChangeListener(this); - if (imageProcessor instanceof ImageryFilterSettings) { - ((ImageryFilterSettings) imageProcessor).removeFilterChangeListener(this); + if (!destroyed) { + removeMouseListener(imgMouseListener); + removeMouseWheelListener(imgMouseListener); + removeMouseMotionListener(imgMouseListener); + Config.getPref().removePreferenceChangeListener(this); + if (imageProcessor instanceof ImageryFilterSettings) { + ((ImageryFilterSettings) imageProcessor).removeFilterChangeListener(this); + } } + destroyed = true; } /** @@ -766,7 +771,7 @@ private void paintErrorMessage(Graphics g, IImageEntry imageEntry, IImageEntr } else { errorMessage = null; } - if (!Utils.isBlank(errorMessage)) { + if (!Utils.isStripEmpty(errorMessage)) { Rectangle2D errorStringSize = g.getFontMetrics(g.getFont()).getStringBounds(errorMessage, g); if (Boolean.TRUE.equals(ERROR_MESSAGE_BACKGROUND.get())) { int height = g.getFontMetrics().getHeight(); diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java index 74e292df7f8..17e8d22db4f 100644 --- a/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java +++ b/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java @@ -179,17 +179,21 @@ private ImageViewerDialog() { // model update, then the image will change instead of remaining the same. this.layers.getModel().addChangeListener(l -> { // We need to check to see whether or not the worker is shut down. See #22922 for details. - if (!MainApplication.worker.isShutdown()) { - MainApplication.worker.execute(() -> GuiHelper.runInEDT(() -> { - Component selected = this.layers.getSelectedComponent(); - if (selected instanceof MoveImgDisplayPanel) { - ((MoveImgDisplayPanel) selected).fireModelUpdate(); - } - })); + if (!MainApplication.worker.isShutdown() && this.isDialogShowing()) { + MainApplication.worker.execute(() -> GuiHelper.runInEDT(this::showNotify)); } }); } + @Override + public void showNotify() { + super.showNotify(); + Component selected = this.layers.getSelectedComponent(); + if (selected instanceof MoveImgDisplayPanel) { + ((MoveImgDisplayPanel) selected).fireModelUpdate(); + } + } + private static JButton createButton(AbstractAction action, Dimension buttonDim) { JButton btn = new JButton(action); btn.setPreferredSize(buttonDim); @@ -225,8 +229,9 @@ private void build() { btnNext = createNavigationButton(imageNextAction, buttonDim); btnLast = createNavigationButton(imageLastAction, buttonDim); + centerView = Config.getPref().getBoolean("geoimage.viewer.centre.on.image", false); tbCentre = new JToggleButton(imageCenterViewAction); - tbCentre.setSelected(Config.getPref().getBoolean("geoimage.viewer.centre.on.image", false)); + tbCentre.setSelected(centerView); tbCentre.setPreferredSize(buttonDim); JButton btnZoomBestFit = new JButton(imageZoomAction); @@ -298,6 +303,8 @@ private void updateLayers(boolean changed) { } else if (selected != null && !selected.layer.containsImage(this.currentEntry)) { this.getImageTabs().filter(m -> m.layer.containsImage(this.currentEntry)).mapToInt(this.layers::indexOfComponent).findFirst() .ifPresent(this.layers::setSelectedIndex); + } else if (selected == null) { + updateTitle(); } this.layers.invalidate(); } @@ -335,6 +342,7 @@ private void addButtonsForImageLayers() { if (index >= 0) { removeImageTab(((MoveImgDisplayPanel) layers.getComponentAt(index)).layer); getImageTabs().forEach(m -> m.setVisible(m.isVisible())); + showNotify(); return; } source = source.getParent(); @@ -400,8 +408,12 @@ public void destroy() { imageRemoveAction.destroy(); imageRemoveFromDiskAction.destroy(); imageZoomAction.destroy(); + toggleAction.destroy(); cancelLoadingImage(); super.destroy(); + // make sure that Image Display is destroyed here, it might not be a component + imgDisplay.destroy(); + // Ensure that this dialog is removed from memory destroyInstance(); } @@ -898,6 +910,7 @@ public void displayImages(List> entries) { .filter(IGeoImageLayer.class::isInstance).map(IGeoImageLayer.class::cast).collect(Collectors.toList()); if (!Config.getPref().getBoolean("geoimage.viewer.show.tabs", true)) { updateRequired = true; + layers.removeAll(); // Clear the selected images in other geoimage layers this.getImageTabs().map(m -> m.layer).filter(IGeoImageLayer.class::isInstance).map(IGeoImageLayer.class::cast) .filter(l -> !Objects.equals(entries, l.getSelection())) @@ -922,7 +935,7 @@ public void displayImages(List> entries) { } if (!isDialogShowing()) { setIsDocked(false); // always open a detached window when an image is clicked and dialog is closed - showDialog(); + unfurlDialog(); } else if (isDocked && isCollapsed) { expand(); dialogsPanel.reconstruct(DialogsPanel.Action.COLLAPSED_TO_DEFAULT, this); @@ -963,6 +976,8 @@ private void updateButtonsNullEntry(List> entries) { private void updateButtonsNonNullEntry(IImageEntry entry, boolean imageChanged) { if (imageChanged) { cancelLoadingImage(); + // don't show unwanted image + imgDisplay.setImage(null); // Set only if the image is new to preserve zoom and position if the same image is redisplayed // (e.g. to update the OSD). imgLoadingFuture = imgDisplay.setImage(entry); @@ -1049,7 +1064,6 @@ protected void stateChanged() { if (btnCollapse != null) { btnCollapse.setVisible(!isDocked); } - this.updateLayers(true); } /** @@ -1100,6 +1114,10 @@ public void layerRemoving(LayerRemoveEvent e) { displayImages(null); } this.updateLayers(true); + if (!layers.isVisible()) { + hideNotify(); + destroy(); + } } @Override diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/RemoteEntry.java b/src/org/openstreetmap/josm/gui/layer/geoimage/RemoteEntry.java index 2db67421809..ab87cd8c05a 100644 --- a/src/org/openstreetmap/josm/gui/layer/geoimage/RemoteEntry.java +++ b/src/org/openstreetmap/josm/gui/layer/geoimage/RemoteEntry.java @@ -295,11 +295,11 @@ public Projections getProjectionType() { @Override public InputStream getInputStream() throws IOException { - URI u = getImageURI(); + final URI u = getImageURI(); if (u.getScheme().contains("file")) { return Files.newInputStream(Paths.get(u)); } - HttpClient client = HttpClient.create(u.toURL()); + final HttpClient client = HttpClient.create(u.toURL()); InputStream actual = client.connect().getContent(); return new BufferedInputStream(actual) { @Override @@ -342,7 +342,7 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (super.equals(obj)) { + if (this == obj) { return true; } if (obj != null && obj.getClass() == this.getClass()) { diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/SynchronizeTimeFromPhotoDialog.java b/src/org/openstreetmap/josm/gui/layer/geoimage/SynchronizeTimeFromPhotoDialog.java index f7c26d37d0f..60207021f80 100644 --- a/src/org/openstreetmap/josm/gui/layer/geoimage/SynchronizeTimeFromPhotoDialog.java +++ b/src/org/openstreetmap/josm/gui/layer/geoimage/SynchronizeTimeFromPhotoDialog.java @@ -162,7 +162,7 @@ private Component buildContent(List images) { JPanel panelLst = new JPanel(new BorderLayout()); - JList imgList = new JList<>(new AbstractListModel() { + JList imgList = new JList<>(new AbstractListModel<>() { @Override public String getElementAt(int i) { return images.get(i).getDisplayName(); diff --git a/src/org/openstreetmap/josm/gui/layer/geoimage/WikimediaCommonsLoader.java b/src/org/openstreetmap/josm/gui/layer/geoimage/WikimediaCommonsLoader.java index ed623858088..d2fb462a693 100644 --- a/src/org/openstreetmap/josm/gui/layer/geoimage/WikimediaCommonsLoader.java +++ b/src/org/openstreetmap/josm/gui/layer/geoimage/WikimediaCommonsLoader.java @@ -15,6 +15,10 @@ import org.openstreetmap.josm.data.Bounds; import org.openstreetmap.josm.gui.MainApplication; import org.openstreetmap.josm.gui.PleaseWaitRunnable; +import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent; +import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener; +import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent; +import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent; import org.openstreetmap.josm.io.OsmTransferException; import org.openstreetmap.josm.tools.Logging; import org.openstreetmap.josm.tools.Mediawiki; @@ -65,12 +69,14 @@ protected void cancel() { * Load images from Wikimedia Commons * @since 18021 */ - public static class WikimediaCommonsLoadImagesAction extends JosmAction { + public static class WikimediaCommonsLoadImagesAction extends JosmAction implements LayerChangeListener { /** * Constructs a new {@code WikimediaCommonsLoadImagesAction} */ public WikimediaCommonsLoadImagesAction() { - super(tr("Load images from Wikimedia Commons"), "wikimedia_commons", null, null, false); + super(tr("Load images from Wikimedia Commons"), "wikimedia_commons", null, null, false, false); + MainApplication.getLayerManager().addLayerChangeListener(this); + initEnabledState(); } @Override @@ -83,5 +89,27 @@ public void actionPerformed(ActionEvent e) { protected void updateEnabledState() { setEnabled(MainApplication.isDisplayingMapView()); } + + @Override + public void layerAdded(LayerAddEvent e) { + updateEnabledState(); + } + + @Override + public void layerRemoving(LayerRemoveEvent e) { + if (e.isLastLayer()) + setEnabled(false); + } + + @Override + public void layerOrderChanged(LayerOrderChangeEvent e) { + // not used + } + + @Override + public void destroy() { + MainApplication.getLayerManager().removeLayerChangeListener(this); + super.destroy(); + } } } diff --git a/src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java b/src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java index e39bf2acf42..a37c18df4f6 100644 --- a/src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java +++ b/src/org/openstreetmap/josm/gui/layer/gpx/GpxDrawHelper.java @@ -592,8 +592,8 @@ public void calculateColors() { for (Line segment : getLinesIterable(null)) { for (WayPoint trkPnt : segment) { Object val = trkPnt.get(GpxConstants.PT_HDOP); - if (val != null) { - double hdop = ((Float) val).doubleValue(); + if (val instanceof Float) { + double hdop = ((Float) val); if (hdop > maxval) { maxval = hdop; } @@ -1465,7 +1465,8 @@ private void drawHeatMap(Graphics2D g, MapView mv, List visibleSegment private static void drawHeatGrayDotMap(Graphics2D gB, MapView mv, List listSegm, int drawSize) { // typical rendering rate -> use realtime preview instead of accurate display - final double maxSegm = 25_000, nrSegms = listSegm.size(); + final double maxSegm = 25_000; + final double nrSegms = listSegm.size(); // determine random drop rate final double randomDrop = Math.min(nrSegms > maxSegm ? (nrSegms - maxSegm) / nrSegms : 0, 0.70f); @@ -1510,8 +1511,10 @@ private static void drawHeatSurfaceLine(Graphics2D g, Point fromPnt, Point toPnt, int drawSize, double rmsSizeX, double rmsSizeY, double dropRate) { // collect frequently used items - final long fromX = (long) fromPnt.getX(); final long deltaX = (long) (toPnt.getX() - fromX); - final long fromY = (long) fromPnt.getY(); final long deltaY = (long) (toPnt.getY() - fromY); + final long fromX = (long) fromPnt.getX(); + final long fromY = (long) fromPnt.getY(); + final long deltaX = (long) (toPnt.getX() - fromX); + final long deltaY = (long) (toPnt.getY() - fromY); // use same random values for each point final Random heatMapRandom = new Random(fromX+fromY+deltaX+deltaY); diff --git a/src/org/openstreetmap/josm/gui/layer/gpx/ImportAudioAction.java b/src/org/openstreetmap/josm/gui/layer/gpx/ImportAudioAction.java index 23de76d3a63..a5cce1f209b 100644 --- a/src/org/openstreetmap/josm/gui/layer/gpx/ImportAudioAction.java +++ b/src/org/openstreetmap/josm/gui/layer/gpx/ImportAudioAction.java @@ -59,7 +59,7 @@ public String getDescription() { } } - private static class Markers { + private static final class Markers { public boolean timedMarkersOmitted; public boolean untimedMarkersOmitted; } diff --git a/src/org/openstreetmap/josm/gui/layer/imagery/ReprojectionTile.java b/src/org/openstreetmap/josm/gui/layer/imagery/ReprojectionTile.java index bbb0febcd81..846b4fea838 100644 --- a/src/org/openstreetmap/josm/gui/layer/imagery/ReprojectionTile.java +++ b/src/org/openstreetmap/josm/gui/layer/imagery/ReprojectionTile.java @@ -65,7 +65,7 @@ public TileAnchor getAnchor() { /** * Get the scale that was used for reprojecting the tile. - * + *

      * This is not necessarily the mapview scale, but may be * adjusted to avoid excessively large cache image. * @return the scale that was used for reprojecting the tile @@ -77,7 +77,7 @@ public double getNativeScale() { /** * Check if it is necessary to refresh the cache to match the current mapview * scale and get optimized image quality. - * + *

      * When the maximum zoom is exceeded, this method will generally return false. * @param currentScale the current mapview scale * @return true if the tile should be reprojected again from the source image. @@ -160,15 +160,15 @@ protected void transform(BufferedImage imageIn) { double scale = scaleFix == null ? scaleMapView : (scaleMapView * scaleFix); ProjectionBounds pbTargetAligned = pbMarginAndAlign(pbTarget, scale, margin); - ImageWarp.PointTransform pointTransform = pt -> { - EastNorth target = new EastNorth(pbTargetAligned.minEast + pt.getX() * scale, - pbTargetAligned.maxNorth - pt.getY() * scale); + ImageWarp.PointTransform pointTransform = (x, y) -> { + EastNorth target = new EastNorth(pbTargetAligned.minEast + x * scale, + pbTargetAligned.maxNorth - y * scale); EastNorth sourceEN = projServer.latlon2eastNorth(projCurrent.eastNorth2latlon(target)); - double x = source.getTileSize() * + double x2 = source.getTileSize() * (sourceEN.east() - pbServer.minEast) / (pbServer.maxEast - pbServer.minEast); - double y = source.getTileSize() * + double y2 = source.getTileSize() * (pbServer.maxNorth - sourceEN.north()) / (pbServer.maxNorth - pbServer.minNorth); - return new Point2D.Double(x, y); + return new Point2D.Double(x2, y2); }; // pixel coordinates of tile origin and opposite tile corner inside the target image @@ -224,7 +224,7 @@ private static Dimension getDimension(ProjectionBounds bounds, double scale) { /** * Make sure, the image is not scaled up too much. - * + *

      * This would not give any significant improvement in image quality and may * exceed the user's memory. The correction factor is a power of 2. * @param lenOrig tile size of original image diff --git a/src/org/openstreetmap/josm/gui/layer/imagery/TileSourceDisplaySettings.java b/src/org/openstreetmap/josm/gui/layer/imagery/TileSourceDisplaySettings.java index d747ec026dc..4496fb60279 100644 --- a/src/org/openstreetmap/josm/gui/layer/imagery/TileSourceDisplaySettings.java +++ b/src/org/openstreetmap/josm/gui/layer/imagery/TileSourceDisplaySettings.java @@ -80,6 +80,7 @@ public class TileSourceDisplaySettings implements SessionAwareReadApply { /** * Create a new {@link TileSourceDisplaySettings} */ + @SuppressWarnings("PMD.UnnecessaryVarargsArrayCreation") // See https://github.com/pmd/pmd/issues/5069 public TileSourceDisplaySettings() { this(new String[] {PREFERENCE_PREFIX}); } diff --git a/src/org/openstreetmap/josm/gui/layer/markerlayer/ImageMarker.java b/src/org/openstreetmap/josm/gui/layer/markerlayer/ImageMarker.java index 3121a70b2a7..611cd36ee61 100644 --- a/src/org/openstreetmap/josm/gui/layer/markerlayer/ImageMarker.java +++ b/src/org/openstreetmap/josm/gui/layer/markerlayer/ImageMarker.java @@ -61,7 +61,7 @@ RemoteEntry getRemoteEntry() { if (this.isLatLonKnown()) { remoteEntry.setPos(this); } - if (!Utils.isBlank(this.getText())) { + if (!Utils.isStripEmpty(this.getText())) { remoteEntry.setDisplayName(this.getText()); } return remoteEntry; diff --git a/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java b/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java index 1961c84f690..e17cb79af6e 100644 --- a/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java +++ b/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java @@ -75,13 +75,13 @@ /** * A layer holding markers. - * + *

      * Markers are GPS points with a name and, optionally, a symbol code attached; * marker layers can be created from waypoints when importing raw GPS data, * but they may also come from other sources. - * + *

      * The symbol code is for future use. - * + *

      * The data is read only. */ public class MarkerLayer extends Layer implements JumpToMarkerLayer, IGeoImageLayer { @@ -94,11 +94,14 @@ public class MarkerLayer extends Layer implements JumpToMarkerLayer, IGeoImageLa public GpxLayer fromLayer; private Marker currentMarker; public AudioMarker syncAudioMarker; - private Color color, realcolor; + private Color color; + private Color realcolor; final int markerSize = new IntegerProperty("draw.rawgps.markers.size", 4).get(); final BasicStroke markerStroke = new StrokeProperty("draw.rawgps.markers.stroke", "1").get(); private final ListenerList imageChangeListenerListenerList = ListenerList.create(); + private MarkerMouseAdapter mouseAdapter; + private MapView mapView; /** * The default color that is used for drawing markers. @@ -191,12 +194,19 @@ public synchronized void destroy() { fromLayer = null; data.forEach(Marker::destroy); data.clear(); + if (mouseAdapter != null && mapView != null) + mapView.removeMouseListener(mouseAdapter); super.destroy(); } @Override public LayerPainter attachToMapView(MapViewEvent event) { - event.getMapView().addMouseListener(new MarkerMouseAdapter()); + if (mapView != null) { + Logging.warn("MarkerLayer was already attached to a MapView"); + } + mapView = event.getMapView(); + mouseAdapter = new MarkerMouseAdapter(); + mapView.addMouseListener(mouseAdapter); if (event.getMapView().playHeadMarker == null) { event.getMapView().playHeadMarker = PlayHeadMarker.create(); @@ -522,7 +532,10 @@ public void clearSelection() { @Override public List> getSelection() { if (this.currentMarker instanceof ImageMarker) { - return Collections.singletonList(((ImageMarker) this.currentMarker).getRemoteEntry()); + final RemoteEntry remoteEntry = ((ImageMarker) this.currentMarker).getRemoteEntry(); + if (remoteEntry != null) { + return Collections.singletonList(remoteEntry); + } } return Collections.emptyList(); } @@ -606,9 +619,16 @@ public void mouseReleased(MouseEvent ev) { } } + /** + * Toggle visibility of the marker text and icons + */ public static final class ShowHideMarkerText extends AbstractAction implements LayerAction { private final transient MarkerLayer layer; + /** + * Create a new {@link ShowHideMarkerText} action + * @param layer The layer to toggle the visible state of the marker text and icons + */ public ShowHideMarkerText(MarkerLayer layer) { super(tr("Show Text/Icons")); new ImageProvider("dialogs", "showhide").getResource().attachImageIcon(this, true); diff --git a/src/org/openstreetmap/josm/gui/mappaint/BooleanStyleSettingGui.java b/src/org/openstreetmap/josm/gui/mappaint/BooleanStyleSettingGui.java index 78beaeea118..b3e6e007e24 100644 --- a/src/org/openstreetmap/josm/gui/mappaint/BooleanStyleSettingGui.java +++ b/src/org/openstreetmap/josm/gui/mappaint/BooleanStyleSettingGui.java @@ -2,7 +2,7 @@ package org.openstreetmap.josm.gui.mappaint; import java.awt.event.ActionEvent; -import java.util.Arrays; +import java.util.Collections; import java.util.Objects; import javax.swing.AbstractAction; @@ -39,7 +39,7 @@ static class BooleanStyleSettingCheckBoxMenuItem extends JCheckBoxMenuItem { public void actionPerformed(ActionEvent e) { setting.setValue(isSelected()); if (!noRepaint) { - MainApplication.worker.submit(new MapPaintStyleLoader(Arrays.asList(setting.parentStyle))); + MainApplication.worker.submit(new MapPaintStyleLoader(Collections.singletonList(setting.parentStyle))); } } }); diff --git a/src/org/openstreetmap/josm/gui/mappaint/Environment.java b/src/org/openstreetmap/josm/gui/mappaint/Environment.java index 21abda2654c..71a286920d9 100644 --- a/src/org/openstreetmap/josm/gui/mappaint/Environment.java +++ b/src/org/openstreetmap/josm/gui/mappaint/Environment.java @@ -284,7 +284,7 @@ public Environment withLinkContext() { * Creates a clone of this environment, with the selector set * @param selector The selector to use * @return A clone of this environment, with the specified selector - * @since xxx + * @since 18757 */ public Environment withSelector(Selector selector) { return new Environment(this, selector); @@ -334,7 +334,7 @@ public String getRole() { /** * Get the selector for this environment * @return The selector. May be {@code null}. - * @since xxx + * @since 18757 */ public Selector selector() { return this.selector; diff --git a/src/org/openstreetmap/josm/gui/mappaint/MapPaintMenu.java b/src/org/openstreetmap/josm/gui/mappaint/MapPaintMenu.java index 2880df75ab1..06469137b3c 100644 --- a/src/org/openstreetmap/josm/gui/mappaint/MapPaintMenu.java +++ b/src/org/openstreetmap/josm/gui/mappaint/MapPaintMenu.java @@ -21,7 +21,7 @@ import org.openstreetmap.josm.tools.ImageProvider; /** - * The View -> Map Paint Styles menu + * The View → Map Paint Styles menu * @since 5086 */ public class MapPaintMenu extends JMenu implements MapPaintStylesUpdateListener { diff --git a/src/org/openstreetmap/josm/gui/mappaint/StyleSetting.java b/src/org/openstreetmap/josm/gui/mappaint/StyleSetting.java index 47fbc579ea0..c5812728e4f 100644 --- a/src/org/openstreetmap/josm/gui/mappaint/StyleSetting.java +++ b/src/org/openstreetmap/josm/gui/mappaint/StyleSetting.java @@ -14,10 +14,10 @@ /** * Setting to customize a MapPaint style. - * + *

      * Can be changed by the user in the right click menu of the mappaint style * dialog. - * + *

      * Defined in the MapCSS style, e.g. *

        * setting::highway_casing {
      @@ -118,6 +118,10 @@ public static StyleSettingGroup create(Cascade c, StyleSource parentStyle, Strin
               }
           }
       
      +    /**
      +     * A setting for a style
      +     * @param  The property type
      +     */
           class PropertyStyleSetting extends LabeledStyleSetting implements StyleSetting {
               private final Class type;
               private final AbstractToStringProperty property;
      diff --git a/src/org/openstreetmap/josm/gui/mappaint/StyleSettingGroupGui.java b/src/org/openstreetmap/josm/gui/mappaint/StyleSettingGroupGui.java
      index 65bd123f734..9725c2e0407 100644
      --- a/src/org/openstreetmap/josm/gui/mappaint/StyleSettingGroupGui.java
      +++ b/src/org/openstreetmap/josm/gui/mappaint/StyleSettingGroupGui.java
      @@ -5,6 +5,7 @@
       
       import java.awt.event.ActionEvent;
       import java.util.Arrays;
      +import java.util.Collections;
       import java.util.List;
       import java.util.Objects;
       import java.util.stream.Collectors;
      @@ -51,12 +52,12 @@ public void addMenuEntry(JMenu menu) {
                       @Override
                       public void actionPerformed(ActionEvent e) {
                           List items = Arrays.stream(submenu.getMenuComponents())
      -                            .filter(c -> c instanceof BooleanStyleSettingCheckBoxMenuItem)
      +                            .filter(BooleanStyleSettingCheckBoxMenuItem.class::isInstance)
                                   .map(c -> (BooleanStyleSettingCheckBoxMenuItem) c)
                                   .collect(Collectors.toList());
                           final boolean select = items.stream().anyMatch(cbi -> !cbi.isSelected());
                           items.stream().filter(cbi -> select != cbi.isSelected()).forEach(cbi -> cbi.doClickWithoutRepaint(0));
      -                    MainApplication.worker.submit(new MapPaintStyleLoader(Arrays.asList(group.parentStyle)));
      +                    MainApplication.worker.submit(new MapPaintStyleLoader(Collections.singletonList(group.parentStyle)));
                       }
                   });
                   item.setUI(new StayOpenCheckBoxMenuItemUI());
      diff --git a/src/org/openstreetmap/josm/gui/mappaint/mapcss/ExpressionFactory.java b/src/org/openstreetmap/josm/gui/mappaint/mapcss/ExpressionFactory.java
      index e71f5f0f63a..c35e70cc993 100644
      --- a/src/org/openstreetmap/josm/gui/mappaint/mapcss/ExpressionFactory.java
      +++ b/src/org/openstreetmap/josm/gui/mappaint/mapcss/ExpressionFactory.java
      @@ -39,13 +39,49 @@ public final class ExpressionFactory {
           @Retention(RetentionPolicy.RUNTIME)
           @interface NullableArguments {}
       
      +    /**
      +     * Represents a function that accepts three arguments and produces a result. This is a specialization of {@link Function}.
      +     * This is a functional interface whose functional method is {@link #apply(Object, Object, Object)}.
      +     * @param  The type of the first argument
      +     * @param  The type of the second argument
      +     * @param  The type of the third argument
      +     * @param  The type of the result of the function
      +     * @see Function
      +     * @see BiFunction
      +     */
           @FunctionalInterface
           public interface TriFunction {
      +        /**
      +         * Call the function
      +         * @param t The first argument
      +         * @param u The second argument
      +         * @param v The third argument
      +         * @return The result of the function call
      +         */
               R apply(T t, U u, V v);
           }
       
      +    /**
      +     * Represents a function that accepts four arguments and produces a result. This is a specialization of {@link Function}.
      +     * This is a functional interface whose functional method is {@link #apply(Object, Object, Object, Object)}.
      +     * @param  The type of the first argument
      +     * @param  The type of the second argument
      +     * @param  The type of the third argument
      +     * @param  The type of the fourth argument
      +     * @param  The type of the result of the function
      +     * @see Function
      +     * @see BiFunction
      +     */
           @FunctionalInterface
           public interface QuadFunction {
      +        /**
      +         * Call the function
      +         * @param t The first argument
      +         * @param u The second argument
      +         * @param v The third argument
      +         * @param w The fourth argument
      +         * @return The result of the function call
      +         */
               R apply(T t, U u, V v, W w);
           }
       
      @@ -218,6 +254,7 @@ private static void initFactories() {
               FACTORY_MAP.put("URL_decode", Factory.of(String.class, Functions::URL_decode));
               FACTORY_MAP.put("URL_encode", Factory.of(String.class, Functions::URL_encode));
               FACTORY_MAP.put("XML_encode", Factory.of(String.class, Functions::XML_encode));
      +        FACTORY_MAP.put("siunit_length", Factory.of(String.class, Functions::siunit_length));
               FACTORY_MAP.put("abs", Factory.of(Math::acos));
               FACTORY_MAP.put("acos", Factory.of(Math::acos));
               FACTORY_MAP.put("alpha", Factory.of(Color.class, Functions::alpha));
      diff --git a/src/org/openstreetmap/josm/gui/mappaint/mapcss/Functions.java b/src/org/openstreetmap/josm/gui/mappaint/mapcss/Functions.java
      index 4036589fa9b..20568c54a4f 100644
      --- a/src/org/openstreetmap/josm/gui/mappaint/mapcss/Functions.java
      +++ b/src/org/openstreetmap/josm/gui/mappaint/mapcss/Functions.java
      @@ -1184,6 +1184,25 @@ public static String XML_encode(String s) {
               return s == null ? null : XmlWriter.encode(s);
           }
       
      +    /**
      +     * Convert a length unit to meters
      +     * 

      + * Tries to convert a length unit to meter value or returns {@code null} when impossible + * @param s arbitrary string representing a length + * @return the length converted to meters + * @since 19089 + */ + public static String siunit_length(String s) { + if (s == null) + return null; + try { + return Utils.unitToMeter(s).toString(); + } catch (IllegalArgumentException e) { + Logging.debug(e); + } + return null; + } + /** * Calculates the CRC32 checksum from a string (based on RFC 1952). * @param s the string diff --git a/src/org/openstreetmap/josm/gui/mappaint/mapcss/PlaceholderExpression.java b/src/org/openstreetmap/josm/gui/mappaint/mapcss/PlaceholderExpression.java index 3afc8801f33..bbe986e1cd0 100644 --- a/src/org/openstreetmap/josm/gui/mappaint/mapcss/PlaceholderExpression.java +++ b/src/org/openstreetmap/josm/gui/mappaint/mapcss/PlaceholderExpression.java @@ -14,7 +14,7 @@ /** * Used for expressions that contain placeholders - * @since xxx + * @since 18758 */ public final class PlaceholderExpression implements Expression { /** diff --git a/src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java b/src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java index 89fcd5ea219..060c90a5337 100644 --- a/src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java +++ b/src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java @@ -714,7 +714,7 @@ public Range getRange() { @Override public String toString() { - return "LinkSelector{conditions=" + getConditions() + '}'; + return "LinkSelector{conditions=" + super.getConditions() + '}'; } } @@ -760,7 +760,7 @@ public boolean matches(Environment e) { * @throws IllegalArgumentException if value is not knwon */ private static String checkBase(String base) { - switch(base) { + switch (base) { case "*": return BASE_ANY; case "node": return BASE_NODE; case "way": return BASE_WAY; @@ -843,7 +843,7 @@ public static int scale2level(double scale) { public String toString() { return base + (Range.ZERO_TO_INFINITY.equals(range) ? "" : range) - + getConditions().stream().map(String::valueOf).collect(Collectors.joining("")) + + super.getConditions().stream().map(String::valueOf).collect(Collectors.joining("")) + (subpart != null && subpart != Subpart.DEFAULT_SUBPART ? ("::" + subpart) : ""); } } diff --git a/src/org/openstreetmap/josm/gui/mappaint/styleelement/LabelCompositionStrategy.java b/src/org/openstreetmap/josm/gui/mappaint/styleelement/LabelCompositionStrategy.java index f4be2d26ee5..efbba5f1c90 100644 --- a/src/org/openstreetmap/josm/gui/mappaint/styleelement/LabelCompositionStrategy.java +++ b/src/org/openstreetmap/josm/gui/mappaint/styleelement/LabelCompositionStrategy.java @@ -1,6 +1,8 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.mappaint.styleelement; +import static java.util.function.Predicate.not; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -144,19 +146,7 @@ class DeriveLabelFromNameTagsCompositionStrategy implements LabelCompositionStra /** * The list of default name tags from which a label candidate is derived. */ - private static final String[] DEFAULT_NAME_TAGS = { - "name:" + LanguageInfo.getJOSMLocaleCode(), - "name", - "int_name", - "distance", - "railway:position", - "ref", - "operator", - "brand", - "addr:unit", - "addr:flats", - "addr:housenumber" - }; + private static final String[] DEFAULT_NAME_TAGS = getDefaultNameTags(); /** * The list of default name complement tags from which a label candidate is derived. @@ -176,12 +166,28 @@ public DeriveLabelFromNameTagsCompositionStrategy() { initNameTagsFromPreferences(); } + /* Is joining an array really that complicated in Java? */ + private static String[] getDefaultNameTags() { + final ArrayList tags = new ArrayList<>(Arrays.asList(LanguageInfo.getOSMLocaleCodes("name:"))); + tags.addAll(Arrays.asList("name", + "int_name", + "distance", + "railway:position", + "ref", + "operator", + "brand", + "addr:unit", + "addr:flats", + "addr:housenumber")); + return tags.toArray(String[]::new); + } + private static List buildNameTags(List nameTags) { if (nameTags == null) { return new ArrayList<>(); } return nameTags.stream() - .filter(tag -> !Utils.isStripEmpty(tag)) + .filter(not(Utils::isStripEmpty)) .collect(Collectors.toList()); } diff --git a/src/org/openstreetmap/josm/gui/oauth/AbstractAuthorizationUI.java b/src/org/openstreetmap/josm/gui/oauth/AbstractAuthorizationUI.java index 4b478c83d59..24d3d2d3e17 100644 --- a/src/org/openstreetmap/josm/gui/oauth/AbstractAuthorizationUI.java +++ b/src/org/openstreetmap/josm/gui/oauth/AbstractAuthorizationUI.java @@ -4,8 +4,8 @@ import java.util.Objects; import org.openstreetmap.josm.data.oauth.IOAuthParameters; +import org.openstreetmap.josm.data.oauth.IOAuthToken; import org.openstreetmap.josm.data.oauth.OAuthParameters; -import org.openstreetmap.josm.data.oauth.OAuthToken; import org.openstreetmap.josm.data.oauth.OAuthVersion; import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel; import org.openstreetmap.josm.tools.CheckParameterUtil; @@ -22,26 +22,23 @@ public abstract class AbstractAuthorizationUI extends VerticallyScrollablePanel public static final String ACCESS_TOKEN_PROP = AbstractAuthorizationUI.class.getName() + ".accessToken"; private String apiUrl; - private final AdvancedOAuthPropertiesPanel pnlAdvancedProperties = new AdvancedOAuthPropertiesPanel(OAuthVersion.OAuth10a); - private transient OAuthToken accessToken; - - /** - * Constructs a new {@code AbstractAuthorizationUI} without API URL. - * @since 10189 - */ - protected AbstractAuthorizationUI() { - } + private final AdvancedOAuthPropertiesPanel pnlAdvancedProperties; + private transient IOAuthToken accessToken; /** * Constructs a new {@code AbstractAuthorizationUI} for the given API URL. - * @param apiUrl The OSM API URL - * @since 5422 + * @param apiUrl The OSM API URL (may be null) + * @param oAuthVersion The oauth version to use + * @since 18991 */ - protected AbstractAuthorizationUI(String apiUrl) { - setApiUrl(apiUrl); + protected AbstractAuthorizationUI(String apiUrl, OAuthVersion oAuthVersion) { + this.pnlAdvancedProperties = new AdvancedOAuthPropertiesPanel(oAuthVersion); + if (apiUrl != null) { + setApiUrl(apiUrl); + } } - protected void fireAccessTokenChanged(OAuthToken oldValue, OAuthToken newValue) { + protected void fireAccessTokenChanged(IOAuthToken oldValue, IOAuthToken newValue) { firePropertyChange(ACCESS_TOKEN_PROP, oldValue, newValue); } @@ -90,7 +87,7 @@ public IOAuthParameters getOAuthParameters() { * * @return the retrieved Access Token */ - public OAuthToken getAccessToken() { + public IOAuthToken getAccessToken() { return accessToken; } @@ -100,8 +97,8 @@ public OAuthToken getAccessToken() { * * @param accessToken the new access token. null, to clear the current access token */ - protected void setAccessToken(OAuthToken accessToken) { - OAuthToken oldValue = this.accessToken; + protected void setAccessToken(IOAuthToken accessToken) { + IOAuthToken oldValue = this.accessToken; this.accessToken = accessToken; if (oldValue == null ^ this.accessToken == null) { fireAccessTokenChanged(oldValue, this.accessToken); @@ -112,6 +109,15 @@ protected void setAccessToken(OAuthToken accessToken) { } } + /** + * Get the OAuth version for this AuthorizationUI + * @return The OAuth version + * @since 18991 + */ + public OAuthVersion getOAuthVersion() { + return this.pnlAdvancedProperties.getAdvancedParameters().getOAuthVersion(); + } + /** * Replies true if this UI currently has an Access Token * diff --git a/src/org/openstreetmap/josm/gui/oauth/AccessTokenInfoPanel.java b/src/org/openstreetmap/josm/gui/oauth/AccessTokenInfoPanel.java index a2a8e1610d8..bc4098dfbcf 100644 --- a/src/org/openstreetmap/josm/gui/oauth/AccessTokenInfoPanel.java +++ b/src/org/openstreetmap/josm/gui/oauth/AccessTokenInfoPanel.java @@ -11,18 +11,19 @@ import javax.swing.JLabel; import javax.swing.JPanel; +import org.openstreetmap.josm.data.oauth.IOAuthToken; +import org.openstreetmap.josm.data.oauth.OAuth20Token; import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder; -import org.openstreetmap.josm.data.oauth.OAuthToken; import org.openstreetmap.josm.gui.widgets.JosmTextField; +import org.openstreetmap.josm.tools.JosmRuntimeException; /** - * Displays the key and the secret of an OAuth Access Token. - * @since 2746 + * Displays the key of an OAuth Access Token. + * @since 2746 (modified for OAuth 2 in 18991) */ public class AccessTokenInfoPanel extends JPanel { private final JosmTextField tfAccessTokenKey = new JosmTextField(null, null, 0, false); - private final JosmTextField tfAccessTokenSecret = new JosmTextField(null, null, 0, false); private final JCheckBox cbSaveAccessTokenInPreferences = new JCheckBox(tr("Save Access Token in preferences")); /** @@ -55,11 +56,7 @@ protected final void build() { gc.insets = new Insets(0, 0, 3, 3); add(new JLabel(tr("Access Token Secret:")), gc); - gc.gridx = 1; gc.weightx = 1.0; - add(tfAccessTokenSecret, gc); - tfAccessTokenSecret.setEditable(false); - // the checkbox gc.gridx = 0; gc.gridy = 2; @@ -86,14 +83,16 @@ protected final void build() { * * @param token the access token. If null, the content in the info panel is cleared */ - public void setAccessToken(OAuthToken token) { + public void setAccessToken(IOAuthToken token) { if (token == null) { tfAccessTokenKey.setText(""); - tfAccessTokenSecret.setText(""); return; } - tfAccessTokenKey.setText(token.getKey()); - tfAccessTokenSecret.setText(token.getSecret()); + if (token instanceof OAuth20Token) { + tfAccessTokenKey.setText(((OAuth20Token) token).getBearerToken()); + } else { + throw new JosmRuntimeException("Unknown token type: " + token.getClass()); + } } public void setSaveToPreferences(boolean saveToPreferences) { diff --git a/src/org/openstreetmap/josm/gui/oauth/AdvancedOAuthPropertiesPanel.java b/src/org/openstreetmap/josm/gui/oauth/AdvancedOAuthPropertiesPanel.java index eb3514e8089..a1c1cf8c0e0 100644 --- a/src/org/openstreetmap/josm/gui/oauth/AdvancedOAuthPropertiesPanel.java +++ b/src/org/openstreetmap/josm/gui/oauth/AdvancedOAuthPropertiesPanel.java @@ -21,7 +21,9 @@ import org.openstreetmap.josm.data.oauth.OAuthVersion; import org.openstreetmap.josm.gui.HelpAwareOptionPane; import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec; +import org.openstreetmap.josm.gui.MainApplication; import org.openstreetmap.josm.gui.help.HelpUtil; +import org.openstreetmap.josm.gui.util.GuiHelper; import org.openstreetmap.josm.gui.widgets.JosmTextField; import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator; import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel; @@ -52,8 +54,6 @@ public class AdvancedOAuthPropertiesPanel extends VerticallyScrollablePanel { private final JosmTextField tfRequestTokenURL = new JosmTextField(); private final JosmTextField tfAccessTokenURL = new JosmTextField(); private final JosmTextField tfAuthoriseURL = new JosmTextField(); - private final JosmTextField tfOsmLoginURL = new JosmTextField(); - private final JosmTextField tfOsmLogoutURL = new JosmTextField(); private final OAuthVersion oauthVersion; private transient UseDefaultItemListener ilUseDefault; private String apiUrl; @@ -83,11 +83,7 @@ protected final void build() { gc.gridy = 1; gc.weightx = 0.0; gc.gridwidth = 1; - if (this.oauthVersion == OAuthVersion.OAuth10a) { - add(new JLabel(tr("Consumer Key:")), gc); - } else { - add(new JLabel(tr("Client ID:")), gc); - } + add(new JLabel(tr("Client ID:")), gc); gc.gridx = 1; gc.weightx = 1.0; @@ -98,11 +94,7 @@ protected final void build() { gc.gridy++; gc.gridx = 0; gc.weightx = 0.0; - if (this.oauthVersion == OAuthVersion.OAuth10a) { - add(new JLabel(tr("Consumer Secret:")), gc); - } else { - add(new JLabel(tr("Client Secret:")), gc); - } + add(new JLabel(tr("Client Secret:")), gc); gc.gridx = 1; gc.weightx = 1.0; @@ -113,11 +105,7 @@ protected final void build() { gc.gridy++; gc.gridx = 0; gc.weightx = 0.0; - if (this.oauthVersion == OAuthVersion.OAuth10a) { - add(new JLabel(tr("Request Token URL:")), gc); - } else { - add(new JLabel(tr("Redirect URL:")), gc); - } + add(new JLabel(tr("Redirect URL:")), gc); gc.gridx = 1; gc.weightx = 1.0; @@ -146,36 +134,13 @@ protected final void build() { add(tfAuthoriseURL, gc); SelectAllOnFocusGainedDecorator.decorate(tfAuthoriseURL); - if (this.oauthVersion == OAuthVersion.OAuth10a) { - // -- OSM login URL - gc.gridy++; - gc.gridx = 0; - gc.weightx = 0.0; - add(new JLabel(tr("OSM login URL:")), gc); - - gc.gridx = 1; - gc.weightx = 1.0; - add(tfOsmLoginURL, gc); - SelectAllOnFocusGainedDecorator.decorate(tfOsmLoginURL); - - // -- OSM logout URL - gc.gridy++; - gc.gridx = 0; - gc.weightx = 0.0; - add(new JLabel(tr("OSM logout URL:")), gc); - - gc.gridx = 1; - gc.weightx = 1.0; - add(tfOsmLogoutURL, gc); - SelectAllOnFocusGainedDecorator.decorate(tfOsmLogoutURL); - } - ilUseDefault = new UseDefaultItemListener(); cbUseDefaults.addItemListener(ilUseDefault); + cbUseDefaults.setSelected(Config.getPref().getBoolean("oauth.settings.use-default", true)); } protected boolean hasCustomSettings() { - OAuthParameters params = OAuthParameters.createDefault(apiUrl); + IOAuthParameters params = OAuthParameters.createDefault(apiUrl, OAuthVersion.OAuth20); return !params.equals(getAdvancedParameters()); } @@ -210,30 +175,24 @@ protected boolean confirmOverwriteCustomSettings() { } protected void resetToDefaultSettings() { - cbUseDefaults.setSelected(true); IOAuthParameters iParams = OAuthParameters.createDefault(apiUrl, this.oauthVersion); - switch (this.oauthVersion) { - case OAuth10a: - OAuthParameters params = (OAuthParameters) iParams; - tfConsumerKey.setText(params.getConsumerKey()); - tfConsumerSecret.setText(params.getConsumerSecret()); - tfRequestTokenURL.setText(params.getRequestTokenUrl()); - tfAccessTokenURL.setText(params.getAccessTokenUrl()); - tfAuthoriseURL.setText(params.getAuthoriseUrl()); - tfOsmLoginURL.setText(params.getOsmLoginUrl()); - tfOsmLogoutURL.setText(params.getOsmLogoutUrl()); - break; - case OAuth20: - case OAuth21: - OAuth20Parameters params20 = (OAuth20Parameters) iParams; - tfConsumerKey.setText(params20.getClientId()); - tfConsumerSecret.setText(params20.getClientSecret()); - tfAccessTokenURL.setText(params20.getAccessTokenUrl()); - tfAuthoriseURL.setText(params20.getAuthorizationUrl()); - tfRequestTokenURL.setText(params20.getRedirectUri()); - } - - setChildComponentsEnabled(false); + GuiHelper.runInEDTAndWaitWithException(() -> { + cbUseDefaults.setSelected(true); + switch (this.oauthVersion) { + case OAuth20: + case OAuth21: + OAuth20Parameters params20 = (OAuth20Parameters) iParams; + tfConsumerKey.setText(params20.getClientId()); + tfConsumerSecret.setText(params20.getClientSecret()); + tfAccessTokenURL.setText(params20.getAccessTokenUrl()); + tfAuthoriseURL.setText(params20.getAuthorizationUrl()); + tfRequestTokenURL.setText(params20.getRedirectUri()); + break; + default: + throw new UnsupportedOperationException("Unsupported OAuth version: " + this.oauthVersion); + } + setChildComponentsEnabled(false); + }); } protected void setChildComponentsEnabled(boolean enabled) { @@ -252,21 +211,12 @@ protected void setChildComponentsEnabled(boolean enabled) { public IOAuthParameters getAdvancedParameters() { if (cbUseDefaults.isSelected()) return OAuthParameters.createDefault(apiUrl, this.oauthVersion); - if (this.oauthVersion == OAuthVersion.OAuth10a) { - return new OAuthParameters( - tfConsumerKey.getText(), - tfConsumerSecret.getText(), - tfRequestTokenURL.getText(), - tfAccessTokenURL.getText(), - tfAuthoriseURL.getText(), - tfOsmLoginURL.getText(), - tfOsmLogoutURL.getText()); - } return new OAuth20Parameters( tfConsumerKey.getText(), tfConsumerSecret.getText(), - tfAuthoriseURL.getText(), tfAccessTokenURL.getText(), + tfAuthoriseURL.getText(), + apiUrl, tfRequestTokenURL.getText() ); } @@ -285,16 +235,7 @@ public void setAdvancedParameters(IOAuthParameters parameters) { } else { cbUseDefaults.setSelected(false); setChildComponentsEnabled(true); - if (parameters instanceof OAuthParameters) { - OAuthParameters parameters10 = (OAuthParameters) parameters; - tfConsumerKey.setText(parameters10.getConsumerKey() == null ? "" : parameters10.getConsumerKey()); - tfConsumerSecret.setText(parameters10.getConsumerSecret() == null ? "" : parameters10.getConsumerSecret()); - tfRequestTokenURL.setText(parameters10.getRequestTokenUrl() == null ? "" : parameters10.getRequestTokenUrl()); - tfAccessTokenURL.setText(parameters10.getAccessTokenUrl() == null ? "" : parameters10.getAccessTokenUrl()); - tfAuthoriseURL.setText(parameters10.getAuthoriseUrl() == null ? "" : parameters10.getAuthoriseUrl()); - tfOsmLoginURL.setText(parameters10.getOsmLoginUrl() == null ? "" : parameters10.getOsmLoginUrl()); - tfOsmLogoutURL.setText(parameters10.getOsmLogoutUrl() == null ? "" : parameters10.getOsmLogoutUrl()); - } else if (parameters instanceof OAuth20Parameters) { + if (parameters instanceof OAuth20Parameters) { OAuth20Parameters parameters20 = (OAuth20Parameters) parameters; tfConsumerKey.setText(parameters20.getClientId()); tfConsumerSecret.setText(parameters20.getClientSecret()); @@ -316,12 +257,16 @@ public void initialize(String paramApiUrl) { setApiUrl(paramApiUrl); boolean useDefault = Config.getPref().getBoolean("oauth.settings.use-default", true); ilUseDefault.setEnabled(false); - if (useDefault) { - resetToDefaultSettings(); - } else { - setAdvancedParameters(OAuthParameters.createFromApiUrl(paramApiUrl)); - } - ilUseDefault.setEnabled(true); + MainApplication.worker.execute(() -> { + if (useDefault) { + this.resetToDefaultSettings(); + } else { + // createFromApiUrl may make a network request + final IOAuthParameters parameters = OAuthParameters.createFromApiUrl(paramApiUrl, OAuthVersion.OAuth20); + GuiHelper.runInEDT(() -> setAdvancedParameters(parameters)); + } + GuiHelper.runInEDT(() -> ilUseDefault.setEnabled(true)); + }); } /** @@ -330,7 +275,7 @@ public void initialize(String paramApiUrl) { public void rememberPreferences() { Config.getPref().putBoolean("oauth.settings.use-default", cbUseDefaults.isSelected()); if (cbUseDefaults.isSelected()) { - new OAuthParameters(null, null, null, null, null, null, null).rememberPreferences(); + MainApplication.worker.execute(() -> OAuthParameters.createDefault().rememberPreferences()); } else { getAdvancedParameters().rememberPreferences(); } @@ -348,7 +293,7 @@ public void itemStateChanged(ItemEvent e) { cbUseDefaults.setSelected(false); return; } - resetToDefaultSettings(); + MainApplication.worker.execute(AdvancedOAuthPropertiesPanel.this::resetToDefaultSettings); break; case ItemEvent.DESELECTED: setChildComponentsEnabled(true); @@ -371,7 +316,7 @@ public void setEnabled(boolean enabled) { public void setApiUrl(String apiUrl) { this.apiUrl = apiUrl; if (cbUseDefaults.isSelected()) { - resetToDefaultSettings(); + MainApplication.worker.execute(this::resetToDefaultSettings); } } } diff --git a/src/org/openstreetmap/josm/gui/oauth/AuthorizationProcedure.java b/src/org/openstreetmap/josm/gui/oauth/AuthorizationProcedure.java index 6067de669d0..a8fa2a4afad 100644 --- a/src/org/openstreetmap/josm/gui/oauth/AuthorizationProcedure.java +++ b/src/org/openstreetmap/josm/gui/oauth/AuthorizationProcedure.java @@ -35,7 +35,7 @@ public enum AuthorizationProcedure { * @return the translated name of this procedure */ public String getText() { - switch(this) { + switch (this) { case FULLY_AUTOMATIC: return tr("Fully automatic"); case SEMI_AUTOMATIC: @@ -51,7 +51,7 @@ public String getText() { * @return a translated description of this procedure */ public String getDescription() { - switch(this) { + switch (this) { case FULLY_AUTOMATIC: return tr( "Run a fully automatic procedure to get an access token from the OSM website.
      " diff --git a/src/org/openstreetmap/josm/gui/oauth/FullyAutomaticAuthorizationUI.java b/src/org/openstreetmap/josm/gui/oauth/FullyAutomaticAuthorizationUI.java index cbac6c32189..48f4eb4334d 100644 --- a/src/org/openstreetmap/josm/gui/oauth/FullyAutomaticAuthorizationUI.java +++ b/src/org/openstreetmap/josm/gui/oauth/FullyAutomaticAuthorizationUI.java @@ -12,8 +12,6 @@ import java.awt.Insets; import java.awt.event.ActionEvent; import java.io.IOException; -import java.net.Authenticator.RequestorType; -import java.net.PasswordAuthentication; import java.util.concurrent.Executor; import javax.swing.AbstractAction; @@ -23,47 +21,29 @@ import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTabbedPane; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import javax.swing.text.JTextComponent; import javax.swing.text.html.HTMLEditorKit; -import org.openstreetmap.josm.data.oauth.OAuthParameters; -import org.openstreetmap.josm.data.oauth.OAuthToken; +import org.openstreetmap.josm.data.oauth.IOAuthToken; +import org.openstreetmap.josm.data.oauth.OAuthVersion; import org.openstreetmap.josm.gui.HelpAwareOptionPane; import org.openstreetmap.josm.gui.PleaseWaitRunnable; import org.openstreetmap.josm.gui.help.HelpUtil; -import org.openstreetmap.josm.gui.preferences.server.UserNameValidator; import org.openstreetmap.josm.gui.util.GuiHelper; -import org.openstreetmap.josm.gui.widgets.DefaultTextComponentValidator; import org.openstreetmap.josm.gui.widgets.HtmlPanel; import org.openstreetmap.josm.gui.widgets.JMultilineLabel; -import org.openstreetmap.josm.gui.widgets.JosmPasswordField; -import org.openstreetmap.josm.gui.widgets.JosmTextField; -import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator; import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel; -import org.openstreetmap.josm.io.OsmApi; import org.openstreetmap.josm.io.OsmTransferException; -import org.openstreetmap.josm.io.auth.CredentialsAgent; -import org.openstreetmap.josm.io.auth.CredentialsAgentException; -import org.openstreetmap.josm.io.auth.CredentialsManager; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.Logging; -import org.openstreetmap.josm.tools.Utils; import org.xml.sax.SAXException; /** - * This is an UI which supports a JOSM user to get an OAuth Access Token in a fully + * This is a UI which supports a JOSM user to get an OAuth Access Token in a fully * automatic process. * * @since 2746 */ public class FullyAutomaticAuthorizationUI extends AbstractAuthorizationUI { - - private final JosmTextField tfUserName = new JosmTextField(); - private final JosmPasswordField tfPassword = new JosmPasswordField(); - private transient UserNameValidator valUserName; - private transient PasswordValidator valPassword; private final AccessTokenInfoPanel pnlAccessTokenInfo = new AccessTokenInfoPanel(); private OsmPrivilegesPanel pnlOsmPrivileges; private JPanel pnlPropertiesPanel; @@ -104,7 +84,7 @@ protected VerticallyScrollablePanel buildUserNamePasswordPanel() { pnlMessage.setText("

      " + tr("Please enter your OSM user name and password. The password will not be saved " + "in clear text in the JOSM preferences and it will be submitted to the OSM server only once. " - + "Subsequent data upload requests don''t use your password any more.").replaceAll("\\. ", ".
      ") + + "Subsequent data upload requests don''t use your password any more.").replace(". ", ".
      ") + "

      " + ""); pnl.add(pnlMessage, gc); @@ -118,13 +98,6 @@ protected VerticallyScrollablePanel buildUserNamePasswordPanel() { gc.insets = new Insets(0, 0, 3, 3); pnl.add(new JLabel(tr("Username: ")), gc); - gc.gridx = 1; - gc.weightx = 1.0; - pnl.add(tfUserName, gc); - SelectAllOnFocusGainedDecorator.decorate(tfUserName); - valUserName = new UserNameValidator(tfUserName); - valUserName.validate(); - // the password input field gc.anchor = GridBagConstraints.NORTHWEST; gc.fill = GridBagConstraints.HORIZONTAL; @@ -133,14 +106,8 @@ protected VerticallyScrollablePanel buildUserNamePasswordPanel() { gc.weightx = 0.0; pnl.add(new JLabel(tr("Password:")), gc); - gc.gridx = 1; - gc.weightx = 1.0; - pnl.add(tfPassword, gc); - SelectAllOnFocusGainedDecorator.decorate(tfPassword); - valPassword = new PasswordValidator(tfPassword); - valPassword.validate(); - // filler - grab remaining space + gc.gridx = 1; gc.gridy = 4; gc.gridwidth = 2; gc.fill = GridBagConstraints.BOTH; @@ -166,30 +133,6 @@ protected JPanel buildPropertiesPanel() { return pnl; } - /** - * Initializes the panel with values from the preferences - * @param paramApiUrl the API URL - */ - @Override - public void initialize(String paramApiUrl) { - super.initialize(paramApiUrl); - CredentialsAgent cm = CredentialsManager.getInstance(); - try { - PasswordAuthentication pa = cm.lookup(RequestorType.SERVER, OsmApi.getOsmApi().getHost()); - if (pa == null) { - tfUserName.setText(""); - tfPassword.setText(""); - } else { - tfUserName.setText(pa.getUserName() == null ? "" : pa.getUserName()); - tfPassword.setText(pa.getPassword() == null ? "" : String.valueOf(pa.getPassword())); - } - } catch (CredentialsAgentException e) { - Logging.error(e); - tfUserName.setText(""); - tfPassword.setText(""); - } - } - /** * Builds the panel with the action button for starting the authorisation * @@ -199,8 +142,6 @@ protected JPanel buildActionButtonPanel() { JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT)); RunAuthorisationAction runAuthorisationAction = new RunAuthorisationAction(); - tfPassword.getDocument().addDocumentListener(runAuthorisationAction); - tfUserName.getDocument().addDocumentListener(runAuthorisationAction); pnl.add(new JButton(runAuthorisationAction)); return pnl; } @@ -288,22 +229,27 @@ protected void prepareUIForResultDisplay() { repaint(); } - protected String getOsmUserName() { - return tfUserName.getText(); - } - - protected String getOsmPassword() { - return String.valueOf(tfPassword.getPassword()); - } - /** * Constructs a new {@code FullyAutomaticAuthorizationUI} for the given API URL. * @param apiUrl The OSM API URL * @param executor the executor used for running the HTTP requests for the authorization * @since 5422 + * @deprecated since 18991 */ + @Deprecated public FullyAutomaticAuthorizationUI(String apiUrl, Executor executor) { - super(apiUrl); + this(apiUrl, executor, OAuthVersion.OAuth10a); + } + + /** + * Constructs a new {@code FullyAutomaticAuthorizationUI} for the given API URL. + * @param apiUrl The OSM API URL + * @param executor the executor used for running the HTTP requests for the authorization + * @param oAuthVersion The OAuth version to use for this UI + * @since 18991 + */ + public FullyAutomaticAuthorizationUI(String apiUrl, Executor executor, OAuthVersion oAuthVersion) { + super(apiUrl, oAuthVersion); this.executor = executor; build(); } @@ -314,7 +260,7 @@ public boolean isSaveAccessTokenToPreferences() { } @Override - protected void setAccessToken(OAuthToken accessToken) { + protected void setAccessToken(IOAuthToken accessToken) { super.setAccessToken(accessToken); pnlAccessTokenInfo.setAccessToken(accessToken); } @@ -322,37 +268,17 @@ protected void setAccessToken(OAuthToken accessToken) { /** * Starts the authorisation process */ - class RunAuthorisationAction extends AbstractAction implements DocumentListener { + class RunAuthorisationAction extends AbstractAction { RunAuthorisationAction() { putValue(NAME, tr("Authorize now")); new ImageProvider("oauth", "oauth-small").getResource().attachImageIcon(this); putValue(SHORT_DESCRIPTION, tr("Click to redirect you to the authorization form on the JOSM web site")); - updateEnabledState(); } @Override public void actionPerformed(ActionEvent evt) { executor.execute(new FullyAutomaticAuthorisationTask(FullyAutomaticAuthorizationUI.this)); } - - protected final void updateEnabledState() { - setEnabled(valPassword.isValid() && valUserName.isValid()); - } - - @Override - public void changedUpdate(DocumentEvent e) { - updateEnabledState(); - } - - @Override - public void insertUpdate(DocumentEvent e) { - updateEnabledState(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - updateEnabledState(); - } } /** @@ -385,18 +311,11 @@ public void actionPerformed(ActionEvent arg0) { executor.execute(new TestAccessTokenTask( FullyAutomaticAuthorizationUI.this, getApiUrl(), - (OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters(), getAccessToken() )); } } - static class PasswordValidator extends DefaultTextComponentValidator { - PasswordValidator(JTextComponent tc) { - super(tc, tr("Please enter your OSM password"), tr("The password cannot be empty. Please enter your OSM password")); - } - } - class FullyAutomaticAuthorisationTask extends PleaseWaitRunnable { private boolean canceled; @@ -438,25 +357,7 @@ protected void alertInvalidLoginUrl() { + "a valid login URL from the OAuth Authorize Endpoint URL ''{0}''.

      " + "Please check your advanced setting and try again." + "", - ((OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters()).getAuthoriseUrl()), - tr("OAuth authorization failed"), - JOptionPane.ERROR_MESSAGE, - HelpUtil.ht("/Dialog/OAuthAuthorisationWizard#FullyAutomaticProcessFailed") - ); - } - - protected void alertLoginFailed() { - final String loginUrl = ((OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters()).getOsmLoginUrl(); - HelpAwareOptionPane.showOptionDialog( - FullyAutomaticAuthorizationUI.this, - tr("" - + "The automatic process for retrieving an OAuth Access Token
      " - + "from the OSM server failed. JOSM failed to log into {0}
      " - + "for user {1}.

      " - + "Please check username and password and try again." - +"", - loginUrl, - Utils.escapeReservedCharactersHTML(getOsmUserName())), + getAdvancedPropertiesPanel().getAdvancedParameters().getAuthorizationUrl()), tr("OAuth authorization failed"), JOptionPane.ERROR_MESSAGE, HelpUtil.ht("/Dialog/OAuthAuthorisationWizard#FullyAutomaticProcessFailed") @@ -464,50 +365,23 @@ protected void alertLoginFailed() { } protected void handleException(final OsmOAuthAuthorizationException e) { - Runnable r = () -> { - if (e instanceof OsmLoginFailedException) { - alertLoginFailed(); - } else { - alertAuthorisationFailed(); - } - }; Logging.error(e); - GuiHelper.runInEDT(r); + GuiHelper.runInEDT(this::alertAuthorisationFailed); } @Override protected void realRun() throws SAXException, IOException, OsmTransferException { - try { - getProgressMonitor().setTicksCount(3); - OsmOAuthAuthorizationClient authClient = new OsmOAuthAuthorizationClient( - (OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters() - ); - OAuthToken requestToken = authClient.getRequestToken( - getProgressMonitor().createSubTaskMonitor(1, false) - ); - getProgressMonitor().worked(1); - if (canceled) return; - authClient.authorise( - requestToken, - getOsmUserName(), - getOsmPassword(), - pnlOsmPrivileges.getPrivileges(), - getProgressMonitor().createSubTaskMonitor(1, false) - ); - getProgressMonitor().worked(1); - if (canceled) return; - final OAuthToken accessToken = authClient.getAccessToken( - getProgressMonitor().createSubTaskMonitor(1, false) - ); - getProgressMonitor().worked(1); - if (canceled) return; - GuiHelper.runInEDT(() -> { - prepareUIForResultDisplay(); - setAccessToken(accessToken); - }); - } catch (final OsmOAuthAuthorizationException e) { - handleException(e); - } + getProgressMonitor().setTicksCount(2); + OAuthAuthorizationWizard.authorize(true, token -> { + if (!canceled) { + getProgressMonitor().worked(1); + GuiHelper.runInEDT(() -> { + prepareUIForResultDisplay(); + setAccessToken(token.orElse(null)); + }); + } + }, getApiUrl(), getOAuthVersion(), getOAuthParameters()); + getProgressMonitor().worked(1); } } } diff --git a/src/org/openstreetmap/josm/gui/oauth/ManualAuthorizationUI.java b/src/org/openstreetmap/josm/gui/oauth/ManualAuthorizationUI.java index 9da3bcf7b8f..8258791d9da 100644 --- a/src/org/openstreetmap/josm/gui/oauth/ManualAuthorizationUI.java +++ b/src/org/openstreetmap/josm/gui/oauth/ManualAuthorizationUI.java @@ -24,14 +24,16 @@ import javax.swing.event.DocumentListener; import javax.swing.text.JTextComponent; +import org.openstreetmap.josm.data.oauth.OAuth20Exception; +import org.openstreetmap.josm.data.oauth.OAuth20Token; import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder; -import org.openstreetmap.josm.data.oauth.OAuthParameters; -import org.openstreetmap.josm.data.oauth.OAuthToken; +import org.openstreetmap.josm.data.oauth.OAuthVersion; import org.openstreetmap.josm.gui.widgets.DefaultTextComponentValidator; import org.openstreetmap.josm.gui.widgets.HtmlPanel; import org.openstreetmap.josm.gui.widgets.JosmTextField; import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator; import org.openstreetmap.josm.tools.ImageProvider; +import org.openstreetmap.josm.tools.JosmRuntimeException; /** * This is an UI which supports a JOSM user to get an OAuth Access Token in a fully manual process. @@ -42,8 +44,6 @@ public class ManualAuthorizationUI extends AbstractAuthorizationUI { private final JosmTextField tfAccessTokenKey = new JosmTextField(); private transient AccessTokenKeyValidator valAccessTokenKey; - private final JosmTextField tfAccessTokenSecret = new JosmTextField(); - private transient AccessTokenSecretValidator valAccessTokenSecret; private final JCheckBox cbSaveToPreferences = new JCheckBox(tr("Save Access Token in preferences")); private final HtmlPanel pnlMessage = new HtmlPanel(); private final transient Executor executor; @@ -53,9 +53,24 @@ public class ManualAuthorizationUI extends AbstractAuthorizationUI { * @param apiUrl The OSM API URL * @param executor the executor used for running the HTTP requests for the authorization * @since 5422 + * @deprecated since 18991, use {@link ManualAuthorizationUI#ManualAuthorizationUI(String, Executor, OAuthVersion)} + * instead. */ + @Deprecated public ManualAuthorizationUI(String apiUrl, Executor executor) { - super(/* dont pass apiURL because setApiUrl is overridden and references a local field */); + this(apiUrl, executor, OAuthVersion.OAuth10a); + } + + /** + * Constructs a new {@code ManualAuthorizationUI} for the given API URL. + * @param apiUrl The OSM API URL + * @param executor the executor used for running the HTTP requests for the authorization + * @param oAuthVersion The OAuthVersion to use for this UI + * @since 18991 + */ + public ManualAuthorizationUI(String apiUrl, Executor executor, OAuthVersion oAuthVersion) { + super(null /* don't pass apiURL because setApiUrl is overridden and references a local field */, + oAuthVersion); setApiUrl(apiUrl); this.executor = executor; build(); @@ -94,20 +109,6 @@ protected JPanel buildAccessTokenPanel() { valAccessTokenKey.validate(); tfAccessTokenKey.getDocument().addDocumentListener(accessTokenBuilder); - // the access token key input field - gc.gridy = 2; - gc.gridx = 0; - gc.weightx = 0.0; - pnl.add(new JLabel(tr("Access Token Secret:")), gc); - - gc.gridx = 1; - gc.weightx = 1.0; - pnl.add(tfAccessTokenSecret, gc); - SelectAllOnFocusGainedDecorator.decorate(tfAccessTokenSecret); - valAccessTokenSecret = new AccessTokenSecretValidator(tfAccessTokenSecret); - valAccessTokenSecret.validate(); - tfAccessTokenSecret.getDocument().addDocumentListener(accessTokenBuilder); - // the checkbox for saving to preferences gc.gridy = 3; gc.gridx = 0; @@ -184,20 +185,18 @@ private static class AccessTokenKeyValidator extends DefaultTextComponentValidat } } - private static class AccessTokenSecretValidator extends DefaultTextComponentValidator { - AccessTokenSecretValidator(JTextComponent tc) { - super(tc, tr("Please enter an Access Token Secret"), - tr("The Access Token Secret must not be empty. Please enter an Access Token Secret")); - } - } - class AccessTokenBuilder implements DocumentListener { public void build() { - if (!valAccessTokenKey.isValid() || !valAccessTokenSecret.isValid()) { + if (!valAccessTokenKey.isValid()) { setAccessToken(null); } else { - setAccessToken(new OAuthToken(tfAccessTokenKey.getText().trim(), tfAccessTokenSecret.getText().trim())); + try { + setAccessToken(new OAuth20Token(getOAuthParameters(), "{\"token_type\":\"bearer\", \"access_token\":\"" + + tfAccessTokenKey.getText().trim() + "\"}")); + } catch (OAuth20Exception e) { + throw new JosmRuntimeException(e); + } } } @@ -233,7 +232,6 @@ public void actionPerformed(ActionEvent evt) { TestAccessTokenTask task = new TestAccessTokenTask( ManualAuthorizationUI.this, getApiUrl(), - (OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters(), getAccessToken() ); executor.execute(task); diff --git a/src/org/openstreetmap/josm/gui/oauth/OAuthAuthorizationWizard.java b/src/org/openstreetmap/josm/gui/oauth/OAuthAuthorizationWizard.java index 35c55ac9050..aa1e37538ca 100644 --- a/src/org/openstreetmap/josm/gui/oauth/OAuthAuthorizationWizard.java +++ b/src/org/openstreetmap/josm/gui/oauth/OAuthAuthorizationWizard.java @@ -8,6 +8,7 @@ import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Font; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.ComponentAdapter; @@ -19,22 +20,29 @@ import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.Executor; import java.util.concurrent.FutureTask; +import java.util.function.Consumer; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JDialog; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.text.html.HTMLEditorKit; +import org.openstreetmap.josm.data.oauth.IOAuthParameters; +import org.openstreetmap.josm.data.oauth.IOAuthToken; +import org.openstreetmap.josm.data.oauth.OAuth20Authorization; import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder; import org.openstreetmap.josm.data.oauth.OAuthParameters; -import org.openstreetmap.josm.data.oauth.OAuthToken; +import org.openstreetmap.josm.data.oauth.OAuthVersion; +import org.openstreetmap.josm.data.oauth.osm.OsmScopes; import org.openstreetmap.josm.gui.MainApplication; import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction; import org.openstreetmap.josm.gui.help.HelpUtil; @@ -42,6 +50,8 @@ import org.openstreetmap.josm.gui.util.WindowGeometry; import org.openstreetmap.josm.gui.widgets.HtmlPanel; import org.openstreetmap.josm.gui.widgets.JMultilineLabel; +import org.openstreetmap.josm.io.auth.CredentialsManager; +import org.openstreetmap.josm.io.remotecontrol.RemoteControl; import org.openstreetmap.josm.spi.preferences.Config; import org.openstreetmap.josm.tools.GBC; import org.openstreetmap.josm.tools.ImageProvider; @@ -58,26 +68,70 @@ public class OAuthAuthorizationWizard extends JDialog { private boolean canceled; private final AuthorizationProcedure procedure; private final String apiUrl; + private final OAuthVersion oAuthVersion; private FullyAutomaticAuthorizationUI pnlFullyAutomaticAuthorisationUI; - private SemiAutomaticAuthorizationUI pnlSemiAutomaticAuthorisationUI; private ManualAuthorizationUI pnlManualAuthorisationUI; private JScrollPane spAuthorisationProcedureUI; private final transient Executor executor; /** - * Launches the wizard, {@link OAuthAccessTokenHolder#setAccessToken(OAuthToken) sets the token} + * Launches the wizard, {@link OAuthAccessTokenHolder#setAccessToken(String, IOAuthToken)} sets the token * and {@link OAuthAccessTokenHolder#setSaveToPreferences(boolean) saves to preferences}. + * @param callback Callback to run when authorization is finished * @throws UserCancelException if user cancels the operation */ - public void showDialog() throws UserCancelException { - setVisible(true); - if (isCanceled()) { - throw new UserCancelException(); + public void showDialog(Consumer> callback) throws UserCancelException { + if ((this.oAuthVersion == OAuthVersion.OAuth20 || this.oAuthVersion == OAuthVersion.OAuth21) + && this.procedure == AuthorizationProcedure.FULLY_AUTOMATIC) { + authorize(true, callback, this.apiUrl, this.oAuthVersion, getOAuthParameters()); + } else { + setVisible(true); + if (isCanceled()) { + throw new UserCancelException(); + } + OAuthAccessTokenHolder holder = OAuthAccessTokenHolder.getInstance(); + holder.setAccessToken(apiUrl, getAccessToken()); + holder.setSaveToPreferences(isSaveAccessTokenToPreferences()); + } + } + + /** + * Perform the oauth dance + * + * @param startRemoteControl {@code true} to start remote control if it is not already running + * @param callback The callback to use to notify that the OAuth dance succeeded + * @param apiUrl The API URL to get the token for + * @param oAuthVersion The OAuth version that the authorization dance is force + * @param oAuthParameters The OAuth parameters to use + */ + static void authorize(boolean startRemoteControl, Consumer> callback, String apiUrl, + OAuthVersion oAuthVersion, IOAuthParameters oAuthParameters) { + final boolean remoteControlIsRunning = Boolean.TRUE.equals(RemoteControl.PROP_REMOTECONTROL_ENABLED.get()); + // TODO: Ask user if they want to start remote control? + if (!remoteControlIsRunning && startRemoteControl) { + RemoteControl.start(); } - OAuthAccessTokenHolder holder = OAuthAccessTokenHolder.getInstance(); - holder.setAccessToken(getAccessToken()); - holder.setSaveToPreferences(isSaveAccessTokenToPreferences()); + new OAuth20Authorization().authorize( + Optional.ofNullable(oAuthParameters).orElseGet(() -> OAuthParameters.createDefault(apiUrl, oAuthVersion)), + token -> { + if (!remoteControlIsRunning) { + RemoteControl.stop(); + } + OAuthAccessTokenHolder.getInstance().setAccessToken(apiUrl, token.orElse(null)); + OAuthAccessTokenHolder.getInstance().save(CredentialsManager.getInstance()); + if (!token.isPresent()) { + GuiHelper.runInEDT(() -> JOptionPane.showMessageDialog(MainApplication.getMainPanel(), + tr("Authentication failed, please check browser for details."), + tr("OAuth Authentication Failed"), + JOptionPane.ERROR_MESSAGE)); + } + if (callback != null) { + callback.accept(token); + } + }, OsmScopes.read_gpx, OsmScopes.write_gpx, + OsmScopes.read_prefs, OsmScopes.write_prefs, + OsmScopes.write_api, OsmScopes.write_notes); } /** @@ -90,7 +144,6 @@ protected JPanel buildButtonRow() { AcceptAccessTokenAction actAcceptAccessToken = new AcceptAccessTokenAction(); pnlFullyAutomaticAuthorisationUI.addPropertyChangeListener(actAcceptAccessToken); - pnlSemiAutomaticAuthorisationUI.addPropertyChangeListener(actAcceptAccessToken); pnlManualAuthorisationUI.addPropertyChangeListener(actAcceptAccessToken); pnl.add(new JButton(actAcceptAccessToken)); @@ -117,7 +170,7 @@ protected JPanel buildHeaderInfoPanel() { + "" ); pnlMessage.enableClickableHyperlinks(); - pnl.add(pnlMessage, GBC.eol().fill(GBC.HORIZONTAL)); + pnl.add(pnlMessage, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); // the authorisation procedure JMultilineLabel lbl = new JMultilineLabel(AuthorizationProcedure.FULLY_AUTOMATIC.getDescription()); @@ -148,19 +201,17 @@ protected JPanel buildHeaderInfoPanel() { * currently selected */ protected void refreshAuthorisationProcedurePanel() { - switch(procedure) { + switch (procedure) { case FULLY_AUTOMATIC: spAuthorisationProcedureUI.getViewport().setView(pnlFullyAutomaticAuthorisationUI); pnlFullyAutomaticAuthorisationUI.revalidate(); break; - case SEMI_AUTOMATIC: - spAuthorisationProcedureUI.getViewport().setView(pnlSemiAutomaticAuthorisationUI); - pnlSemiAutomaticAuthorisationUI.revalidate(); - break; case MANUALLY: spAuthorisationProcedureUI.getViewport().setView(pnlManualAuthorisationUI); pnlManualAuthorisationUI.revalidate(); break; + default: + throw new UnsupportedOperationException("Unsupported auth type: " + procedure); } validate(); repaint(); @@ -176,9 +227,8 @@ protected final void build() { setTitle(tr("Get an Access Token for ''{0}''", apiUrl)); this.setMinimumSize(new Dimension(500, 300)); - pnlFullyAutomaticAuthorisationUI = new FullyAutomaticAuthorizationUI(apiUrl, executor); - pnlSemiAutomaticAuthorisationUI = new SemiAutomaticAuthorizationUI(apiUrl, executor); - pnlManualAuthorisationUI = new ManualAuthorizationUI(apiUrl, executor); + pnlFullyAutomaticAuthorisationUI = new FullyAutomaticAuthorizationUI(apiUrl, executor, oAuthVersion); + pnlManualAuthorisationUI = new ManualAuthorizationUI(apiUrl, executor, oAuthVersion); spAuthorisationProcedureUI = GuiHelper.embedInVerticalScrollPane(new JPanel()); spAuthorisationProcedureUI.getVerticalScrollBar().addComponentListener( @@ -208,18 +258,26 @@ public void componentHidden(ComponentEvent e) { /** * Creates the wizard. * - * @param parent the component relative to which the dialog is displayed - * @param procedure the authorization procedure to use - * @param apiUrl the API URL. Must not be null. - * @param executor the executor used for running the HTTP requests for the authorization + * @param parent the component relative to which the dialog is displayed + * @param procedure the authorization procedure to use + * @param apiUrl the API URL. Must not be null. + * @param executor the executor used for running the HTTP requests for the authorization + * @param oAuthVersion The OAuth version this wizard is for + * @param advancedParameters The OAuth parameters to initialize the wizard with * @throws IllegalArgumentException if apiUrl is null */ - public OAuthAuthorizationWizard(Component parent, AuthorizationProcedure procedure, String apiUrl, Executor executor) { + public OAuthAuthorizationWizard(Component parent, AuthorizationProcedure procedure, String apiUrl, + Executor executor, OAuthVersion oAuthVersion, IOAuthParameters advancedParameters) { super(GuiHelper.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL); this.procedure = Objects.requireNonNull(procedure, "procedure"); this.apiUrl = Objects.requireNonNull(apiUrl, "apiUrl"); this.executor = executor; + this.oAuthVersion = oAuthVersion; build(); + if (advancedParameters != null) { + pnlFullyAutomaticAuthorisationUI.getAdvancedPropertiesPanel().setAdvancedParameters(advancedParameters); + pnlManualAuthorisationUI.getAdvancedPropertiesPanel().setAdvancedParameters(advancedParameters); + } } /** @@ -232,10 +290,9 @@ public boolean isCanceled() { } protected AbstractAuthorizationUI getCurrentAuthorisationUI() { - switch(procedure) { + switch (procedure) { case FULLY_AUTOMATIC: return pnlFullyAutomaticAuthorisationUI; case MANUALLY: return pnlManualAuthorisationUI; - case SEMI_AUTOMATIC: return pnlSemiAutomaticAuthorisationUI; default: return null; } } @@ -245,7 +302,7 @@ protected AbstractAuthorizationUI getCurrentAuthorisationUI() { * * @return the access token. May be null if the wizard was canceled. */ - public OAuthToken getAccessToken() { + public IOAuthToken getAccessToken() { return getCurrentAuthorisationUI().getAccessToken(); } @@ -254,8 +311,8 @@ public OAuthToken getAccessToken() { * * @return the current OAuth parameters. */ - public OAuthParameters getOAuthParameters() { - return (OAuthParameters) getCurrentAuthorisationUI().getOAuthParameters(); + public IOAuthParameters getOAuthParameters() { + return getCurrentAuthorisationUI().getOAuthParameters(); } /** @@ -275,7 +332,6 @@ public boolean isSaveAccessTokenToPreferences() { */ public void initFromPreferences() { pnlFullyAutomaticAuthorisationUI.initialize(apiUrl); - pnlSemiAutomaticAuthorisationUI.initialize(apiUrl); pnlManualAuthorisationUI.initialize(apiUrl); } @@ -315,8 +371,9 @@ public static void obtainAccessToken(final URL serverUrl) throws InvocationTarge final OAuthAuthorizationWizard wizard = new OAuthAuthorizationWizard( MainApplication.getMainFrame(), AuthorizationProcedure.FULLY_AUTOMATIC, - serverUrl.toExternalForm(), Utils.newDirectExecutor()); - wizard.showDialog(); + serverUrl.toString(), Utils.newDirectExecutor(), + OAuthVersion.OAuth20, null); + wizard.showDialog(null); return wizard; }); // exception handling differs from implementation at GuiHelper.runInEDTAndWait() @@ -367,7 +424,12 @@ public void actionPerformed(ActionEvent evt) { setVisible(false); } - public final void updateEnabledState(OAuthToken token) { + /** + * Update the enabled state + * @param token The token to use + * @since 18991 + */ + public final void updateEnabledState(IOAuthToken token) { setEnabled(token != null); } @@ -375,7 +437,7 @@ public final void updateEnabledState(OAuthToken token) { public void propertyChange(PropertyChangeEvent evt) { if (!evt.getPropertyName().equals(AbstractAuthorizationUI.ACCESS_TOKEN_PROP)) return; - updateEnabledState((OAuthToken) evt.getNewValue()); + updateEnabledState((IOAuthToken) evt.getNewValue()); } } diff --git a/src/org/openstreetmap/josm/gui/oauth/OsmLoginFailedException.java b/src/org/openstreetmap/josm/gui/oauth/OsmLoginFailedException.java deleted file mode 100644 index 454dde77995..00000000000 --- a/src/org/openstreetmap/josm/gui/oauth/OsmLoginFailedException.java +++ /dev/null @@ -1,17 +0,0 @@ -// License: GPL. For details, see LICENSE file. -package org.openstreetmap.josm.gui.oauth; - -/** - * OSM login failure exception. - * @since 2746 - */ -public class OsmLoginFailedException extends OsmOAuthAuthorizationException { - - /** - * Constructs a new {@code OsmLoginFailedException} with the specified cause. - * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). - */ - public OsmLoginFailedException(Throwable cause) { - super(cause); - } -} diff --git a/src/org/openstreetmap/josm/gui/oauth/OsmOAuthAuthorizationClient.java b/src/org/openstreetmap/josm/gui/oauth/OsmOAuthAuthorizationClient.java deleted file mode 100644 index ba8e4bf78e4..00000000000 --- a/src/org/openstreetmap/josm/gui/oauth/OsmOAuthAuthorizationClient.java +++ /dev/null @@ -1,457 +0,0 @@ -// License: GPL. For details, see LICENSE file. -package org.openstreetmap.josm.gui.oauth; - -import static org.openstreetmap.josm.tools.I18n.tr; - -import java.io.BufferedReader; -import java.io.IOException; -import java.net.CookieHandler; -import java.net.HttpURLConnection; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.openstreetmap.josm.data.oauth.OAuthParameters; -import org.openstreetmap.josm.data.oauth.OAuthToken; -import org.openstreetmap.josm.data.oauth.OsmPrivileges; -import org.openstreetmap.josm.gui.progress.NullProgressMonitor; -import org.openstreetmap.josm.gui.progress.ProgressMonitor; -import org.openstreetmap.josm.io.OsmTransferCanceledException; -import org.openstreetmap.josm.tools.CheckParameterUtil; -import org.openstreetmap.josm.tools.HttpClient; -import org.openstreetmap.josm.tools.Logging; -import org.openstreetmap.josm.tools.Utils; - -import oauth.signpost.OAuth; -import oauth.signpost.OAuthConsumer; -import oauth.signpost.OAuthProvider; -import oauth.signpost.exception.OAuthException; - -/** - * An OAuth 1.0 authorization client. - * @since 2746 - */ -public class OsmOAuthAuthorizationClient { - private final OAuthParameters oauthProviderParameters; - private final OAuthConsumer consumer; - private final OAuthProvider provider; - private boolean canceled; - private HttpClient connection; - - protected static class SessionId { - protected String id; - protected String token; - protected String userName; - } - - /** - * Creates a new authorisation client with the parameters parameters. - * - * @param parameters the OAuth parameters. Must not be null. - * @throws IllegalArgumentException if parameters is null - */ - public OsmOAuthAuthorizationClient(OAuthParameters parameters) { - CheckParameterUtil.ensureParameterNotNull(parameters, "parameters"); - oauthProviderParameters = new OAuthParameters(parameters); - consumer = oauthProviderParameters.buildConsumer(); - provider = oauthProviderParameters.buildProvider(consumer); - } - - /** - * Creates a new authorisation client with the parameters parameters - * and an already known Request Token. - * - * @param parameters the OAuth parameters. Must not be null. - * @param requestToken the request token. Must not be null. - * @throws IllegalArgumentException if parameters is null - * @throws IllegalArgumentException if requestToken is null - */ - public OsmOAuthAuthorizationClient(OAuthParameters parameters, OAuthToken requestToken) { - CheckParameterUtil.ensureParameterNotNull(parameters, "parameters"); - oauthProviderParameters = new OAuthParameters(parameters); - consumer = oauthProviderParameters.buildConsumer(); - provider = oauthProviderParameters.buildProvider(consumer); - consumer.setTokenWithSecret(requestToken.getKey(), requestToken.getSecret()); - } - - /** - * Cancels the current OAuth operation. - */ - public void cancel() { - canceled = true; - synchronized (this) { - if (connection != null) { - connection.disconnect(); - } - } - } - - /** - * Submits a request for a Request Token to the Request Token Endpoint Url of the OAuth Service - * Provider and replies the request token. - * - * @param monitor a progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null - * @return the OAuth Request Token - * @throws OsmOAuthAuthorizationException if something goes wrong when retrieving the request token - * @throws OsmTransferCanceledException if the user canceled the request - */ - public OAuthToken getRequestToken(ProgressMonitor monitor) throws OsmOAuthAuthorizationException, OsmTransferCanceledException { - if (monitor == null) { - monitor = NullProgressMonitor.INSTANCE; - } - try { - monitor.beginTask(""); - monitor.indeterminateSubTask(tr("Retrieving OAuth Request Token from ''{0}''", oauthProviderParameters.getRequestTokenUrl())); - provider.retrieveRequestToken(consumer, ""); - return OAuthToken.createToken(consumer); - } catch (OAuthException e) { - if (canceled) - throw new OsmTransferCanceledException(e); - throw new OsmOAuthAuthorizationException(e); - } finally { - monitor.finishTask(); - } - } - - /** - * Submits a request for an Access Token to the Access Token Endpoint Url of the OAuth Service - * Provider and replies the request token. - * - * You must have requested a Request Token using {@link #getRequestToken(ProgressMonitor)} first. - * - * @param monitor a progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null - * @return the OAuth Access Token - * @throws OsmOAuthAuthorizationException if something goes wrong when retrieving the request token - * @throws OsmTransferCanceledException if the user canceled the request - * @see #getRequestToken(ProgressMonitor) - */ - public OAuthToken getAccessToken(ProgressMonitor monitor) throws OsmOAuthAuthorizationException, OsmTransferCanceledException { - if (monitor == null) { - monitor = NullProgressMonitor.INSTANCE; - } - try { - monitor.beginTask(""); - monitor.indeterminateSubTask(tr("Retrieving OAuth Access Token from ''{0}''", oauthProviderParameters.getAccessTokenUrl())); - provider.retrieveAccessToken(consumer, null); - return OAuthToken.createToken(consumer); - } catch (OAuthException e) { - if (canceled) - throw new OsmTransferCanceledException(e); - throw new OsmOAuthAuthorizationException(e); - } finally { - monitor.finishTask(); - } - } - - /** - * Builds the authorise URL for a given Request Token. Users can be redirected to this URL. - * There they can login to OSM and authorise the request. - * - * @param requestToken the request token - * @return the authorise URL for this request - */ - public String getAuthoriseUrl(OAuthToken requestToken) { - StringBuilder sb = new StringBuilder(32); - - // OSM is an OAuth 1.0 provider and JOSM isn't a web app. We just add the oauth request token to - // the authorisation request, no callback parameter. - // - sb.append(oauthProviderParameters.getAuthoriseUrl()).append('?'+OAuth.OAUTH_TOKEN+'=').append(requestToken.getKey()); - return sb.toString(); - } - - protected String extractToken() { - try (BufferedReader r = connection.getResponse().getContentReader()) { - String c; - Pattern p = Pattern.compile(".*authenticity_token.*value=\"([^\"]+)\".*"); - while ((c = r.readLine()) != null) { - Matcher m = p.matcher(c); - if (m.find()) { - return m.group(1); - } - } - } catch (IOException e) { - Logging.error(e); - return null; - } - Logging.warn("No authenticity_token found in response!"); - return null; - } - - protected SessionId extractOsmSession() throws IOException, URISyntaxException { - // response headers might not contain the cookie, see #12584 - final List setCookies = CookieHandler.getDefault() - .get(connection.getURL().toURI(), Collections.>emptyMap()) - .get("Cookie"); - if (setCookies == null) { - Logging.warn("No 'Set-Cookie' in response header!"); - return null; - } - - for (String setCookie: setCookies) { - String[] kvPairs = setCookie.split(";", -1); - for (String kvPair : kvPairs) { - kvPair = kvPair.trim(); - String[] kv = kvPair.split("=", -1); - if (kv.length != 2) { - continue; - } - if ("_osm_session".equals(kv[0])) { - // osm session cookie found - String token = extractToken(); - if (token == null) - return null; - SessionId si = new SessionId(); - si.id = kv[1]; - si.token = token; - return si; - } - } - } - Logging.warn("No suitable 'Set-Cookie' in response header found! {0}", setCookies); - return null; - } - - protected static String buildPostRequest(Map parameters) { - StringBuilder sb = new StringBuilder(32); - - for (Iterator> it = parameters.entrySet().iterator(); it.hasNext();) { - Entry entry = it.next(); - String value = entry.getValue(); - value = (value == null) ? "" : value; - sb.append(entry.getKey()).append('=').append(Utils.encodeUrl(value)); - if (it.hasNext()) { - sb.append('&'); - } - } - return sb.toString(); - } - - /** - * Submits a request to the OSM website for a login form. The OSM website replies a session ID in - * a cookie. - * - * @return the session ID structure - * @throws OsmOAuthAuthorizationException if something went wrong - */ - protected SessionId fetchOsmWebsiteSessionId() throws OsmOAuthAuthorizationException { - try { - final URL url = new URL(oauthProviderParameters.getOsmLoginUrl() + "?cookie_test=true"); - synchronized (this) { - connection = HttpClient.create(url).useCache(false); - connection.connect(); - } - SessionId sessionId = extractOsmSession(); - if (sessionId == null) - throw new OsmOAuthAuthorizationException( - tr("OSM website did not return a session cookie in response to ''{0}'',", url.toString())); - return sessionId; - } catch (IOException | URISyntaxException e) { - throw new OsmOAuthAuthorizationException(e); - } finally { - synchronized (this) { - connection = null; - } - } - } - - /** - * Submits a request to the OSM website for a OAuth form. The OSM website replies a session token in - * a hidden parameter. - * @param sessionId session id - * @param requestToken request token - * - * @throws OsmOAuthAuthorizationException if something went wrong - */ - protected void fetchOAuthToken(SessionId sessionId, OAuthToken requestToken) throws OsmOAuthAuthorizationException { - try { - URL url = new URL(getAuthoriseUrl(requestToken)); - synchronized (this) { - connection = HttpClient.create(url) - .useCache(false) - .setHeader("Cookie", "_osm_session=" + sessionId.id + "; _osm_username=" + sessionId.userName); - connection.connect(); - } - sessionId.token = extractToken(); - if (sessionId.token == null) - throw new OsmOAuthAuthorizationException(tr("OSM website did not return a session cookie in response to ''{0}'',", - url.toString())); - } catch (IOException e) { - throw new OsmOAuthAuthorizationException(e); - } finally { - synchronized (this) { - connection = null; - } - } - } - - protected void authenticateOsmSession(SessionId sessionId, String userName, String password) throws OsmLoginFailedException { - try { - final URL url = new URL(oauthProviderParameters.getOsmLoginUrl()); - final HttpClient client = HttpClient.create(url, "POST").useCache(false); - - Map parameters = new HashMap<>(); - parameters.put("username", userName); - parameters.put("password", password); - parameters.put("referer", "/"); - parameters.put("commit", "Login"); - parameters.put("authenticity_token", sessionId.token); - client.setRequestBody(buildPostRequest(parameters).getBytes(StandardCharsets.UTF_8)); - - client.setHeader("Content-Type", "application/x-www-form-urlencoded"); - client.setHeader("Cookie", "_osm_session=" + sessionId.id); - // make sure we can catch 302 Moved Temporarily below - client.setMaxRedirects(-1); - - synchronized (this) { - connection = client; - connection.connect(); - } - - // after a successful login the OSM website sends a redirect to a follow up page. Everything - // else, including a 200 OK, is a failed login. A 200 OK is replied if the login form with - // an error page is sent to back to the user. - // - int retCode = connection.getResponse().getResponseCode(); - if (retCode != HttpURLConnection.HTTP_MOVED_TEMP) - throw new OsmOAuthAuthorizationException(tr("Failed to authenticate user ''{0}'' with password ''***'' as OAuth user", - userName)); - } catch (OsmOAuthAuthorizationException | IOException e) { - throw new OsmLoginFailedException(e); - } finally { - synchronized (this) { - connection = null; - } - } - } - - protected void logoutOsmSession(SessionId sessionId) throws OsmOAuthAuthorizationException { - try { - URL url = new URL(oauthProviderParameters.getOsmLogoutUrl()); - synchronized (this) { - connection = HttpClient.create(url).setMaxRedirects(-1); - connection.connect(); - } - } catch (IOException e) { - throw new OsmOAuthAuthorizationException(e); - } finally { - synchronized (this) { - connection = null; - } - } - } - - protected void sendAuthorisationRequest(SessionId sessionId, OAuthToken requestToken, OsmPrivileges privileges) - throws OsmOAuthAuthorizationException { - Map parameters = new HashMap<>(); - fetchOAuthToken(sessionId, requestToken); - parameters.put("oauth_token", requestToken.getKey()); - parameters.put("oauth_callback", ""); - parameters.put("authenticity_token", sessionId.token); - parameters.put("allow_write_api", booleanParam(privileges.isAllowWriteApi())); - parameters.put("allow_write_gpx", booleanParam(privileges.isAllowWriteGpx())); - parameters.put("allow_read_gpx", booleanParam(privileges.isAllowReadGpx())); - parameters.put("allow_write_prefs", booleanParam(privileges.isAllowWritePrefs())); - parameters.put("allow_read_prefs", booleanParam(privileges.isAllowReadPrefs())); - parameters.put("allow_write_notes", booleanParam(privileges.isAllowModifyNotes())); - parameters.put("allow_write_diary", booleanParam(privileges.isAllowWriteDiary())); - - String request = buildPostRequest(parameters); - try { - URL url = new URL(oauthProviderParameters.getAuthoriseUrl()); - final HttpClient client = HttpClient.create(url, "POST").useCache(false); - client.setHeader("Content-Type", "application/x-www-form-urlencoded"); - client.setHeader("Cookie", "_osm_session=" + sessionId.id + "; _osm_username=" + sessionId.userName); - client.setMaxRedirects(-1); - client.setRequestBody(request.getBytes(StandardCharsets.UTF_8)); - - synchronized (this) { - connection = client; - connection.connect(); - } - - int retCode = connection.getResponse().getResponseCode(); - if (retCode != HttpURLConnection.HTTP_OK) - throw new OsmOAuthAuthorizationException(tr("Failed to authorize OAuth request ''{0}''", requestToken.getKey())); - } catch (IOException e) { - throw new OsmOAuthAuthorizationException(e); - } finally { - synchronized (this) { - connection = null; - } - } - } - - private static String booleanParam(boolean param) { - return param ? "1" : "0"; - } - - /** - * Automatically authorises a request token for a set of privileges. - * - * @param requestToken the request token. Must not be null. - * @param userName the OSM user name. Must not be null. - * @param password the OSM password. Must not be null. - * @param privileges the set of privileges. Must not be null. - * @param monitor a progress monitor. Defaults to {@link NullProgressMonitor#INSTANCE} if null - * @throws IllegalArgumentException if requestToken is null - * @throws IllegalArgumentException if osmUserName is null - * @throws IllegalArgumentException if osmPassword is null - * @throws IllegalArgumentException if privileges is null - * @throws OsmOAuthAuthorizationException if the authorisation fails - * @throws OsmTransferCanceledException if the task is canceled by the user - */ - public void authorise(OAuthToken requestToken, String userName, String password, OsmPrivileges privileges, ProgressMonitor monitor) - throws OsmOAuthAuthorizationException, OsmTransferCanceledException { - CheckParameterUtil.ensureParameterNotNull(requestToken, "requestToken"); - CheckParameterUtil.ensureParameterNotNull(userName, "userName"); - CheckParameterUtil.ensureParameterNotNull(password, "password"); - CheckParameterUtil.ensureParameterNotNull(privileges, "privileges"); - - if (monitor == null) { - monitor = NullProgressMonitor.INSTANCE; - } - try { - monitor.beginTask(tr("Authorizing OAuth Request token ''{0}'' at the OSM website ...", requestToken.getKey())); - monitor.setTicksCount(4); - monitor.indeterminateSubTask(tr("Initializing a session at the OSM website...")); - SessionId sessionId = fetchOsmWebsiteSessionId(); - sessionId.userName = userName; - if (canceled) - throw new OsmTransferCanceledException("Authorization canceled"); - monitor.worked(1); - - monitor.indeterminateSubTask(tr("Authenticating the session for user ''{0}''...", userName)); - authenticateOsmSession(sessionId, userName, password); - if (canceled) - throw new OsmTransferCanceledException("Authorization canceled"); - monitor.worked(1); - - monitor.indeterminateSubTask(tr("Authorizing request token ''{0}''...", requestToken.getKey())); - sendAuthorisationRequest(sessionId, requestToken, privileges); - if (canceled) - throw new OsmTransferCanceledException("Authorization canceled"); - monitor.worked(1); - - monitor.indeterminateSubTask(tr("Logging out session ''{0}''...", sessionId)); - logoutOsmSession(sessionId); - if (canceled) - throw new OsmTransferCanceledException("Authorization canceled"); - monitor.worked(1); - } catch (OsmOAuthAuthorizationException e) { - if (canceled) - throw new OsmTransferCanceledException(e); - throw e; - } finally { - monitor.finishTask(); - } - } -} diff --git a/src/org/openstreetmap/josm/gui/oauth/RetrieveAccessTokenTask.java b/src/org/openstreetmap/josm/gui/oauth/RetrieveAccessTokenTask.java deleted file mode 100644 index 56ec8e28f9c..00000000000 --- a/src/org/openstreetmap/josm/gui/oauth/RetrieveAccessTokenTask.java +++ /dev/null @@ -1,118 +0,0 @@ -// License: GPL. For details, see LICENSE file. -package org.openstreetmap.josm.gui.oauth; - -import static org.openstreetmap.josm.tools.I18n.tr; - -import java.awt.Component; -import java.io.IOException; - -import javax.swing.JOptionPane; - -import org.openstreetmap.josm.data.oauth.OAuthParameters; -import org.openstreetmap.josm.data.oauth.OAuthToken; -import org.openstreetmap.josm.gui.HelpAwareOptionPane; -import org.openstreetmap.josm.gui.PleaseWaitRunnable; -import org.openstreetmap.josm.gui.help.HelpUtil; -import org.openstreetmap.josm.gui.util.GuiHelper; -import org.openstreetmap.josm.io.OsmTransferCanceledException; -import org.openstreetmap.josm.io.OsmTransferException; -import org.openstreetmap.josm.tools.CheckParameterUtil; -import org.openstreetmap.josm.tools.Logging; -import org.xml.sax.SAXException; - -/** - * Asynchronous task for retrieving an Access Token. - * - */ -public class RetrieveAccessTokenTask extends PleaseWaitRunnable { - - private boolean canceled; - private OAuthToken accessToken; - private final OAuthParameters parameters; - private OsmOAuthAuthorizationClient client; - private final OAuthToken requestToken; - private final Component parent; - - /** - * Creates the task - * - * @param parent the parent component relative to which the {@link PleaseWaitRunnable}-Dialog - * is displayed - * @param parameters the OAuth parameters. Must not be null. - * @param requestToken the request token for which an Access Token is retrieved. Must not be null. - * @throws IllegalArgumentException if parameters is null. - * @throws IllegalArgumentException if requestToken is null. - */ - public RetrieveAccessTokenTask(Component parent, OAuthParameters parameters, OAuthToken requestToken) { - super(parent, tr("Retrieving OAuth Access Token..."), false /* don't ignore exceptions */); - CheckParameterUtil.ensureParameterNotNull(parameters, "parameters"); - CheckParameterUtil.ensureParameterNotNull(requestToken, "requestToken"); - this.parameters = parameters; - this.requestToken = requestToken; - this.parent = parent; - } - - @Override - protected void cancel() { - canceled = true; - synchronized (this) { - if (client != null) { - client.cancel(); - } - } - } - - @Override - protected void finish() { /* not used in this task */} - - protected void alertRetrievingAccessTokenFailed() { - HelpAwareOptionPane.showOptionDialog( - parent, - tr( - "Retrieving an OAuth Access Token from ''{0}'' failed.", - parameters.getAccessTokenUrl() - ), - tr("Request Failed"), - JOptionPane.ERROR_MESSAGE, - HelpUtil.ht("/OAuth#NotAuthorizedException") - ); - } - - @Override - protected void realRun() throws SAXException, IOException, OsmTransferException { - try { - synchronized (this) { - client = new OsmOAuthAuthorizationClient(parameters, requestToken); - } - accessToken = client.getAccessToken(getProgressMonitor().createSubTaskMonitor(0, false)); - } catch (OsmTransferCanceledException e) { - Logging.trace(e); - } catch (final OsmOAuthAuthorizationException e) { - Logging.error(e); - GuiHelper.runInEDT(this::alertRetrievingAccessTokenFailed); - accessToken = null; - } finally { - synchronized (this) { - client = null; - } - } - } - - /** - * Replies true if the task was canceled. - * - * @return {@code true} if user aborted operation - */ - public boolean isCanceled() { - return canceled; - } - - /** - * Replies the retrieved Access Token. null, if something went wrong. - * - * @return the retrieved Access Token - */ - public OAuthToken getAccessToken() { - return accessToken; - } -} diff --git a/src/org/openstreetmap/josm/gui/oauth/RetrieveRequestTokenTask.java b/src/org/openstreetmap/josm/gui/oauth/RetrieveRequestTokenTask.java deleted file mode 100644 index ee90dfd4b9d..00000000000 --- a/src/org/openstreetmap/josm/gui/oauth/RetrieveRequestTokenTask.java +++ /dev/null @@ -1,112 +0,0 @@ -// License: GPL. For details, see LICENSE file. -package org.openstreetmap.josm.gui.oauth; - -import static org.openstreetmap.josm.tools.I18n.tr; - -import java.awt.Component; -import java.io.IOException; - -import javax.swing.JOptionPane; - -import org.openstreetmap.josm.data.oauth.OAuthParameters; -import org.openstreetmap.josm.data.oauth.OAuthToken; -import org.openstreetmap.josm.gui.HelpAwareOptionPane; -import org.openstreetmap.josm.gui.PleaseWaitRunnable; -import org.openstreetmap.josm.gui.help.HelpUtil; -import org.openstreetmap.josm.gui.util.GuiHelper; -import org.openstreetmap.josm.io.OsmTransferCanceledException; -import org.openstreetmap.josm.io.OsmTransferException; -import org.openstreetmap.josm.tools.CheckParameterUtil; -import org.openstreetmap.josm.tools.Logging; -import org.xml.sax.SAXException; - -/** - * Asynchronous task for retrieving a request token - */ -public class RetrieveRequestTokenTask extends PleaseWaitRunnable { - - private boolean canceled; - private OAuthToken requestToken; - private final OAuthParameters parameters; - private OsmOAuthAuthorizationClient client; - private final Component parent; - - /** - * Creates the task - * - * @param parent the parent component relative to which the {@link PleaseWaitRunnable}-Dialog - * is displayed - * @param parameters the OAuth parameters. Must not be null. - * @throws IllegalArgumentException if parameters is null. - */ - public RetrieveRequestTokenTask(Component parent, OAuthParameters parameters) { - super(parent, tr("Retrieving OAuth Request Token..."), false /* don't ignore exceptions */); - CheckParameterUtil.ensureParameterNotNull(parameters, "parameters"); - this.parameters = parameters; - this.parent = parent; - } - - @Override - protected void cancel() { - canceled = true; - synchronized (this) { - if (client != null) { - client.cancel(); - } - } - } - - @Override - protected void finish() { /* not used in this task */} - - protected void alertRetrievingRequestTokenFailed() { - HelpAwareOptionPane.showOptionDialog( - parent, - tr( - "Retrieving an OAuth Request Token from ''{0}'' failed.", - parameters.getRequestTokenUrl() - ), - tr("Request Failed"), - JOptionPane.ERROR_MESSAGE, - HelpUtil.ht("/OAuth#NotAuthorizedException") - ); - } - - @Override - protected void realRun() throws SAXException, IOException, OsmTransferException { - try { - synchronized (this) { - client = new OsmOAuthAuthorizationClient(parameters); - } - requestToken = client.getRequestToken(getProgressMonitor().createSubTaskMonitor(0, false)); - } catch (OsmTransferCanceledException e) { - Logging.trace(e); - } catch (final OsmOAuthAuthorizationException e) { - Logging.error(e); - GuiHelper.runInEDT(this::alertRetrievingRequestTokenFailed); - requestToken = null; - } finally { - synchronized (this) { - client = null; - } - } - } - - /** - * Replies true if the task was canceled - * - * @return true if the task was canceled - */ - public boolean isCanceled() { - return canceled; - } - - /** - * Replies the request token. null, if something went wrong. - * - * @return the request token - */ - public OAuthToken getRequestToken() { - return requestToken; - } -} diff --git a/src/org/openstreetmap/josm/gui/oauth/SemiAutomaticAuthorizationUI.java b/src/org/openstreetmap/josm/gui/oauth/SemiAutomaticAuthorizationUI.java deleted file mode 100644 index 15aa415ee7a..00000000000 --- a/src/org/openstreetmap/josm/gui/oauth/SemiAutomaticAuthorizationUI.java +++ /dev/null @@ -1,460 +0,0 @@ -// License: GPL. For details, see LICENSE file. -package org.openstreetmap.josm.gui.oauth; - -import static org.openstreetmap.josm.tools.I18n.tr; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.FlowLayout; -import java.awt.Font; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ItemEvent; -import java.util.concurrent.Executor; - -import javax.swing.AbstractAction; -import javax.swing.BorderFactory; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JLabel; -import javax.swing.JPanel; - -import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder; -import org.openstreetmap.josm.data.oauth.OAuthParameters; -import org.openstreetmap.josm.data.oauth.OAuthToken; -import org.openstreetmap.josm.gui.util.GuiHelper; -import org.openstreetmap.josm.gui.widgets.HtmlPanel; -import org.openstreetmap.josm.gui.widgets.JMultilineLabel; -import org.openstreetmap.josm.gui.widgets.JosmTextField; -import org.openstreetmap.josm.tools.ImageProvider; -import org.openstreetmap.josm.tools.OpenBrowser; - -/** - * This is the UI for running a semi-automatic authorisation procedure. - * - * In contrast to the fully-automatic procedure the user is dispatched to an - * external browser for login and authorisation. - * - * @since 2746 - */ -public class SemiAutomaticAuthorizationUI extends AbstractAuthorizationUI { - private final AccessTokenInfoPanel pnlAccessTokenInfo = new AccessTokenInfoPanel(); - private transient OAuthToken requestToken; - - private RetrieveRequestTokenPanel pnlRetrieveRequestToken; - private RetrieveAccessTokenPanel pnlRetrieveAccessToken; - private ShowAccessTokenPanel pnlShowAccessToken; - private final transient Executor executor; - - /** - * build the UI - */ - protected final void build() { - setLayout(new BorderLayout()); - setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - pnlRetrieveRequestToken = new RetrieveRequestTokenPanel(); - pnlRetrieveAccessToken = new RetrieveAccessTokenPanel(); - pnlShowAccessToken = new ShowAccessTokenPanel(); - add(pnlRetrieveRequestToken, BorderLayout.CENTER); - } - - /** - * Constructs a new {@code SemiAutomaticAuthorizationUI} for the given API URL. - * @param apiUrl The OSM API URL - * @param executor the executor used for running the HTTP requests for the authorization - * @since 5422 - */ - public SemiAutomaticAuthorizationUI(String apiUrl, Executor executor) { - super(apiUrl); - this.executor = executor; - build(); - } - - @Override - public boolean isSaveAccessTokenToPreferences() { - return pnlAccessTokenInfo.isSaveToPreferences(); - } - - protected void transitionToRetrieveAccessToken() { - OsmOAuthAuthorizationClient client = new OsmOAuthAuthorizationClient( - (OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters() - ); - String authoriseUrl = client.getAuthoriseUrl(requestToken); - OpenBrowser.displayUrl(authoriseUrl); - - removeAll(); - pnlRetrieveAccessToken.setAuthoriseUrl(authoriseUrl); - add(pnlRetrieveAccessToken, BorderLayout.CENTER); - pnlRetrieveAccessToken.invalidate(); - validate(); - repaint(); - } - - protected void transitionToRetrieveRequestToken() { - requestToken = null; - setAccessToken(null); - removeAll(); - add(pnlRetrieveRequestToken, BorderLayout.CENTER); - pnlRetrieveRequestToken.invalidate(); - validate(); - repaint(); - } - - protected void transitionToShowAccessToken() { - removeAll(); - add(pnlShowAccessToken, BorderLayout.CENTER); - pnlShowAccessToken.invalidate(); - validate(); - repaint(); - pnlShowAccessToken.setAccessToken(getAccessToken()); - } - - static class StepLabel extends JLabel { - StepLabel(String text) { - super(text); - setFont(getFont().deriveFont(16f)); - } - } - - /** - * This is the panel displayed in the first step of the semi-automatic authorisation process. - */ - private class RetrieveRequestTokenPanel extends JPanel { - - /** - * Constructs a new {@code RetrieveRequestTokenPanel}. - */ - RetrieveRequestTokenPanel() { - build(); - } - - protected JPanel buildAdvancedParametersPanel() { - JPanel pnl = new JPanel(new GridBagLayout()); - GridBagConstraints gc = new GridBagConstraints(); - - gc.anchor = GridBagConstraints.NORTHWEST; - gc.fill = GridBagConstraints.HORIZONTAL; - gc.weightx = 0.0; - gc.insets = new Insets(0, 0, 0, 3); - JCheckBox cbShowAdvancedParameters = new JCheckBox(); - pnl.add(cbShowAdvancedParameters, gc); - cbShowAdvancedParameters.setSelected(false); - cbShowAdvancedParameters.addItemListener( - evt -> getAdvancedPropertiesPanel().setVisible(evt.getStateChange() == ItemEvent.SELECTED) - ); - - gc.gridx = 1; - gc.weightx = 1.0; - JMultilineLabel lbl = new JMultilineLabel(tr("Display Advanced OAuth Parameters")); - lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN)); - pnl.add(lbl, gc); - - gc.gridy = 1; - gc.gridx = 1; - gc.insets = new Insets(3, 0, 3, 0); - gc.fill = GridBagConstraints.BOTH; - gc.weightx = 1.0; - gc.weighty = 1.0; - pnl.add(getAdvancedPropertiesPanel(), gc); - getAdvancedPropertiesPanel().setBorder( - BorderFactory.createCompoundBorder( - BorderFactory.createLineBorder(Color.GRAY, 1), - BorderFactory.createEmptyBorder(3, 3, 3, 3) - ) - ); - getAdvancedPropertiesPanel().setVisible(false); - return pnl; - } - - protected JPanel buildCommandPanel() { - JPanel pnl = new JPanel(new GridBagLayout()); - GridBagConstraints gc = new GridBagConstraints(); - - gc.anchor = GridBagConstraints.NORTHWEST; - gc.fill = GridBagConstraints.BOTH; - gc.weightx = 1.0; - gc.weighty = 1.0; - gc.insets = new Insets(0, 0, 0, 3); - - - HtmlPanel h = new HtmlPanel(); - h.setText(tr("" - + "Please click on {0} to retrieve an OAuth Request Token from " - + "''{1}''.", - tr("Retrieve Request Token"), - ((OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters()).getRequestTokenUrl() - )); - pnl.add(h, gc); - - JPanel pnl1 = new JPanel(new FlowLayout(FlowLayout.LEFT)); - pnl1.add(new JButton(new RetrieveRequestTokenAction())); - gc.fill = GridBagConstraints.HORIZONTAL; - gc.weightx = 1.0; - gc.gridy = 1; - pnl.add(pnl1, gc); - return pnl; - - } - - protected final void build() { - setLayout(new BorderLayout(0, 5)); - add(new StepLabel(tr("Step 1/3: Retrieve an OAuth Request Token")), BorderLayout.NORTH); - add(buildAdvancedParametersPanel(), BorderLayout.CENTER); - add(buildCommandPanel(), BorderLayout.SOUTH); - } - } - - /** - * This is the panel displayed in the second step of the semi-automatic authorization process. - */ - private class RetrieveAccessTokenPanel extends JPanel { - - private final JosmTextField tfAuthoriseUrl = new JosmTextField(null, null, 0, false); - - /** - * Constructs a new {@code RetrieveAccessTokenPanel}. - */ - RetrieveAccessTokenPanel() { - build(); - } - - protected JPanel buildTitlePanel() { - JPanel pnl = new JPanel(new BorderLayout()); - pnl.add(new StepLabel(tr("Step 2/3: Authorize and retrieve an Access Token")), BorderLayout.CENTER); - return pnl; - } - - protected JPanel buildContentPanel() { - JPanel pnl = new JPanel(new GridBagLayout()); - GridBagConstraints gc = new GridBagConstraints(); - - gc.anchor = GridBagConstraints.NORTHWEST; - gc.fill = GridBagConstraints.HORIZONTAL; - gc.weightx = 1.0; - gc.gridwidth = 2; - HtmlPanel html = new HtmlPanel(); - html.setText(tr("" - + "JOSM successfully retrieved a Request Token. " - + "JOSM is now launching an authorization page in an external browser. " - + "Please login with your OSM username and password and follow the instructions " - + "to authorize the Request Token. Then switch back to this dialog and click on " - + "{0}

      " - + "If launching the external browser fails you can copy the following authorize URL " - + "and paste it into the address field of your browser.", - tr("Request Access Token") - )); - pnl.add(html, gc); - - gc.gridx = 0; - gc.gridy = 1; - gc.weightx = 0.0; - gc.gridwidth = 1; - pnl.add(new JLabel(tr("Authorize URL:")), gc); - - gc.gridx = 1; - gc.weightx = 1.0; - pnl.add(tfAuthoriseUrl, gc); - tfAuthoriseUrl.setEditable(false); - - return pnl; - } - - protected JPanel buildActionPanel() { - JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT)); - pnl.add(new JButton(new BackAction())); - pnl.add(new JButton(new RetrieveAccessTokenAction())); - return pnl; - } - - protected final void build() { - setLayout(new BorderLayout()); - add(buildTitlePanel(), BorderLayout.NORTH); - add(buildContentPanel(), BorderLayout.CENTER); - add(buildActionPanel(), BorderLayout.SOUTH); - } - - public void setAuthoriseUrl(String url) { - tfAuthoriseUrl.setText(url); - } - - /** - * Action to go back to step 1 in the process - */ - class BackAction extends AbstractAction { - BackAction() { - putValue(NAME, tr("Back")); - putValue(SHORT_DESCRIPTION, tr("Go back to step 1/3")); - new ImageProvider("dialogs", "previous").getResource().attachImageIcon(this); - } - - @Override - public void actionPerformed(ActionEvent arg0) { - transitionToRetrieveRequestToken(); - } - } - } - - /** - * Displays the retrieved Access Token in step 3. - */ - class ShowAccessTokenPanel extends JPanel { - - /** - * Constructs a new {@code ShowAccessTokenPanel}. - */ - ShowAccessTokenPanel() { - build(); - } - - protected JPanel buildTitlePanel() { - JPanel pnl = new JPanel(new BorderLayout()); - pnl.add(new StepLabel(tr("Step 3/3: Successfully retrieved an Access Token")), BorderLayout.CENTER); - return pnl; - } - - protected JPanel buildContentPanel() { - JPanel pnl = new JPanel(new GridBagLayout()); - GridBagConstraints gc = new GridBagConstraints(); - - gc.anchor = GridBagConstraints.NORTHWEST; - gc.fill = GridBagConstraints.HORIZONTAL; - gc.weightx = 1.0; - HtmlPanel html = new HtmlPanel(); - html.setText(tr("" - + "JOSM has successfully retrieved an Access Token. " - + "You can now accept this token. JOSM will use it in the future for authentication " - + "and authorization to the OSM server.

      " - + "The access token is: " - )); - pnl.add(html, gc); - - gc.gridx = 0; - gc.gridy = 1; - gc.weightx = 1.0; - gc.gridwidth = 1; - pnl.add(pnlAccessTokenInfo, gc); - pnlAccessTokenInfo.setSaveToPreferences( - OAuthAccessTokenHolder.getInstance().isSaveToPreferences() - ); - return pnl; - } - - protected JPanel buildActionPanel() { - JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT)); - pnl.add(new JButton(new RestartAction())); - pnl.add(new JButton(new TestAccessTokenAction())); - return pnl; - } - - protected final void build() { - setLayout(new BorderLayout()); - add(buildTitlePanel(), BorderLayout.NORTH); - add(buildContentPanel(), BorderLayout.CENTER); - add(buildActionPanel(), BorderLayout.SOUTH); - } - - /** - * Action to go back to step 1 in the process - */ - class RestartAction extends AbstractAction { - RestartAction() { - putValue(NAME, tr("Restart")); - putValue(SHORT_DESCRIPTION, tr("Go back to step 1/3")); - new ImageProvider("dialogs", "previous").getResource().attachImageIcon(this); - } - - @Override - public void actionPerformed(ActionEvent arg0) { - transitionToRetrieveRequestToken(); - } - } - - public void setAccessToken(OAuthToken accessToken) { - pnlAccessTokenInfo.setAccessToken(accessToken); - } - } - - /** - * Action for retrieving a request token - */ - class RetrieveRequestTokenAction extends AbstractAction { - - RetrieveRequestTokenAction() { - putValue(NAME, tr("Retrieve Request Token")); - new ImageProvider("oauth", "oauth-small").getResource().attachImageIcon(this); - putValue(SHORT_DESCRIPTION, tr("Click to retrieve a Request Token")); - } - - @Override - public void actionPerformed(ActionEvent evt) { - final RetrieveRequestTokenTask task = new RetrieveRequestTokenTask( - SemiAutomaticAuthorizationUI.this, - (OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters() - ); - executor.execute(task); - Runnable r = () -> { - if (task.isCanceled()) return; - if (task.getRequestToken() == null) return; - requestToken = task.getRequestToken(); - GuiHelper.runInEDT(SemiAutomaticAuthorizationUI.this::transitionToRetrieveAccessToken); - }; - executor.execute(r); - } - } - - /** - * Action for retrieving an Access Token - */ - class RetrieveAccessTokenAction extends AbstractAction { - - RetrieveAccessTokenAction() { - putValue(NAME, tr("Retrieve Access Token")); - new ImageProvider("oauth", "oauth-small").getResource().attachImageIcon(this); - putValue(SHORT_DESCRIPTION, tr("Click to retrieve an Access Token")); - } - - @Override - public void actionPerformed(ActionEvent evt) { - final RetrieveAccessTokenTask task = new RetrieveAccessTokenTask( - SemiAutomaticAuthorizationUI.this, - (OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters(), - requestToken - ); - executor.execute(task); - Runnable r = () -> { - if (task.isCanceled()) return; - if (task.getAccessToken() == null) return; - GuiHelper.runInEDT(() -> { - setAccessToken(task.getAccessToken()); - transitionToShowAccessToken(); - }); - }; - executor.execute(r); - } - } - - /** - * Action for testing an Access Token - */ - class TestAccessTokenAction extends AbstractAction { - - TestAccessTokenAction() { - putValue(NAME, tr("Test Access Token")); - new ImageProvider("oauth", "oauth-small").getResource().attachImageIcon(this); - putValue(SHORT_DESCRIPTION, tr("Click to test the Access Token")); - } - - @Override - public void actionPerformed(ActionEvent evt) { - TestAccessTokenTask task = new TestAccessTokenTask( - SemiAutomaticAuthorizationUI.this, - getApiUrl(), - (OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters(), - getAccessToken() - ); - executor.execute(task); - } - } -} diff --git a/src/org/openstreetmap/josm/gui/oauth/TestAccessTokenTask.java b/src/org/openstreetmap/josm/gui/oauth/TestAccessTokenTask.java index 16fd52f36da..86fe3d9a314 100644 --- a/src/org/openstreetmap/josm/gui/oauth/TestAccessTokenTask.java +++ b/src/org/openstreetmap/josm/gui/oauth/TestAccessTokenTask.java @@ -11,11 +11,9 @@ import javax.swing.JOptionPane; import javax.xml.parsers.ParserConfigurationException; -import org.openstreetmap.josm.data.oauth.IOAuthParameters; import org.openstreetmap.josm.data.oauth.IOAuthToken; import org.openstreetmap.josm.data.oauth.OAuth20Token; -import org.openstreetmap.josm.data.oauth.OAuthParameters; -import org.openstreetmap.josm.data.oauth.OAuthToken; +import org.openstreetmap.josm.data.oauth.OAuthException; import org.openstreetmap.josm.data.osm.UserInfo; import org.openstreetmap.josm.gui.HelpAwareOptionPane; import org.openstreetmap.josm.gui.PleaseWaitRunnable; @@ -33,20 +31,15 @@ import org.w3c.dom.Document; import org.xml.sax.SAXException; -import oauth.signpost.OAuthConsumer; -import oauth.signpost.exception.OAuthException; - /** * Checks whether an OSM API server can be accessed with a specific Access Token. - * + *

      * It retrieves the user details for the user which is authorized to access the server with * this token. * */ public class TestAccessTokenTask extends PleaseWaitRunnable { - private final OAuthToken tokenOAuth1; private final IOAuthToken tokenOAuth2; - private final IOAuthParameters oauthParameters; private boolean canceled; private final Component parent; private final String apiUrl; @@ -57,38 +50,14 @@ public class TestAccessTokenTask extends PleaseWaitRunnable { * * @param parent the parent component relative to which the {@link PleaseWaitRunnable}-Dialog is displayed * @param apiUrl the API URL. Must not be null. - * @param parameters the OAuth parameters. Must not be null. * @param accessToken the Access Token. Must not be null. + * @since 18991 */ - public TestAccessTokenTask(Component parent, String apiUrl, OAuthParameters parameters, OAuthToken accessToken) { + public TestAccessTokenTask(Component parent, String apiUrl, IOAuthToken accessToken) { super(parent, tr("Testing OAuth Access Token"), false /* don't ignore exceptions */); CheckParameterUtil.ensureParameterNotNull(apiUrl, "apiUrl"); - CheckParameterUtil.ensureParameterNotNull(parameters, "parameters"); CheckParameterUtil.ensureParameterNotNull(accessToken, "accessToken"); - this.tokenOAuth1 = accessToken; - this.tokenOAuth2 = null; - this.oauthParameters = parameters; - this.parent = parent; - this.apiUrl = apiUrl; - } - - /** - * Create the task - * - * @param parent the parent component relative to which the {@link PleaseWaitRunnable}-Dialog is displayed - * @param apiUrl the API URL. Must not be null. - * @param parameters the OAuth parameters. Must not be null. - * @param accessToken the Access Token. Must not be null. - * @since xxx - */ - public TestAccessTokenTask(Component parent, String apiUrl, IOAuthParameters parameters, IOAuthToken accessToken) { - super(parent, tr("Testing OAuth Access Token"), false /* don't ignore exceptions */); - CheckParameterUtil.ensureParameterNotNull(apiUrl, "apiUrl"); - CheckParameterUtil.ensureParameterNotNull(parameters, "parameters"); - CheckParameterUtil.ensureParameterNotNull(accessToken, "accessToken"); - this.tokenOAuth1 = null; this.tokenOAuth2 = accessToken; - this.oauthParameters = parameters; this.parent = parent; this.apiUrl = apiUrl; } @@ -109,18 +78,7 @@ protected void finish() { } protected void sign(HttpClient con) throws OAuthException { - if (oauthParameters instanceof OAuthParameters) { - OAuthConsumer consumer = ((OAuthParameters) oauthParameters).buildConsumer(); - consumer.setTokenWithSecret(tokenOAuth1.getKey(), tokenOAuth1.getSecret()); - consumer.sign(con); - } else { - try { - this.tokenOAuth2.sign(con); - } catch (org.openstreetmap.josm.data.oauth.OAuthException e) { - // Adapt our OAuthException to the SignPost OAuth exception - throw new OAuthException(e) {}; - } - } + this.tokenOAuth2.sign(con); } protected String normalizeApiUrl(String url) { @@ -314,12 +272,9 @@ protected void realRun() throws SAXException, IOException, OsmTransferException } private String getAuthKey() { - if (this.tokenOAuth1 != null) { - return this.tokenOAuth1.getKey(); - } if (this.tokenOAuth2 instanceof OAuth20Token) { return ((OAuth20Token) this.tokenOAuth2).getBearerToken(); } - throw new IllegalArgumentException("Only OAuth1 and OAuth2 tokens are understood: " + this.tokenOAuth2); + throw new IllegalArgumentException("Only OAuth2 tokens are understood: " + this.tokenOAuth2); } } diff --git a/src/org/openstreetmap/josm/gui/preferences/PreferenceTabbedPane.java b/src/org/openstreetmap/josm/gui/preferences/PreferenceTabbedPane.java index bb311319e0b..e775289a57b 100644 --- a/src/org/openstreetmap/josm/gui/preferences/PreferenceTabbedPane.java +++ b/src/org/openstreetmap/josm/gui/preferences/PreferenceTabbedPane.java @@ -82,7 +82,6 @@ * * @author imi */ -@SuppressWarnings("deprecation") public final class PreferenceTabbedPane extends JTabbedPane implements ExpertModeChangeListener, ChangeListener { private final class PluginDownloadAfterTask implements Runnable { @@ -117,9 +116,9 @@ public void run() { sb.append(PluginPreference.buildDownloadSummary(task)); } if (requiresRestart) { - sb.append(tr("You have to restart JOSM for some settings to take effect.")); - sb.append("

      "); - sb.append(tr("Would you like to restart now?")); + sb.append(tr("You have to restart JOSM for some settings to take effect.")) + .append("

      ") + .append(tr("Would you like to restart now?")); } sb.append(""); @@ -357,8 +356,8 @@ public boolean selectSubTabByPref(Class clazz) { final TabPreferenceSetting tab = sub.getTabPreferenceSetting(this); selectTabBy(tps -> tps.equals(tab)); return tab.selectSubTab(sub); - } catch (NoSuchElementException ignore) { - Logging.trace(ignore); + } catch (NoSuchElementException e) { + Logging.trace(e); return false; } } @@ -563,7 +562,8 @@ private void addGUITabs(boolean clear) { private int computeMaxTabWidth() { FontMetrics fm = getFontMetrics(getFont()); - return settings.stream().filter(x -> x instanceof TabPreferenceSetting).map(x -> ((TabPreferenceSetting) x).getTitle()) + return settings.stream().filter(TabPreferenceSetting.class::isInstance) + .map(TabPreferenceSetting.class::cast).map(TabPreferenceSetting::getTitle) .filter(Objects::nonNull).mapToInt(fm::stringWidth).max().orElse(120); } diff --git a/src/org/openstreetmap/josm/gui/preferences/SourceEditor.java b/src/org/openstreetmap/josm/gui/preferences/SourceEditor.java index 543060ca5c3..47b190b3e0e 100644 --- a/src/org/openstreetmap/josm/gui/preferences/SourceEditor.java +++ b/src/org/openstreetmap/josm/gui/preferences/SourceEditor.java @@ -58,6 +58,7 @@ import javax.swing.JToolBar; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; +import javax.swing.SwingConstants; import javax.swing.UIManager; import javax.swing.event.CellEditorListener; import javax.swing.event.ChangeEvent; @@ -111,6 +112,8 @@ * @since 1743 */ public abstract class SourceEditor extends JPanel { + private static final String DELETE = "delete"; + private static final String DIALOGS = "dialogs"; /** the type of source entry **/ protected final SourceType sourceType; @@ -218,8 +221,8 @@ public void mouseClicked(MouseEvent e) { RemoveActiveSourcesAction removeActiveSourcesAction = new RemoveActiveSourcesAction(); tblActiveSources.getSelectionModel().addListSelectionListener(removeActiveSourcesAction); - tblActiveSources.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete"); - tblActiveSources.getActionMap().put("delete", removeActiveSourcesAction); + tblActiveSources.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), DELETE); + tblActiveSources.getActionMap().put(DELETE, removeActiveSourcesAction); MoveUpDownAction moveUp = null; MoveUpDownAction moveDown = null; @@ -296,7 +299,7 @@ public void mouseClicked(MouseEvent e) { gbc.fill = GBC.VERTICAL; gbc.insets = new Insets(0, 0, 0, 6); - JToolBar sideButtonTB = new JToolBar(JToolBar.VERTICAL); + JToolBar sideButtonTB = new JToolBar(SwingConstants.VERTICAL); sideButtonTB.setFloatable(false); sideButtonTB.setBorderPainted(false); sideButtonTB.setOpaque(false); @@ -362,8 +365,8 @@ private void buildIcons(GridBagConstraints gbc) { RemoveIconPathAction removeIconPathAction = new RemoveIconPathAction(); tblIconPaths.getSelectionModel().addListSelectionListener(removeIconPathAction); - tblIconPaths.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete"); - tblIconPaths.getActionMap().put("delete", removeIconPathAction); + tblIconPaths.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), DELETE); + tblIconPaths.getActionMap().put(DELETE, removeIconPathAction); gbc.gridx = 0; gbc.gridy++; @@ -720,7 +723,7 @@ public void removeSelected() { public void removeIdxs(Collection idxs) { data = IntStream.range(0, data.size()) .filter(i -> !idxs.contains(i)) - .mapToObj(i -> data.get(i)) + .mapToObj(data::get) .collect(Collectors.toList()); fireTableDataChanged(); } @@ -763,7 +766,7 @@ public SourceEntry setValue(int index, SourceEntry value) { } private static void prepareFileChooser(String url, AbstractFileChooser fc) { - if (Utils.isBlank(url)) return; + if (Utils.isStripEmpty(url)) return; URL sourceUrl; try { sourceUrl = new URL(url); @@ -908,7 +911,7 @@ class NewActiveSourceAction extends AbstractAction { NewActiveSourceAction() { putValue(NAME, tr("New")); putValue(SHORT_DESCRIPTION, getStr(I18nString.NEW_SOURCE_ENTRY_TOOLTIP)); - new ImageProvider("dialogs", "add").getResource().attachImageIcon(this); + new ImageProvider(DIALOGS, "add").getResource().attachImageIcon(this); } @Override @@ -938,7 +941,7 @@ class RemoveActiveSourcesAction extends AbstractAction implements ListSelectionL RemoveActiveSourcesAction() { putValue(NAME, tr("Remove")); putValue(SHORT_DESCRIPTION, getStr(I18nString.REMOVE_SOURCE_TOOLTIP)); - new ImageProvider("dialogs", "delete").getResource().attachImageIcon(this); + new ImageProvider(DIALOGS, DELETE).getResource().attachImageIcon(this); updateEnabledState(); } @@ -961,7 +964,7 @@ class EditActiveSourceAction extends AbstractAction implements ListSelectionList EditActiveSourceAction() { putValue(NAME, tr("Edit")); putValue(SHORT_DESCRIPTION, getStr(I18nString.EDIT_SOURCE_TOOLTIP)); - new ImageProvider("dialogs", "edit").getResource().attachImageIcon(this); + new ImageProvider(DIALOGS, "edit").getResource().attachImageIcon(this); updateEnabledState(); } @@ -1007,7 +1010,7 @@ class MoveUpDownAction extends AbstractAction implements ListSelectionListener, MoveUpDownAction(boolean isDown) { increment = isDown ? 1 : -1; - new ImageProvider("dialogs", isDown ? "down" : "up").getResource().attachImageIcon(this, true); + new ImageProvider(DIALOGS, isDown ? "down" : "up").getResource().attachImageIcon(this, true); putValue(SHORT_DESCRIPTION, isDown ? tr("Move the selected entry one row down.") : tr("Move the selected entry one row up.")); updateEnabledState(); } @@ -1109,7 +1112,7 @@ class ReloadSourcesAction extends AbstractAction { ReloadSourcesAction(String url, List sourceProviders) { putValue(NAME, tr("Reload")); putValue(SHORT_DESCRIPTION, tr(getStr(I18nString.RELOAD_ALL_AVAILABLE), url)); - new ImageProvider("dialogs", "refresh").getResource().attachImageIcon(this); + new ImageProvider(DIALOGS, "refresh").getResource().attachImageIcon(this); this.url = url; this.sourceProviders = sourceProviders; setEnabled(!NetworkManager.isOffline(OnlineResource.JOSM_WEBSITE)); @@ -1251,7 +1254,7 @@ class NewIconPathAction extends AbstractAction { NewIconPathAction() { putValue(NAME, tr("New")); putValue(SHORT_DESCRIPTION, tr("Add a new icon path")); - new ImageProvider("dialogs", "add").getResource().attachImageIcon(this); + new ImageProvider(DIALOGS, "add").getResource().attachImageIcon(this); } @Override @@ -1265,7 +1268,7 @@ class RemoveIconPathAction extends AbstractAction implements ListSelectionListen RemoveIconPathAction() { putValue(NAME, tr("Remove")); putValue(SHORT_DESCRIPTION, tr("Remove the selected icon paths")); - new ImageProvider("dialogs", "delete").getResource().attachImageIcon(this); + new ImageProvider(DIALOGS, DELETE).getResource().attachImageIcon(this); updateEnabledState(); } @@ -1288,7 +1291,7 @@ class EditIconPathAction extends AbstractAction implements ListSelectionListener EditIconPathAction() { putValue(NAME, tr("Edit")); putValue(SHORT_DESCRIPTION, tr("Edit the selected icon path")); - new ImageProvider("dialogs", "edit").getResource().attachImageIcon(this); + new ImageProvider(DIALOGS, "edit").getResource().attachImageIcon(this); updateEnabledState(); } @@ -1607,11 +1610,7 @@ public boolean stopCellEditing() { public void setInitialValue(String initialValue) { this.value = initialValue; - if (initialValue == null) { - this.tfFileName.setText(""); - } else { - this.tfFileName.setText(initialValue); - } + this.tfFileName.setText(Objects.requireNonNullElse(initialValue, "")); } @Override diff --git a/src/org/openstreetmap/josm/gui/preferences/ToolbarPreferences.java b/src/org/openstreetmap/josm/gui/preferences/ToolbarPreferences.java index 947ae93fd6f..e7561b64420 100644 --- a/src/org/openstreetmap/josm/gui/preferences/ToolbarPreferences.java +++ b/src/org/openstreetmap/josm/gui/preferences/ToolbarPreferences.java @@ -7,6 +7,7 @@ import java.awt.Container; import java.awt.Dimension; import java.awt.GraphicsEnvironment; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.LayoutManager; @@ -96,6 +97,8 @@ public class ToolbarPreferences implements PreferenceSettingFactory, TaggingPresetListener { private static final String EMPTY_TOOLBAR_MARKER = ""; + private static final String TOOLBAR = "toolbar"; + private static final String DIALOGS = "dialogs"; /** * The prefix for imagery toolbar entries. @@ -239,6 +242,9 @@ public boolean hasParameters() { } } + /** + * Parse actions from a name + */ public static class ActionParser { private final Map actions; private final StringBuilder result = new StringBuilder(); @@ -358,7 +364,7 @@ private void escape(String s) { public String saveAction(ActionDefinition action) { result.setLength(0); - String val = (String) action.getAction().getValue("toolbar"); + String val = (String) action.getAction().getValue(TOOLBAR); if (val == null) return null; escape(val); @@ -390,8 +396,8 @@ public String saveAction(ActionDefinition action) { } tmp = action.getIcon(); if (!tmp.isEmpty()) { - result.append(first ? "{" : ","); - result.append("icon="); + result.append(first ? "{" : ",") + .append("icon="); escape(tmp); first = false; } @@ -404,7 +410,7 @@ public String saveAction(ActionDefinition action) { } } - private static class ActionParametersTableModel extends AbstractTableModel { + private static final class ActionParametersTableModel extends AbstractTableModel { private transient ActionDefinition currentAction = ActionDefinition.getSeparator(); @@ -489,7 +495,7 @@ public void setCurrentAction(ActionDefinition currentAction) { } } - private class ToolbarPopupMenu extends JPopupMenu { + private final class ToolbarPopupMenu extends JPopupMenu { private transient ActionDefinition act; private void setActionAndAdapt(ActionDefinition action) { @@ -508,7 +514,7 @@ public void actionPerformed(ActionEvent e) { String res = parser.saveAction(act); // remove the button from toolbar preferences t.remove(res); - Config.getPref().putList("toolbar", t); + Config.getPref().putList(TOOLBAR, t); MainApplication.getToolbar().refreshToolbarControl(); } }); @@ -517,7 +523,7 @@ public void actionPerformed(ActionEvent e) { @Override public void actionPerformed(ActionEvent e) { final PreferenceDialog p = new PreferenceDialog(MainApplication.getMainFrame()); - SwingUtilities.invokeLater(() -> p.selectPreferencesTabByName("toolbar")); + SwingUtilities.invokeLater(() -> p.selectPreferencesTabByName(TOOLBAR)); p.setVisible(true); } }); @@ -745,7 +751,7 @@ public boolean isDataFlavorSupported(DataFlavor flavor) { } } - private class ActionDefinitionModel extends DefaultListModel implements ReorderableTableModel { + private final class ActionDefinitionModel extends DefaultListModel implements ReorderableTableModel { @Override public ListSelectionModel getSelectionModel() { return selectedList.getSelectionModel(); @@ -791,7 +797,7 @@ public ActionDefinition setValue(int index, ActionDefinition value) { * @param rootActionsNode root actions node */ public Settings(DefaultMutableTreeNode rootActionsNode) { - super(/* ICON(preferences/) */ "toolbar", tr("Toolbar"), tr("Customize the elements on the toolbar.")); + super(/* ICON(preferences/) */ TOOLBAR, tr("Toolbar"), tr("Customize the elements on the toolbar.")); actionsTreeModel = new DefaultTreeModel(rootActionsNode); actionsTree = new JTree(actionsTreeModel); } @@ -800,11 +806,11 @@ private JButton createButton(String name) { JButton b = new JButton(); switch (name) { case "up": - b.setIcon(ImageProvider.get("dialogs", "up", ImageSizes.LARGEICON)); + b.setIcon(ImageProvider.get(DIALOGS, "up", ImageSizes.LARGEICON)); b.setToolTipText(tr("Move the currently selected members up")); break; case "down": - b.setIcon(ImageProvider.get("dialogs", "down", ImageSizes.LARGEICON)); + b.setIcon(ImageProvider.get(DIALOGS, "down", ImageSizes.LARGEICON)); b.setToolTipText(tr("Move the currently selected members down")); break; case "<": @@ -812,7 +818,7 @@ private JButton createButton(String name) { b.setToolTipText(tr("Add all objects selected in the current dataset before the first selected member")); break; case ">": - b.setIcon(ImageProvider.get("dialogs", "delete", ImageSizes.LARGEICON)); + b.setIcon(ImageProvider.get(DIALOGS, "delete", ImageSizes.LARGEICON)); b.setToolTipText(tr("Remove")); break; default: @@ -852,11 +858,12 @@ public Component getTreeCellRendererComponent(JTree tree, Object value, boolean } }); - ListCellRenderer renderer = new ListCellRenderer() { + ListCellRenderer renderer = new ListCellRenderer<>() { private final DefaultListCellRenderer def = new DefaultListCellRenderer(); + @Override public Component getListCellRendererComponent(JList list, - ActionDefinition action, int index, boolean isSelected, boolean cellHasFocus) { + ActionDefinition action, int index, boolean isSelected, boolean cellHasFocus) { String s; Icon i; if (!action.isSeparator()) { @@ -919,11 +926,11 @@ protected Transferable createTransferable(JComponent c) { final JPanel left = new JPanel(new GridBagLayout()); left.add(new JLabel(tr("Toolbar")), GBC.eol()); - left.add(new JScrollPane(selectedList), GBC.std().fill(GBC.BOTH)); + left.add(new JScrollPane(selectedList), GBC.std().fill(GridBagConstraints.BOTH)); final JPanel right = new JPanel(new GridBagLayout()); right.add(new JLabel(tr("Available")), GBC.eol()); - right.add(new JScrollPane(actionsTree), GBC.eol().fill(GBC.BOTH)); + right.add(new JScrollPane(actionsTree), GBC.eol().fill(GridBagConstraints.BOTH)); final JPanel buttons = new JPanel(new GridLayout(6, 1)); buttons.add(upButton); @@ -977,13 +984,13 @@ public void layoutContainer(Container parent) { actionParametersPanel.add(new JLabel(tr("Action parameters")), GBC.eol().insets(0, 10, 0, 20)); actionParametersTable.getColumnModel().getColumn(0).setHeaderValue(tr("Parameter name")); actionParametersTable.getColumnModel().getColumn(1).setHeaderValue(tr("Parameter value")); - actionParametersPanel.add(actionParametersTable.getTableHeader(), GBC.eol().fill(GBC.HORIZONTAL)); - actionParametersPanel.add(actionParametersTable, GBC.eol().fill(GBC.BOTH).insets(0, 0, 0, 10)); + actionParametersPanel.add(actionParametersTable.getTableHeader(), GBC.eol().fill(GridBagConstraints.HORIZONTAL)); + actionParametersPanel.add(actionParametersTable, GBC.eol().fill(GridBagConstraints.BOTH).insets(0, 0, 0, 10)); actionParametersPanel.setVisible(false); JPanel panel = gui.createPreferenceTab(this); - panel.add(p, GBC.eol().fill(GBC.BOTH)); - panel.add(actionParametersPanel, GBC.eol().fill(GBC.HORIZONTAL)); + panel.add(p, GBC.eol().fill(GridBagConstraints.BOTH)); + panel.add(actionParametersPanel, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); selected.removeAllElements(); for (ActionDefinition actionDefinition: getDefinedActions()) { selected.addElement(actionDefinition); @@ -1009,7 +1016,7 @@ public boolean ok() { if (t.isEmpty()) { t = Collections.singletonList(EMPTY_TOOLBAR_MARKER); } - Config.getPref().putList("toolbar", t); + Config.getPref().putList(TOOLBAR, t); MainApplication.getToolbar().refreshToolbarControl(); return false; } @@ -1045,7 +1052,7 @@ private static void loadAction(DefaultMutableTreeNode node, MenuElement menu, Ma if (menuItem.getAction() != null) { Action action = menuItem.getAction(); userObject = action; - Object tb = action.getValue("toolbar"); + Object tb = action.getValue(TOOLBAR); if (tb == null) { Logging.info(tr("Toolbar action without name: {0}", action.getClass().getName())); @@ -1096,7 +1103,7 @@ private void loadActions(Map actionsInMenu) { "tagginggroup_Man Made/Man Made"}; public static Collection getToolString() { - Collection toolStr = Config.getPref().getList("toolbar", Arrays.asList(deftoolbar)); + Collection toolStr = Config.getPref().getList(TOOLBAR, Arrays.asList(deftoolbar)); if (Utils.isEmpty(toolStr)) { toolStr = Arrays.asList(deftoolbar); } @@ -1136,7 +1143,7 @@ private Collection getDefinedActions() { * @return The parameter (for better chaining) */ public Action register(Action action) { - String toolbar = (String) action.getValue("toolbar"); + String toolbar = (String) action.getValue(TOOLBAR); if (toolbar == null) { Logging.info(tr("Registered toolbar action without name: {0}", action.getClass().getName())); @@ -1160,7 +1167,7 @@ public Action register(Action action) { * @since 11654 */ public Action unregister(Action action) { - Object toolbar = action.getValue("toolbar"); + Object toolbar = action.getValue(TOOLBAR); if (toolbar instanceof String) { return regactions.remove(toolbar); } @@ -1238,7 +1245,7 @@ public void addCustomButton(String definitionText, int preferredIndex, boolean r t.add(definitionText); // add to the end } } - Config.getPref().putList("toolbar", t); + Config.getPref().putList(TOOLBAR, t); MainApplication.getToolbar().refreshToolbarControl(); } @@ -1268,7 +1275,7 @@ private AbstractButton addButtonAndShortcut(ActionDefinition action) { String tt = Optional.ofNullable(action.getDisplayTooltip()).orElse(""); if (sc == null || paramCode != 0) { - String name = Optional.ofNullable((String) action.getAction().getValue("toolbar")).orElseGet(action::getDisplayName); + String name = Optional.ofNullable((String) action.getAction().getValue(TOOLBAR)).orElseGet(action::getDisplayName); if (paramCode != 0) { name = name+paramCode; } diff --git a/src/org/openstreetmap/josm/gui/preferences/advanced/AbstractTableListEditor.java b/src/org/openstreetmap/josm/gui/preferences/advanced/AbstractTableListEditor.java index 18d1d3336df..3cf42368eb6 100644 --- a/src/org/openstreetmap/josm/gui/preferences/advanced/AbstractTableListEditor.java +++ b/src/org/openstreetmap/josm/gui/preferences/advanced/AbstractTableListEditor.java @@ -17,6 +17,7 @@ import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JToolBar; +import javax.swing.SwingConstants; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.AbstractTableModel; @@ -102,7 +103,7 @@ public void actionPerformed(ActionEvent e) { } } - private class EntryListener implements ListSelectionListener { + private final class EntryListener implements ListSelectionListener { @Override public void valueChanged(ListSelectionEvent e) { TableCellEditor editor = table.getCellEditor(); @@ -131,7 +132,7 @@ protected JPanel build() { JScrollPane scroll = new JScrollPane(entryList); left.add(scroll, GBC.eol().fill()); - JToolBar sideButtonTB = new JToolBar(JToolBar.HORIZONTAL); + JToolBar sideButtonTB = new JToolBar(SwingConstants.HORIZONTAL); sideButtonTB.setBorderPainted(false); sideButtonTB.setOpaque(false); sideButtonTB.add(new NewEntryAction()); diff --git a/src/org/openstreetmap/josm/gui/preferences/advanced/ListListEditor.java b/src/org/openstreetmap/josm/gui/preferences/advanced/ListListEditor.java index 0fea23bdfa6..83560551275 100644 --- a/src/org/openstreetmap/josm/gui/preferences/advanced/ListListEditor.java +++ b/src/org/openstreetmap/josm/gui/preferences/advanced/ListListEditor.java @@ -49,7 +49,7 @@ protected final JPanel build() { return super.build(); } - private class EntryListModel extends AbstractEntryListModel { + private final class EntryListModel extends AbstractEntryListModel { @Override public String getElementAt(int index) { @@ -63,7 +63,7 @@ public int getSize() { @Override public void add() { - data.add(new ArrayList()); + data.add(new ArrayList<>()); fireIntervalAdded(this, getSize() - 1, getSize() - 1); } @@ -74,7 +74,7 @@ public void remove(int idx) { } } - private class ListTableModel extends AbstractTableModel { + private final class ListTableModel extends AbstractTableModel { private List data() { return entryIdx == null ? Collections.emptyList() : data.get(entryIdx); diff --git a/src/org/openstreetmap/josm/gui/preferences/advanced/MapListEditor.java b/src/org/openstreetmap/josm/gui/preferences/advanced/MapListEditor.java index 467b5c2af5f..62f4656d4c4 100644 --- a/src/org/openstreetmap/josm/gui/preferences/advanced/MapListEditor.java +++ b/src/org/openstreetmap/josm/gui/preferences/advanced/MapListEditor.java @@ -77,7 +77,7 @@ protected final JPanel build() { return super.build(); } - private class EntryListModel extends AbstractEntryListModel { + private final class EntryListModel extends AbstractEntryListModel { @Override public String getElementAt(int index) { @@ -91,8 +91,8 @@ public int getSize() { @Override public void add() { - dataKeys.add(new ArrayList()); - dataValues.add(new ArrayList()); + dataKeys.add(new ArrayList<>()); + dataValues.add(new ArrayList<>()); dataLabels.add(""); fireIntervalAdded(this, getSize() - 1, getSize() - 1); } @@ -106,10 +106,10 @@ public void remove(int idx) { } } - private class MapTableModel extends AbstractTableModel { + private final class MapTableModel extends AbstractTableModel { private List> data() { - return entryIdx == null ? Collections.>emptyList() : Arrays.asList(dataKeys.get(entryIdx), dataValues.get(entryIdx)); + return entryIdx == null ? Collections.emptyList() : Arrays.asList(dataKeys.get(entryIdx), dataValues.get(entryIdx)); } private int size() { diff --git a/src/org/openstreetmap/josm/gui/preferences/display/GPXSettingsPanel.java b/src/org/openstreetmap/josm/gui/preferences/display/GPXSettingsPanel.java index c36b1f6e184..625e1f11653 100644 --- a/src/org/openstreetmap/josm/gui/preferences/display/GPXSettingsPanel.java +++ b/src/org/openstreetmap/josm/gui/preferences/display/GPXSettingsPanel.java @@ -292,7 +292,7 @@ public static void putLayerPrefLocal(GpxLayer layer, String key, String value) { public static void putDataPrefLocal(IGpxLayerPrefs data, String key, String value) { if (data == null) return; data.setModified(true); - if (Utils.isBlank(value) || + if (Utils.isStripEmpty(value) || (getLayerPref(null, key).equals(value) && DEFAULT_PREFS.get(key) != null && DEFAULT_PREFS.get(key).toString().equals(value))) { data.getLayerPrefs().remove(key); } else { diff --git a/src/org/openstreetmap/josm/gui/preferences/imagery/CacheSettingsPanel.java b/src/org/openstreetmap/josm/gui/preferences/imagery/CacheSettingsPanel.java index 85d92116c99..ce386ebd8dc 100644 --- a/src/org/openstreetmap/josm/gui/preferences/imagery/CacheSettingsPanel.java +++ b/src/org/openstreetmap/josm/gui/preferences/imagery/CacheSettingsPanel.java @@ -3,6 +3,7 @@ import static org.openstreetmap.josm.tools.I18n.tr; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.io.File; @@ -59,7 +60,7 @@ public class CacheSettingsPanel extends JPanel { private final JosmTextField cacheDir = new JosmTextField(11); private final JSpinner maxElementsOnDisk = new JSpinner(new SpinnerNumberModel( - AbstractCachedTileSourceLayer.MAX_DISK_CACHE_SIZE.get().intValue(), 0, Integer.MAX_VALUE, 1)); + (int) AbstractCachedTileSourceLayer.MAX_DISK_CACHE_SIZE.get(), 0, Integer.MAX_VALUE, 1)); /** * Creates cache content panel @@ -69,7 +70,7 @@ public CacheSettingsPanel() { add(new JLabel(tr("Tile cache directory: ")), GBC.std()); add(GBC.glue(5, 0), GBC.std()); - add(cacheDir, GBC.eol().fill(GBC.HORIZONTAL)); + add(cacheDir, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); add(new JLabel(tr("Maximum size of disk cache (per imagery) in MB: ")), GBC.std()); add(GBC.glue(5, 0), GBC.std()); @@ -93,7 +94,7 @@ private void addToPanel(final CacheAccess cache add(new JLabel(tr("{0} cache, total cache size: {1}", name, sizeString)), GBC.eol().insets(5, 5, 0, 0)); add(new JScrollPane(getTableForCache(cache, tableModel)), - GBC.eol().fill(GBC.BOTH)); + GBC.eol().fill(GridBagConstraints.BOTH)); }); } @@ -211,7 +212,9 @@ boolean saveSettings() { private static boolean removeCacheFiles(String path, long maxSize) { File directory = new File(path); - File[] cacheFiles = directory.listFiles((dir, name) -> name.endsWith(".data") || name.endsWith(".key")); + final String data = ".data"; + final String key = ".key"; + File[] cacheFiles = directory.listFiles((dir, name) -> name.endsWith(data) || name.endsWith(key)); boolean restartRequired = false; if (cacheFiles != null) { for (File cacheFile: cacheFiles) { @@ -222,10 +225,10 @@ private static boolean removeCacheFiles(String path, long maxSize) { } Utils.deleteFile(cacheFile); File otherFile = null; - if (cacheFile.getName().endsWith(".data")) { - otherFile = new File(cacheFile.getPath().replaceAll("\\.data$", ".key")); - } else if (cacheFile.getName().endsWith(".key")) { - otherFile = new File(cacheFile.getPath().replaceAll("\\.key$", ".data")); + if (cacheFile.getName().endsWith(data)) { + otherFile = new File(cacheFile.getPath().replaceAll("\\.data$", key)); + } else if (cacheFile.getName().endsWith(key)) { + otherFile = new File(cacheFile.getPath().replaceAll("\\.key$", data)); } if (otherFile != null) { Utils.deleteFileIfExists(otherFile); diff --git a/src/org/openstreetmap/josm/gui/preferences/imagery/TMSSettingsPanel.java b/src/org/openstreetmap/josm/gui/preferences/imagery/TMSSettingsPanel.java index db22cc116f1..ac04120a767 100644 --- a/src/org/openstreetmap/josm/gui/preferences/imagery/TMSSettingsPanel.java +++ b/src/org/openstreetmap/josm/gui/preferences/imagery/TMSSettingsPanel.java @@ -3,6 +3,7 @@ import static org.openstreetmap.josm.tools.I18n.tr; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import javax.swing.JCheckBox; @@ -39,26 +40,26 @@ public class TMSSettingsPanel extends JPanel { public TMSSettingsPanel() { super(new GridBagLayout()); minZoomLvl = new JSpinner(new SpinnerNumberModel( - Utils.clamp(TMSLayer.PROP_MIN_ZOOM_LVL.get().intValue(), TMSLayer.MIN_ZOOM, TMSLayer.MAX_ZOOM), + Utils.clamp(TMSLayer.PROP_MIN_ZOOM_LVL.get(), TMSLayer.MIN_ZOOM, TMSLayer.MAX_ZOOM), TMSLayer.MIN_ZOOM, TMSLayer.MAX_ZOOM, 1)); maxZoomLvl = new JSpinner(new SpinnerNumberModel( - Utils.clamp(TMSLayer.PROP_MAX_ZOOM_LVL.get().intValue(), TMSLayer.MIN_ZOOM, TMSLayer.MAX_ZOOM), + Utils.clamp(TMSLayer.PROP_MAX_ZOOM_LVL.get(), TMSLayer.MIN_ZOOM, TMSLayer.MAX_ZOOM), TMSLayer.MIN_ZOOM, TMSLayer.MAX_ZOOM, 1)); maxConcurrentDownloads = new JSpinner(new SpinnerNumberModel( - TMSCachedTileLoader.THREAD_LIMIT.get().intValue(), 0, Integer.MAX_VALUE, 1)); + (int) TMSCachedTileLoader.THREAD_LIMIT.get(), 0, Integer.MAX_VALUE, 1)); maxDownloadsPerHost = new JSpinner(new SpinnerNumberModel( - TMSCachedTileLoader.HOST_LIMIT.get().intValue(), 0, Integer.MAX_VALUE, 1)); + (int) TMSCachedTileLoader.HOST_LIMIT.get(), 0, Integer.MAX_VALUE, 1)); add(new JLabel(tr("Auto zoom by default: ")), GBC.std()); add(GBC.glue(5, 0), GBC.std()); - add(autozoomActive, GBC.eol().fill(GBC.HORIZONTAL)); + add(autozoomActive, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); add(new JLabel(tr("Autoload tiles by default: ")), GBC.std()); add(GBC.glue(5, 0), GBC.std()); - add(autoloadTiles, GBC.eol().fill(GBC.HORIZONTAL)); + add(autoloadTiles, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); add(new JLabel(tr("Min. zoom level: ")), GBC.std()); add(GBC.glue(5, 0), GBC.std()); @@ -70,7 +71,7 @@ public TMSSettingsPanel() { add(new JLabel(tr("Add to slippymap chooser: ")), GBC.std()); add(GBC.glue(5, 0), GBC.std()); - add(addToSlippyMapChosser, GBC.eol().fill(GBC.HORIZONTAL)); + add(addToSlippyMapChosser, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); add(new JLabel(tr("Maximum concurrent downloads: ")), GBC.std()); add(GBC.glue(5, 0), GBC.std()); diff --git a/src/org/openstreetmap/josm/gui/preferences/imagery/WMSLayerTree.java b/src/org/openstreetmap/josm/gui/preferences/imagery/WMSLayerTree.java index 850454a92b1..dd79fa1c513 100644 --- a/src/org/openstreetmap/josm/gui/preferences/imagery/WMSLayerTree.java +++ b/src/org/openstreetmap/josm/gui/preferences/imagery/WMSLayerTree.java @@ -106,7 +106,7 @@ public void updateTreeList(Collection layers) { getLayerTree().expandRow(1); } - private static class LayerTreeCellRenderer extends DefaultTreeCellRenderer { + private static final class LayerTreeCellRenderer extends DefaultTreeCellRenderer { @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, @@ -123,7 +123,7 @@ public Component getTreeCellRendererComponent(JTree tree, Object value, } } - private class WMSTreeSelectionListener implements TreeSelectionListener { + private final class WMSTreeSelectionListener implements TreeSelectionListener { @Override public void valueChanged(TreeSelectionEvent e) { diff --git a/src/org/openstreetmap/josm/gui/preferences/map/TaggingPresetPreference.java b/src/org/openstreetmap/josm/gui/preferences/map/TaggingPresetPreference.java index 36bee85258c..74679759695 100644 --- a/src/org/openstreetmap/josm/gui/preferences/map/TaggingPresetPreference.java +++ b/src/org/openstreetmap/josm/gui/preferences/map/TaggingPresetPreference.java @@ -4,6 +4,7 @@ import static org.openstreetmap.josm.tools.I18n.marktr; import static org.openstreetmap.josm.tools.I18n.tr; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.io.IOException; import java.util.ArrayList; @@ -181,15 +182,21 @@ public void addGui(PreferenceTabbedPane gui) { panel.add(sortMenu, GBC.eol().insets(5, 0, 5, 0)); sources = new TaggingPresetSourceEditor(); - panel.add(sources, GBC.eol().fill(GBC.BOTH)); + panel.add(sources, GBC.eol().fill(GridBagConstraints.BOTH)); PreferencePanel preferencePanel = gui.createPreferenceTab(this); - preferencePanel.add(panel, GBC.eol().fill(GBC.BOTH)); + preferencePanel.add(panel, GBC.eol().fill(GridBagConstraints.BOTH)); sources.deferLoading(gui, preferencePanel); gui.addValidationListener(validationListener); } + /** + * An editor for what preset source locations are used + */ public static class TaggingPresetSourceEditor extends SourceEditor { + /** + * Create a new {@link TaggingPresetSourceEditor} with the default providers + */ public TaggingPresetSourceEditor() { super(SourceType.TAGGING_PRESET, Config.getUrls().getJOSMWebsite()+"/presets", presetSourceProviders, true); } diff --git a/src/org/openstreetmap/josm/gui/preferences/plugin/PluginListPanel.java b/src/org/openstreetmap/josm/gui/preferences/plugin/PluginListPanel.java index aa1537fe3e3..b7cbd20bbd8 100644 --- a/src/org/openstreetmap/josm/gui/preferences/plugin/PluginListPanel.java +++ b/src/org/openstreetmap/josm/gui/preferences/plugin/PluginListPanel.java @@ -63,7 +63,7 @@ public PluginListPanel(PluginPreferencesModel model) { protected static String formatPluginRemoteVersion(PluginInformation pi) { StringBuilder sb = new StringBuilder(); - if (Utils.isBlank(pi.version)) { + if (Utils.isStripEmpty(pi.version)) { sb.append(tr("unknown")); } else { sb.append(pi.version); @@ -77,7 +77,7 @@ protected static String formatPluginRemoteVersion(PluginInformation pi) { protected static String formatPluginLocalVersion(PluginInformation pi) { if (pi == null) return tr("unknown"); - if (Utils.isBlank(pi.localversion)) + if (Utils.isStripEmpty(pi.localversion)) return tr("unknown"); return pi.localversion; } diff --git a/src/org/openstreetmap/josm/gui/preferences/plugin/PluginPreference.java b/src/org/openstreetmap/josm/gui/preferences/plugin/PluginPreference.java index 5c197ef85fa..83cd17e93f9 100644 --- a/src/org/openstreetmap/josm/gui/preferences/plugin/PluginPreference.java +++ b/src/org/openstreetmap/josm/gui/preferences/plugin/PluginPreference.java @@ -2,6 +2,7 @@ package org.openstreetmap.josm.gui.preferences.plugin; import static java.awt.GridBagConstraints.HORIZONTAL; +import static org.openstreetmap.josm.tools.I18n.marktr; import static org.openstreetmap.josm.tools.I18n.tr; import static org.openstreetmap.josm.tools.I18n.trc; import static org.openstreetmap.josm.tools.I18n.trn; @@ -70,6 +71,9 @@ * @since 168 */ public final class PluginPreference extends ExtensibleTabPreferenceSetting { + private static final String HTML_START = ""; + private static final String HTML_END = ""; + private static final String UPDATE_PLUGINS = marktr("Update plugins"); /** * Factory used to create a new {@code PluginPreference}. @@ -111,8 +115,8 @@ public static String buildDownloadSummary(PluginDownloadTask task) { "The following {0} plugins have been downloaded successfully:", downloaded.size(), downloaded.size() - )); - sb.append("

        "); + )) + .append("
          "); for (PluginInformation pi: downloaded) { sb.append("
        • ").append(pi.name).append(" (").append(pi.version).append(")
        • "); } @@ -124,8 +128,8 @@ public static String buildDownloadSummary(PluginDownloadTask task) { "Downloading the following {0} plugins has failed:", failed.size(), failed.size() - )); - sb.append("
            "); + )) + .append("
              "); for (PluginInformation pi: failed) { sb.append("
            • ").append(pi.name).append("
            • "); } @@ -148,16 +152,16 @@ public static String buildDownloadSummary(PluginDownloadTask task) { public static void notifyDownloadResults(final Component parent, PluginDownloadTask task, boolean restartRequired) { final Collection failed = task.getFailedPlugins(); final StringBuilder sb = new StringBuilder(); - sb.append("") + sb.append(HTML_START) .append(buildDownloadSummary(task)); if (restartRequired) { sb.append(tr("Please restart JOSM to activate the downloaded plugins.")); } - sb.append(""); + sb.append(HTML_END); GuiHelper.runInEDTAndWait(() -> HelpAwareOptionPane.showOptionDialog( parent, sb.toString(), - tr("Update plugins"), + tr(UPDATE_PLUGINS), !failed.isEmpty() ? JOptionPane.WARNING_MESSAGE : JOptionPane.INFORMATION_MESSAGE, HelpUtil.ht("/Preferences/Plugins") )); @@ -374,7 +378,7 @@ public void actionPerformed(ActionEvent e) { */ class UpdateSelectedPluginsAction extends AbstractAction { UpdateSelectedPluginsAction() { - putValue(NAME, tr("Update plugins")); + putValue(NAME, tr(UPDATE_PLUGINS)); putValue(SHORT_DESCRIPTION, tr("Update the selected plugins")); new ImageProvider("dialogs", "refresh").getResource().attachImageIcon(this); } @@ -387,8 +391,11 @@ protected void alertNothingToUpdate() { tr("Plugins up to date"), JOptionPane.INFORMATION_MESSAGE, null // FIXME: provide help context - )); - } catch (InterruptedException | InvocationTargetException e) { + )); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + Logging.error(e); + } catch (InvocationTargetException e) { Logging.error(e); } } @@ -400,7 +407,7 @@ public void actionPerformed(ActionEvent e) { final PluginDownloadTask pluginDownloadTask = new PluginDownloadTask( pnlPluginPreferences, toUpdate, - tr("Update plugins") + tr(UPDATE_PLUGINS) ); // the async task for downloading plugin information final ReadRemotePluginInformationTask pluginInfoDownloadTask = new ReadRemotePluginInformationTask( @@ -443,7 +450,7 @@ public void actionPerformed(ActionEvent e) { if (!PluginHandler.checkRequiredPluginsPreconditions(null, enabledPlugins, pi, false)) { // Time to find the missing plugins... toAdd.addAll(pi.getRequiredPlugins().stream().filter(plugin -> PluginHandler.getPlugin(plugin) == null) - .map(plugin -> model.getPluginInformation(plugin)) + .map(model::getPluginInformation) .collect(Collectors.toSet())); } } @@ -501,10 +508,10 @@ public void actionPerformed(ActionEvent e) { JTextArea textField = new JTextArea(10, 0); JCheckBox deleteNotInList = new JCheckBox(tr("Disable all other plugins")); - JLabel helpLabel = new JLabel("" + String.join("
              ", + JLabel helpLabel = new JLabel(HTML_START + String.join("
              ", tr("Enter a list of plugins you want to download."), tr("You should add one plugin id per line, version information is ignored."), - tr("You can copy+paste the list of a status report here.")) + ""); + tr("You can copy+paste the list of a status report here.")) + HTML_END); if (JOptionPane.OK_OPTION == JOptionPane.showConfirmDialog(GuiHelper.getFrameForComponent(getTabPane()), new Object[] {helpLabel, new JScrollPane(textField), deleteNotInList}, @@ -555,7 +562,7 @@ private void activatePlugins(List toActivate, boolean deleteNotInList) { private boolean confirmIgnoreNotFound(List notFound) { String list = "
              • " + String.join("
              • ", notFound) + "
              "; - String message = "" + tr("The following plugins were not found. Continue anyway?") + list + ""; + String message = HTML_START + tr("The following plugins were not found. Continue anyway?") + list + HTML_END; return JOptionPane.showConfirmDialog(GuiHelper.getFrameForComponent(getTabPane()), message) == JOptionPane.OK_OPTION; } diff --git a/src/org/openstreetmap/josm/gui/preferences/projection/CodeSelectionPanel.java b/src/org/openstreetmap/josm/gui/preferences/projection/CodeSelectionPanel.java index 6e3f6aac3fe..d128df8f625 100644 --- a/src/org/openstreetmap/josm/gui/preferences/projection/CodeSelectionPanel.java +++ b/src/org/openstreetmap/josm/gui/preferences/projection/CodeSelectionPanel.java @@ -4,6 +4,7 @@ import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.Dimension; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionListener; import java.util.ArrayList; @@ -59,7 +60,7 @@ public CodeSelectionPanel(String initialCode, ActionListener listener) { /** * List model for the filtered view on the list of all codes. */ - private class ProjectionCodeModel extends AbstractTableModel { + private final class ProjectionCodeModel extends AbstractTableModel { @Override public int getRowCount() { return filteredData.size(); @@ -104,8 +105,8 @@ private void build() { scroll.setPreferredSize(new Dimension(200, 214)); this.setLayout(new GridBagLayout()); - this.add(filter, GBC.eol().fill(GBC.HORIZONTAL).weight(1.0, 0.0)); - this.add(scroll, GBC.eol().fill(GBC.HORIZONTAL)); + this.add(filter, GBC.eol().fill(GridBagConstraints.HORIZONTAL).weight(1.0, 0.0)); + this.add(scroll, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); } /** diff --git a/src/org/openstreetmap/josm/gui/preferences/projection/CustomProjectionChoice.java b/src/org/openstreetmap/josm/gui/preferences/projection/CustomProjectionChoice.java index d772dc99507..bc6937e04ed 100644 --- a/src/org/openstreetmap/josm/gui/preferences/projection/CustomProjectionChoice.java +++ b/src/org/openstreetmap/josm/gui/preferences/projection/CustomProjectionChoice.java @@ -4,6 +4,7 @@ import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.BorderLayout; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionListener; @@ -119,25 +120,31 @@ public boolean isValid() { this.setLayout(new GridBagLayout()); JPanel p2 = new JPanel(new GridBagLayout()); - p2.add(cbInput, GBC.std().fill(GBC.HORIZONTAL).insets(0, 20, 5, 5)); + p2.add(cbInput, GBC.std().fill(GridBagConstraints.HORIZONTAL).insets(0, 20, 5, 5)); p2.add(btnCheck, GBC.eol().insets(0, 20, 0, 5)); - this.add(p2, GBC.eol().fill(GBC.HORIZONTAL)); + this.add(p2, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); p2 = new JPanel(new GridBagLayout()); - p2.add(valStatus, GBC.std().anchor(GBC.WEST).weight(0.0001, 0)); - p2.add(errorsPanel, GBC.eol().fill(GBC.HORIZONTAL)); - this.add(p2, GBC.eol().fill(GBC.HORIZONTAL)); + p2.add(valStatus, GBC.std().anchor(GridBagConstraints.WEST).weight(0.0001, 0)); + p2.add(errorsPanel, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); + this.add(p2, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); p2 = new JPanel(new GridBagLayout()); p2.add(btnInfo, GBC.std().insets(0, 20, 0, 0)); - p2.add(GBC.glue(1, 0), GBC.eol().fill(GBC.HORIZONTAL)); - this.add(p2, GBC.eol().fill(GBC.HORIZONTAL)); + p2.add(GBC.glue(1, 0), GBC.eol().fill(GridBagConstraints.HORIZONTAL)); + this.add(p2, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); } + /** + * Remember the current input + */ public void rememberHistory() { cbInput.addCurrentItemToHistory(); cbInput.getModel().prefs().save("projection.custom.value.history"); } } + /** + * A dialog for the available parameters of the custom projection + */ public static class ParameterInfoDialog extends ExtendedDialog { /** @@ -211,9 +218,9 @@ public Collection getPreferences(JPanel panel) { throw new IllegalArgumentException("Unsupported panel: "+panel); } PreferencePanel prefPanel = (PreferencePanel) panel; - String pref = prefPanel.cbInput.getEditorComponent().getText(); + String savePreferences = prefPanel.cbInput.getEditorComponent().getText(); prefPanel.rememberHistory(); - return Collections.singleton(pref); + return Collections.singleton(savePreferences); } @Override diff --git a/src/org/openstreetmap/josm/gui/preferences/projection/ProjectionPreference.java b/src/org/openstreetmap/josm/gui/preferences/projection/ProjectionPreference.java index 93c1be6e9b3..70affbfa1cf 100644 --- a/src/org/openstreetmap/josm/gui/preferences/projection/ProjectionPreference.java +++ b/src/org/openstreetmap/josm/gui/preferences/projection/ProjectionPreference.java @@ -97,7 +97,7 @@ public PreferenceSetting createPreferenceSetting() { /** * Lambert conic conform 4 zones using the French geodetic system NTF. *

              - * This newer version uses the grid translation NTF<->RGF93 provided by IGN for a submillimetric accuracy. + * This newer version uses the grid translation NTF ⟷ RGF93 provided by IGN for a submillimetric accuracy. * (RGF93 is the French geodetic system similar to WGS84 but not mathematically equal) *

              * Source: Changement_systeme_geodesique.pdf diff --git a/src/org/openstreetmap/josm/gui/preferences/server/AuthenticationPreferencesPanel.java b/src/org/openstreetmap/josm/gui/preferences/server/AuthenticationPreferencesPanel.java index 898aa62b803..f2792c6d436 100644 --- a/src/org/openstreetmap/josm/gui/preferences/server/AuthenticationPreferencesPanel.java +++ b/src/org/openstreetmap/josm/gui/preferences/server/AuthenticationPreferencesPanel.java @@ -17,7 +17,6 @@ import javax.swing.JPanel; import javax.swing.JRadioButton; -import org.openstreetmap.josm.actions.ExpertToggleAction; import org.openstreetmap.josm.data.UserIdentityManager; import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder; import org.openstreetmap.josm.data.oauth.OAuthVersion; @@ -38,29 +37,15 @@ public class AuthenticationPreferencesPanel extends VerticallyScrollablePanel im /** indicates whether we use basic authentication */ private final JRadioButton rbBasicAuthentication = new JRadioButton(); - /** indicates whether we use OAuth 1.0a as authentication scheme */ - private final JRadioButton rbOAuth = new JRadioButton(); /** indicates whether we use OAuth 2.0 as authentication scheme */ private final JRadioButton rbOAuth20 = new JRadioButton(); /** the panel which contains the authentication parameters for the respective authentication scheme */ private final JPanel pnlAuthenticationParameters = new JPanel(new BorderLayout()); /** the panel for the basic authentication parameters */ private BasicAuthenticationPreferencesPanel pnlBasicAuthPreferences; - /** the panel for the OAuth 1.0a authentication parameters */ - private OAuthAuthenticationPreferencesPanel pnlOAuthPreferences; /** the panel for the OAuth 2.0 authentication parameters */ private OAuthAuthenticationPreferencesPanel pnlOAuth20Preferences; - /** Used to determine which API we are using for disabling/enabling Basic Auth/OAuth 1.0a */ - private String apiUrl = OsmApi.getOsmApi().getServerUrl(); - /** ExpertToggleAction uses weak references; we don't want this listener to be garbage collected */ - private final ExpertToggleAction.ExpertModeChangeListener expertModeChangeListener = isExpert -> { - final String authMethod = OsmApi.getAuthMethod(); - final boolean defaultApi = JosmUrls.getInstance().getDefaultOsmApiUrl().equals(apiUrl); - rbBasicAuthentication.setEnabled(rbBasicAuthentication.isSelected() || "basic".equals(authMethod) || isExpert || !defaultApi); - rbOAuth.setEnabled(rbOAuth.isSelected() || "oauth".equals(authMethod) || isExpert || !defaultApi); - }; - /** * Constructs a new {@code AuthenticationPreferencesPanel}. */ @@ -84,15 +69,9 @@ protected final void build() { rbBasicAuthentication.setText(tr("Use Basic Authentication")); rbBasicAuthentication.setToolTipText(tr("Select to use HTTP basic authentication with your OSM username and password")); rbBasicAuthentication.addItemListener(authChangeListener); - - //-- radio button for OAuth 1.0a - buttonPanel.add(rbOAuth); - rbOAuth.setText(tr("Use OAuth {0}", "1.0a")); - rbOAuth.setToolTipText(tr("Select to use OAuth {0} as authentication mechanism", "1.0a")); - rbOAuth.addItemListener(authChangeListener); - //-- radio button for OAuth 2.0 buttonPanel.add(rbOAuth20); + rbOAuth20.setSelected(true); // This must before adding the listener; otherwise, saveToPreferences is called prior to initFromPreferences rbOAuth20.setText(tr("Use OAuth {0}", "2.0")); rbOAuth20.setToolTipText(tr("Select to use OAuth {0} as authentication mechanism", "2.0")); rbOAuth20.addItemListener(authChangeListener); @@ -101,7 +80,6 @@ protected final void build() { //-- radio button for OAuth ButtonGroup bg = new ButtonGroup(); bg.add(rbBasicAuthentication); - bg.add(rbOAuth); bg.add(rbOAuth20); //-- add the panel which will hold the authentication parameters @@ -118,13 +96,10 @@ protected final void build() { //-- the two panels for authentication parameters pnlBasicAuthPreferences = new BasicAuthenticationPreferencesPanel(); - pnlOAuthPreferences = new OAuthAuthenticationPreferencesPanel(OAuthVersion.OAuth10a); pnlOAuth20Preferences = new OAuthAuthenticationPreferencesPanel(OAuthVersion.OAuth20); - ExpertToggleAction.addExpertModeChangeListener(expertModeChangeListener, true); - - rbOAuth20.setSelected(true); pnlAuthenticationParameters.add(pnlOAuth20Preferences, BorderLayout.CENTER); + this.updateAcceptableAuthenticationMethods(OsmApi.getOsmApi().getServerUrl()); } /** @@ -132,23 +107,17 @@ protected final void build() { */ public final void initFromPreferences() { final String authMethod = OsmApi.getAuthMethod(); - switch (authMethod) { - case "basic": - rbBasicAuthentication.setSelected(true); - break; - case "oauth": - rbOAuth.setSelected(true); - break; - case "oauth20": - rbOAuth20.setSelected(true); - break; - default: - Logging.warn(tr("Unsupported value in preference ''{0}'', got ''{1}''. Using authentication method ''Basic Authentication''.", - "osm-server.auth-method", authMethod)); - rbBasicAuthentication.setSelected(true); + if ("basic".equals(authMethod)) { + rbBasicAuthentication.setSelected(true); + } else if ("oauth20".equals(authMethod)) { + rbOAuth20.setSelected(true); + } else { + Logging.warn( + tr("Unsupported value in preference ''{0}'', got ''{1}''. Using authentication method ''OAuth 2.0 Authentication''.", + "osm-server.auth-method", authMethod)); + rbOAuth20.setSelected(true); } pnlBasicAuthPreferences.initFromPreferences(); - pnlOAuthPreferences.initFromPreferences(); pnlOAuth20Preferences.initFromPreferences(); } @@ -160,8 +129,6 @@ public final void saveToPreferences() { String authMethod; if (rbBasicAuthentication.isSelected()) { authMethod = "basic"; - } else if (rbOAuth.isSelected()) { - authMethod = "oauth"; } else if (rbOAuth20.isSelected()) { authMethod = "oauth20"; } else { @@ -173,25 +140,21 @@ public final void saveToPreferences() { pnlBasicAuthPreferences.saveToPreferences(); OAuthAccessTokenHolder.getInstance().clear(); OAuthAccessTokenHolder.getInstance().save(CredentialsManager.getInstance()); - } else if ("oauth".equals(authMethod)) { - // clear the password in the preferences - pnlBasicAuthPreferences.clearPassword(); - pnlBasicAuthPreferences.saveToPreferences(); - pnlOAuthPreferences.saveToPreferences(); - } else { // oauth20 + } else if ("oauth20".equals(authMethod)) { + // oauth20 // clear the password in the preferences pnlBasicAuthPreferences.clearPassword(); - pnlBasicAuthPreferences.saveToPreferences(); pnlOAuth20Preferences.saveToPreferences(); } if (initUser) { if ("basic".equals(authMethod)) { UserIdentityManager.getInstance().initFromPreferences(); - } else { + } else if (OsmApi.isUsingOAuthAndOAuthSetUp(OsmApi.getOsmApi())) { UserIdentityManager.getInstance().initFromOAuth(); + } else { + UserIdentityManager.getInstance().setAnonymous(); } } - ExpertToggleAction.removeExpertModeChangeListener(this.expertModeChangeListener); } /** @@ -204,11 +167,6 @@ public void itemStateChanged(ItemEvent e) { if (rbBasicAuthentication.isSelected()) { pnlAuthenticationParameters.add(pnlBasicAuthPreferences, BorderLayout.CENTER); pnlBasicAuthPreferences.revalidate(); - } else if (rbOAuth.isSelected()) { - pnlAuthenticationParameters.add(pnlOAuthPreferences, BorderLayout.CENTER); - pnlOAuthPreferences.saveToPreferences(); - pnlOAuthPreferences.initFromPreferences(); - pnlOAuthPreferences.revalidate(); } else if (rbOAuth20.isSelected()) { pnlAuthenticationParameters.add(pnlOAuth20Preferences, BorderLayout.CENTER); pnlOAuth20Preferences.saveToPreferences(); @@ -221,15 +179,22 @@ public void itemStateChanged(ItemEvent e) { @Override public void propertyChange(PropertyChangeEvent evt) { - if (pnlOAuthPreferences != null) { - pnlOAuthPreferences.propertyChange(evt); - } if (pnlOAuth20Preferences != null) { pnlOAuth20Preferences.propertyChange(evt); } if (OsmApiUrlInputPanel.API_URL_PROP.equals(evt.getPropertyName())) { - this.apiUrl = (String) evt.getNewValue(); - this.expertModeChangeListener.expertChanged(ExpertToggleAction.isExpert()); + this.updateAcceptableAuthenticationMethods((String) evt.getNewValue()); } } + + /** + * Update the acceptable authentications methods + * @param apiUrl The API url to check + */ + private void updateAcceptableAuthenticationMethods(String apiUrl) { + final String authMethod = OsmApi.getAuthMethod(); + final boolean defaultApi = JosmUrls.getInstance().getDefaultOsmApiUrl().equals(apiUrl); + rbBasicAuthentication.setEnabled(rbBasicAuthentication.isSelected() || "basic".equals(authMethod) || !defaultApi); + } + } diff --git a/src/org/openstreetmap/josm/gui/preferences/server/OAuthAuthenticationPreferencesPanel.java b/src/org/openstreetmap/josm/gui/preferences/server/OAuthAuthenticationPreferencesPanel.java index ec905061414..dde4ca262f9 100644 --- a/src/org/openstreetmap/josm/gui/preferences/server/OAuthAuthenticationPreferencesPanel.java +++ b/src/org/openstreetmap/josm/gui/preferences/server/OAuthAuthenticationPreferencesPanel.java @@ -5,6 +5,7 @@ import java.awt.BorderLayout; import java.awt.Color; +import java.awt.Component; import java.awt.FlowLayout; import java.awt.Font; import java.awt.GridBagConstraints; @@ -14,24 +15,25 @@ import java.awt.event.ItemEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.Objects; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JLabel; -import javax.swing.JOptionPane; import javax.swing.JPanel; import org.openstreetmap.josm.actions.ExpertToggleAction; import org.openstreetmap.josm.data.oauth.IOAuthToken; -import org.openstreetmap.josm.data.oauth.OAuth20Authorization; import org.openstreetmap.josm.data.oauth.OAuth20Token; import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder; import org.openstreetmap.josm.data.oauth.OAuthParameters; -import org.openstreetmap.josm.data.oauth.OAuthToken; import org.openstreetmap.josm.data.oauth.OAuthVersion; -import org.openstreetmap.josm.data.oauth.osm.OsmScopes; +import org.openstreetmap.josm.data.validation.routines.DomainValidator; import org.openstreetmap.josm.gui.MainApplication; import org.openstreetmap.josm.gui.oauth.AdvancedOAuthPropertiesPanel; import org.openstreetmap.josm.gui.oauth.AuthorizationProcedure; @@ -42,11 +44,11 @@ import org.openstreetmap.josm.gui.widgets.JosmTextField; import org.openstreetmap.josm.io.OsmApi; import org.openstreetmap.josm.io.auth.CredentialsManager; -import org.openstreetmap.josm.io.remotecontrol.RemoteControl; import org.openstreetmap.josm.tools.GBC; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.Logging; import org.openstreetmap.josm.tools.UserCancelException; +import org.openstreetmap.josm.tools.Utils; /** * The preferences panel for the OAuth 1.0a preferences. This just a summary panel @@ -67,13 +69,6 @@ public class OAuthAuthenticationPreferencesPanel extends JPanel implements Prope private final OAuthVersion oAuthVersion; private String apiUrl; - /** - * Create the panel. Uses {@link OAuthVersion#OAuth10a}. - */ - public OAuthAuthenticationPreferencesPanel() { - this(OAuthVersion.OAuth10a); - } - /** * Create the panel. * @param oAuthVersion The OAuth version to use @@ -81,8 +76,8 @@ public OAuthAuthenticationPreferencesPanel() { public OAuthAuthenticationPreferencesPanel(OAuthVersion oAuthVersion) { this.oAuthVersion = oAuthVersion; // These must come after we set the oauth version - this.pnlNotYetAuthorised = new NotYetAuthorisedPanel(); this.pnlAdvancedProperties = new AdvancedOAuthPropertiesPanel(this.oAuthVersion); + this.pnlNotYetAuthorised = new NotYetAuthorisedPanel(); this.pnlAlreadyAuthorised = new AlreadyAuthorisedPanel(); build(); } @@ -123,24 +118,18 @@ protected JPanel buildAdvancedPropertiesPanel() { protected final void build() { setLayout(new GridBagLayout()); setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - GridBagConstraints gc = new GridBagConstraints(); - // the panel for the OAuth parameters. pnlAuthorisationMessage is an // empty panel. It is going to be filled later, depending on the // current OAuth state in JOSM. - gc.fill = GridBagConstraints.BOTH; - gc.anchor = GridBagConstraints.NORTHWEST; - gc.weighty = 1.0; - gc.weightx = 1.0; - gc.insets = new Insets(10, 0, 0, 0); - add(pnlAuthorisationMessage, gc); + add(pnlAuthorisationMessage, GBC.eol().fill(GridBagConstraints.BOTH).anchor(GridBagConstraints.NORTHWEST) + .weight(1, 1).insets(0, 10, 0, 0)); + // the panel with the advanced options + add(buildAdvancedPropertiesPanel(), GBC.eol().fill(GridBagConstraints.HORIZONTAL)); } protected void refreshView() { pnlAuthorisationMessage.removeAll(); - if ((this.oAuthVersion == OAuthVersion.OAuth10a && - OAuthAccessTokenHolder.getInstance().containsAccessToken()) - || OAuthAccessTokenHolder.getInstance().getAccessToken(this.apiUrl, this.oAuthVersion) != null) { + if (OAuthAccessTokenHolder.getInstance().getAccessToken(this.apiUrl, this.oAuthVersion) != null) { pnlAuthorisationMessage.add(pnlAlreadyAuthorised, BorderLayout.CENTER); pnlAlreadyAuthorised.refreshView(); pnlAlreadyAuthorised.revalidate(); @@ -159,6 +148,13 @@ protected void refreshView() { public void setApiUrl(String apiUrl) { this.apiUrl = apiUrl; pnlAdvancedProperties.setApiUrl(apiUrl); + for (JPanel panel : Arrays.asList(this.pnlNotYetAuthorised, (JPanel) this.pnlAlreadyAuthorised.getComponent(6))) { + for (Component component : panel.getComponents()) { + if (component instanceof JButton && ((JButton) component).getAction() instanceof AuthoriseNowAction) { + ((AuthoriseNowAction) ((JButton) component).getAction()).updateEnabledState(); + } + } + } } /** @@ -203,15 +199,10 @@ protected void build() { lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN)); // Action for authorising now - if (oAuthVersion == OAuthVersion.OAuth10a) { - add(new JButton(new AuthoriseNowAction(AuthorizationProcedure.FULLY_AUTOMATIC)), GBC.eol()); - } - add(new JButton(new AuthoriseNowAction(AuthorizationProcedure.SEMI_AUTOMATIC)), GBC.eol()); - if (oAuthVersion == OAuthVersion.OAuth10a) { - JButton authManually = new JButton(new AuthoriseNowAction(AuthorizationProcedure.MANUALLY)); - add(authManually, GBC.eol()); - ExpertToggleAction.addVisibilitySwitcher(authManually); - } + add(new JButton(new AuthoriseNowAction(AuthorizationProcedure.FULLY_AUTOMATIC)), GBC.eol()); + JButton authManually = new JButton(new AuthoriseNowAction(AuthorizationProcedure.MANUALLY)); + add(authManually, GBC.eol()); + ExpertToggleAction.addVisibilitySwitcher(authManually); // filler - grab remaining space add(new JPanel(), GBC.std().fill(GBC.BOTH)); @@ -224,7 +215,6 @@ protected void build() { */ private class AlreadyAuthorisedPanel extends JPanel { private final JosmTextField tfAccessTokenKey = new JosmTextField(null, null, 0, false); - private final JosmTextField tfAccessTokenSecret = new JosmTextField(null, null, 0, false); /** * Constructs a new {@code AlreadyAuthorisedPanel}. @@ -265,11 +255,6 @@ protected void build() { gc.weightx = 0.0; add(new JLabel(tr("Access Token Secret:")), gc); - gc.gridx = 1; - gc.weightx = 1.0; - add(tfAccessTokenSecret, gc); - tfAccessTokenSecret.setEditable(false); - // -- access token secret gc.gridy = 3; gc.gridx = 0; @@ -280,11 +265,8 @@ protected void build() { // -- action buttons JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT)); - if (oAuthVersion == OAuthVersion.OAuth10a) { - // these want the OAuth 1.0 token information - btns.add(new JButton(new RenewAuthorisationAction(AuthorizationProcedure.FULLY_AUTOMATIC))); - } - btns.add(new JButton(new TestAuthorisationAction(oAuthVersion))); + btns.add(new JButton(new RenewAuthorisationAction(AuthorizationProcedure.FULLY_AUTOMATIC))); + btns.add(new JButton(new TestAuthorisationAction())); btns.add(new JButton(new RemoveAuthorisationAction())); gc.gridy = 4; gc.gridx = 0; @@ -292,13 +274,6 @@ protected void build() { gc.weightx = 1.0; add(btns, gc); - // the panel with the advanced options - gc.gridy = 5; - gc.gridx = 0; - gc.gridwidth = 2; - gc.weightx = 1.0; - add(buildAdvancedPropertiesPanel(), gc); - // filler - grab the remaining space gc.gridy = 6; gc.fill = GridBagConstraints.BOTH; @@ -309,13 +284,6 @@ protected void build() { protected final void refreshView() { switch (oAuthVersion) { - case OAuth10a: - String v = OAuthAccessTokenHolder.getInstance().getAccessTokenKey(); - tfAccessTokenKey.setText(v == null ? "" : v); - v = OAuthAccessTokenHolder.getInstance().getAccessTokenSecret(); - tfAccessTokenSecret.setText(v == null ? "" : v); - tfAccessTokenSecret.setVisible(true); - break; case OAuth20: case OAuth21: String token = ""; @@ -324,7 +292,8 @@ protected final void refreshView() { token = bearerToken == null ? "" : bearerToken.getBearerToken(); } tfAccessTokenKey.setText(token == null ? "" : token); - tfAccessTokenSecret.setVisible(false); + break; + default: } cbSaveToPreferences.setSelected(OAuthAccessTokenHolder.getInstance().isSaveToPreferences()); } @@ -340,52 +309,55 @@ private class AuthoriseNowAction extends AbstractAction { this.procedure = procedure; putValue(NAME, tr("{0} ({1})", tr("Authorize now"), procedure.getText())); putValue(SHORT_DESCRIPTION, procedure.getDescription()); - if (procedure == AuthorizationProcedure.FULLY_AUTOMATIC - || OAuthAuthenticationPreferencesPanel.this.oAuthVersion != OAuthVersion.OAuth10a) { + if (procedure == AuthorizationProcedure.FULLY_AUTOMATIC) { new ImageProvider("oauth", "oauth-small").getResource().attachImageIcon(this); } + updateEnabledState(); } - @Override - public void actionPerformed(ActionEvent arg0) { - if (OAuthAuthenticationPreferencesPanel.this.oAuthVersion == OAuthVersion.OAuth10a) { - OAuthAuthorizationWizard wizard = new OAuthAuthorizationWizard( - OAuthAuthenticationPreferencesPanel.this, - procedure, - apiUrl, - MainApplication.worker); + void updateEnabledState() { + if (procedure == AuthorizationProcedure.MANUALLY) { + this.setEnabled(true); + } else if (Utils.isValidUrl(apiUrl)) { + final URI apiURI; try { - wizard.showDialog(); - } catch (UserCancelException ignore) { - Logging.trace(ignore); + apiURI = new URI(apiUrl); + } catch (URISyntaxException e) { + Logging.trace(e); return; } - pnlAdvancedProperties.setAdvancedParameters(wizard.getOAuthParameters()); - refreshView(); - } else { - final boolean remoteControlIsRunning = Boolean.TRUE.equals(RemoteControl.PROP_REMOTECONTROL_ENABLED.get()); - // TODO: Ask user if they want to start remote control? - if (!remoteControlIsRunning) { - RemoteControl.start(); + if (DomainValidator.getInstance().isValid(apiURI.getHost())) { + // We want to avoid trying to make connection with an invalid URL + final String currentApiUrl = apiUrl; + MainApplication.worker.execute(() -> { + final String clientId = OAuthParameters.createFromApiUrl(apiUrl, oAuthVersion).getClientId(); + if (Objects.equals(apiUrl, currentApiUrl)) { + GuiHelper.runInEDT(() -> this.setEnabled(!Utils.isEmpty(clientId))); + } + }); } - new OAuth20Authorization().authorize(OAuthParameters.createDefault(OsmApi.getOsmApi().getServerUrl(), oAuthVersion), token -> { - if (!remoteControlIsRunning) { - RemoteControl.stop(); - } - OAuthAccessTokenHolder.getInstance().setAccessToken(OsmApi.getOsmApi().getServerUrl(), token.orElse(null)); - OAuthAccessTokenHolder.getInstance().save(CredentialsManager.getInstance()); - GuiHelper.runInEDT(OAuthAuthenticationPreferencesPanel.this::refreshView); - if (!token.isPresent()) { - GuiHelper.runInEDT(() -> JOptionPane.showMessageDialog(MainApplication.getMainPanel(), - tr("Authentication failed, please check browser for details."), - tr("OAuth Authentication Failed"), - JOptionPane.ERROR_MESSAGE)); - } - }, OsmScopes.read_gpx, OsmScopes.write_gpx, - OsmScopes.read_prefs, OsmScopes.write_prefs, - OsmScopes.write_api, OsmScopes.write_notes); } } + + @Override + public void actionPerformed(ActionEvent arg0) { + OAuthAuthorizationWizard wizard = new OAuthAuthorizationWizard( + OAuthAuthenticationPreferencesPanel.this, + procedure, + apiUrl, + MainApplication.worker, + oAuthVersion, + pnlAdvancedProperties.getAdvancedParameters() + ); + try { + wizard.showDialog(token -> GuiHelper.runInEDT(OAuthAuthenticationPreferencesPanel.this::refreshView)); + } catch (UserCancelException userCancelException) { + Logging.trace(userCancelException); + return; + } + pnlAdvancedProperties.setAdvancedParameters(wizard.getOAuthParameters()); + refreshView(); + } } /** @@ -400,11 +372,7 @@ private class RemoveAuthorisationAction extends AbstractAction { @Override public void actionPerformed(ActionEvent e) { - if (oAuthVersion == OAuthVersion.OAuth10a) { - OAuthAccessTokenHolder.getInstance().setAccessToken(null); - } else { - OAuthAccessTokenHolder.getInstance().setAccessToken(apiUrl, (IOAuthToken) null); - } + OAuthAccessTokenHolder.getInstance().setAccessToken(apiUrl, null); OAuthAccessTokenHolder.getInstance().save(CredentialsManager.getInstance()); refreshView(); } @@ -429,13 +397,10 @@ private class RenewAuthorisationAction extends AuthoriseNowAction { * Runs a test whether we can access the OSM server with the current Access Token */ private class TestAuthorisationAction extends AbstractAction { - private final OAuthVersion oAuthVersion; - /** * Constructs a new {@code TestAuthorisationAction}. */ - TestAuthorisationAction(OAuthVersion oAuthVersion) { - this.oAuthVersion = oAuthVersion; + TestAuthorisationAction() { putValue(NAME, tr("Test Access Token")); putValue(SHORT_DESCRIPTION, tr("Click test access to the OSM server with the current access token")); new ImageProvider("oauth", "oauth-small").getResource().attachImageIcon(this); @@ -443,26 +408,13 @@ private class TestAuthorisationAction extends AbstractAction { @Override public void actionPerformed(ActionEvent evt) { - if (this.oAuthVersion == OAuthVersion.OAuth10a) { - OAuthToken token = OAuthAccessTokenHolder.getInstance().getAccessToken(); - OAuthParameters parameters = OAuthParameters.createFromApiUrl(OsmApi.getOsmApi().getServerUrl()); - TestAccessTokenTask task = new TestAccessTokenTask( - OAuthAuthenticationPreferencesPanel.this, - apiUrl, - parameters, - token - ); - MainApplication.worker.submit(task); - } else { - IOAuthToken token = OAuthAccessTokenHolder.getInstance().getAccessToken(OsmApi.getOsmApi().getBaseUrl(), OAuthVersion.OAuth20); - TestAccessTokenTask task = new TestAccessTokenTask( - OAuthAuthenticationPreferencesPanel.this, - apiUrl, - token.getParameters(), - token - ); - MainApplication.worker.submit(task); - } + IOAuthToken token = OAuthAccessTokenHolder.getInstance().getAccessToken(apiUrl, OAuthVersion.OAuth20); + TestAccessTokenTask task = new TestAccessTokenTask( + OAuthAuthenticationPreferencesPanel.this, + apiUrl, + token + ); + MainApplication.worker.submit(task); } } diff --git a/src/org/openstreetmap/josm/gui/preferences/server/OsmApiUrlInputPanel.java b/src/org/openstreetmap/josm/gui/preferences/server/OsmApiUrlInputPanel.java index 1a35f8620d5..ca7f38d4665 100644 --- a/src/org/openstreetmap/josm/gui/preferences/server/OsmApiUrlInputPanel.java +++ b/src/org/openstreetmap/josm/gui/preferences/server/OsmApiUrlInputPanel.java @@ -4,6 +4,7 @@ import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.Font; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -82,11 +83,11 @@ protected final void build() { setLayout(new GridBagLayout()); // the checkbox for the default UL - add(buildDefaultServerUrlPanel(), GBC.eop().fill(GBC.HORIZONTAL)); + add(buildDefaultServerUrlPanel(), GBC.eop().fill(GridBagConstraints.HORIZONTAL)); // the input field for the URL add(lblApiUrl, GBC.std().insets(0, 0, 3, 0)); - add(tfOsmServerUrl, GBC.std().fill(GBC.HORIZONTAL).insets(0, 0, 3, 0)); + add(tfOsmServerUrl, GBC.std().fill(GridBagConstraints.HORIZONTAL).insets(0, 0, 3, 0)); lblApiUrl.setLabelFor(tfOsmServerUrl); SelectAllOnFocusGainedDecorator.decorate(tfOsmServerUrl.getEditorComponent()); valOsmServerUrl = new ApiUrlValidator(tfOsmServerUrl.getEditorComponent()); @@ -256,7 +257,7 @@ public void validate() { class UseDefaultServerUrlChangeHandler implements ItemListener { @Override public void itemStateChanged(ItemEvent e) { - switch(e.getStateChange()) { + switch (e.getStateChange()) { case ItemEvent.SELECTED: setApiUrlInputEnabled(false); propagator.propagate(Config.getUrls().getDefaultOsmApiUrl()); diff --git a/src/org/openstreetmap/josm/gui/preferences/shortcut/PrefJPanel.java b/src/org/openstreetmap/josm/gui/preferences/shortcut/PrefJPanel.java index 2dd85c17d72..317a44bf034 100644 --- a/src/org/openstreetmap/josm/gui/preferences/shortcut/PrefJPanel.java +++ b/src/org/openstreetmap/josm/gui/preferences/shortcut/PrefJPanel.java @@ -10,6 +10,7 @@ import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Toolkit; +import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.im.InputContext; import java.lang.reflect.Field; @@ -55,14 +56,14 @@ public class PrefJPanel extends JPanel { // on the physical keyboard. What language pack is installed in JOSM is completely // independent from the keyboard's labelling. But the operation system's locale // usually matches the keyboard. This even works with my English Windows and my German keyboard. - private static final String SHIFT = KeyEvent.getModifiersExText(KeyStroke.getKeyStroke(KeyEvent.VK_A, - KeyEvent.SHIFT_DOWN_MASK).getModifiers()); - private static final String CTRL = KeyEvent.getModifiersExText(KeyStroke.getKeyStroke(KeyEvent.VK_A, - KeyEvent.CTRL_DOWN_MASK).getModifiers()); - private static final String ALT = KeyEvent.getModifiersExText(KeyStroke.getKeyStroke(KeyEvent.VK_A, - KeyEvent.ALT_DOWN_MASK).getModifiers()); - private static final String META = KeyEvent.getModifiersExText(KeyStroke.getKeyStroke(KeyEvent.VK_A, - KeyEvent.META_DOWN_MASK).getModifiers()); + private static final String SHIFT = InputEvent.getModifiersExText(KeyStroke.getKeyStroke(KeyEvent.VK_A, + InputEvent.SHIFT_DOWN_MASK).getModifiers()); + private static final String CTRL = InputEvent.getModifiersExText(KeyStroke.getKeyStroke(KeyEvent.VK_A, + InputEvent.CTRL_DOWN_MASK).getModifiers()); + private static final String ALT = InputEvent.getModifiersExText(KeyStroke.getKeyStroke(KeyEvent.VK_A, + InputEvent.ALT_DOWN_MASK).getModifiers()); + private static final String META = InputEvent.getModifiersExText(KeyStroke.getKeyStroke(KeyEvent.VK_A, + InputEvent.META_DOWN_MASK).getModifiers()); // A list of keys to present the user. Sadly this really is a list of keys Java knows about, // not a list of real physical keys. If someone knows how to get that list? @@ -96,8 +97,8 @@ private static Map setKeyList() { try { int i = field.getInt(null); String s = KeyEvent.getKeyText(i); - if (s != null && !s.isEmpty() && !s.contains(unknown)) { - list.put(Integer.valueOf(i), s); + if (!s.isEmpty() && !s.contains(unknown)) { + list.put(i, s); } } catch (IllegalArgumentException | IllegalAccessException e) { Logging.error(e); @@ -106,7 +107,7 @@ private static Map setKeyList() { } KeyboardUtils.getExtendedKeyCodes(InputContext.getInstance().getLocale()) .forEach((key, value) -> list.put(key, value.toString())); - list.put(Integer.valueOf(-1), ""); + list.put(-1, ""); // Remove "look-alike" values. See JOSM #22020 comment 2. These override the standard left/right/up/down keys. list.remove(KeyboardUtils.EXTENDED_KEYCODE_FLAG + 0x2190); // '←' (LEFTWARDS ARROW) @@ -291,10 +292,10 @@ public void valueChanged(ListSelectionEvent e) { Shortcut sc = (Shortcut) panel.model.getValueAt(row, -1); panel.cbDefault.setSelected(!sc.isAssignedUser()); panel.cbDisable.setSelected(sc.getKeyStroke() == null); - panel.cbShift.setSelected(sc.getAssignedModifier() != -1 && (sc.getAssignedModifier() & KeyEvent.SHIFT_DOWN_MASK) != 0); - panel.cbCtrl.setSelected(sc.getAssignedModifier() != -1 && (sc.getAssignedModifier() & KeyEvent.CTRL_DOWN_MASK) != 0); - panel.cbAlt.setSelected(sc.getAssignedModifier() != -1 && (sc.getAssignedModifier() & KeyEvent.ALT_DOWN_MASK) != 0); - panel.cbMeta.setSelected(sc.getAssignedModifier() != -1 && (sc.getAssignedModifier() & KeyEvent.META_DOWN_MASK) != 0); + panel.cbShift.setSelected(sc.getAssignedModifier() != -1 && (sc.getAssignedModifier() & InputEvent.SHIFT_DOWN_MASK) != 0); + panel.cbCtrl.setSelected(sc.getAssignedModifier() != -1 && (sc.getAssignedModifier() & InputEvent.CTRL_DOWN_MASK) != 0); + panel.cbAlt.setSelected(sc.getAssignedModifier() != -1 && (sc.getAssignedModifier() & InputEvent.ALT_DOWN_MASK) != 0); + panel.cbMeta.setSelected(sc.getAssignedModifier() != -1 && (sc.getAssignedModifier() & InputEvent.META_DOWN_MASK) != 0); if (sc.getKeyStroke() != null) { panel.tfKey.setSelectedItem(keyList.get(sc.getKeyStroke().getKeyCode())); } else { @@ -328,10 +329,10 @@ public void actionPerformed(java.awt.event.ActionEvent e) { sc.setAssignedModifier(KeyEvent.VK_CANCEL); } else { sc.setAssignedModifier( - (panel.cbShift.isSelected() ? KeyEvent.SHIFT_DOWN_MASK : 0) | - (panel.cbCtrl.isSelected() ? KeyEvent.CTRL_DOWN_MASK : 0) | - (panel.cbAlt.isSelected() ? KeyEvent.ALT_DOWN_MASK : 0) | - (panel.cbMeta.isSelected() ? KeyEvent.META_DOWN_MASK : 0) + (panel.cbShift.isSelected() ? InputEvent.SHIFT_DOWN_MASK : 0) | + (panel.cbCtrl.isSelected() ? InputEvent.CTRL_DOWN_MASK : 0) | + (panel.cbAlt.isSelected() ? InputEvent.ALT_DOWN_MASK : 0) | + (panel.cbMeta.isSelected() ? InputEvent.META_DOWN_MASK : 0) ); for (Map.Entry entry : keyList.entrySet()) { if (entry.getValue().equals(selectedKey)) { diff --git a/src/org/openstreetmap/josm/gui/progress/AbstractProgressMonitor.java b/src/org/openstreetmap/josm/gui/progress/AbstractProgressMonitor.java index f8d2381f66a..9c43dc1f6e0 100644 --- a/src/org/openstreetmap/josm/gui/progress/AbstractProgressMonitor.java +++ b/src/org/openstreetmap/josm/gui/progress/AbstractProgressMonitor.java @@ -11,7 +11,7 @@ */ public abstract class AbstractProgressMonitor implements ProgressMonitor { - private static class Request { + private static final class Request { private AbstractProgressMonitor originator; private int childTicks; private double currentValue; diff --git a/src/org/openstreetmap/josm/gui/progress/CLIProgressMonitor.java b/src/org/openstreetmap/josm/gui/progress/CLIProgressMonitor.java index b88a70ea9b1..607ea8c0464 100644 --- a/src/org/openstreetmap/josm/gui/progress/CLIProgressMonitor.java +++ b/src/org/openstreetmap/josm/gui/progress/CLIProgressMonitor.java @@ -37,7 +37,7 @@ public CLIProgressMonitor() { @Override protected void doBeginTask() { - if (!Utils.isBlank(this.title)) { + if (!Utils.isStripEmpty(this.title)) { Logging.info(tr("Beginning task {2}: {0}{1}", this.title, this.customText, Optional.ofNullable(this.taskId).map(ProgressTaskId::getId).map(id -> ' ' + id).orElse(""))); } diff --git a/src/org/openstreetmap/josm/gui/tagging/TagCellRenderer.java b/src/org/openstreetmap/josm/gui/tagging/TagCellRenderer.java index 350d8be3aa2..0e8e5d20849 100644 --- a/src/org/openstreetmap/josm/gui/tagging/TagCellRenderer.java +++ b/src/org/openstreetmap/josm/gui/tagging/TagCellRenderer.java @@ -97,7 +97,7 @@ public Component getTableCellRendererComponent(JTable table, Object value, setForeground(UIManager.getColor("Table.foreground")); } - switch(vColIndex) { + switch (vColIndex) { case 0: renderTagName((TagModel) value); break; case 1: renderTagValue((TagModel) value); break; default: throw new JosmRuntimeException("unexpected index in switch statement"); diff --git a/src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java b/src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java index 08523515184..58ec7f6d7fe 100644 --- a/src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java +++ b/src/org/openstreetmap/josm/gui/tagging/TagEditorModel.java @@ -1,6 +1,7 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.tagging; +import static java.util.function.Predicate.not; import static org.openstreetmap.josm.tools.I18n.trn; import java.beans.PropertyChangeListener; @@ -59,7 +60,7 @@ public class TagEditorModel extends AbstractTableModel { /** * Creates a new tag editor model. Internally allocates two selection models * for row selection and column selection. - * + *

              * To create a {@link javax.swing.JTable} with this model: *

                    *    TagEditorModel model = new TagEditorModel();
              @@ -156,7 +157,7 @@ public Object getValueAt(int rowIndex, int columnIndex) {
                   public void setValueAt(Object value, int row, int col) {
                       TagModel tag = get(row);
                       if (tag != null) {
              -            switch(col) {
              +            switch (col) {
                           case 0:
                               updateTagName(tag, (String) value);
                               break;
              @@ -218,10 +219,10 @@ public void prepend(TagModel tag) {
               
                   /**
                    * adds a tag given by a name/value pair to the tag editor model.
              -     *
              +     * 

              * If there is no tag with name name yet, a new {@link TagModel} is created * and append to this model. - * + *

              * If there is a tag with name name, value is merged to the list * of values for this tag. * @@ -531,7 +532,7 @@ protected Command createDeleteTagsCommand(Collection primitives) { public List getKeys() { return tags.stream() .map(TagModel::getName) - .filter(name -> !Utils.isStripEmpty(name)) + .filter(not(Utils::isStripEmpty)) .collect(Collectors.toList()); } diff --git a/src/org/openstreetmap/josm/gui/tagging/TagTable.java b/src/org/openstreetmap/josm/gui/tagging/TagTable.java index faf04d1288d..842487db4da 100644 --- a/src/org/openstreetmap/josm/gui/tagging/TagTable.java +++ b/src/org/openstreetmap/josm/gui/tagging/TagTable.java @@ -8,6 +8,7 @@ import java.awt.KeyboardFocusManager; import java.awt.Window; import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; @@ -133,23 +134,21 @@ public void actionPerformed(ActionEvent e) { /** * Action to be run when the user invokes a delete action on the table, for * instance by pressing DEL. - * + *

              * Depending on the shape on the current selection the action deletes individual * values or entire tags from the model. - * + *

              * If the current selection consists of cells in the second column only, the keys of * the selected tags are set to the empty string. - * + *

              * If the current selection consists of cell in the third column only, the values of the * selected tags are set to the empty string. - * + *

              * If the current selection consists of cells in the second and the third column, * the selected tags are removed from the model. - * + *

              * This action listens to the table selection. It becomes enabled when the selection * is non-empty, otherwise it is disabled. - * - * */ class DeleteAction extends AbstractAction implements ListSelectionListener { @@ -189,7 +188,7 @@ protected void deleteTags() { public void actionPerformed(ActionEvent e) { if (!isEnabled()) return; - switch(getSelectedColumnCount()) { + switch (getSelectedColumnCount()) { case 1: if (getSelectedColumn() == 0) { deleteTagNames(); @@ -350,7 +349,7 @@ protected final void init(final int maxCharacters) { // addAction = new AddAction(); getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) - .put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, KeyEvent.CTRL_DOWN_MASK), "addTag"); + .put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, InputEvent.CTRL_DOWN_MASK), "addTag"); getActionMap().put("addTag", addAction); pasteAction = new PasteAction(); @@ -565,7 +564,7 @@ public void removeNotify() { /** * This is a custom implementation of the CellEditorRemover used in JTable * to handle the client property terminateEditOnFocusLost. - * + *

              * This implementation also checks whether focus is transferred to one of a list * of dedicated components, see {@link TagTable#doNotStopCellEditingWhenFocused}. * A typical example for such a component is a button in {@link TagEditorPanel} diff --git a/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBox.java b/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBox.java index e8ffc078fc9..86b14002bac 100644 --- a/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBox.java +++ b/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBox.java @@ -31,7 +31,7 @@ public class AutoCompComboBox extends JosmComboBox implements AutoCompList * Constructs an {@code AutoCompletingComboBox}. */ public AutoCompComboBox() { - this(new AutoCompComboBoxModel()); + this(new AutoCompComboBoxModel<>()); } /** @@ -41,7 +41,7 @@ public AutoCompComboBox() { */ public AutoCompComboBox(AutoCompComboBoxModel model) { super(model); - setEditor(new AutoCompComboBoxEditor()); + setEditor(new AutoCompComboBoxEditor<>()); setEditable(true); getEditorComponent().setModel(model); getEditorComponent().addAutoCompListener(this); diff --git a/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompEvent.java b/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompEvent.java index 8b9f5d8eb05..7d05a2f9306 100644 --- a/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompEvent.java +++ b/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompEvent.java @@ -79,7 +79,7 @@ public Object getItem() { @Override public String paramString() { String typeStr; - switch(id) { + switch (id) { case AUTOCOMP_BEFORE: typeStr = "AUTOCOMP_BEFORE"; break; diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java index 10d50e64eaa..214c0db70ba 100644 --- a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java +++ b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java @@ -67,6 +67,7 @@ import org.openstreetmap.josm.gui.tagging.presets.items.PresetLink; import org.openstreetmap.josm.gui.tagging.presets.items.Roles; import org.openstreetmap.josm.gui.tagging.presets.items.Space; +import org.openstreetmap.josm.gui.tagging.presets.items.RegionSpecific; import org.openstreetmap.josm.gui.util.GuiHelper; import org.openstreetmap.josm.tools.GBC; import org.openstreetmap.josm.tools.ImageProvider; @@ -88,7 +89,8 @@ * It is also able to construct dialogs out of preset definitions. * @since 294 */ -public class TaggingPreset extends AbstractAction implements ActiveLayerChangeListener, AdaptableAction, Predicate { +public class TaggingPreset extends AbstractAction implements ActiveLayerChangeListener, AdaptableAction, Predicate, + RegionSpecific { /** The user pressed the "Apply" button */ public static final int DIALOG_ANSWER_APPLY = 1; @@ -141,6 +143,14 @@ public class TaggingPreset extends AbstractAction implements ActiveLayerChangeLi * The types as preparsed collection. */ public transient Set types; + /** + * List of regions the preset is applicable for. + */ + private Collection regions; + /** + * If true, invert the meaning of regions. + */ + private boolean excludeRegions; /** * The list of preset items */ @@ -349,6 +359,26 @@ public void setMatch_expression(String filter) throws SAXException { } } + @Override + public final Collection regions() { + return this.regions != null || this.group == null ? this.regions : this.group.regions(); + } + + @Override + public final void realSetRegions(Collection regions) { + this.regions = regions; + } + + @Override + public final boolean exclude_regions() { + return this.excludeRegions; + } + + @Override + public final void setExclude_regions(boolean excludeRegions) { + this.excludeRegions = excludeRegions; + } + private static class PresetPanel extends JPanel { private boolean hasElements; diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemGuiSupport.java b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemGuiSupport.java index b41ec177800..026a250ee26 100644 --- a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemGuiSupport.java +++ b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemGuiSupport.java @@ -134,7 +134,7 @@ public Tagged getTagged() { // if there is only one primitive selected, get its tags Tagged tagged = Tagged.ofMap(selected.iterator().next().getKeys()); // update changed tags - changedTagsSupplier.get().forEach(tag -> tagged.put(tag)); + changedTagsSupplier.get().forEach(tagged::put); return tagged; } diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetMenu.java b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetMenu.java index 046d256bd08..27a7d45018b 100644 --- a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetMenu.java +++ b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetMenu.java @@ -37,7 +37,7 @@ public class TaggingPresetMenu extends TaggingPreset { /** The menu to show users */ public JMenu menu; // set by TaggingPresets - private static class PresetTextComparator implements Comparator, Serializable { + private static final class PresetTextComparator implements Comparator, Serializable { private static final long serialVersionUID = 1L; @Override public int compare(JMenuItem o1, JMenuItem o2) { diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReader.java b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReader.java index 7798e42f53c..56016acceb9 100644 --- a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReader.java +++ b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReader.java @@ -106,6 +106,11 @@ public String toString() { } } + /** + * A {@link LinkedHashSet} with the ability to get the "last" object. + * Note that this is unnecessary in Java 21 (see JEP 431). + * @param The object type in the set + */ static class HashSetWithLast extends LinkedHashSet { private static final long serialVersionUID = 1L; protected transient E last; diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSelector.java b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSelector.java index 8f1931e3fd0..2f6fc087062 100644 --- a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSelector.java +++ b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetSelector.java @@ -74,7 +74,7 @@ public class TaggingPresetSelector extends SearchTextResultListPanel { + private static final class ResultListCellRenderer implements ListCellRenderer { private final DefaultListCellRenderer def = new DefaultListCellRenderer(); @Override public Component getListCellRendererComponent(JList list, TaggingPreset tp, int index, diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetValidation.java b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetValidation.java index 1bae43cc854..1b845f01816 100644 --- a/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetValidation.java +++ b/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetValidation.java @@ -24,7 +24,9 @@ import org.openstreetmap.josm.data.validation.TestError; import org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker; import org.openstreetmap.josm.data.validation.tests.OpeningHourTest; +import org.openstreetmap.josm.data.validation.tests.TagChecker; import org.openstreetmap.josm.gui.MainApplication; +import org.openstreetmap.josm.gui.progress.NullProgressMonitor; import org.openstreetmap.josm.gui.util.GuiHelper; import org.openstreetmap.josm.tools.Logging; import org.openstreetmap.josm.tools.SubclassFilteredCollection; @@ -55,11 +57,16 @@ static void validate(OsmPrimitive primitive, JLabel validationLabel) { try { MapCSSTagChecker mapCSSTagChecker = OsmValidator.getTest(MapCSSTagChecker.class); OpeningHourTest openingHourTest = OsmValidator.getTest(OpeningHourTest.class); - OsmValidator.initializeTests(Arrays.asList(mapCSSTagChecker, openingHourTest)); + TagChecker tagChecker = OsmValidator.getTest(TagChecker.class); + tagChecker.startTest(NullProgressMonitor.INSTANCE); //since initializeTest works if test is enabled + OsmValidator.initializeTests(Arrays.asList(mapCSSTagChecker, openingHourTest, tagChecker)); + List errors = new ArrayList<>(); openingHourTest.addErrorsForPrimitive(primitive, errors); errors.addAll(mapCSSTagChecker.getErrorsForPrimitive(primitive, ValidatorPrefHelper.PREF_OTHER.get())); + tagChecker.check(primitive); + errors.addAll(tagChecker.getErrors()); boolean visible = !errors.isEmpty(); String toolTipText = "" + Utils.joinAsHtmlUnorderedList(Utils.transform(errors, e -> diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java index d8f64412eff..3fbeae9434b 100644 --- a/src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java +++ b/src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java @@ -10,7 +10,7 @@ import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; -import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import javax.swing.AbstractAction; @@ -103,9 +103,7 @@ protected boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) { } addEntry(PresetListEntry.ENTRY_EMPTY); - usage.map.forEach((value, count) -> { - addEntry(new PresetListEntry(value, this)); - }); + usage.map.forEach((value, count) -> addEntry(new PresetListEntry(value, this))); combobox = new JosmComboBox<>(dropDownModel); AutoCompComboBoxEditor editor = new AutoCompComboBoxEditor<>(); @@ -124,13 +122,13 @@ protected boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) { combobox.setEditable(editable); autoCompModel = new AutoCompComboBoxModel<>(Comparator.naturalOrder()); - getAllForKeys(Arrays.asList(key)).forEach(autoCompModel::addElement); + getAllForKeys(Collections.singletonList(key)).forEach(autoCompModel::addElement); getDisplayValues().forEach(s -> autoCompModel.addElement(new AutoCompletionItem(s, AutoCompletionPriority.IS_IN_STANDARD))); AutoCompTextField tf = editor.getEditorComponent(); tf.setModel(autoCompModel); - if (TaggingPresetItem.DISPLAY_KEYS_AS_HINT.get()) { + if (Boolean.TRUE.equals(TaggingPresetItem.DISPLAY_KEYS_AS_HINT.get())) { combobox.setHint(key); } if (length > 0) { @@ -217,7 +215,7 @@ protected PresetListEntry getSelectedItem() { return (PresetListEntry) sel; if (sel instanceof String) { // free edit. If the free edit corresponds to a known entry, use that entry. This is - // to avoid that we write a display_value to the tag's value, eg. if the user did an + // to avoid that we write a display_value to the tag's value, e.g. if the user did an // undo. PresetListEntry selItem = dropDownModel.find((String) sel); if (selItem != null) diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/ComboMultiSelect.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/ComboMultiSelect.java index 4dfb2de61d4..58ea3b56539 100644 --- a/src/org/openstreetmap/josm/gui/tagging/presets/items/ComboMultiSelect.java +++ b/src/org/openstreetmap/josm/gui/tagging/presets/items/ComboMultiSelect.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -148,7 +149,7 @@ public JLabel getListCellRendererComponent( /** * allow escaped comma in comma separated list: - * "A\, B\, C,one\, two" --> ["A, B, C", "one, two"] + * "A\, B\, C,one\, two" → ["A, B, C", "one, two"] * @param delimiter the delimiter, e.g. a comma. separates the entries and * must be escaped within one entry * @param s the string @@ -309,7 +310,7 @@ protected void initListEntriesFromAttributes() { addListEntry(e); } - if (values_sort && TaggingPresets.SORT_MENU.get()) { + if (values_sort && Boolean.TRUE.equals(TaggingPresets.SORT_MENU.get())) { presetListEntries.sort((a, b) -> AlphanumComparator.getInstance().compare(a.getDisplayValue(), b.getDisplayValue())); } } @@ -340,7 +341,7 @@ protected String getInitialValue(Usage usage, TaggingPresetItemGuiSupport suppor // at least one primitive has a value for this key (but not all have the same one) initialValue = DIFFERENT; originalValue = initialValue; - } else if (!usage.hadKeys() || isForceUseLastAsDefault() || PROP_FILL_DEFAULT.get()) { + } else if (!usage.hadKeys() || isForceUseLastAsDefault() || Boolean.TRUE.equals(PROP_FILL_DEFAULT.get())) { // at this point no primitive had any value for this key if (!support.isPresetInitiallyMatches() && isUseLastAsDefault() && LAST_VALUES.containsKey(key)) { initialValue = LAST_VALUES.get(key); @@ -402,6 +403,14 @@ protected boolean isForceUseLastAsDefault() { return use_last_as_default == 2; } + /** + * Get the entries for this {@link ComboMultiSelect} object + * @return The {@link PresetListEntry} values for this object + */ + public List presetListEntries() { + return Collections.unmodifiableList(this.presetListEntries); + } + /** * Adds a preset list entry. * @param e list entry to add diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java index a52bf2c77e7..a54a7d980f2 100644 --- a/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java +++ b/src/org/openstreetmap/josm/gui/tagging/presets/items/KeyedItem.java @@ -1,14 +1,15 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.tagging.presets.items; +import static org.openstreetmap.josm.tools.I18n.marktr; import static org.openstreetmap.josm.tools.I18n.tr; import java.util.Collection; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; -import java.util.SortedMap; import java.util.NoSuchElementException; +import java.util.SortedMap; import java.util.TreeMap; import javax.swing.JPopupMenu; @@ -24,12 +25,12 @@ /** * Preset item associated to an OSM key. */ -public abstract class KeyedItem extends TextItem { +public abstract class KeyedItem extends TextItem implements RegionSpecific { /** The constant value {@code ""}. */ - protected static final String DIFFERENT = ""; + protected static final String DIFFERENT = marktr(""); /** Translation of {@code ""}. */ - public static final String DIFFERENT_I18N = tr(""); + public static final String DIFFERENT_I18N = tr(DIFFERENT); /** True if the default value should also be set on primitives that already have tags. */ protected static final BooleanProperty PROP_FILL_DEFAULT = new BooleanProperty("taggingpreset.fill-default-for-tagged-primitives", false); @@ -52,6 +53,15 @@ public abstract class KeyedItem extends TextItem { */ public String match = getDefaultMatch().getValue(); // NOSONAR + /** + * List of regions the preset is applicable for. + */ + private Collection regions; + /** + * If true, invert the meaning of regions. + */ + private boolean excludeRegions; + /** * Enum denoting how a match (see {@link TaggingPresetItem#matches}) is performed. */ @@ -260,6 +270,26 @@ protected JPopupMenu getPopupMenu() { return popupMenu; } + @Override + public final Collection regions() { + return this.regions; + } + + @Override + public final void realSetRegions(Collection regions) { + this.regions = regions; + } + + @Override + public final boolean exclude_regions() { + return this.excludeRegions; + } + + @Override + public final void setExclude_regions(boolean excludeRegions) { + this.excludeRegions = excludeRegions; + } + @Override public String toString() { return "KeyedItem [key=" + key + ", text=" + text diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetListEntry.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetListEntry.java index 2ab29149e72..5936cf3a16c 100644 --- a/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetListEntry.java +++ b/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetListEntry.java @@ -5,6 +5,7 @@ import static org.openstreetmap.josm.tools.I18n.tr; import static org.openstreetmap.josm.tools.I18n.trc; +import java.util.Collection; import java.util.Objects; import javax.swing.ImageIcon; @@ -20,7 +21,7 @@ * Used for controls that offer a list of items to choose from like {@link Combo} and * {@link MultiSelect}. */ -public class PresetListEntry implements Comparable { +public class PresetListEntry implements Comparable, RegionSpecific { /** Used to display an entry matching several different values. */ protected static final PresetListEntry ENTRY_DIFFERENT = new PresetListEntry(KeyedItem.DIFFERENT, null); /** Used to display an empty entry used to clear values. */ @@ -47,13 +48,23 @@ public class PresetListEntry implements Comparable { /** The localized version of {@link #short_description}. */ public String locale_short_description; // NOSONAR + /** + * List of regions the entry is applicable for. + */ + private Collection regions; + + /** + * If true, invert the meaning of regions. + */ + private boolean excludeRegions; + private String cachedDisplayValue; private String cachedShortDescription; private ImageIcon cachedIcon; /** * Constructs a new {@code PresetListEntry}, uninitialized. - * + *

              * Public default constructor is needed by {@link org.openstreetmap.josm.tools.XmlObjectParser.Parser#startElement} */ public PresetListEntry() { @@ -73,7 +84,7 @@ public PresetListEntry(String value, ComboMultiSelect cms) { /** * Returns the contents displayed in the dropdown list. - * + *

              * This is the contents that would be displayed in the current view plus a short description to * aid the user. The whole content is wrapped to {@code width}. * @@ -163,6 +174,26 @@ public String getToolTipText(String key) { return tr("Clears the key ''{0}''.", key); } + @Override + public Collection regions() { + return this.regions; + } + + @Override + public void realSetRegions(Collection regions) { + this.regions = regions; + } + + @Override + public boolean exclude_regions() { + return this.excludeRegions; + } + + @Override + public void setExclude_regions(boolean excludeRegions) { + this.excludeRegions = excludeRegions; + } + // toString is mainly used to initialize the Editor @Override public String toString() { diff --git a/src/org/openstreetmap/josm/gui/tagging/presets/items/RegionSpecific.java b/src/org/openstreetmap/josm/gui/tagging/presets/items/RegionSpecific.java new file mode 100644 index 00000000000..0c8f1111ba8 --- /dev/null +++ b/src/org/openstreetmap/josm/gui/tagging/presets/items/RegionSpecific.java @@ -0,0 +1,62 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.gui.tagging.presets.items; + +import static org.openstreetmap.josm.tools.I18n.tr; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; + +import org.openstreetmap.josm.tools.Territories; +import org.openstreetmap.josm.tools.Utils; +import org.xml.sax.SAXException; + +/** + * Indicates that this object may be specific to a region + * @since 18918 + */ +public interface RegionSpecific { + /** + * Get the regions for the item + * @return The regions that the item is valid for + * @apiNote This is not {@code getRegions} just in case we decide to make the {@link RegionSpecific} record classes. + */ + Collection regions(); + + /** + * Set the regions for the preset + * @param regions The region list (comma delimited) + * @throws SAXException if an unknown ISO 3166-2 is found + */ + default void setRegions(String regions) throws SAXException { + Set regionSet = Collections.unmodifiableSet(Arrays.stream(regions.split(",")) + .map(Utils::intern).collect(Collectors.toSet())); + for (String region : regionSet) { + if (!Territories.getKnownIso3166Codes().contains(region)) { + throw new SAXException(tr("Unknown ISO-3166 Code: {0}", region)); + } + } + this.realSetRegions(regionSet); + } + + /** + * Set the regions for the preset + * @param regions The region collection + */ + void realSetRegions(Collection regions); + + /** + * Get the exclude_regions for the preset + * @apiNote This is not {@code getExclude_regions} just in case we decide to make {@link RegionSpecific} a record class. + * @return {@code true} if the meaning of {@link #regions()} should be inverted + */ + boolean exclude_regions(); + + /** + * Set if the preset should not be used in the given region + * @param excludeRegions if true the function of regions is inverted + */ + void setExclude_regions(boolean excludeRegions); +} diff --git a/src/org/openstreetmap/josm/gui/util/AdjustmentSynchronizer.java b/src/org/openstreetmap/josm/gui/util/AdjustmentSynchronizer.java index 2643030d30b..9b5c70d7ac1 100644 --- a/src/org/openstreetmap/josm/gui/util/AdjustmentSynchronizer.java +++ b/src/org/openstreetmap/josm/gui/util/AdjustmentSynchronizer.java @@ -58,7 +58,7 @@ public void participateInSynchronizedScrolling(Adjustable adjustable) { */ @Override public void adjustmentValueChanged(AdjustmentEvent e) { - if (!enabledMap.get(e.getAdjustable())) + if (Boolean.FALSE.equals(enabledMap.get(e.getAdjustable()))) return; for (Adjustable a : synchronizedAdjustables) { if (a != e.getAdjustable() && isParticipatingInSynchronizedScrolling(a)) { @@ -122,7 +122,7 @@ public void adapt(final JCheckBox view, final Adjustable adjustable) { // register an item lister with the check box // view.addItemListener(e -> { - switch(e.getStateChange()) { + switch (e.getStateChange()) { case ItemEvent.SELECTED: if (!isParticipatingInSynchronizedScrolling(adjustable)) { setParticipatingInSynchronizedScrolling(adjustable, true); diff --git a/src/org/openstreetmap/josm/gui/util/GuiHelper.java b/src/org/openstreetmap/josm/gui/util/GuiHelper.java index 598bf6d0e44..49d8a672d0b 100644 --- a/src/org/openstreetmap/josm/gui/util/GuiHelper.java +++ b/src/org/openstreetmap/josm/gui/util/GuiHelper.java @@ -404,7 +404,7 @@ public static Timer scheduleTimer(int initialDelay, ActionListener actionListene /** * Return s new BasicStroke object with given thickness and style - * @param code = 3.5 -> thickness=3.5px; 3.5 10 5 -> thickness=3.5px, dashed: 10px filled + 5px empty + * @param code = 3.5 → thickness=3.5px; 3.5 10 5 → thickness=3.5px, dashed: 10px filled + 5px empty * @return stroke for drawing * @see StrokeProperty */ diff --git a/src/org/openstreetmap/josm/gui/util/MultikeyActionsHandler.java b/src/org/openstreetmap/josm/gui/util/MultikeyActionsHandler.java index cfdc2c26e60..0c2a7204fdd 100644 --- a/src/org/openstreetmap/josm/gui/util/MultikeyActionsHandler.java +++ b/src/org/openstreetmap/josm/gui/util/MultikeyActionsHandler.java @@ -110,7 +110,7 @@ public void run() { } } - private class MyKeyEventDispatcher implements KeyEventDispatcher { + private final class MyKeyEventDispatcher implements KeyEventDispatcher { @Override public boolean dispatchKeyEvent(KeyEvent e) { diff --git a/src/org/openstreetmap/josm/gui/util/MultikeyShortcutAction.java b/src/org/openstreetmap/josm/gui/util/MultikeyShortcutAction.java index a7ee63aba79..c752295bc5c 100644 --- a/src/org/openstreetmap/josm/gui/util/MultikeyShortcutAction.java +++ b/src/org/openstreetmap/josm/gui/util/MultikeyShortcutAction.java @@ -13,6 +13,9 @@ */ public interface MultikeyShortcutAction extends Action { + /** + * Information for a Multikey action + */ class MultikeyInfo { private final int index; private final String description; @@ -42,6 +45,11 @@ public String getDescription() { Shortcut getMultikeyShortcut(); + /** + * Execute a multi key action + * @param index The index to execute + * @param repeatLastAction {@code true} if the last action should be executed if no action is found for the given index. + */ void executeMultikeyAction(int index, boolean repeatLastAction); List getMultikeyCombinations(); diff --git a/src/org/openstreetmap/josm/gui/util/WindowOnTopListener.java b/src/org/openstreetmap/josm/gui/util/WindowOnTopListener.java new file mode 100644 index 00000000000..84df6de37e9 --- /dev/null +++ b/src/org/openstreetmap/josm/gui/util/WindowOnTopListener.java @@ -0,0 +1,74 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.gui.util; + +import java.awt.Container; +import java.awt.Dialog; +import java.awt.Window; +import java.awt.event.WindowEvent; +import java.awt.event.WindowFocusListener; + +import javax.swing.event.AncestorEvent; +import javax.swing.event.AncestorListener; + +/** + * A listener for windows that block other inputs, to ensure they are always on top + * @since 18923 + */ +public class WindowOnTopListener implements AncestorListener, WindowFocusListener { + + /** + * {@code true} indicates that the window was always on top prior to the change + */ + private boolean wasAlwaysOnTop; + @Override + public void windowGainedFocus(WindowEvent e) { + final Window window = e.getWindow(); + if (window != null && window.isAlwaysOnTop() != wasAlwaysOnTop) { + window.setAlwaysOnTop(wasAlwaysOnTop); + } + } + + @Override + public void windowLostFocus(WindowEvent e) { + final Window window = e.getWindow(); + if (window != null) { + wasAlwaysOnTop = window.isAlwaysOnTop(); + } + } + + @Override + public void ancestorAdded(AncestorEvent event) { + final Container ancestor = event.getAncestor(); + if (ancestor instanceof Dialog) { + Dialog dialog = (Dialog) ancestor; + wasAlwaysOnTop = dialog.isAlwaysOnTop(); + if (dialog.isVisible() && dialog.isModal()) { + dialog.setAlwaysOnTop(true); + } + } + if (ancestor instanceof Window) { + Window window = (Window) ancestor; + window.addWindowFocusListener(this); + } + } + + @Override + public void ancestorRemoved(AncestorEvent event) { + final Container ancestor = event.getAncestor(); + if (ancestor instanceof Dialog) { + Dialog dialog = (Dialog) ancestor; + if (dialog.isVisible() && dialog.isModal()) { + dialog.setAlwaysOnTop(wasAlwaysOnTop); + } + } + if (ancestor instanceof Window) { + Window window = (Window) ancestor; + window.removeWindowFocusListener(this); + } + } + + @Override + public void ancestorMoved(AncestorEvent event) { + // Do nothing + } +} diff --git a/src/org/openstreetmap/josm/gui/util/imagery/CameraPlane.java b/src/org/openstreetmap/josm/gui/util/imagery/CameraPlane.java index 7347f75fe38..0ab377ad6b2 100644 --- a/src/org/openstreetmap/josm/gui/util/imagery/CameraPlane.java +++ b/src/org/openstreetmap/josm/gui/util/imagery/CameraPlane.java @@ -14,6 +14,7 @@ import org.openstreetmap.josm.tools.Logging; import jakarta.annotation.Nullable; +import org.openstreetmap.josm.tools.bugreport.BugReport; /** * The plane that the camera appears on and rotates around. @@ -265,84 +266,7 @@ public void mapping(BufferedImage sourceImage, BufferedImage targetImage, Rectan DataBuffer targetBuffer = targetImage.getRaster().getDataBuffer(); // Faster mapping if (sourceBuffer.getDataType() == DataBuffer.TYPE_BYTE && targetBuffer.getDataType() == DataBuffer.TYPE_BYTE) { - byte[] sourceImageBuffer = ((DataBufferByte) sourceImage.getRaster().getDataBuffer()).getData(); - byte[] targetImageBuffer = ((DataBufferByte) targetImage.getRaster().getDataBuffer()).getData(); - final boolean sourceHasAlphaChannel = sourceImage.getAlphaRaster() != null; - final boolean targetHasAlphaChannel = targetImage.getAlphaRaster() != null; - if (sourceHasAlphaChannel && targetHasAlphaChannel) { - final int pixelLength = 4; - IntStream.range(visibleRect.y, visibleRect.y + visibleRect.height).parallel() - .forEach(y -> IntStream.range(visibleRect.x, visibleRect.x + visibleRect.width).forEach(x -> { - final Point2D.Double p = mapPoint(x, y); - int tx = ((int) (p.x * (sourceImage.getWidth() - 1))); - int ty = ((int) (p.y * (sourceImage.getHeight() - 1))); - int sourceOffset = (ty * sourceImage.getWidth() + tx) * pixelLength; - int targetOffset = (y * targetImage.getWidth() + x) * pixelLength; - byte a = sourceImageBuffer[sourceOffset]; - byte b = sourceImageBuffer[sourceOffset + 1]; - byte g = sourceImageBuffer[sourceOffset + 2]; - byte r = sourceImageBuffer[sourceOffset + 3]; - targetImageBuffer[targetOffset] = a; - targetImageBuffer[targetOffset + 1] = b; - targetImageBuffer[targetOffset + 2] = g; - targetImageBuffer[targetOffset + 3] = r; - })); - } else if (sourceHasAlphaChannel) { - final int sourcePixelLength = 4; - final int targetPixelLength = 3; - IntStream.range(visibleRect.y, visibleRect.y + visibleRect.height).parallel() - .forEach(y -> IntStream.range(visibleRect.x, visibleRect.x + visibleRect.width).forEach(x -> { - final Point2D.Double p = mapPoint(x, y); - int tx = ((int) (p.x * (sourceImage.getWidth() - 1))); - int ty = ((int) (p.y * (sourceImage.getHeight() - 1))); - int sourceOffset = (ty * sourceImage.getWidth() + tx) * sourcePixelLength; - int targetOffset = (y * targetImage.getWidth() + x) * targetPixelLength; - //byte a = sourceImageBuffer[sourceOffset]; - byte b = sourceImageBuffer[sourceOffset + 1]; - byte g = sourceImageBuffer[sourceOffset + 2]; - byte r = sourceImageBuffer[sourceOffset + 3]; - targetImageBuffer[targetOffset] = b; - targetImageBuffer[targetOffset + 1] = g; - targetImageBuffer[targetOffset + 2] = r; - - })); - } else if (targetHasAlphaChannel) { - final int sourcePixelLength = 3; - final int targetPixelLength = 4; - IntStream.range(visibleRect.y, visibleRect.y + visibleRect.height).parallel() - .forEach(y -> IntStream.range(visibleRect.x, visibleRect.x + visibleRect.width).forEach(x -> { - final Point2D.Double p = mapPoint(x, y); - int tx = ((int) (p.x * (sourceImage.getWidth() - 1))); - int ty = ((int) (p.y * (sourceImage.getHeight() - 1))); - int sourceOffset = (ty * sourceImage.getWidth() + tx) * sourcePixelLength; - int targetOffset = (y * targetImage.getWidth() + x) * targetPixelLength; - byte a = (byte) 255; - byte b = sourceImageBuffer[sourceOffset]; - byte g = sourceImageBuffer[sourceOffset + 1]; - byte r = sourceImageBuffer[sourceOffset + 2]; - targetImageBuffer[targetOffset] = a; - targetImageBuffer[targetOffset + 1] = b; - targetImageBuffer[targetOffset + 2] = g; - targetImageBuffer[targetOffset + 3] = r; - - })); - } else { - final int pixelLength = 3; - IntStream.range(visibleRect.y, visibleRect.y + visibleRect.height).parallel() - .forEach(y -> IntStream.range(visibleRect.x, visibleRect.x + visibleRect.width).forEach(x -> { - final Point2D.Double p = mapPoint(x, y); - int tx = ((int) (p.x * (sourceImage.getWidth() - 1))); - int ty = ((int) (p.y * (sourceImage.getHeight() - 1))); - int sourceOffset = (ty * sourceImage.getWidth() + tx) * pixelLength; - int targetOffset = (y * targetImage.getWidth() + x) * pixelLength; - byte b = sourceImageBuffer[sourceOffset]; - byte g = sourceImageBuffer[sourceOffset + 1]; - byte r = sourceImageBuffer[sourceOffset + 2]; - targetImageBuffer[targetOffset] = b; - targetImageBuffer[targetOffset + 1] = g; - targetImageBuffer[targetOffset + 2] = r; - })); - } + commonFastByteMapping(sourceImage, targetImage, visibleRect); } else if (sourceBuffer.getDataType() == DataBuffer.TYPE_INT && targetBuffer.getDataType() == DataBuffer.TYPE_INT) { int[] sourceImageBuffer = ((DataBufferInt) sourceImage.getRaster().getDataBuffer()).getData(); @@ -376,6 +300,46 @@ public void mapping(BufferedImage sourceImage, BufferedImage targetImage, Rectan } } + private void commonFastByteMapping(BufferedImage sourceImage, BufferedImage targetImage, Rectangle visibleRect) { + final byte[] sourceImageBuffer = ((DataBufferByte) sourceImage.getRaster().getDataBuffer()).getData(); + final byte[] targetImageBuffer = ((DataBufferByte) targetImage.getRaster().getDataBuffer()).getData(); + final boolean sourceHasAlphaChannel = sourceImage.getAlphaRaster() != null; + final boolean targetHasAlphaChannel = targetImage.getAlphaRaster() != null; + final int sourcePixelLength = sourceHasAlphaChannel ? 4 : 3; + final int targetPixelLength = targetHasAlphaChannel ? 4 : 3; + final int addSourceAlpha = sourceHasAlphaChannel ? 1 : 0; + final int addTargetAlpha = targetHasAlphaChannel ? 1 : 0; + IntStream.range(visibleRect.y, visibleRect.y + visibleRect.height).parallel() + .forEach(y -> IntStream.range(visibleRect.x, visibleRect.x + visibleRect.width).forEach(x -> { + final Point2D.Double p = mapPoint(x, y); + int tx = ((int) (p.x * (sourceImage.getWidth() - 1))); + int ty = ((int) (p.y * (sourceImage.getHeight() - 1))); + int sourceOffset = (ty * sourceImage.getWidth() + tx) * sourcePixelLength; + int targetOffset = (y * targetImage.getWidth() + x) * targetPixelLength; + try { + // Alpha, if present + if (targetHasAlphaChannel) { + byte a = sourceHasAlphaChannel ? sourceImageBuffer[sourceOffset] : (byte) 255; + targetImageBuffer[targetOffset] = a; + } + // Blue + targetImageBuffer[targetOffset + addTargetAlpha] = sourceImageBuffer[sourceOffset + addSourceAlpha]; + // Green + targetImageBuffer[targetOffset + addTargetAlpha + 1] = sourceImageBuffer[sourceOffset + addSourceAlpha + 1]; + // Red + targetImageBuffer[targetOffset + addTargetAlpha + 2] = sourceImageBuffer[sourceOffset + addSourceAlpha + 2]; + } catch (ArrayIndexOutOfBoundsException aioobe) { + // For debugging #22590, #23055, and #23697 + throw BugReport.intercept(aioobe) + .put("visibleRect", visibleRect) + .put("sourceImageBuffer", sourceImageBuffer.length) + .put("targetImageBuffer", targetImageBuffer.length) + .put("sourceHasAlphaChannel", sourceHasAlphaChannel) + .put("targetHasAlphaChannel", targetHasAlphaChannel); + } + })); + } + /** * Map a real point to the displayed point. This method uses cached vectors. * @param x The original x coordinate diff --git a/src/org/openstreetmap/josm/gui/widgets/AbstractIdTextField.java b/src/org/openstreetmap/josm/gui/widgets/AbstractIdTextField.java index e8345e9c91b..d7c2a0aefbb 100644 --- a/src/org/openstreetmap/josm/gui/widgets/AbstractIdTextField.java +++ b/src/org/openstreetmap/josm/gui/widgets/AbstractIdTextField.java @@ -81,7 +81,7 @@ public void tryToPasteFromClipboard() { * @return true if text has been pasted and valid ids have been read */ public boolean tryToPasteFrom(String contents) { - if (!Utils.isBlank(contents)) { + if (!Utils.isStripEmpty(contents)) { setText(contents.trim()); clearTextIfInvalid(); return readIds(); diff --git a/src/org/openstreetmap/josm/gui/widgets/ChangesetIdTextField.java b/src/org/openstreetmap/josm/gui/widgets/ChangesetIdTextField.java index be959761439..4c69f7894b8 100644 --- a/src/org/openstreetmap/josm/gui/widgets/ChangesetIdTextField.java +++ b/src/org/openstreetmap/josm/gui/widgets/ChangesetIdTextField.java @@ -74,11 +74,11 @@ public void validate() { */ public boolean readChangesetId() { String value = getComponent().getText(); - if (!Utils.isBlank(value)) { + if (!Utils.isStripEmpty(value)) { value = value.trim(); id = 0; try { - if (value.matches("http.*/changeset/[0-9]+")) { + if (value.matches("http.*/changeset/\\d+")) { // full URL given, extract id value = value.substring(value.lastIndexOf('/') + 1); } diff --git a/src/org/openstreetmap/josm/gui/widgets/EditableList.java b/src/org/openstreetmap/josm/gui/widgets/EditableList.java index ce96fbbcbb5..3a683257212 100644 --- a/src/org/openstreetmap/josm/gui/widgets/EditableList.java +++ b/src/org/openstreetmap/josm/gui/widgets/EditableList.java @@ -33,7 +33,7 @@ public class EditableList extends JPanel { /** * The list items */ - public final JList sourcesList = new JList<>(new DefaultListModel()); + public final JList sourcesList = new JList<>(new DefaultListModel<>()); /** * The add button */ diff --git a/src/org/openstreetmap/josm/gui/widgets/HistoryComboBoxModel.java b/src/org/openstreetmap/josm/gui/widgets/HistoryComboBoxModel.java index 3e5e7a4851a..041463b3540 100644 --- a/src/org/openstreetmap/josm/gui/widgets/HistoryComboBoxModel.java +++ b/src/org/openstreetmap/josm/gui/widgets/HistoryComboBoxModel.java @@ -30,7 +30,7 @@ public class HistoryComboBoxModel extends AutoCompComboBoxModel { * @param strings the strings to add */ public void addAllStrings(List strings) { - strings.forEach(s -> addElement(s)); + strings.forEach(this::addElement); } /** @@ -40,7 +40,7 @@ public void addAllStrings(List strings) { */ public List asStringList() { List list = new ArrayList<>(getSize()); - this.forEach(item -> list.add(item)); + this.forEach(list::add); return list; } diff --git a/src/org/openstreetmap/josm/gui/widgets/JosmComboBox.java b/src/org/openstreetmap/josm/gui/widgets/JosmComboBox.java index 16dfded7559..380040ff9ce 100644 --- a/src/org/openstreetmap/josm/gui/widgets/JosmComboBox.java +++ b/src/org/openstreetmap/josm/gui/widgets/JosmComboBox.java @@ -66,7 +66,7 @@ public class JosmComboBox extends JComboBox implements PopupMenuListener, * in the data model becomes selected. */ public JosmComboBox() { - super(new JosmComboBoxModel()); + super(new JosmComboBoxModel<>()); init(); } @@ -84,9 +84,9 @@ public JosmComboBox() { * @since 5450 * @deprecated use {@link #setPrototypeDisplayValue} instead. */ - @Deprecated + @Deprecated(since = "18221", forRemoval = true) public JosmComboBox(E prototypeDisplayValue) { - super(new JosmComboBoxModel()); + super(new JosmComboBoxModel<>()); setPrototypeDisplayValue(prototypeDisplayValue); init(); } @@ -110,7 +110,7 @@ public JosmComboBox(JosmComboBoxModel aModel) { * @param prototypeDisplayValue use this item to size the combobox (may be null) * @deprecated use {@link #setPrototypeDisplayValue} instead. */ - @Deprecated + @Deprecated(since = "18221", forRemoval = true) public JosmComboBox(JosmComboBoxModel aModel, E prototypeDisplayValue) { super(aModel); setPrototypeDisplayValue(prototypeDisplayValue); @@ -125,7 +125,7 @@ public JosmComboBox(JosmComboBoxModel aModel, E prototypeDisplayValue) { * @param items an array of objects to insert into the combo box */ public JosmComboBox(E[] items) { - super(new JosmComboBoxModel()); + super(new JosmComboBoxModel<>()); init(); for (E elem : items) { getModel().addElement(elem); @@ -205,7 +205,7 @@ public void setText(String value) { /** * Selects an item and/or sets text - * + *

              * Selects the item whose {@code toString()} equals {@code text}. If an item could not be found, * selects nothing and sets the text anyway. * @@ -233,16 +233,16 @@ public String getHint() { /** * Sets the hint to display when no text has been entered. * - * @param hint the hint to set + * @param newHint the hint to set * @return the old hint * @since 18221 */ - public String setHint(String hint) { - String old = hint; - this.hint = hint; + public String setHint(String newHint) { + String old = this.hint; + this.hint = newHint; JosmTextField tf = getEditorComponent(); if (tf != null) - tf.setHint(hint); + tf.setHint(newHint); return old; } @@ -287,7 +287,7 @@ public Dimension getPreferredSize() { * the editor becomes too big. With this method we can set the editor height to a fixed value. *

              * Set this to -1 to get the default behaviour back. - * + *

              * See also: #6157 * * @param height the preferred height or -1 @@ -309,7 +309,7 @@ public int setPreferredHeight(int height) { @SuppressWarnings("rawtypes") public JList getList() { Object popup = getUI().getAccessibleChild(this, 0); - if (popup != null && popup instanceof javax.swing.plaf.basic.ComboPopup) { + if (popup instanceof javax.swing.plaf.basic.ComboPopup) { return ((javax.swing.plaf.basic.ComboPopup) popup).getList(); } return null; @@ -420,7 +420,8 @@ public void popupMenuWillBecomeVisible(PopupMenuEvent ev) { // Calculate how many rows fit into the free space. Rows may have variable heights. int rowCount = Math.min(configMaximumRowCount, getItemCount()); ListCellRenderer r = jList.getCellRenderer(); // must take this from list, not combo: flatlaf bug - int i, h = 0; + int i; + int h = 0; for (i = 0; i < rowCount; ++i) { Component c = r.getListCellRendererComponent(jList, getModel().getElementAt(i), i, false, false); h += c.getPreferredSize().height; diff --git a/src/org/openstreetmap/josm/gui/widgets/JosmComboBoxModel.java b/src/org/openstreetmap/josm/gui/widgets/JosmComboBoxModel.java index 706354623df..892d1058a79 100644 --- a/src/org/openstreetmap/josm/gui/widgets/JosmComboBoxModel.java +++ b/src/org/openstreetmap/josm/gui/widgets/JosmComboBoxModel.java @@ -49,7 +49,7 @@ public Collection asCollection() { /** * Returns the index of the specified element - * + *

              * Note: This is not part of the {@link javax.swing.ComboBoxModel} interface but is defined in * {@link javax.swing.DefaultComboBoxModel}. * @@ -183,7 +183,7 @@ public E getElementAt(int index) { */ public void addAllElements(Collection elems) { int index0 = elements.size(); - elems.forEach(e -> doAddElement(e)); + elems.forEach(this::doAddElement); int index1 = elements.size() - 1; if (index0 <= index1) fireIntervalAdded(this, index0, index1); @@ -268,9 +268,9 @@ public Preferences prefs(Function readE, Function writeE) public final class Preferences { /** A {@link Function} that builds an {@code } from a {@code String}. */ - private Function readE; + private final Function readE; /** A {@code Function} that serializes {@code } to a {@code String}. */ - private Function writeE; + private final Function writeE; /** * Private constructor diff --git a/src/org/openstreetmap/josm/gui/widgets/JosmTextField.java b/src/org/openstreetmap/josm/gui/widgets/JosmTextField.java index 3320917643b..3ece8eea8d8 100644 --- a/src/org/openstreetmap/josm/gui/widgets/JosmTextField.java +++ b/src/org/openstreetmap/josm/gui/widgets/JosmTextField.java @@ -29,6 +29,7 @@ import org.openstreetmap.josm.gui.MainApplication; import org.openstreetmap.josm.gui.MapFrame; import org.openstreetmap.josm.tools.Destroyable; +import org.openstreetmap.josm.tools.Logging; import org.openstreetmap.josm.tools.Utils; /** @@ -289,8 +290,9 @@ public void paintComponent(Graphics g) { public void drawHint(Graphics g) { int x; try { - x = modelToView(0).x; + x = (int) Math.round(modelToView2D(0).getX()); } catch (BadLocationException exc) { + Logging.trace(exc); return; // can't happen } // Taken from http://stackoverflow.com/a/24571681/2257172 diff --git a/src/org/openstreetmap/josm/gui/widgets/MultiSplitLayout.java b/src/org/openstreetmap/josm/gui/widgets/MultiSplitLayout.java index e24b5c2f8a1..5c675ee131a 100644 --- a/src/org/openstreetmap/josm/gui/widgets/MultiSplitLayout.java +++ b/src/org/openstreetmap/josm/gui/widgets/MultiSplitLayout.java @@ -76,7 +76,7 @@ public class MultiSplitLayout implements LayoutManager { /** * Create a MultiSplitLayout with a default model with a single * Leaf node named "default". - * + *

              * #see setModel */ public MultiSplitLayout() { @@ -85,7 +85,7 @@ public MultiSplitLayout() { /** * Create a MultiSplitLayout with the specified model. - * + *

              * #see setModel * @param model model */ @@ -126,7 +126,7 @@ public PropertyChangeListener[] getPropertyChangeListeners() { } private void firePCS(String propertyName, Object oldValue, Object newValue) { - if (!(oldValue != null && newValue != null && oldValue.equals(newValue))) { + if (!(oldValue != null && oldValue.equals(newValue))) { pcs.firePropertyChange(propertyName, oldValue, newValue); } } @@ -148,16 +148,16 @@ public Node getModel() { * (the typical case) or a Leaf. The default value of this * property is a Leaf named "default". * - * @param model the root of the tree of Split, Leaf, and Divider node + * @param newModel the root of the tree of Split, Leaf, and Divider node * @throws IllegalArgumentException if model is a Divider or null * @see #getModel */ - public void setModel(Node model) { - if ((model == null) || (model instanceof Divider)) + public void setModel(Node newModel) { + if ((newModel == null) || (newModel instanceof Divider)) throw new IllegalArgumentException("invalid model"); - Node oldModel = model; - this.model = model; - firePCS("model", oldModel, model); + Node oldModel = this.model; + this.model = newModel; + firePCS("model", oldModel, newModel); } /** @@ -269,8 +269,8 @@ private Dimension preferredNodeSize(Node root) { if (root instanceof Leaf) return preferredComponentSize(root); else if (root instanceof Divider) { - int dividerSize = getDividerSize(); - return new Dimension(dividerSize, dividerSize); + int currentDividerSize = getDividerSize(); + return new Dimension(currentDividerSize, currentDividerSize); } else { Split split = (Split) root; List splitChildren = split.getChildren(); @@ -298,8 +298,8 @@ private Dimension minimumNodeSize(Node root) { Component child = childForNode(root); return (child != null) ? child.getMinimumSize() : new Dimension(0, 0); } else if (root instanceof Divider) { - int dividerSize = getDividerSize(); - return new Dimension(dividerSize, dividerSize); + int currentDividerSize = getDividerSize(); + return new Dimension(currentDividerSize, currentDividerSize); } else { Split split = (Split) root; List splitChildren = split.getChildren(); @@ -610,7 +610,7 @@ private void layout1(Node root, Rectangle bounds) { Split split = (Split) root; Iterator splitChildren = split.getChildren().iterator(); Rectangle childBounds; - int dividerSize = getDividerSize(); + int currentDividerSize = getDividerSize(); /* Layout the Split's child Nodes' along the X axis. The bounds * of each child will have the same y coordinate and height as the @@ -646,7 +646,7 @@ private void layout1(Node root, Rectangle bounds) { if (getFloatingDividers() && (dividerChild != null)) { double dividerX = childBounds.getMaxX(); - Rectangle dividerBounds = boundsWithXandWidth(bounds, dividerX, dividerSize); + Rectangle dividerBounds = boundsWithXandWidth(bounds, dividerX, currentDividerSize); dividerChild.setBounds(dividerBounds); } if (dividerChild != null) { @@ -679,7 +679,7 @@ private void layout1(Node root, Rectangle bounds) { if (getFloatingDividers() && (dividerChild != null)) { double dividerY = childBounds.getMaxY(); - Rectangle dividerBounds = boundsWithYandHeight(bounds, dividerY, dividerSize); + Rectangle dividerBounds = boundsWithYandHeight(bounds, dividerY, currentDividerSize); dividerChild.setBounds(dividerBounds); } if (dividerChild != null) { @@ -853,7 +853,7 @@ protected Node() { /** * Returns the Split parent of this Node, or null. - * + *

              * This method isn't called getParent(), in order to avoid problems * with recursive object creation when using XmlDecoder. * @@ -867,7 +867,7 @@ public Split getParent() { /** * Set the value of this Node's parent property. The default * value of this property is null. - * + *

              * This method isn't called setParent(), in order to avoid problems * with recursive object creation when using XmlEncoder. * @@ -935,10 +935,10 @@ public void setWeight(double weight) { } private Node siblingAtOffset(int offset) { - Split parent = getParent(); - if (parent == null) + Split currentParent = getParent(); + if (currentParent == null) return null; - List siblings = parent.getChildren(); + List siblings = currentParent.getChildren(); int index = siblings.indexOf(this); if (index == -1) return null; @@ -1049,9 +1049,9 @@ public void setChildren(List children) { * @see Node#getWeight */ public final Node lastWeightedChild() { - List children = getChildren(); + List currentChildren = getChildren(); Node weightedChild = null; - for (Node child : children) { + for (Node child : currentChildren) { if (child.getWeight() > 0.0) { weightedChild = child; } @@ -1062,13 +1062,11 @@ public final Node lastWeightedChild() { @Override public String toString() { int nChildren = getChildren().size(); - StringBuilder sb = new StringBuilder("MultiSplitLayout.Split"); - sb.append(isRowLayout() ? " ROW [" : " COLUMN [") - .append(nChildren) - .append((nChildren == 1) ? " child" : " children") - .append("] ") - .append(getBounds()); - return sb.toString(); + return "MultiSplitLayout.Split" + (isRowLayout() ? " ROW [" : " COLUMN [") + + nChildren + + ((nChildren == 1) ? " child" : " children") + + "] " + + getBounds(); } } diff --git a/src/org/openstreetmap/josm/gui/widgets/MultiSplitPane.java b/src/org/openstreetmap/josm/gui/widgets/MultiSplitPane.java index 4a0c092a711..99cc44c0e24 100644 --- a/src/org/openstreetmap/josm/gui/widgets/MultiSplitPane.java +++ b/src/org/openstreetmap/josm/gui/widgets/MultiSplitPane.java @@ -153,7 +153,7 @@ public interface DividerPainter { void paint(Graphics g, Divider divider); } - private class DefaultDividerPainter implements DividerPainter { + private final class DefaultDividerPainter implements DividerPainter { @Override public void paint(Graphics g, Divider divider) { if (g instanceof Graphics2D && divider == activeDivider() && !isContinuousLayout()) { @@ -343,7 +343,7 @@ private void updateCursor(int x, int y, boolean show) { setCursor(Cursor.getPredefinedCursor(cursorID)); } - private class InputHandler extends MouseInputAdapter implements KeyListener { + private final class InputHandler extends MouseInputAdapter implements KeyListener { @Override public void mouseEntered(MouseEvent e) { diff --git a/src/org/openstreetmap/josm/gui/widgets/OsmIdTextField.java b/src/org/openstreetmap/josm/gui/widgets/OsmIdTextField.java index 9851916a9d6..6e39b9d052c 100644 --- a/src/org/openstreetmap/josm/gui/widgets/OsmIdTextField.java +++ b/src/org/openstreetmap/josm/gui/widgets/OsmIdTextField.java @@ -94,7 +94,7 @@ public void validate() { public boolean readOsmIds() { String value = getComponent().getText(); char c; - if (Utils.isBlank(value)) { + if (Utils.isStripEmpty(value)) { return false; } ids.clear(); diff --git a/src/org/openstreetmap/josm/io/AbstractParser.java b/src/org/openstreetmap/josm/io/AbstractParser.java index f02d9939dfb..895b94a00fc 100644 --- a/src/org/openstreetmap/josm/io/AbstractParser.java +++ b/src/org/openstreetmap/josm/io/AbstractParser.java @@ -4,6 +4,8 @@ import static org.openstreetmap.josm.tools.I18n.tr; import java.time.Instant; +import java.util.HashMap; +import java.util.Map; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.OsmPrimitiveType; @@ -30,6 +32,7 @@ public abstract class AbstractParser extends DefaultHandler { protected Locator locator; /** if true, replace user information in input by anonymous user */ protected boolean useAnonymousUser; + private Map memberCache = new HashMap<>(); @Override public void setDocumentLocator(Locator locator) { @@ -181,7 +184,9 @@ protected void handleMember(Attributes atts) throws SAXException { } String role = getMandatoryAttributeString(atts, "role"); RelationMemberData member = new RelationMemberData(role, type, ref); - ((HistoryRelation) currentPrimitive).addMember(member); + // see #20405: cache equal instances of members + RelationMemberData cachedMember = memberCache.computeIfAbsent(member, m -> m); + ((HistoryRelation) currentPrimitive).addMember(cachedMember); } protected final boolean doStartElement(String qName, Attributes atts) throws SAXException { diff --git a/src/org/openstreetmap/josm/io/AbstractReader.java b/src/org/openstreetmap/josm/io/AbstractReader.java index e36827f70db..56e9847385b 100644 --- a/src/org/openstreetmap/josm/io/AbstractReader.java +++ b/src/org/openstreetmap/josm/io/AbstractReader.java @@ -86,6 +86,11 @@ public static void deregisterPostprocessor(OsmServerReadPostprocessor pp) { } } + /** + * A lookup table to avoid calling {@link String#intern()} unnecessarily. + */ + private final Map tagMap = new HashMap<>(); + /** * The dataset to add parsed objects to. */ @@ -369,6 +374,7 @@ private DataSet doParseDataSet(InputStream source, ProgressMonitor progressMonit } } } + this.tagMap.clear(); progressMonitor.finishTask(); progressMonitor.removeCancelListener(cancelListener); } @@ -604,7 +610,7 @@ protected final void parseTag(Tagged t, String key, String value) throws Illegal // Drop the tag on import, but flag the primitive as modified ((AbstractPrimitive) t).setModified(true); } else { - t.put(key.intern(), value.intern()); + t.put(this.tagMap.computeIfAbsent(key, Utils::intern), this.tagMap.computeIfAbsent(value, Utils::intern)); } } diff --git a/src/org/openstreetmap/josm/io/BoundingBoxDownloader.java b/src/org/openstreetmap/josm/io/BoundingBoxDownloader.java index a951d832f0e..4cf802c1f7e 100644 --- a/src/org/openstreetmap/josm/io/BoundingBoxDownloader.java +++ b/src/org/openstreetmap/josm/io/BoundingBoxDownloader.java @@ -6,6 +6,8 @@ import java.io.IOException; import java.io.InputStream; import java.net.SocketException; +import java.util.Collection; +import java.util.Collections; import java.util.List; import org.openstreetmap.josm.data.Bounds; @@ -73,7 +75,7 @@ private GpxData downloadRawGps(Bounds b, ProgressMonitor progressMonitor) throws Object trackUrl = track.get("url"); if (trackUrl instanceof String) { String sTrackUrl = (String) trackUrl; - if (!Utils.isBlank(sTrackUrl) && !sTrackUrl.startsWith("http")) { + if (!Utils.isStripEmpty(sTrackUrl) && !sTrackUrl.startsWith("http")) { track.put("url", browseUrl + sTrackUrl); } } @@ -215,6 +217,23 @@ public DataSet parseOsm(ProgressMonitor progressMonitor) throws OsmTransferExcep ds = parseDataSet(in, progressMonitor.createSubTaskMonitor(1, false)); } } + // From https://wiki.openstreetmap.org/wiki/API_v0.6#Retrieving_map_data_by_bounding_box:_GET_/api/0.6/map, + // relations are not recursed up, so they *may* have parent relations. + // Nodes inside the download area should have all relations and ways that refer to them. + // Ways should have all relations that refer to them and all child nodes, but those child nodes may not + // have their parent referrers. + // Relations will have the *first* parent relations downloaded, but those are not split out in the returns. + // So we always assume that a relation has referrers that need to be downloaded unless it has no child relations. + // Our "full" overpass query doesn't return the same data as a standard download, so we cannot + // mark relations with no child relations as fully downloaded *yet*. + if (this.considerAsFullDownload()) { + final Collection bounds = this.getBounds(); + // We cannot use OsmPrimitive#isOutsideDownloadArea yet since some download methods haven't added + // the download bounds to the dataset yet. This is specifically the case for overpass downloads. + ds.getNodes().stream().filter(n -> bounds.stream().anyMatch(b -> b.contains(n))) + .forEach(i -> i.setReferrersDownloaded(true)); + ds.getWays().forEach(i -> i.setReferrersDownloaded(true)); + } return ds; } catch (OsmTransferException e) { throw e; @@ -279,4 +298,12 @@ public boolean considerAsFullDownload() { return true; } + /** + * Get the bounds for this downloader + * @return The bounds for this downloader + * @since xxx + */ + protected Collection getBounds() { + return Collections.singleton(new Bounds(this.lat1, this.lon1, this.lat2, this.lon2)); + } } diff --git a/src/org/openstreetmap/josm/io/CachedFile.java b/src/org/openstreetmap/josm/io/CachedFile.java index 76ba83cabcc..5fb53c05911 100644 --- a/src/org/openstreetmap/josm/io/CachedFile.java +++ b/src/org/openstreetmap/josm/io/CachedFile.java @@ -242,7 +242,7 @@ public InputStream getInputStream() throws IOException { * @throws IOException in case of an I/O error */ public byte[] getByteContent() throws IOException { - return Utils.readBytesFromStream(getInputStream()); + return getInputStream().readAllBytes(); } /** diff --git a/src/org/openstreetmap/josm/io/CertificateAmendment.java b/src/org/openstreetmap/josm/io/CertificateAmendment.java index ba5ff135b4e..5922c9f7eb2 100644 --- a/src/org/openstreetmap/josm/io/CertificateAmendment.java +++ b/src/org/openstreetmap/josm/io/CertificateAmendment.java @@ -173,11 +173,6 @@ public String toString() { "CA_Disig_Root_R2.pem", "e23d4a036d7b70e9f595b1422079d2b91edfbb1fb651a0633eaa8a9dc5f80703", "https://eidas.disig.sk"), - // #17062 - Government of Taiwan - for https://data.gov.tw/license - https://grca.nat.gov.tw/GRCAeng/index.html (expires 2032) - new NativeCertAmend(Arrays.asList("TW Government Root Certification Authority", "Government Root Certification Authority"), - "Taiwan_GRCA.pem", - "7600295eefe85b9e1fd624db76062aaaae59818a54d2774cd4c0b2c01131e1b3", - "https://grca.nat.gov.tw"), // #17668 - used by city of Budapest - for https://terinfo.ujbuda.hu - https://e-szigno.hu/ (expires 2029) new NativeCertAmend(Collections.singleton("MicroSec e-Szigno Root CA 2009"), "Microsec_e-Szigno_Root_CA_2009.pem", diff --git a/src/org/openstreetmap/josm/io/ChangesetQuery.java b/src/org/openstreetmap/josm/io/ChangesetQuery.java index 082cb407869..64beb1e3921 100644 --- a/src/org/openstreetmap/josm/io/ChangesetQuery.java +++ b/src/org/openstreetmap/josm/io/ChangesetQuery.java @@ -1,6 +1,7 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.io; +import static org.openstreetmap.josm.tools.I18n.marktr; import static org.openstreetmap.josm.tools.I18n.tr; import java.text.MessageFormat; @@ -30,6 +31,9 @@ * @see OSM API 0.6 call "/changesets?" */ public class ChangesetQuery { + private static final String ERROR_UNEXPECTED_VALUE_CHANGESET_QUERY_URL = + marktr("Unexpected value for ''{0}'' in changeset query url, got {1}"); + private static final String DISPLAY_NAME = "display_name"; /** * Maximum number of changesets returned by the OSM API call "/changesets?" @@ -101,7 +105,7 @@ public ChangesetQuery forUser(int uid) { /** * Restricts the query to changesets owned by the user with user name username. - * + *

              * Caveat: for historical reasons the username might not be unique! It is recommended to use * {@link #forUser(int)} to restrict the query to a specific user. * @@ -225,7 +229,7 @@ public ChangesetQuery inBbox(double minLon, double minLat, double maxLon, double if (!LatLon.isValidLat(minLat)) throw new IllegalArgumentException(tr("Illegal latitude value for parameter ''{0}'', got {1}", "minLat", minLat)); if (!LatLon.isValidLat(maxLat)) - throw new IllegalArgumentException(tr("Illegal longitude value for parameter ''{0}'', got {1}", "maxLat", maxLat)); + throw new IllegalArgumentException(tr("Illegal latitude value for parameter ''{0}'', got {1}", "maxLat", maxLat)); return inBbox(new LatLon(minLon, minLat), new LatLon(maxLon, maxLat)); } @@ -355,8 +359,8 @@ public String getQueryString() { if (sb.length() > 0) { sb.append('&'); } - sb.append("time=").append(closedAfter); - sb.append(',').append(createdBefore); + sb.append("time=").append(closedAfter) + .append(',').append(createdBefore); } else if (closedAfter != null) { if (sb.length() > 0) { sb.append('&'); @@ -368,7 +372,7 @@ public String getQueryString() { if (sb.length() > 0) { sb.append('&'); } - sb.append("open=").append(Boolean.toString(open)); + sb.append("open=").append(open); } else if (closed != null) { if (sb.length() > 0) { sb.append('&'); @@ -431,64 +435,55 @@ public ChangesetQueryUrlException(Throwable cause) { */ public static class ChangesetQueryUrlParser { protected int parseUid(String value) throws ChangesetQueryUrlException { - if (Utils.isBlank(value)) - throw new ChangesetQueryUrlException( - tr("Unexpected value for ''{0}'' in changeset query url, got {1}", "uid", value)); + if (Utils.isStripEmpty(value)) + throw new ChangesetQueryUrlException(tr(ERROR_UNEXPECTED_VALUE_CHANGESET_QUERY_URL, "uid", value)); int id; try { id = Integer.parseInt(value); if (id <= 0) - throw new ChangesetQueryUrlException( - tr("Unexpected value for ''{0}'' in changeset query url, got {1}", "uid", value)); + throw new ChangesetQueryUrlException(tr(ERROR_UNEXPECTED_VALUE_CHANGESET_QUERY_URL, "uid", value)); } catch (NumberFormatException e) { - throw new ChangesetQueryUrlException( - tr("Unexpected value for ''{0}'' in changeset query url, got {1}", "uid", value), e); + throw new ChangesetQueryUrlException(tr(ERROR_UNEXPECTED_VALUE_CHANGESET_QUERY_URL, "uid", value), e); } return id; } protected boolean parseBoolean(String value, String parameter) throws ChangesetQueryUrlException { - if (Utils.isBlank(value)) - throw new ChangesetQueryUrlException( - tr("Unexpected value for ''{0}'' in changeset query url, got {1}", parameter, value)); + if (Utils.isStripEmpty(value)) + throw new ChangesetQueryUrlException(tr(ERROR_UNEXPECTED_VALUE_CHANGESET_QUERY_URL, parameter, value)); switch (value) { case "true": return true; case "false": return false; default: - throw new ChangesetQueryUrlException( - tr("Unexpected value for ''{0}'' in changeset query url, got {1}", parameter, value)); + throw new ChangesetQueryUrlException(tr(ERROR_UNEXPECTED_VALUE_CHANGESET_QUERY_URL, parameter, value)); } } protected Instant parseDate(String value, String parameter) throws ChangesetQueryUrlException { - if (Utils.isBlank(value)) - throw new ChangesetQueryUrlException( - tr("Unexpected value for ''{0}'' in changeset query url, got {1}", parameter, value)); + if (Utils.isStripEmpty(value)) + throw new ChangesetQueryUrlException(tr(ERROR_UNEXPECTED_VALUE_CHANGESET_QUERY_URL, parameter, value)); try { return DateUtils.parseInstant(value); } catch (UncheckedParseException e) { - throw new ChangesetQueryUrlException( - tr("Unexpected value for ''{0}'' in changeset query url, got {1}", parameter, value), e); + throw new ChangesetQueryUrlException(tr(ERROR_UNEXPECTED_VALUE_CHANGESET_QUERY_URL, parameter, value), e); } } protected Instant[] parseTime(String value) throws ChangesetQueryUrlException { String[] dates = value.split(",", -1); if (dates.length == 0 || dates.length > 2) - throw new ChangesetQueryUrlException( - tr("Unexpected value for ''{0}'' in changeset query url, got {1}", "time", value)); + throw new ChangesetQueryUrlException(tr(ERROR_UNEXPECTED_VALUE_CHANGESET_QUERY_URL, "time", value)); if (dates.length == 1) return new Instant[]{parseDate(dates[0], "time")}; - else if (dates.length == 2) - return new Instant[]{parseDate(dates[0], "time"), parseDate(dates[1], "time")}; - return new Instant[]{}; + // This will always have length 2, due to the (dates.length == 0 || dates.length > 2) check above. + return new Instant[]{parseDate(dates[0], "time"), parseDate(dates[1], "time")}; } protected Collection parseLongs(String value) { if (Utils.isEmpty(value)) { - return Collections.emptySet(); + return Collections.emptySet(); } else { return Stream.of(value.split(",", -1)).map(Long::valueOf).collect(Collectors.toSet()); } @@ -499,18 +494,18 @@ protected ChangesetQuery createFromMap(Map queryParams) throws C for (Entry entry: queryParams.entrySet()) { String k = entry.getKey(); - switch(k) { + switch (k) { case "uid": - if (queryParams.containsKey("display_name")) + if (queryParams.containsKey(DISPLAY_NAME)) throw new ChangesetQueryUrlException( tr("Cannot create a changeset query including both the query parameters ''uid'' and ''display_name''")); csQuery.forUser(parseUid(queryParams.get("uid"))); break; - case "display_name": + case DISPLAY_NAME: if (queryParams.containsKey("uid")) throw new ChangesetQueryUrlException( tr("Cannot create a changeset query including both the query parameters ''uid'' and ''display_name''")); - csQuery.forUser(queryParams.get("display_name")); + csQuery.forUser(queryParams.get(DISPLAY_NAME)); break; case "open": csQuery.beingOpen(parseBoolean(entry.getValue(), "open")); @@ -520,7 +515,7 @@ protected ChangesetQuery createFromMap(Map queryParams) throws C break; case "time": Instant[] dates = parseTime(entry.getValue()); - switch(dates.length) { + switch (dates.length) { case 1: csQuery.closedAfter(dates[0]); break; @@ -565,10 +560,10 @@ protected Map createMapFromQueryString(String query) { /** * Parses the changeset query given as URL query parameters and replies a {@link ChangesetQuery}. - * - * query is the query part of a API url for querying changesets, + *

              + * query is the query part of an API url for querying changesets, * see OSM API. - * + *

              * Example for a query string:
              *

                        *    uid=1234&open=true
              diff --git a/src/org/openstreetmap/josm/io/ChangesetUpdater.java b/src/org/openstreetmap/josm/io/ChangesetUpdater.java
              index 86605d16856..da4075ea943 100644
              --- a/src/org/openstreetmap/josm/io/ChangesetUpdater.java
              +++ b/src/org/openstreetmap/josm/io/ChangesetUpdater.java
              @@ -34,7 +34,7 @@ private ChangesetUpdater() {
               
                   private static volatile ScheduledFuture task;
               
              -    private static class Worker implements Runnable {
              +    private static final class Worker implements Runnable {
               
                       private long lastTimeInMillis;
               
              diff --git a/src/org/openstreetmap/josm/io/DefaultProxySelector.java b/src/org/openstreetmap/josm/io/DefaultProxySelector.java
              index dfcc2f37173..4d0e89c99b3 100644
              --- a/src/org/openstreetmap/josm/io/DefaultProxySelector.java
              +++ b/src/org/openstreetmap/josm/io/DefaultProxySelector.java
              @@ -147,7 +147,7 @@ public final void initFromPreferences() {
                       int port = parseProxyPortValue(PROXY_HTTP_PORT, Config.getPref().get(PROXY_HTTP_PORT, null));
                       httpProxySocketAddress = null;
                       if (proxyPolicy == ProxyPolicy.USE_HTTP_PROXY) {
              -            if (!Utils.isBlank(host) && port > 0) {
              +            if (!Utils.isStripEmpty(host) && port > 0) {
                               httpProxySocketAddress = new InetSocketAddress(host, port);
                           } else {
                               Logging.warn(tr("Unexpected parameters for HTTP proxy. Got host ''{0}'' and port ''{1}''.", host, port));
              @@ -159,7 +159,7 @@ public final void initFromPreferences() {
                       port = parseProxyPortValue(PROXY_SOCKS_PORT, Config.getPref().get(PROXY_SOCKS_PORT, null));
                       socksProxySocketAddress = null;
                       if (proxyPolicy == ProxyPolicy.USE_SOCKS_PROXY) {
              -            if (!Utils.isBlank(host) && port > 0) {
              +            if (!Utils.isStripEmpty(host) && port > 0) {
                               socksProxySocketAddress = new InetSocketAddress(host, port);
                           } else {
                               Logging.warn(tr("Unexpected parameters for SOCKS proxy. Got host ''{0}'' and port ''{1}''.", host, port));
              diff --git a/src/org/openstreetmap/josm/io/DiffResultProcessor.java b/src/org/openstreetmap/josm/io/DiffResultProcessor.java
              index bc0d8f0a0f2..647d44ed566 100644
              --- a/src/org/openstreetmap/josm/io/DiffResultProcessor.java
              +++ b/src/org/openstreetmap/josm/io/DiffResultProcessor.java
              @@ -166,7 +166,7 @@ protected Set postProcess(Changeset cs, ProgressMonitor monitor) {
                       }
                   }
               
              -    private class Parser extends DefaultHandler {
              +    private final class Parser extends DefaultHandler {
                       private Locator locator;
               
                       @Override
              @@ -174,7 +174,7 @@ public void setDocumentLocator(Locator locator) {
                           this.locator = locator;
                       }
               
              -        protected void throwException(String msg) throws XmlParsingException {
              +        void throwException(String msg) throws XmlParsingException {
                           throw new XmlParsingException(msg).rememberLocation(locator);
                       }
               
              diff --git a/src/org/openstreetmap/josm/io/FileWatcher.java b/src/org/openstreetmap/josm/io/FileWatcher.java
              index af40cf904b8..f2c2c9dd279 100644
              --- a/src/org/openstreetmap/josm/io/FileWatcher.java
              +++ b/src/org/openstreetmap/josm/io/FileWatcher.java
              @@ -34,7 +34,7 @@ public class FileWatcher {
                   private static final Map> loaderMap = new EnumMap<>(SourceType.class);
                   private final Map sourceMap = new HashMap<>();
               
              -    private static class InstanceHolder {
              +    private static final class InstanceHolder {
                       static final FileWatcher INSTANCE = new FileWatcher();
                   }
               
              diff --git a/src/org/openstreetmap/josm/io/GeoJSONWriter.java b/src/org/openstreetmap/josm/io/GeoJSONWriter.java
              index 5075f96411a..3179d3bf7e9 100644
              --- a/src/org/openstreetmap/josm/io/GeoJSONWriter.java
              +++ b/src/org/openstreetmap/josm/io/GeoJSONWriter.java
              @@ -79,7 +79,7 @@ enum Options {
                    * Used to avoid many calls to {@link JsonProvider#provider} in {@link #getCoorArray(JsonArrayBuilder, EastNorth)}.
                    * For validating Mesa County, CO, this reduces CPU and memory usage of {@link #write()} by ~80%. By using this for
                    * other {@link Json} calls, {@link #write()} takes ~95% less resources than the original. And the entire process
              -     * takes 1/4 of the time (38 minutes -> <10 minutes).
              +     * takes 1/4 of the time (38 minutes → <10 minutes).
                    * 

              * For more details, see JSONP #346. */ diff --git a/src/org/openstreetmap/josm/io/GpxParser.java b/src/org/openstreetmap/josm/io/GpxParser.java index 2031985b84f..d294d20224b 100644 --- a/src/org/openstreetmap/josm/io/GpxParser.java +++ b/src/org/openstreetmap/josm/io/GpxParser.java @@ -141,7 +141,7 @@ private static LatLon parseLatLon(Attributes attributes) { @Override public void startElement(String namespaceURI, String localName, String qName, Attributes attributes) throws SAXException { elements.push(new String[] {namespaceURI, localName, qName}); - switch(currentState) { + switch (currentState) { case INIT: startElementInit(attributes); break; @@ -615,6 +615,7 @@ private void endElementWpt(String localName) throws SAXException { case "urlname": case "cmt": case "desc": + case "dgpsid": case "fix": currentWayPoint.put(localName, accumulator.toString()); break; @@ -780,7 +781,7 @@ GpxData getData() { } /** - * convert url/urlname to link element (GPX 1.0 -> GPX 1.1). + * convert url/urlname to link element (GPX 1.0 → GPX 1.1). * @param attr attributes */ private static void convertUrlToLink(Map attr) { diff --git a/src/org/openstreetmap/josm/io/GpxReader.java b/src/org/openstreetmap/josm/io/GpxReader.java index 8775873231d..88b501df293 100644 --- a/src/org/openstreetmap/josm/io/GpxReader.java +++ b/src/org/openstreetmap/josm/io/GpxReader.java @@ -74,7 +74,7 @@ public boolean parse(boolean tryToFinish) throws SAXException, IOException { if (dot) message += '.'; } - if (!Utils.isBlank(parser.getData().creator)) { + if (!Utils.isStripEmpty(parser.getData().creator)) { message += "\n" + tr("The file was created by \"{0}\".", parser.getData().creator); } SAXException ex = new SAXException(message, e); diff --git a/src/org/openstreetmap/josm/io/GpxWriter.java b/src/org/openstreetmap/josm/io/GpxWriter.java index 6deef446df0..568eb44c567 100644 --- a/src/org/openstreetmap/josm/io/GpxWriter.java +++ b/src/org/openstreetmap/josm/io/GpxWriter.java @@ -146,7 +146,7 @@ public void write(GpxData data, ColorFormat colorFormat, boolean savePrefs) { .filter(Objects::nonNull) .collect(Collectors.toList()); - validprefixes = namespaces.stream().map(n -> n.getPrefix()).collect(Collectors.toList()); + validprefixes = namespaces.stream().map(XMLNamespace::getPrefix).collect(Collectors.toList()); data.creator = JOSM_CREATOR_NAME; out.println(""); @@ -387,7 +387,7 @@ private void gpxLink(GpxLink link) { */ private void wayPoint(WayPoint pnt, int mode) { String type; - switch(mode) { + switch (mode) { case WAY_POINT: type = "wpt"; break; diff --git a/src/org/openstreetmap/josm/io/MaxChangesetSizeExceededPolicy.java b/src/org/openstreetmap/josm/io/MaxChangesetSizeExceededPolicy.java index 8fb92b95130..416bf837cad 100644 --- a/src/org/openstreetmap/josm/io/MaxChangesetSizeExceededPolicy.java +++ b/src/org/openstreetmap/josm/io/MaxChangesetSizeExceededPolicy.java @@ -5,6 +5,7 @@ * This determines what to do when the max changeset size was exceeded by a upload. * @since 12687 (moved from {@code gui.io} package) */ +@SuppressWarnings("PMD.LongVariable") public enum MaxChangesetSizeExceededPolicy { /** * Abort uploading. Send the user back to map editing. diff --git a/src/org/openstreetmap/josm/io/MessageNotifier.java b/src/org/openstreetmap/josm/io/MessageNotifier.java index 9d25da8e95d..5ab9a33d7b5 100644 --- a/src/org/openstreetmap/josm/io/MessageNotifier.java +++ b/src/org/openstreetmap/josm/io/MessageNotifier.java @@ -69,7 +69,7 @@ public static void setNotifierCallback(NotifierCallback notifierCallback) { private static volatile ScheduledFuture task; - private static class Worker implements Runnable { + private static final class Worker implements Runnable { private int lastUnreadCount; private long lastTimeInMillis; @@ -144,9 +144,7 @@ public static boolean isUserEnoughIdentified() { CredentialsManager credManager = CredentialsManager.getInstance(); try { if (JosmPreferencesCredentialAgent.class.equals(credManager.getCredentialsAgentClass())) { - if (OsmApi.isUsingOAuth(OAuthVersion.OAuth10a)) { - return credManager.lookupOAuthAccessToken() != null; - } else if (OsmApi.isUsingOAuth(OAuthVersion.OAuth20) || OsmApi.isUsingOAuth(OAuthVersion.OAuth21)) { + if (OsmApi.isUsingOAuth(OAuthVersion.OAuth20) || OsmApi.isUsingOAuth(OAuthVersion.OAuth21)) { return credManager.lookupOAuthAccessToken(OsmApi.getOsmApi().getHost()) != null; } else if (OsmApi.isUsingOAuth()) { // Ensure we do not forget to update this section diff --git a/src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java b/src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java index 120b980da2b..cc69b10a30c 100644 --- a/src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java +++ b/src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java @@ -134,7 +134,7 @@ public static MultiFetchServerObjectReader create(final boolean fromMirror) { */ public void append(PrimitiveId id) { if (id.isNew()) return; - switch(id.getType()) { + switch (id.getType()) { case NODE: nodes.add(id.getUniqueId()); break; case WAY: ways.add(id.getUniqueId()); break; case RELATION: relations.add(id.getUniqueId()); break; @@ -356,7 +356,7 @@ protected void fetchPrimitives(Set ids, OsmPrimitiveType type, ProgressMon } Logging.error(e); if (e.getCause() instanceof OsmTransferException) - throw (OsmTransferException) e.getCause(); + throw (OsmTransferException) e.getCause(); // NOPMD } } exec.shutdown(); diff --git a/src/org/openstreetmap/josm/io/NameFinder.java b/src/org/openstreetmap/josm/io/NameFinder.java index 3e8ca33e6e6..25364e2e80a 100644 --- a/src/org/openstreetmap/josm/io/NameFinder.java +++ b/src/org/openstreetmap/josm/io/NameFinder.java @@ -237,7 +237,7 @@ public Bounds getDownloadArea() { * A very primitive parser for the name finder's output. * Structure of xml described here: http://wiki.openstreetmap.org/index.php/Name_finder */ - private static class NameFinderResultParser extends DefaultHandler { + private static final class NameFinderResultParser extends DefaultHandler { private SearchResult currentResult; private StringBuilder description; private int depth; diff --git a/src/org/openstreetmap/josm/io/NoteReader.java b/src/org/openstreetmap/josm/io/NoteReader.java index 6020360afed..42cad8d2997 100644 --- a/src/org/openstreetmap/josm/io/NoteReader.java +++ b/src/org/openstreetmap/josm/io/NoteReader.java @@ -51,7 +51,7 @@ private enum NoteParseMode { * SAX handler to read note information from its XML representation. * Reads both API style and planet dump style formats. */ - private class Parser extends DefaultHandler { + private final class Parser extends DefaultHandler { private NoteParseMode parseMode; private final StringBuilder buffer = new StringBuilder(); @@ -72,7 +72,7 @@ public void characters(char[] ch, int start, int length) throws SAXException { @Override public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException { buffer.setLength(0); - switch(qName) { + switch (qName) { case "osm": parseMode = NoteParseMode.API; notes = new ArrayList<>(100); @@ -81,6 +81,7 @@ public void startElement(String uri, String localName, String qName, Attributes parseMode = NoteParseMode.DUMP; notes = new ArrayList<>(10_000); return; + default: // Keep going } if (parseMode == NoteParseMode.API) { @@ -91,7 +92,7 @@ public void startElement(String uri, String localName, String qName, Attributes } //The rest only applies for dump mode - switch(qName) { + switch (qName) { case "note": thisNote = parseNoteFull(attrs); break; @@ -112,9 +113,11 @@ public void endElement(String namespaceURI, String localName, String qName) { notes.add(thisNote); } if ("comment".equals(qName)) { - User commentUser = User.createOsmUser(commentUid, commentUsername); + final User commentUser; if (commentUid == 0) { commentUser = User.getAnonymous(); + } else { + commentUser = User.createOsmUser(commentUid, commentUsername); } if (parseMode == NoteParseMode.API) { commentIsNew = false; @@ -166,6 +169,7 @@ public void endElement(String namespaceURI, String localName, String qName) { case "note": //nothing to do for comment or note, already handled above case "comment": break; + default: // Keep going (return) } } @@ -176,8 +180,8 @@ public void endDocument() throws SAXException { } static LatLon parseLatLon(UnaryOperator attrs) { - double lat = Double.parseDouble(attrs.apply("lat")); - double lon = Double.parseDouble(attrs.apply("lon")); + final double lat = Double.parseDouble(attrs.apply("lat")); + final double lon = Double.parseDouble(attrs.apply("lon")); return new LatLon(lat, lon); } @@ -194,7 +198,7 @@ static Note parseNoteFull(Attributes attrs) { } static Note parseNoteFull(UnaryOperator attrs) { - Note note = parseNoteBasic(attrs); + final Note note = parseNoteBasic(attrs); String id = attrs.apply("id"); if (id != null) { note.setId(Long.parseLong(id)); diff --git a/src/org/openstreetmap/josm/io/OnlineResource.java b/src/org/openstreetmap/josm/io/OnlineResource.java index 87e9f27e75f..9fa4dbed15a 100644 --- a/src/org/openstreetmap/josm/io/OnlineResource.java +++ b/src/org/openstreetmap/josm/io/OnlineResource.java @@ -35,7 +35,7 @@ public enum OnlineResource { * Replies the localized name. * @return the localized name */ - public final String getLocName() { + public String getLocName() { return locName; } @@ -44,7 +44,7 @@ public final String getLocName() { * @return the offline icon * @since 17041 */ - public final String getOfflineIcon() { + public String getOfflineIcon() { switch (this) { case OSM_API: return /* ICON() */ "offline_osm_api"; @@ -66,7 +66,7 @@ public final String getOfflineIcon() { * @param url the URL to check * @return whether the given URL matches this online resource */ - public final boolean matches(String url) { + public boolean matches(String url) { final String baseUrl; switch (this) { case ALL: diff --git a/src/org/openstreetmap/josm/io/OsmApi.java b/src/org/openstreetmap/josm/io/OsmApi.java index c253fd9a25b..ac839026d98 100644 --- a/src/org/openstreetmap/josm/io/OsmApi.java +++ b/src/org/openstreetmap/josm/io/OsmApi.java @@ -1,6 +1,7 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.io; +import static org.openstreetmap.josm.tools.I18n.marktr; import static org.openstreetmap.josm.tools.I18n.tr; import static org.openstreetmap.josm.tools.I18n.trn; @@ -51,6 +52,8 @@ import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; +import jakarta.annotation.Nullable; + /** * Class that encapsulates the communications with the OSM API.

              * @@ -61,6 +64,9 @@ * @since 1523 */ public class OsmApi extends OsmConnection { + private static final String CHANGESET_STR = "changeset"; + private static final String CHANGESET_SLASH = "changeset/"; + private static final String ERROR_MESSAGE = marktr("Changeset ID > 0 expected. Got {0}."); /** * Maximum number of retries to send a request in case of HTTP 500 errors or timeouts @@ -84,8 +90,6 @@ public class OsmApi extends OsmConnection { private static final Map instances = new HashMap<>(); private static final ListenerList listeners = ListenerList.create(); - /** This is used to make certain we have set osm-server.auth-method to the "right" default */ - private static boolean oauthCompatibilitySwitch; private URL url; @@ -464,7 +468,7 @@ public void deletePrimitive(OsmPrimitive osm, ProgressMonitor monitor) throws Os * @throws IllegalArgumentException if changeset is null */ public void openChangeset(Changeset changeset, ProgressMonitor progressMonitor) throws OsmTransferException { - CheckParameterUtil.ensureParameterNotNull(changeset, "changeset"); + CheckParameterUtil.ensureParameterNotNull(changeset, CHANGESET_STR); try { progressMonitor.beginTask(tr("Creating changeset...")); initialize(progressMonitor); @@ -495,17 +499,17 @@ public void openChangeset(Changeset changeset, ProgressMonitor progressMonitor) * */ public void updateChangeset(Changeset changeset, ProgressMonitor monitor) throws OsmTransferException { - CheckParameterUtil.ensureParameterNotNull(changeset, "changeset"); + CheckParameterUtil.ensureParameterNotNull(changeset, CHANGESET_STR); if (monitor == null) { monitor = NullProgressMonitor.INSTANCE; } if (changeset.getId() <= 0) - throw new IllegalArgumentException(tr("Changeset ID > 0 expected. Got {0}.", changeset.getId())); + throw new IllegalArgumentException(tr(ERROR_MESSAGE, changeset.getId())); try { monitor.beginTask(tr("Updating changeset...")); initialize(monitor); monitor.setCustomText(tr("Updating changeset {0}...", changeset.getId())); - sendPutRequest("changeset/" + changeset.getId(), toXml(changeset), monitor); + sendPutRequest(CHANGESET_SLASH + changeset.getId(), toXml(changeset), monitor); } catch (ChangesetClosedException e) { e.setSource(ChangesetClosedException.Source.UPDATE_CHANGESET); throw e; @@ -530,17 +534,17 @@ public void updateChangeset(Changeset changeset, ProgressMonitor monitor) throws * @throws IllegalArgumentException if changeset.getId() <= 0 */ public void closeChangeset(Changeset changeset, ProgressMonitor monitor) throws OsmTransferException { - CheckParameterUtil.ensureParameterNotNull(changeset, "changeset"); + CheckParameterUtil.ensureParameterNotNull(changeset, CHANGESET_STR); if (monitor == null) { monitor = NullProgressMonitor.INSTANCE; } if (changeset.getId() <= 0) - throw new IllegalArgumentException(tr("Changeset ID > 0 expected. Got {0}.", changeset.getId())); + throw new IllegalArgumentException(tr(ERROR_MESSAGE, changeset.getId())); try { monitor.beginTask(tr("Closing changeset...")); initialize(monitor); // send "\r\n" instead of empty string, so we don't send zero payload - workaround bugs in proxy software - sendPutRequest("changeset/" + changeset.getId() + "/close", "\r\n", monitor); + sendPutRequest(CHANGESET_SLASH + changeset.getId() + "/close", "\r\n", monitor); } catch (ChangesetClosedException e) { e.setSource(ChangesetClosedException.Source.CLOSE_CHANGESET); throw e; @@ -564,8 +568,8 @@ public void addCommentToChangeset(Changeset changeset, String comment, ProgressM if (changeset.isOpen()) throw new IllegalArgumentException(tr("Changeset must be closed in order to add a comment")); else if (changeset.getId() <= 0) - throw new IllegalArgumentException(tr("Changeset ID > 0 expected. Got {0}.", changeset.getId())); - sendRequest("POST", "changeset/" + changeset.getId() + "/comment?text="+ Utils.encodeUrl(comment), + throw new IllegalArgumentException(tr(ERROR_MESSAGE, changeset.getId())); + sendRequest("POST", CHANGESET_SLASH + changeset.getId() + "/comment?text="+ Utils.encodeUrl(comment), null, monitor, "application/x-www-form-urlencoded", true, false); } @@ -598,7 +602,7 @@ public Collection uploadDiff(Collection li // monitor.indeterminateSubTask( trn("Uploading {0} object...", "Uploading {0} objects...", list.size(), list.size())); - String diffUploadResponse = sendPostRequest("changeset/" + changeset.getId() + "/upload", diffUploadRequest, monitor); + String diffUploadResponse = sendPostRequest(CHANGESET_SLASH + changeset.getId() + "/upload", diffUploadRequest, monitor); // Process the response from the server // @@ -652,8 +656,7 @@ protected int getMaxRetries() { * @since 6349 */ public static boolean isUsingOAuth() { - return isUsingOAuth(OAuthVersion.OAuth10a) - || isUsingOAuth(OAuthVersion.OAuth20) + return isUsingOAuth(OAuthVersion.OAuth20) || isUsingOAuth(OAuthVersion.OAuth21); } @@ -664,10 +667,8 @@ public static boolean isUsingOAuth() { * @since 18650 */ public static boolean isUsingOAuth(OAuthVersion version) { - if (version == OAuthVersion.OAuth10a) { - return "oauth".equalsIgnoreCase(getAuthMethod()); - } else if (version == OAuthVersion.OAuth20 || version == OAuthVersion.OAuth21) { - return "oauth20".equalsIgnoreCase(getAuthMethod()); + if (version == OAuthVersion.OAuth20 || version == OAuthVersion.OAuth21) { + return getAuthMethodVersion() == OAuthVersion.OAuth20 || getAuthMethodVersion() == OAuthVersion.OAuth21; } return false; } @@ -679,9 +680,6 @@ public static boolean isUsingOAuth(OAuthVersion version) { */ public static boolean isUsingOAuthAndOAuthSetUp(OsmApi api) { if (OsmApi.isUsingOAuth()) { - if (OsmApi.isUsingOAuth(OAuthVersion.OAuth10a)) { - return OAuthAccessTokenHolder.getInstance().containsAccessToken(); - } if (OsmApi.isUsingOAuth(OAuthVersion.OAuth20)) { return OAuthAccessTokenHolder.getInstance().getAccessToken(api.getBaseUrl(), OAuthVersion.OAuth20) != null; } @@ -697,24 +695,23 @@ public static boolean isUsingOAuthAndOAuthSetUp(OsmApi api) { * @return the authentication method */ public static String getAuthMethod() { - setCurrentAuthMethod(); return Config.getPref().get("osm-server.auth-method", "oauth20"); } /** - * This is a compatibility method for users who currently use OAuth 1.0 -- we are changing the default from oauth to oauth20, - * but since oauth was the default, pre-existing users will suddenly be switched to oauth20. - * This should be removed whenever {@link OAuthVersion#OAuth10a} support is removed. - */ - private static void setCurrentAuthMethod() { - if (!oauthCompatibilitySwitch) { - oauthCompatibilitySwitch = true; - final String prefKey = "osm-server.auth-method"; - if ("oauth20".equals(Config.getPref().get(prefKey, "oauth20")) - && !isUsingOAuthAndOAuthSetUp(OsmApi.getOsmApi()) - && OAuthAccessTokenHolder.getInstance().containsAccessToken()) { - Config.getPref().put(prefKey, "oauth"); - } + * Returns the authentication method set in the preferences + * @return the authentication method + * @since 18991 + */ + @Nullable + public static OAuthVersion getAuthMethodVersion() { + switch (getAuthMethod()) { + case "oauth20": return OAuthVersion.OAuth20; + case "oauth21": return OAuthVersion.OAuth21; + case "basic": return null; + default: + Config.getPref().put("osm-server.auth-method", null); + return getAuthMethodVersion(); } } @@ -815,7 +812,7 @@ protected final String sendRequest(String requestMethod, String urlSuffix, Strin errorHeader = errorHeader == null ? null : errorHeader.trim(); String errorBody = responseBody.isEmpty() ? null : responseBody.trim(); - switch(retCode) { + switch (retCode) { case HttpURLConnection.HTTP_OK: return responseBody; case HttpURLConnection.HTTP_GONE: @@ -899,7 +896,7 @@ public void setChangeset(Changeset changeset) { return; } if (changeset.getId() <= 0) - throw new IllegalArgumentException(tr("Changeset ID > 0 expected. Got {0}.", changeset.getId())); + throw new IllegalArgumentException(tr(ERROR_MESSAGE, changeset.getId())); if (!changeset.isOpen()) throw new IllegalArgumentException(tr("Open changeset expected. Got closed changeset with id {0}.", changeset.getId())); this.changeset = changeset; @@ -960,8 +957,8 @@ public Note closeNote(Note note, String closeMessage, ProgressMonitor monitor) t StringBuilder urlBuilder = noteStringBuilder(note) .append("/close"); if (!encodedMessage.trim().isEmpty()) { - urlBuilder.append("?text="); - urlBuilder.append(encodedMessage); + urlBuilder.append("?text=") + .append(encodedMessage); } return parseSingleNote(sendPostRequest(urlBuilder.toString(), null, monitor)); @@ -981,8 +978,8 @@ public Note reopenNote(Note note, String reactivateMessage, ProgressMonitor moni StringBuilder urlBuilder = noteStringBuilder(note) .append("/reopen"); if (!encodedMessage.trim().isEmpty()) { - urlBuilder.append("?text="); - urlBuilder.append(encodedMessage); + urlBuilder.append("?text=") + .append(encodedMessage); } return parseSingleNote(sendPostRequest(urlBuilder.toString(), null, monitor)); diff --git a/src/org/openstreetmap/josm/io/OsmChangesetParser.java b/src/org/openstreetmap/josm/io/OsmChangesetParser.java index 80f98613cdc..715653304cf 100644 --- a/src/org/openstreetmap/josm/io/OsmChangesetParser.java +++ b/src/org/openstreetmap/josm/io/OsmChangesetParser.java @@ -58,7 +58,7 @@ public List getChangesets() { return changesets; } - private class Parser extends DefaultHandler { + private final class Parser extends DefaultHandler { private Locator locator; @Override @@ -66,7 +66,7 @@ public void setDocumentLocator(Locator locator) { this.locator = locator; } - protected void throwException(String msg) throws XmlParsingException { + void throwException(String msg) throws XmlParsingException { throw new XmlParsingException(msg).rememberLocation(locator); } @@ -79,7 +79,7 @@ protected void throwException(String msg) throws XmlParsingException { /** The current comment text */ private StringBuilder text; - protected void parseChangesetAttributes(Attributes atts) throws XmlParsingException { + void parseChangesetAttributes(Attributes atts) throws XmlParsingException { // -- id String value = atts.getValue("id"); if (value == null) { @@ -247,7 +247,7 @@ public void endElement(String uri, String localName, String qName) throws SAXExc } } - protected User createUser(Attributes atts) throws XmlParsingException { + User createUser(Attributes atts) throws XmlParsingException { String name = atts.getValue("user"); String uid = atts.getValue("uid"); if (uid == null) { diff --git a/src/org/openstreetmap/josm/io/OsmConnection.java b/src/org/openstreetmap/josm/io/OsmConnection.java index 4a489445f77..cc29e6f454f 100644 --- a/src/org/openstreetmap/josm/io/OsmConnection.java +++ b/src/org/openstreetmap/josm/io/OsmConnection.java @@ -5,7 +5,6 @@ import java.lang.reflect.InvocationTargetException; import java.net.Authenticator.RequestorType; -import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Base64; @@ -35,9 +34,6 @@ import org.openstreetmap.josm.tools.JosmRuntimeException; import org.openstreetmap.josm.tools.Logging; -import oauth.signpost.OAuthConsumer; -import oauth.signpost.exception.OAuthException; - /** * Base class that handles common things like authentication for the reader and writer * to the osm server. @@ -50,7 +46,6 @@ public class OsmConnection { protected boolean cancel; protected HttpClient activeConnection; - protected OAuthParameters oauthParameters; protected IOAuthParameters oAuth20Parameters; /** @@ -144,57 +139,6 @@ protected void addBasicAuthorizationHeader(HttpClient con) throws OsmTransferExc } } - /** - * Signs the connection with an OAuth authentication header - * - * @param connection the connection - * - * @throws MissingOAuthAccessTokenException if there is currently no OAuth Access Token configured - * @throws OsmTransferException if signing fails - */ - protected void addOAuthAuthorizationHeader(HttpClient connection) throws OsmTransferException { - if (oauthParameters == null) { - oauthParameters = OAuthParameters.createFromApiUrl(OsmApi.getOsmApi().getServerUrl()); - } - OAuthConsumer consumer = oauthParameters.buildConsumer(); - OAuthAccessTokenHolder holder = OAuthAccessTokenHolder.getInstance(); - if (!holder.containsAccessToken()) { - obtainAccessToken(connection); - } - if (!holder.containsAccessToken()) { // check if wizard completed - throw new MissingOAuthAccessTokenException(); - } - consumer.setTokenWithSecret(holder.getAccessTokenKey(), holder.getAccessTokenSecret()); - try { - consumer.sign(connection); - } catch (OAuthException e) { - throw new OsmTransferException(tr("Failed to sign a HTTP connection with an OAuth Authentication header"), e); - } - } - - /** - * Obtains an OAuth access token for the connection. - * Afterwards, the token is accessible via {@link OAuthAccessTokenHolder} / {@link CredentialsManager}. - * @param connection connection for which the access token should be obtained - * @throws MissingOAuthAccessTokenException if the process cannot be completed successfully - */ - protected void obtainAccessToken(final HttpClient connection) throws MissingOAuthAccessTokenException { - try { - final URL apiUrl = new URL(OsmApi.getOsmApi().getServerUrl()); - if (!Objects.equals(apiUrl.getHost(), connection.getURL().getHost())) { - throw new MissingOAuthAccessTokenException(); - } - fetcher.obtainAccessToken(apiUrl); - OAuthAccessTokenHolder.getInstance().setSaveToPreferences(true); - OAuthAccessTokenHolder.getInstance().save(CredentialsManager.getInstance()); - } catch (MalformedURLException | InvocationTargetException e) { - throw new MissingOAuthAccessTokenException(e); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new MissingOAuthAccessTokenException(e); - } - } - /** * Obtains an OAuth access token for the connection. * Afterwards, the token is accessible via {@link OAuthAccessTokenHolder} / {@link CredentialsManager}. @@ -220,7 +164,6 @@ private void obtainOAuth20Token() throws MissingOAuthAccessTokenException { RemoteControl.stop(); } // Clean up old token/password - OAuthAccessTokenHolder.getInstance().setAccessToken(null); OAuthAccessTokenHolder.getInstance().setAccessToken(OsmApi.getOsmApi().getServerUrl(), authToken.orElse(null)); OAuthAccessTokenHolder.getInstance().save(CredentialsManager.getInstance()); done.countDown(); @@ -280,9 +223,6 @@ protected void addAuth(HttpClient connection) throws OsmTransferException { case "basic": addBasicAuthorizationHeader(connection); return; - case "oauth": - addOAuthAuthorizationHeader(connection); - return; case "oauth20": addOAuth20AuthorizationHeader(connection); return; diff --git a/src/org/openstreetmap/josm/io/OsmHistoryReader.java b/src/org/openstreetmap/josm/io/OsmHistoryReader.java index 97ee549515e..6bfebb1a10e 100644 --- a/src/org/openstreetmap/josm/io/OsmHistoryReader.java +++ b/src/org/openstreetmap/josm/io/OsmHistoryReader.java @@ -34,9 +34,9 @@ public class OsmHistoryReader { private final InputStream in; private final HistoryDataSet data; - private class Parser extends AbstractParser { + private final class Parser extends AbstractParser { - protected String getCurrentPosition() { + String getCurrentPosition() { if (locator == null) return ""; return "(" + locator.getLineNumber() + diff --git a/src/org/openstreetmap/josm/io/OsmPbfReader.java b/src/org/openstreetmap/josm/io/OsmPbfReader.java index 4028ba7f645..b554d914721 100644 --- a/src/org/openstreetmap/josm/io/OsmPbfReader.java +++ b/src/org/openstreetmap/josm/io/OsmPbfReader.java @@ -15,10 +15,6 @@ import java.util.Map; import java.util.Set; -import jakarta.annotation.Nonnull; -import jakarta.annotation.Nullable; - -import org.apache.commons.compress.utils.CountingInputStream; import org.openstreetmap.josm.data.Bounds; import org.openstreetmap.josm.data.DataSource; import org.openstreetmap.josm.data.coor.LatLon; @@ -30,6 +26,7 @@ import org.openstreetmap.josm.data.osm.RelationData; import org.openstreetmap.josm.data.osm.RelationMemberData; import org.openstreetmap.josm.data.osm.Tagged; +import org.openstreetmap.josm.data.osm.UploadPolicy; import org.openstreetmap.josm.data.osm.User; import org.openstreetmap.josm.data.osm.WayData; import org.openstreetmap.josm.data.osm.pbf.Blob; @@ -44,11 +41,86 @@ import org.openstreetmap.josm.gui.progress.ProgressMonitor; import org.openstreetmap.josm.tools.Utils; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; + /** * Read OSM data from an OSM PBF file * @since 18695 */ public final class OsmPbfReader extends AbstractReader { + /** + * This could be replaced by {@link org.apache.commons.io.input.BoundedInputStream} from Apache Commons IO. + * However, Commons IO is not currently (2024-05-13) a required JOSM dependency, so we should avoid using it + * for now. Commons IO is a transitive dependency, currently pulled in by {@link org.apache.commons.compress} + * (see {@link org.apache.commons.compress.utils.BoundedInputStream}). + */ + private static final class BoundedInputStream extends InputStream { + private final InputStream source; + private long count; + private long mark; + + BoundedInputStream(InputStream source) { + this.source = source; + } + + @Override + public int read() throws IOException { + final int read = this.source.read(); + if (read > 0) { + count++; + } + return read; + } + + @Override + public int read(@Nonnull byte[] b, int off, int len) throws IOException { + final int read = this.source.read(b, off, len); + if (read > 0) { + this.count += read; + } + return read; + } + + @Override + public long skip(long n) throws IOException { + long skipped = super.skip(n); + this.count += skipped; + return skipped; + } + + @Override + public int available() throws IOException { + return this.source.available(); + } + + @Override + public void close() throws IOException { + this.source.close(); + } + + @Override + public synchronized void mark(int readlimit) { + this.source.mark(readlimit); + this.mark = this.count; + } + + @Override + public synchronized void reset() throws IOException { + this.source.reset(); + this.count = this.mark; + } + + @Override + public boolean markSupported() { + return this.source.markSupported(); + } + + long getCount() { + return this.count; + } + } + private static final long[] EMPTY_LONG = new long[0]; /** * Nano degrees @@ -86,11 +158,11 @@ protected DataSet doParseDataSet(InputStream source, ProgressMonitor progressMon } private void parse(InputStream source) throws IllegalDataException, IOException { - final CountingInputStream inputStream; + final BoundedInputStream inputStream; if (source.markSupported()) { - inputStream = new CountingInputStream(source); + inputStream = new BoundedInputStream(source); } else { - inputStream = new CountingInputStream(new BufferedInputStream(source)); + inputStream = new BoundedInputStream(new BufferedInputStream(source)); } try (ProtobufParser parser = new ProtobufParser(inputStream)) { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -104,7 +176,7 @@ private void parse(InputStream source) throws IllegalDataException, IOException throw new IllegalDataException("Too many header blocks in protobuf"); } // OSM PBF is fun -- it has *nested* pbf data - Blob blob = parseBlob(blobHeader, inputStream, parser, baos); + final Blob blob = parseBlob(blobHeader, inputStream, parser, baos); headerBlock = parseHeaderBlock(blob, baos); checkRequiredFeatures(headerBlock); blobHeader = null; @@ -112,7 +184,7 @@ private void parse(InputStream source) throws IllegalDataException, IOException if (headerBlock == null) { throw new IllegalStateException("A header block must occur before the first data block"); } - Blob blob = parseBlob(blobHeader, inputStream, parser, baos); + final Blob blob = parseBlob(blobHeader, inputStream, parser, baos); parseDataBlock(baos, headerBlock, blob); blobHeader = null; } // Other software *may* extend the FileBlocks (from just "OSMHeader" and "OSMData"), so don't throw an error. @@ -131,14 +203,14 @@ private void parse(InputStream source) throws IllegalDataException, IOException * @throws IllegalDataException If the OSM PBF is (probably) corrupted */ @Nonnull - private static BlobHeader parseBlobHeader(CountingInputStream cis, ByteArrayOutputStream baos, ProtobufParser parser) + private static BlobHeader parseBlobHeader(BoundedInputStream cis, ByteArrayOutputStream baos, ProtobufParser parser) throws IOException, IllegalDataException { String type = null; byte[] indexData = null; int datasize = Integer.MIN_VALUE; int length = 0; - long start = cis.getBytesRead(); - while (parser.hasNext() && (length == 0 || cis.getBytesRead() - start < length)) { + long start = cis.getCount(); + while (parser.hasNext() && (length == 0 || cis.getCount() - start < length)) { final ProtobufRecord current = new ProtobufRecord(baos, parser); switch (current.getField()) { case 1: @@ -151,7 +223,7 @@ private static BlobHeader parseBlobHeader(CountingInputStream cis, ByteArrayOutp datasize = current.asUnsignedVarInt().intValue(); break; default: - start = cis.getBytesRead(); + start = cis.getCount(); length += current.asUnsignedVarInt().intValue(); if (length > MAX_BLOBHEADER_SIZE) { // There is a hard limit of 64 KiB for the BlobHeader. It *should* be less than 32 KiB. throw new IllegalDataException("OSM PBF BlobHeader is too large. PBF is probably corrupted. (" + @@ -179,44 +251,52 @@ private static BlobHeader parseBlobHeader(CountingInputStream cis, ByteArrayOutp * @throws IOException If one of the streams has an issue */ @Nonnull - private static Blob parseBlob(BlobHeader header, CountingInputStream cis, ProtobufParser parser, ByteArrayOutputStream baos) + private static Blob parseBlob(BlobHeader header, BoundedInputStream cis, ProtobufParser parser, ByteArrayOutputStream baos) throws IOException { - long start = cis.getBytesRead(); + long start = cis.getCount(); int size = Integer.MIN_VALUE; Blob.CompressionType type = null; - ProtobufRecord current = null; - while (parser.hasNext() && cis.getBytesRead() - start < header.dataSize()) { - current = new ProtobufRecord(baos, parser); - switch (current.getField()) { - case 1: - type = Blob.CompressionType.raw; - break; - case 2: - size = current.asUnsignedVarInt().intValue(); - break; - case 3: - type = Blob.CompressionType.zlib; - break; - case 4: - type = Blob.CompressionType.lzma; - break; - case 5: - type = Blob.CompressionType.bzip2; - break; - case 6: - type = Blob.CompressionType.lz4; - break; - case 7: - type = Blob.CompressionType.zstd; - break; - default: - throw new IllegalStateException("Unknown compression type: " + current.getField()); + // Needed since size and compression type + compression data may be in a different order + byte[] bytes = null; + while (parser.hasNext() && cis.getCount() - start < header.dataSize()) { + try (ProtobufRecord current = new ProtobufRecord(baos, parser)) { + switch (current.getField()) { + case 1: + type = Blob.CompressionType.raw; + bytes = current.getBytes(); + break; + case 2: + size = current.asUnsignedVarInt().intValue(); + break; + case 3: + type = Blob.CompressionType.zlib; + bytes = current.getBytes(); + break; + case 4: + type = Blob.CompressionType.lzma; + bytes = current.getBytes(); + break; + case 5: + type = Blob.CompressionType.bzip2; + bytes = current.getBytes(); + break; + case 6: + type = Blob.CompressionType.lz4; + bytes = current.getBytes(); + break; + case 7: + type = Blob.CompressionType.zstd; + bytes = current.getBytes(); + break; + default: + throw new IllegalStateException("Unknown compression type: " + current.getField()); + } } } if (type == null) { throw new IllegalStateException("Compression type not found, pbf may be malformed"); } - return new Blob(size, type, current.getBytes()); + return new Blob(size, type, bytes); } /** @@ -301,7 +381,7 @@ private static void checkRequiredFeatures(HeaderBlock headerBlock) throws Illega private void parseDataBlock(ByteArrayOutputStream baos, HeaderBlock headerBlock, Blob blob) throws IOException, IllegalDataException { String[] stringTable = null; // field 1, note that stringTable[0] is a delimiter, so it is always blank and unused // field 2 -- we cannot parse these live just in case the following fields come later - List primitiveGroups = new ArrayList<>(); + final List primitiveGroups = new ArrayList<>(); int granularity = 100; // field 17 long latOffset = 0; // field 19 long lonOffset = 0; // field 20 @@ -309,7 +389,7 @@ private void parseDataBlock(ByteArrayOutputStream baos, HeaderBlock headerBlock, try (InputStream inputStream = blob.inputStream(); ProtobufParser parser = new ProtobufParser(inputStream)) { while (parser.hasNext()) { - ProtobufRecord protobufRecord = new ProtobufRecord(baos, parser); + final ProtobufRecord protobufRecord = new ProtobufRecord(baos, parser); switch (protobufRecord.getField()) { case 1: stringTable = parseStringTable(baos, protobufRecord.getBytes()); @@ -346,7 +426,7 @@ private void parseDataBlock(ByteArrayOutputStream baos, HeaderBlock headerBlock, } } for (ProtobufRecord primitiveGroup : primitiveGroups) { - try { + try (primitiveGroup) { ds.beginUpdate(); parsePrimitiveGroup(baos, primitiveGroup.getBytes(), primitiveBlockRecord); } finally { @@ -372,7 +452,7 @@ private static BBox parseBBox(ByteArrayOutputStream baos, ProtobufRecord current double top = Double.NaN; double bottom = Double.NaN; while (bboxParser.hasNext()) { - ProtobufRecord protobufRecord = new ProtobufRecord(baos, bboxParser); + final ProtobufRecord protobufRecord = new ProtobufRecord(baos, bboxParser); if (protobufRecord.getType() == WireType.VARINT) { double value = protobufRecord.asSignedVarInt().longValue() * NANO_DEGREES; switch (protobufRecord.getField()) { @@ -411,9 +491,9 @@ private static BBox parseBBox(ByteArrayOutputStream baos, ProtobufRecord current private static String[] parseStringTable(ByteArrayOutputStream baos, byte[] bytes) throws IOException { try (ByteArrayInputStream is = new ByteArrayInputStream(bytes); ProtobufParser parser = new ProtobufParser(is)) { - List list = new ArrayList<>(); + final List list = new ArrayList<>(); while (parser.hasNext()) { - ProtobufRecord protobufRecord = new ProtobufRecord(baos, parser); + final ProtobufRecord protobufRecord = new ProtobufRecord(baos, parser); if (protobufRecord.getField() == 1) { list.add(protobufRecord.asString().intern()); // field is technically repeated bytes } @@ -437,7 +517,7 @@ private void parsePrimitiveGroup(ByteArrayOutputStream baos, byte[] bytes, Primi try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ProtobufParser parser = new ProtobufParser(bais)) { while (parser.hasNext()) { - ProtobufRecord protobufRecord = new ProtobufRecord(baos, parser); + final ProtobufRecord protobufRecord = new ProtobufRecord(baos, parser); switch (protobufRecord.getField()) { case 1: // Nodes, repeated parseNode(baos, protobufRecord.getBytes(), primitiveBlockRecord); @@ -473,13 +553,13 @@ private void parseNode(ByteArrayOutputStream baos, byte[] bytes, PrimitiveBlockR try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ProtobufParser parser = new ProtobufParser(bais)) { long id = Long.MIN_VALUE; - List keys = new ArrayList<>(); - List values = new ArrayList<>(); + final List keys = new ArrayList<>(); + final List values = new ArrayList<>(); Info info = null; long lat = Long.MIN_VALUE; long lon = Long.MIN_VALUE; while (parser.hasNext()) { - ProtobufRecord protobufRecord = new ProtobufRecord(baos, parser); + final ProtobufRecord protobufRecord = new ProtobufRecord(baos, parser); switch (protobufRecord.getField()) { case 1: id = protobufRecord.asSignedVarInt().intValue(); @@ -509,11 +589,13 @@ private void parseNode(ByteArrayOutputStream baos, byte[] bytes, PrimitiveBlockR if (id == Long.MIN_VALUE || lat == Long.MIN_VALUE || lon == Long.MIN_VALUE) { throw new IllegalDataException("OSM PBF did not provide all the required node information"); } - NodeData node = new NodeData(id); + final NodeData node = new NodeData(id); node.setCoor(calculateLatLon(primitiveBlockRecord, lat, lon)); addTags(node, keys, values); if (info != null) { setOsmPrimitiveData(primitiveBlockRecord, node, info); + } else { + ds.setUploadPolicy(UploadPolicy.DISCOURAGED); } buildPrimitive(node); } @@ -538,7 +620,7 @@ private void parseDenseNodes(ByteArrayOutputStream baos, byte[] bytes, Primitive try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ProtobufParser parser = new ProtobufParser(bais)) { while (parser.hasNext()) { - ProtobufRecord protobufRecord = new ProtobufRecord(baos, parser); + final ProtobufRecord protobufRecord = new ProtobufRecord(baos, parser); switch (protobufRecord.getField()) { case 1: // packed node ids, DELTA encoded long[] tids = decodePackedSInt64(new ProtobufPacked(protobufRecord.getBytes()).getArray()); @@ -563,6 +645,7 @@ private void parseDenseNodes(ByteArrayOutputStream baos, byte[] bytes, Primitive } } } + int keyValIndex = 0; // This index must not reset between nodes, and must always increment if (ids.length == lats.length && lats.length == lons.length && (denseInfo == null || denseInfo.length == lons.length)) { long id = 0; @@ -570,13 +653,13 @@ private void parseDenseNodes(ByteArrayOutputStream baos, byte[] bytes, Primitive long lon = 0; for (int i = 0; i < ids.length; i++) { final NodeData node; + id += ids[i]; + node = new NodeData(id); if (denseInfo != null) { - Info info = denseInfo[i]; - id += ids[i]; - node = new NodeData(id); + final Info info = denseInfo[i]; setOsmPrimitiveData(primitiveBlockRecord, node, info); } else { - node = new NodeData(ids[i]); + ds.setUploadPolicy(UploadPolicy.DISCOURAGED); } lat += lats[i]; lon += lons[i]; @@ -628,7 +711,7 @@ private void parseWay(ByteArrayOutputStream baos, byte[] bytes, PrimitiveBlockRe try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ProtobufParser parser = new ProtobufParser(bais)) { while (parser.hasNext()) { - ProtobufRecord protobufRecord = new ProtobufRecord(baos, parser); + final ProtobufRecord protobufRecord = new ProtobufRecord(baos, parser); switch (protobufRecord.getField()) { case 1: id = protobufRecord.asUnsignedVarInt().longValue(); @@ -659,8 +742,8 @@ private void parseWay(ByteArrayOutputStream baos, byte[] bytes, PrimitiveBlockRe if (refs.length == 0 || id == Long.MIN_VALUE) { throw new IllegalDataException("A way with either no id or no nodes was found"); } - WayData wayData = new WayData(id); - List nodeIds = new ArrayList<>(refs.length); + final WayData wayData = new WayData(id); + final List nodeIds = new ArrayList<>(refs.length); long ref = 0; for (long tRef : refs) { ref += tRef; @@ -670,6 +753,8 @@ private void parseWay(ByteArrayOutputStream baos, byte[] bytes, PrimitiveBlockRe addTags(wayData, keys, values); if (info != null) { setOsmPrimitiveData(primitiveBlockRecord, wayData, info); + } else { + ds.setUploadPolicy(UploadPolicy.DISCOURAGED); } buildPrimitive(wayData); } @@ -686,8 +771,8 @@ private void parseWay(ByteArrayOutputStream baos, byte[] bytes, PrimitiveBlockRe private void parseRelation(ByteArrayOutputStream baos, byte[] bytes, PrimitiveBlockRecord primitiveBlockRecord) throws IllegalDataException, IOException { long id = Long.MIN_VALUE; - List keys = new ArrayList<>(); - List values = new ArrayList<>(); + final List keys = new ArrayList<>(); + final List values = new ArrayList<>(); Info info = null; long[] rolesStringId = EMPTY_LONG; // Technically int long[] memids = EMPTY_LONG; @@ -695,7 +780,7 @@ private void parseRelation(ByteArrayOutputStream baos, byte[] bytes, PrimitiveBl try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ProtobufParser parser = new ProtobufParser(bais)) { while (parser.hasNext()) { - ProtobufRecord protobufRecord = new ProtobufRecord(baos, parser); + final ProtobufRecord protobufRecord = new ProtobufRecord(baos, parser); switch (protobufRecord.getField()) { case 1: id = protobufRecord.asUnsignedVarInt().longValue(); @@ -732,9 +817,11 @@ private void parseRelation(ByteArrayOutputStream baos, byte[] bytes, PrimitiveBl if (keys.size() != values.size() || rolesStringId.length != memids.length || memids.length != types.length || id == Long.MIN_VALUE) { throw new IllegalDataException("OSM PBF contains a bad relation definition"); } - RelationData data = new RelationData(id); + final RelationData data = new RelationData(id); if (info != null) { setOsmPrimitiveData(primitiveBlockRecord, data, info); + } else { + ds.setUploadPolicy(UploadPolicy.DISCOURAGED); } addTags(data, keys, values); OsmPrimitiveType[] valueTypes = OsmPrimitiveType.values(); @@ -769,7 +856,7 @@ private static Info parseInfo(ByteArrayOutputStream baos, byte[] bytes) throws I Integer userSid = null; boolean visible = true; while (parser.hasNext()) { - ProtobufRecord protobufRecord = new ProtobufRecord(baos, parser); + final ProtobufRecord protobufRecord = new ProtobufRecord(baos, parser); switch (protobufRecord.getField()) { case 1: version = protobufRecord.asUnsignedVarInt().intValue(); @@ -787,7 +874,7 @@ private static Info parseInfo(ByteArrayOutputStream baos, byte[] bytes) throws I userSid = protobufRecord.asUnsignedVarInt().intValue(); break; case 6: - visible = protobufRecord.asUnsignedVarInt().byteValue() == 0; + visible = protobufRecord.asUnsignedVarInt().byteValue() == 1; break; default: // Fall through, since the PBF format could be extended } @@ -908,7 +995,7 @@ private static Info[] parseDenseInfo(ByteArrayOutputStream baos, byte[] bytes) t try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ProtobufParser parser = new ProtobufParser(bais)) { while (parser.hasNext()) { - ProtobufRecord protobufRecord = new ProtobufRecord(baos, parser); + final ProtobufRecord protobufRecord = new ProtobufRecord(baos, parser); switch (protobufRecord.getField()) { case 1: long[] tVersion = new ProtobufPacked(protobufRecord.getBytes()).getArray(); @@ -938,18 +1025,21 @@ private static Info[] parseDenseInfo(ByteArrayOutputStream baos, byte[] bytes) t } } } - if (version.length == timestamp.length && timestamp.length == changeset.length && changeset.length == uid.length && - uid.length == userSid.length && (visible == EMPTY_LONG || visible.length == userSid.length)) { - Info[] infos = new Info[version.length]; + if (version.length > 0) { + final Info[] infos = new Info[version.length]; long lastTimestamp = 0; // delta encoded long lastChangeset = 0; // delta encoded long lastUid = 0; // delta encoded, long lastUserSid = 0; // delta encoded, string id for username for (int i = 0; i < version.length; i++) { - lastTimestamp += timestamp[i]; - lastChangeset += changeset[i]; - lastUid += uid[i]; - lastUserSid += userSid[i]; + if (timestamp.length > i) + lastTimestamp += timestamp[i]; + if (changeset.length > i) + lastChangeset += changeset[i]; + if (uid.length > i && userSid.length > i) { + lastUid += uid[i]; + lastUserSid += userSid[i]; + } infos[i] = new Info((int) version[i], lastTimestamp, lastChangeset, (int) lastUid, (int) lastUserSid, visible == EMPTY_LONG || visible[i] == 1); } diff --git a/src/org/openstreetmap/josm/io/OsmServerWriter.java b/src/org/openstreetmap/josm/io/OsmServerWriter.java index b8d1a7a198b..170fdcf96c6 100644 --- a/src/org/openstreetmap/josm/io/OsmServerWriter.java +++ b/src/org/openstreetmap/josm/io/OsmServerWriter.java @@ -98,7 +98,7 @@ protected void uploadChangesIndividually(Collection prim uploadStartTime = System.currentTimeMillis(); for (OsmPrimitive osm : primitives) { String msg; - switch(OsmPrimitiveType.from(osm)) { + switch (OsmPrimitiveType.from(osm)) { case NODE: msg = marktr("{0}% ({1}/{2}), {3} left. Uploading node ''{4}'' (id: {5})"); break; case WAY: msg = marktr("{0}% ({1}/{2}), {3} left. Uploading way ''{4}'' (id: {5})"); break; case RELATION: msg = marktr("{0}% ({1}/{2}), {3} left. Uploading relation ''{4}'' (id: {5})"); break; @@ -206,7 +206,7 @@ public void uploadOsm(UploadStrategySpecification strategy, Collection ds.addDataSource(new DataSource(bounds, getBaseUrl()))); } return ds; } @@ -440,4 +431,17 @@ public static String fixQuery(String query) { public boolean considerAsFullDownload() { return overpassQuery.equals(OverpassDownloadSource.FULL_DOWNLOAD_QUERY); } + + @Override + protected Collection getBounds() { + if (this.overpassQuery.contains("{{bbox}}")) { + if (crosses180th) { + return Set.of(new Bounds(lat1, lon1, lat2, 180.0), + new Bounds(lat1, -180.0, lat2, lon2)); + } else { + return Collections.singleton(new Bounds(lat1, lon1, lat2, lon2)); + } + } + return Collections.emptySet(); + } } diff --git a/src/org/openstreetmap/josm/io/UploadStrategySpecification.java b/src/org/openstreetmap/josm/io/UploadStrategySpecification.java index 855cfc36ef4..de3db93d875 100644 --- a/src/org/openstreetmap/josm/io/UploadStrategySpecification.java +++ b/src/org/openstreetmap/josm/io/UploadStrategySpecification.java @@ -136,7 +136,7 @@ public boolean isCloseChangesetAfterUpload() { public int getNumRequests(int numObjects) { if (numObjects <= 0) return 0; - switch(strategy) { + switch (strategy) { case INDIVIDUAL_OBJECTS_STRATEGY: return numObjects; case SINGLE_REQUEST_STRATEGY: return 1; case CHUNKED_DATASET_STRATEGY: diff --git a/src/org/openstreetmap/josm/io/auth/CredentialsAgent.java b/src/org/openstreetmap/josm/io/auth/CredentialsAgent.java index a7d5731e055..e5ec7d4be66 100644 --- a/src/org/openstreetmap/josm/io/auth/CredentialsAgent.java +++ b/src/org/openstreetmap/josm/io/auth/CredentialsAgent.java @@ -6,7 +6,6 @@ import java.net.PasswordAuthentication; import org.openstreetmap.josm.data.oauth.IOAuthToken; -import org.openstreetmap.josm.data.oauth.OAuthToken; import jakarta.annotation.Nullable; @@ -64,8 +63,12 @@ CredentialsAgentResponse getCredentials(RequestorType requestorType, String host * * @return the current OAuth Access Token to access the OSM server. * @throws CredentialsAgentException if something goes wrong + * @deprecated since 18991 -- OAuth 1.0 is being removed from the OSM API */ - OAuthToken lookupOAuthAccessToken() throws CredentialsAgentException; + @Deprecated + default IOAuthToken lookupOAuthAccessToken() throws CredentialsAgentException { + throw new CredentialsAgentException("Call to deprecated method"); + } /** * Lookup the current OAuth Access Token to access the specified server. Replies null, if no @@ -84,15 +87,19 @@ CredentialsAgentResponse getCredentials(RequestorType requestorType, String host * * @param accessToken the access Token. null, to remove the Access Token. * @throws CredentialsAgentException if something goes wrong + * @deprecated since 18991 -- OAuth 1.0 is being removed from the OSM API */ - void storeOAuthAccessToken(OAuthToken accessToken) throws CredentialsAgentException; + @Deprecated + default void storeOAuthAccessToken(IOAuthToken accessToken) throws CredentialsAgentException { + throw new CredentialsAgentException("Call to deprecated method"); + } /** * Stores the OAuth Access Token accessToken. * * @param host The host the access token is for * @param accessToken the access Token. null, to remove the Access Token. This will remove all IOAuthTokens not managed by - * {@link #storeOAuthAccessToken(OAuthToken)}. + * {@link #storeOAuthAccessToken(IOAuthToken)}. * @throws CredentialsAgentException if something goes wrong * @since 18650 */ diff --git a/src/org/openstreetmap/josm/io/auth/CredentialsManager.java b/src/org/openstreetmap/josm/io/auth/CredentialsManager.java index 9e72c262fa1..b9b6ddf0dbb 100644 --- a/src/org/openstreetmap/josm/io/auth/CredentialsManager.java +++ b/src/org/openstreetmap/josm/io/auth/CredentialsManager.java @@ -8,7 +8,6 @@ import org.openstreetmap.josm.data.UserIdentityManager; import org.openstreetmap.josm.data.oauth.IOAuthToken; -import org.openstreetmap.josm.data.oauth.OAuthToken; import org.openstreetmap.josm.io.OsmApi; import org.openstreetmap.josm.tools.CheckParameterUtil; import org.openstreetmap.josm.tools.Logging; @@ -16,7 +15,7 @@ /** * CredentialManager is a factory for the single credential agent used. - * + *

              * Currently, it defaults to replying an instance of {@link JosmPreferencesCredentialAgent}. * @since 2641 */ @@ -129,7 +128,7 @@ public PasswordAuthentication lookup(RequestorType requestorType, String host) t public void store(RequestorType requestorType, String host, PasswordAuthentication credentials) throws CredentialsAgentException { if (requestorType == RequestorType.SERVER && Objects.equals(OsmApi.getOsmApi().getHost(), host)) { String username = credentials.getUserName(); - if (!Utils.isBlank(username)) { + if (!Utils.isStripEmpty(username)) { UserIdentityManager.getInstance().setPartiallyIdentified(username); } } @@ -156,21 +155,11 @@ public CredentialsAgentResponse getCredentials(RequestorType requestorType, Stri return credentials; } - @Override - public OAuthToken lookupOAuthAccessToken() throws CredentialsAgentException { - return delegate.lookupOAuthAccessToken(); - } - @Override public IOAuthToken lookupOAuthAccessToken(String host) throws CredentialsAgentException { return delegate.lookupOAuthAccessToken(host); } - @Override - public void storeOAuthAccessToken(OAuthToken accessToken) throws CredentialsAgentException { - delegate.storeOAuthAccessToken(accessToken); - } - @Override public void storeOAuthAccessToken(String host, IOAuthToken accessToken) throws CredentialsAgentException { delegate.storeOAuthAccessToken(host, accessToken); diff --git a/src/org/openstreetmap/josm/io/auth/JosmPreferencesCredentialAgent.java b/src/org/openstreetmap/josm/io/auth/JosmPreferencesCredentialAgent.java index d04194208f1..6c228404b65 100644 --- a/src/org/openstreetmap/josm/io/auth/JosmPreferencesCredentialAgent.java +++ b/src/org/openstreetmap/josm/io/auth/JosmPreferencesCredentialAgent.java @@ -10,14 +10,12 @@ import java.util.Objects; import java.util.Set; -import jakarta.json.JsonException; import javax.swing.text.html.HTMLEditorKit; import org.openstreetmap.josm.data.oauth.IOAuthToken; import org.openstreetmap.josm.data.oauth.OAuth20Exception; import org.openstreetmap.josm.data.oauth.OAuth20Parameters; import org.openstreetmap.josm.data.oauth.OAuth20Token; -import org.openstreetmap.josm.data.oauth.OAuthToken; import org.openstreetmap.josm.data.oauth.OAuthVersion; import org.openstreetmap.josm.gui.widgets.HtmlPanel; import org.openstreetmap.josm.io.DefaultProxySelector; @@ -25,6 +23,8 @@ import org.openstreetmap.josm.spi.preferences.Config; import org.openstreetmap.josm.tools.Utils; +import jakarta.json.JsonException; + /** * This is the default credentials agent in JOSM. It keeps username and password for both * the OSM API and an optional HTTP proxy in the JOSM preferences file. @@ -41,7 +41,7 @@ public PasswordAuthentication lookup(RequestorType requestorType, String host) t return null; String user; String password; - switch(requestorType) { + switch (requestorType) { case SERVER: if (Objects.equals(OsmApi.getOsmApi().getHost(), host)) { user = Config.getPref().get("osm-server.username", null); @@ -73,7 +73,7 @@ public PasswordAuthentication lookup(RequestorType requestorType, String host) t public void store(RequestorType requestorType, String host, PasswordAuthentication credentials) throws CredentialsAgentException { if (requestorType == null) return; - switch(requestorType) { + switch (requestorType) { case SERVER: if (Objects.equals(OsmApi.getOsmApi().getHost(), host)) { Config.getPref().put("osm-server.username", credentials.getUserName()); @@ -102,22 +102,6 @@ public void store(RequestorType requestorType, String host, PasswordAuthenticati } } - /** - * Lookup the current OAuth Access Token to access the OSM server. Replies null, if no - * Access Token is currently managed by this CredentialManager. - * - * @return the current OAuth Access Token to access the OSM server. - * @throws CredentialsAgentException if something goes wrong - */ - @Override - public OAuthToken lookupOAuthAccessToken() throws CredentialsAgentException { - String accessTokenKey = Config.getPref().get("oauth.access-token.key", null); - String accessTokenSecret = Config.getPref().get("oauth.access-token.secret", null); - if (accessTokenKey == null && accessTokenSecret == null) - return null; - return new OAuthToken(accessTokenKey, accessTokenSecret); - } - @Override public IOAuthToken lookupOAuthAccessToken(String host) throws CredentialsAgentException { Set keySet = new HashSet<>(Config.getPref().getKeySet()); @@ -130,7 +114,7 @@ public IOAuthToken lookupOAuthAccessToken(String host) throws CredentialsAgentEx } String token = Config.getPref().get(hostKey, null); String parameters = Config.getPref().get(parametersKey, null); - if (!Utils.isBlank(token) && !Utils.isBlank(parameters) && OAuthVersion.OAuth20 == oauthType) { + if (!Utils.isStripEmpty(token) && !Utils.isStripEmpty(parameters) && OAuthVersion.OAuth20 == oauthType) { try { OAuth20Parameters oAuth20Parameters = new OAuth20Parameters(parameters); return new OAuth20Token(oAuth20Parameters, token); @@ -142,23 +126,6 @@ public IOAuthToken lookupOAuthAccessToken(String host) throws CredentialsAgentEx return null; } - /** - * Stores the OAuth Access Token accessToken. - * - * @param accessToken the access Token. null, to remove the Access Token. - * @throws CredentialsAgentException if something goes wrong - */ - @Override - public void storeOAuthAccessToken(OAuthToken accessToken) throws CredentialsAgentException { - if (accessToken == null) { - Config.getPref().put("oauth.access-token.key", null); - Config.getPref().put("oauth.access-token.secret", null); - } else { - Config.getPref().put("oauth.access-token.key", accessToken.getKey()); - Config.getPref().put("oauth.access-token.secret", accessToken.getSecret()); - } - } - @Override public void storeOAuthAccessToken(String host, IOAuthToken accessToken) throws CredentialsAgentException { Objects.requireNonNull(host, "host"); diff --git a/src/org/openstreetmap/josm/io/imagery/ImageryReader.java b/src/org/openstreetmap/josm/io/imagery/ImageryReader.java index ee0ed9aed88..7fa0c92fb3e 100644 --- a/src/org/openstreetmap/josm/io/imagery/ImageryReader.java +++ b/src/org/openstreetmap/josm/io/imagery/ImageryReader.java @@ -111,7 +111,7 @@ public List parse() throws SAXException, IOException { } } - private static class Parser extends DefaultHandler { + private static final class Parser extends DefaultHandler { private static final String MAX_ZOOM = "max-zoom"; private static final String MIN_ZOOM = "min-zoom"; private static final String TILE_SIZE = "tile-size"; @@ -365,7 +365,7 @@ public void endElement(String namespaceURI, String qName, String rqName) { break; case MIRROR_ATTRIBUTE: if (mirrorEntry != null) { - switch(qName) { + switch (qName) { case "type": Optional type = Arrays.stream(ImageryType.values()) .filter(t -> Objects.equals(accumulator.toString(), t.getTypeString())) @@ -411,7 +411,7 @@ public void endElement(String namespaceURI, String qName, String rqName) { } break; case ENTRY_ATTRIBUTE: - switch(qName) { + switch (qName) { case "name": entry.setName(lang == null ? LanguageInfo.getJOSMLocaleCode(null) : lang, accumulator.toString()); break; diff --git a/src/org/openstreetmap/josm/io/imagery/WMSImagery.java b/src/org/openstreetmap/josm/io/imagery/WMSImagery.java index 691b5a4210e..5c10eb158bf 100644 --- a/src/org/openstreetmap/josm/io/imagery/WMSImagery.java +++ b/src/org/openstreetmap/josm/io/imagery/WMSImagery.java @@ -164,7 +164,7 @@ public WMSImagery(String url, Map headers) throws IOException, W * @throws IOException when connection error when fetching get capabilities document * @throws WMSGetCapabilitiesException when there are errors when parsing get capabilities document * @throws InvalidPathException if a Path object cannot be constructed for the capabilities cached file - * @since xxx + * @since 18780 */ public WMSImagery(String url, Map headers, ProgressMonitor monitor) throws IOException, WMSGetCapabilitiesException { diff --git a/src/org/openstreetmap/josm/io/nmea/NmeaParser.java b/src/org/openstreetmap/josm/io/nmea/NmeaParser.java index 4b91a41d26c..b9faf9d0312 100644 --- a/src/org/openstreetmap/josm/io/nmea/NmeaParser.java +++ b/src/org/openstreetmap/josm/io/nmea/NmeaParser.java @@ -386,7 +386,7 @@ public boolean parseNMEASentence(String s) throws IllegalDataException { accu = e[GGA.QUALITY.position]; if (!accu.isEmpty()) { int fixtype = Integer.parseInt(accu); - switch(fixtype) { + switch (fixtype) { case 0: currentwp.put(GpxConstants.PT_FIX, "none"); break; diff --git a/src/org/openstreetmap/josm/io/remotecontrol/AddTagsDialog.java b/src/org/openstreetmap/josm/io/remotecontrol/AddTagsDialog.java index 95965381fc0..a6f67b2c977 100644 --- a/src/org/openstreetmap/josm/io/remotecontrol/AddTagsDialog.java +++ b/src/org/openstreetmap/josm/io/remotecontrol/AddTagsDialog.java @@ -8,6 +8,7 @@ import java.awt.Font; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.util.Collection; @@ -97,7 +98,7 @@ static class ExistingValues { int addValue(String val) { Integer c = valueCount.get(val); - int r = c == null ? 1 : (c.intValue()+1); + int r = c == null ? 1 : (c + 1); valueCount.put(val, r); return r; } @@ -137,8 +138,9 @@ public AddTagsDialog(String[][] tags, String senderName, Collection 0 && markerNodes.item(0).getNodeType() == Node.ELEMENT_NODE) { - Element markerEl = (Element) markerNodes.item(0); - try { - int index = Integer.parseInt(markerEl.getAttribute("index")); - support.addSubLayer(index, importData.getMarkerLayer(), markerEl); - } catch (NumberFormatException ex) { - Logging.warn(ex); - } + if (NMEAImporter.FILE_FILTER.acceptName(fileStr)) { + importData = NMEAImporter.loadLayers(in, support.getFile(fileStr), support.getLayerName()); + } else if (RtkLibImporter.FILE_FILTER.acceptName(fileStr)) { + importData = RtkLibImporter.loadLayers(in, support.getFile(fileStr), support.getLayerName()); + } else { + importData = GpxImporter.loadLayers(in, support.getFile(fileStr), support.getLayerName(), progressMonitor); + } + if (importData.getGpxLayer() != null && importData.getGpxLayer().data != null) { + importData.getGpxLayer().data.fromSession = true; + } + NodeList markerNodes = elem.getElementsByTagName("markerLayer"); + if (markerNodes.getLength() > 0 && markerNodes.item(0).getNodeType() == Node.ELEMENT_NODE) { + Element markerEl = (Element) markerNodes.item(0); + try { + int index = Integer.parseInt(markerEl.getAttribute("index")); + support.addSubLayer(index, importData.getMarkerLayer(), markerEl); + } catch (NumberFormatException ex) { + Logging.warn(ex); } - - support.addPostLayersTask(importData.getPostLayerTask()); - return getLayer(importData); } - } catch (XPathExpressionException e) { - throw new IllegalDataException(e); + support.addPostLayersTask(importData.getPostLayerTask()); + return getLayer(importData); } } diff --git a/src/org/openstreetmap/josm/io/session/MarkerSessionExporter.java b/src/org/openstreetmap/josm/io/session/MarkerSessionExporter.java index a43095965ec..92dd685e410 100644 --- a/src/org/openstreetmap/josm/io/session/MarkerSessionExporter.java +++ b/src/org/openstreetmap/josm/io/session/MarkerSessionExporter.java @@ -2,6 +2,7 @@ package org.openstreetmap.josm.io.session; import java.awt.Component; +import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.io.IOException; import java.io.OutputStream; @@ -17,6 +18,7 @@ import javax.swing.JPanel; import javax.swing.SwingConstants; +import org.openstreetmap.josm.data.gpx.GpxConstants; import org.openstreetmap.josm.data.gpx.GpxData; import org.openstreetmap.josm.gui.MainApplication; import org.openstreetmap.josm.gui.layer.Layer; @@ -65,7 +67,7 @@ public Component getExportPanel() { lbl.setLabelFor(export); p.add(export, GBC.std()); p.add(lbl, GBC.std()); - p.add(GBC.glue(1, 0), GBC.std().fill(GBC.HORIZONTAL)); + p.add(GBC.glue(1, 0), GBC.std().fill(GridBagConstraints.HORIZONTAL)); return p; } @@ -92,7 +94,8 @@ public Element export(ExportSupport support) throws IOException { return layerEl; } - @SuppressWarnings("resource") + // The new closable resources in this method will close the input OutputStream + @SuppressWarnings({"squid:S2095", "PMD.CloseResource"}) protected void addDataFile(OutputStream out) { Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8); MarkerWriter w = new MarkerWriter(new PrintWriter(writer)); @@ -131,7 +134,7 @@ public void write(MarkerLayer layer) { data.getLayerPrefs().put(k, v); } }); - data.put(GpxData.META_DESC, "exported JOSM marker layer"); + data.put(GpxConstants.META_DESC, "exported JOSM marker layer"); for (Marker m : layer.data) { data.waypoints.add(m.convertToWayPoint()); } diff --git a/src/org/openstreetmap/josm/io/session/MarkerSessionImporter.java b/src/org/openstreetmap/josm/io/session/MarkerSessionImporter.java index ca71fe9371f..f8d0dceda05 100644 --- a/src/org/openstreetmap/josm/io/session/MarkerSessionImporter.java +++ b/src/org/openstreetmap/josm/io/session/MarkerSessionImporter.java @@ -6,19 +6,12 @@ import java.io.IOException; import java.io.InputStream; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpression; -import javax.xml.xpath.XPathExpressionException; -import javax.xml.xpath.XPathFactory; - import org.openstreetmap.josm.gui.io.importexport.GpxImporter; import org.openstreetmap.josm.gui.layer.Layer; import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer; import org.openstreetmap.josm.gui.progress.ProgressMonitor; import org.openstreetmap.josm.io.IllegalDataException; import org.openstreetmap.josm.io.session.SessionReader.ImportSupport; -import org.openstreetmap.josm.tools.Utils; import org.w3c.dom.Element; /** @@ -33,26 +26,16 @@ public Layer load(Element elem, ImportSupport support, ProgressMonitor progressM if (!"0.1".equals(version)) { throw new IllegalDataException(tr("Version ''{0}'' of meta data for marker layer is not supported. Expected: 0.1", version)); } - try { - XPathFactory xPathFactory = XPathFactory.newInstance(); - XPath xpath = xPathFactory.newXPath(); - XPathExpression fileExp = xpath.compile("file/text()"); - String fileStr = (String) fileExp.evaluate(elem, XPathConstants.STRING); - if (Utils.isEmpty(fileStr)) { - throw new IllegalDataException(tr("File name expected for layer no. {0}", support.getLayerIndex())); - } - - try (InputStream in = support.getInputStream(fileStr)) { - GpxImporter.GpxImporterData importData = GpxImporter.loadLayers(in, support.getFile(fileStr), support.getLayerName(), - progressMonitor); - - support.addPostLayersTask(importData.getPostLayerTask()); - - importData.getGpxLayer().destroy(); - return importData.getMarkerLayer(); - } - } catch (XPathExpressionException e) { - throw new IllegalDataException(e); + String fileStr = OsmDataSessionImporter.extractFileName(elem, support); + + try (InputStream in = support.getInputStream(fileStr)) { + GpxImporter.GpxImporterData importData = GpxImporter.loadLayers(in, support.getFile(fileStr), support.getLayerName(), + progressMonitor); + + support.addPostLayersTask(importData.getPostLayerTask()); + + importData.getGpxLayer().destroy(); + return importData.getMarkerLayer(); } } } diff --git a/src/org/openstreetmap/josm/io/session/NoteSessionImporter.java b/src/org/openstreetmap/josm/io/session/NoteSessionImporter.java index e2cd120b706..845815b40e6 100644 --- a/src/org/openstreetmap/josm/io/session/NoteSessionImporter.java +++ b/src/org/openstreetmap/josm/io/session/NoteSessionImporter.java @@ -6,19 +6,12 @@ import java.io.IOException; import java.io.InputStream; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpression; -import javax.xml.xpath.XPathExpressionException; -import javax.xml.xpath.XPathFactory; - import org.openstreetmap.josm.gui.io.importexport.NoteImporter; import org.openstreetmap.josm.gui.layer.Layer; import org.openstreetmap.josm.gui.layer.NoteLayer; import org.openstreetmap.josm.gui.progress.ProgressMonitor; import org.openstreetmap.josm.io.IllegalDataException; import org.openstreetmap.josm.io.session.SessionReader.ImportSupport; -import org.openstreetmap.josm.tools.Utils; import org.w3c.dom.Element; import org.xml.sax.SAXException; @@ -34,20 +27,13 @@ public Layer load(Element elem, ImportSupport support, ProgressMonitor progressM if (!"0.1".equals(version)) { throw new IllegalDataException(tr("Version ''{0}'' of meta data for note layer is not supported. Expected: 0.1", version)); } - try { - XPathFactory xPathFactory = XPathFactory.newInstance(); - XPath xpath = xPathFactory.newXPath(); - XPathExpression fileExp = xpath.compile("file/text()"); - String fileStr = (String) fileExp.evaluate(elem, XPathConstants.STRING); - if (Utils.isEmpty(fileStr)) { - throw new IllegalDataException(tr("File name expected for layer no. {0}", support.getLayerIndex())); - } - NoteImporter importer = new NoteImporter(); - try (InputStream in = support.getInputStream(fileStr)) { - return importer.loadLayer(in, support.getFile(fileStr), support.getLayerName(), progressMonitor); - } - } catch (XPathExpressionException | SAXException e) { + String fileStr = OsmDataSessionImporter.extractFileName(elem, support); + + NoteImporter importer = new NoteImporter(); + try (InputStream in = support.getInputStream(fileStr)) { + return importer.loadLayer(in, support.getFile(fileStr), support.getLayerName(), progressMonitor); + } catch (SAXException e) { throw new IllegalDataException(e); } } diff --git a/src/org/openstreetmap/josm/io/session/OsmDataSessionExporter.java b/src/org/openstreetmap/josm/io/session/OsmDataSessionExporter.java index ae93127064d..3140525c0b1 100644 --- a/src/org/openstreetmap/josm/io/session/OsmDataSessionExporter.java +++ b/src/org/openstreetmap/josm/io/session/OsmDataSessionExporter.java @@ -34,11 +34,15 @@ protected void addDataFile(OutputStream out) { /** * Exports OSM data to the given output stream. * @param data data set - * @param out output stream - * @since 15386 + * @param out output stream (must be closed by caller; note: if caller has access, caller should use + * {@link org.apache.commons.io.output.CloseShieldOutputStream} when calling this method to + * avoid potential future issues) */ + @SuppressWarnings({"squid:S2095", "PMD.CloseResource"}) // All the closeables in this method will close the input OutputStream. public static void exportData(DataSet data, OutputStream out) { + // This writer will close out when it is closed Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8); + // The PrintWriter will close the writer when it is closed, and the OsmWriter will close the PrintWriter when it is closed. OsmWriter w = OsmWriterFactory.createOsmWriter(new PrintWriter(writer), false, data.getVersion()); data.getReadLock().lock(); try { diff --git a/src/org/openstreetmap/josm/io/session/OsmDataSessionImporter.java b/src/org/openstreetmap/josm/io/session/OsmDataSessionImporter.java index 12bd0426f0d..b3699a56343 100644 --- a/src/org/openstreetmap/josm/io/session/OsmDataSessionImporter.java +++ b/src/org/openstreetmap/josm/io/session/OsmDataSessionImporter.java @@ -23,6 +23,8 @@ import org.openstreetmap.josm.io.session.SessionReader.ImportSupport; import org.openstreetmap.josm.tools.Utils; import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; /** * Session importer for {@link OsmDataLayer}. @@ -67,6 +69,13 @@ public static void checkMetaVersion(Element elem) throws IllegalDataException { */ public static String extractFileName(Element elem, ImportSupport support) throws IllegalDataException { try { + // see #23427: try first to avoid possibly very slow XPath call + NodeList x = elem.getElementsByTagName("file"); + if (x.getLength() > 0 && x.item(0).getNodeType() == Node.ELEMENT_NODE) { + String fileStr = x.item(0).getTextContent(); + if (!Utils.isEmpty(fileStr)) + return fileStr; + } XPathFactory xPathFactory = XPathFactory.newInstance(); XPath xpath = xPathFactory.newXPath(); XPathExpression fileExp = xpath.compile("file/text()"); diff --git a/src/org/openstreetmap/josm/io/session/SessionReader.java b/src/org/openstreetmap/josm/io/session/SessionReader.java index 6d2f0ad0e95..0f1c94df826 100644 --- a/src/org/openstreetmap/josm/io/session/SessionReader.java +++ b/src/org/openstreetmap/josm/io/session/SessionReader.java @@ -447,6 +447,9 @@ public String toString() { } } + /** + * A dependency of another layer + */ public static class LayerDependency { private final Integer index; private final Layer layer; @@ -570,9 +573,8 @@ private void parseJos(Document doc, ProgressMonitor progressMonitor) throws Ille dialog.show( tr("Unable to load layer"), tr("Cannot load layer of type ''{0}'' because no suitable importer was found.", type), - JOptionPane.WARNING_MESSAGE, - progressMonitor - ); + JOptionPane.WARNING_MESSAGE + ); if (dialog.isCancel()) { progressMonitor.cancel(); return; @@ -589,9 +591,8 @@ private void parseJos(Document doc, ProgressMonitor progressMonitor) throws Ille dialog.show( tr("Unable to load layer"), tr("Cannot load layer {0} because it depends on layer {1} which has been skipped.", idx, d), - JOptionPane.WARNING_MESSAGE, - progressMonitor - ); + JOptionPane.WARNING_MESSAGE + ); if (dialog.isCancel()) { progressMonitor.cancel(); return; @@ -621,9 +622,8 @@ private void parseJos(Document doc, ProgressMonitor progressMonitor) throws Ille tr("Could not load layer {0} ''{1}''.
              Error is:
              {2}", idx, Utils.escapeReservedCharactersHTML(name), Utils.escapeReservedCharactersHTML(exception.getMessage())), - JOptionPane.ERROR_MESSAGE, - progressMonitor - ); + JOptionPane.ERROR_MESSAGE + ); if (dialog.isCancel()) { progressMonitor.cancel(); return; @@ -647,6 +647,8 @@ private void parseJos(Document doc, ProgressMonitor progressMonitor) throws Ille } } + if (progressMonitor.isCanceled()) + return; progressMonitor.worked(1); } @@ -729,11 +731,11 @@ private static SessionProjectionChoiceData readProjectionChoiceData(Element root * This is expected to run in a worker thread (PleaseWaitRunnable), so invokeAndWait is * needed to block the current thread and wait for the result of the modal dialog from EDT. */ - private static class CancelOrContinueDialog { + private static final class CancelOrContinueDialog { private boolean cancel; - public void show(final String title, final String message, final int icon, final ProgressMonitor progressMonitor) { + void show(final String title, final String message, final int icon) { try { SwingUtilities.invokeAndWait(() -> { ExtendedDialog dlg = new ExtendedDialog( diff --git a/src/org/openstreetmap/josm/plugins/Plugin.java b/src/org/openstreetmap/josm/plugins/Plugin.java index 4ac75ef4cde..7493035cf35 100644 --- a/src/org/openstreetmap/josm/plugins/Plugin.java +++ b/src/org/openstreetmap/josm/plugins/Plugin.java @@ -23,19 +23,19 @@ /** * For all purposes of loading dynamic resources, the Plugin's class loader should be used * (or else, the plugin jar will not be within the class path). - * + *

              * A plugin may subclass this abstract base class (but it is optional). - * + *

              * The actual implementation of this class is optional, as all functions will be called * via reflection. This is to be able to change this interface without the need of * recompiling or even breaking the plugins. If your class does not provide a * function here (or does provide a function with a mismatching signature), it will not * be called. That simple. - * + *

              * Or in other words: See this base class as an documentation of what automatic callbacks * are provided (you can register yourself to more callbacks in your plugin class * constructor). - * + *

              * Subclassing Plugin and overriding some functions makes it easy for you to keep sync * with the correct actual plugin architecture of JOSM. * @@ -46,7 +46,7 @@ public abstract class Plugin implements MapFrameListener { /** * This is the info available for this plugin. You can access this from your * constructor. - * + *

              * (The actual implementation to request the info from a static variable * is a bit hacky, but it works). */ @@ -54,7 +54,7 @@ public abstract class Plugin implements MapFrameListener { private final IBaseDirectories pluginBaseDirectories = new PluginBaseDirectories(); - private class PluginBaseDirectories implements IBaseDirectories { + private final class PluginBaseDirectories implements IBaseDirectories { private File preferencesDir; private File cacheDir; private File userdataDir; diff --git a/src/org/openstreetmap/josm/plugins/PluginClassLoader.java b/src/org/openstreetmap/josm/plugins/PluginClassLoader.java index a53865d5f09..58606b90fcc 100644 --- a/src/org/openstreetmap/josm/plugins/PluginClassLoader.java +++ b/src/org/openstreetmap/josm/plugins/PluginClassLoader.java @@ -58,17 +58,7 @@ public boolean addDependency(PluginClassLoader dependency) { protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { Class result = findLoadedClass(name); if (result == null) { - for (PluginClassLoader dep : dependencies) { - try { - result = dep.loadClass(name, resolve); - if (result != null) { - return result; - } - } catch (ClassNotFoundException e) { - Logging.trace("Plugin class not found in dep {0}: {1}", dep, e.getMessage()); - Logging.trace(e); - } - } + result = findClassInDependencies(name, resolve); try { // Will delegate to parent.loadClass(name, resolve) if needed result = super.loadClass(name, resolve); @@ -92,7 +82,30 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE throw new ClassNotFoundException(name); } + /** + * Try to find the specified class in this classes dependencies + * @param name The name of the class to find + * @param resolve {@code true} to resolve the class + * @return the class, if found, otherwise {@code null} + */ + @SuppressWarnings("PMD.CloseResource") // NOSONAR We do *not* want to close class loaders in this method... + private Class findClassInDependencies(String name, boolean resolve) { + for (PluginClassLoader dep : dependencies) { + try { + Class result = dep.loadClass(name, resolve); + if (result != null) { + return result; + } + } catch (ClassNotFoundException e) { + Logging.trace("Plugin class not found in dep {0}: {1}", dep, e.getMessage()); + Logging.trace(e); + } + } + return null; + } + @Override + @SuppressWarnings("PMD.CloseResource") // NOSONAR We do *not* want to close class loaders in this method... public URL findResource(String name) { URL resource = super.findResource(name); if (resource == null) { diff --git a/src/org/openstreetmap/josm/plugins/PluginHandler.java b/src/org/openstreetmap/josm/plugins/PluginHandler.java index 64fa497d2cb..c56aed350fd 100644 --- a/src/org/openstreetmap/josm/plugins/PluginHandler.java +++ b/src/org/openstreetmap/josm/plugins/PluginHandler.java @@ -55,6 +55,7 @@ import javax.swing.JScrollPane; import javax.swing.UIManager; +import jakarta.annotation.Nullable; import org.openstreetmap.josm.actions.RestartAction; import org.openstreetmap.josm.data.Preferences; import org.openstreetmap.josm.data.PreferencesUtils; @@ -89,6 +90,17 @@ * @since 1326 */ public final class PluginHandler { + private static final String DIALOGS = "dialogs"; + private static final String WARNING = marktr("Warning"); + private static final String HTML_START = ""; + private static final String HTML_END = ""; + private static final String UPDATE_PLUGINS = marktr("Update plugins"); + private static final String CANCEL = "cancel"; + private static final String PLUGINS = "plugins"; + private static final String DISABLE_PLUGIN = marktr("Disable plugin"); + private static final String PLUGINMANAGER_VERSION_BASED_UPDATE_POLICY = "pluginmanager.version-based-update.policy"; + private static final String PLUGINMANAGER_LASTUPDATE = "pluginmanager.lastupdate"; + private static final String PLUGINMANAGER_TIME_BASED_UPDATE_POLICY = "pluginmanager.time-based-update.policy"; /** * Deprecated plugins that are removed on start @@ -196,10 +208,10 @@ public String getText() { Map sorted = new TreeMap<>(Comparator.comparing(String::valueOf)); sorted.putAll(info.attr); for (Entry e : sorted.entrySet()) { - b.append(e.getKey()); - b.append(": "); - b.append(e.getValue()); - b.append('\n'); + b.append(e.getKey()) + .append(": ") + .append(e.getValue()) + .append('\n'); } return b.toString(); } @@ -274,7 +286,7 @@ public int compareTo(DeprecatedPlugin o) { /** * List of unmaintained plugins. Not really up-to-date as the vast majority of plugins are not maintained after a few months, sadly... */ - static final List UNMAINTAINED_PLUGINS = Collections.unmodifiableList(Arrays.asList( + static final List UNMAINTAINED_PLUGINS = List.of( "irsrectify", // See https://josm.openstreetmap.de/changeset/29404/osm/ "surveyor2", // See https://josm.openstreetmap.de/changeset/29404/osm/ "gpsbabelgui", @@ -282,7 +294,7 @@ public int compareTo(DeprecatedPlugin o) { "ContourOverlappingMerge", // See #11202, #11518, https://github.com/bularcasergiu/ContourOverlappingMerge/issues/1 "LaneConnector", // See #11468, #11518, https://github.com/TrifanAdrian/LanecConnectorPlugin/issues/1 "Remove.redundant.points" // See #11468, #11518, https://github.com/bularcasergiu/RemoveRedundantPoints (not even created an issue...) - )); + ); /** * Default time-based update interval, in days (pluginmanager.time-based-update.interval) @@ -384,7 +396,7 @@ static void filterDeprecatedPlugins(Component parent, Collection plugins for (DeprecatedPlugin depr : DEPRECATED_PLUGINS) { if (plugins.contains(depr.name)) { plugins.remove(depr.name); - PreferencesUtils.removeFromList(Config.getPref(), "plugins", depr.name); + PreferencesUtils.removeFromList(Config.getPref(), PLUGINS, depr.name); removedPlugins.add(depr); } } @@ -396,14 +408,14 @@ static void filterDeprecatedPlugins(Component parent, Collection plugins JOptionPane.showMessageDialog( parent, getRemovedPluginsMessage(removedPlugins), - tr("Warning"), + tr(WARNING), JOptionPane.WARNING_MESSAGE ); } static String getRemovedPluginsMessage(Collection removedPlugins) { StringBuilder sb = new StringBuilder(32); - sb.append("") + sb.append(HTML_START) .append(trn( "The following plugin is no longer necessary and has been deactivated:", "The following plugins are no longer necessary and have been deactivated:", @@ -416,7 +428,7 @@ static String getRemovedPluginsMessage(Collection removedPlugi } sb.append(""); } - sb.append("

            "); + sb.append("
          ").append(HTML_END); return sb.toString(); } @@ -436,7 +448,7 @@ static void filterUnmaintainedPlugins(Component parent, Collection plugi continue; } if (confirmDisablePlugin(parent, getUnmaintainedPluginMessage(unmaintained), unmaintained)) { - PreferencesUtils.removeFromList(Config.getPref(), "plugins", unmaintained); + PreferencesUtils.removeFromList(Config.getPref(), PLUGINS, unmaintained); plugins.remove(unmaintained); } } @@ -469,26 +481,26 @@ public static boolean checkAndConfirmPluginUpdate(Component parent) { int v = Version.getInstance().getVersion(); if (Config.getPref().getInt("pluginmanager.version", 0) < v) { message = - "" + HTML_START + tr("You updated your JOSM software.
          " + "To prevent problems the plugins should be updated as well.

          " + "Update plugins now?" ) - + ""; - togglePreferenceKey = "pluginmanager.version-based-update.policy"; + + HTML_END; + togglePreferenceKey = PLUGINMANAGER_VERSION_BASED_UPDATE_POLICY; } else { long tim = System.currentTimeMillis(); - long last = Config.getPref().getLong("pluginmanager.lastupdate", 0); + long last = Config.getPref().getLong(PLUGINMANAGER_LASTUPDATE, 0); int maxTime = Config.getPref().getInt("pluginmanager.time-based-update.interval", DEFAULT_TIME_BASED_UPDATE_INTERVAL); long d = TimeUnit.MILLISECONDS.toDays(tim - last); if ((last <= 0) || (maxTime <= 0)) { - Config.getPref().put("pluginmanager.lastupdate", Long.toString(tim)); + Config.getPref().put(PLUGINMANAGER_LASTUPDATE, Long.toString(tim)); } else if (d > maxTime) { message = - "" + HTML_START + tr("Last plugin update more than {0} days ago.", d) - + ""; - togglePreferenceKey = "pluginmanager.time-based-update.policy"; + + HTML_END; + togglePreferenceKey = PLUGINMANAGER_TIME_BASED_UPDATE_POLICY; } } if (message == null) return false; @@ -500,19 +512,19 @@ public static boolean checkAndConfirmPluginUpdate(Component parent) { // check whether automatic update at startup was disabled // String policy = Config.getPref().get(togglePreferenceKey, "ask").trim().toLowerCase(Locale.ENGLISH); - switch(policy) { + switch (policy) { case "never": - if ("pluginmanager.version-based-update.policy".equals(togglePreferenceKey)) { + if (PLUGINMANAGER_VERSION_BASED_UPDATE_POLICY.equals(togglePreferenceKey)) { Logging.info(tr("Skipping plugin update after JOSM upgrade. Automatic update at startup is disabled.")); - } else if ("pluginmanager.time-based-update.policy".equals(togglePreferenceKey)) { + } else if (PLUGINMANAGER_TIME_BASED_UPDATE_POLICY.equals(togglePreferenceKey)) { Logging.info(tr("Skipping plugin update after elapsed update interval. Automatic update at startup is disabled.")); } return false; case "always": - if ("pluginmanager.version-based-update.policy".equals(togglePreferenceKey)) { + if (PLUGINMANAGER_VERSION_BASED_UPDATE_POLICY.equals(togglePreferenceKey)) { Logging.info(tr("Running plugin update after JOSM upgrade. Automatic update at startup is enabled.")); - } else if ("pluginmanager.time-based-update.policy".equals(togglePreferenceKey)) { + } else if (PLUGINMANAGER_TIME_BASED_UPDATE_POLICY.equals(togglePreferenceKey)) { Logging.info(tr("Running plugin update after elapsed update interval. Automatic update at startup is disabled.")); } return true; @@ -526,14 +538,14 @@ public static boolean checkAndConfirmPluginUpdate(Component parent) { ButtonSpec[] options = { new ButtonSpec( - tr("Update plugins"), - new ImageProvider("dialogs", "refresh"), + tr(UPDATE_PLUGINS), + new ImageProvider(DIALOGS, "refresh"), tr("Click to update the activated plugins"), null /* no specific help context */ ), new ButtonSpec( tr("Skip update"), - new ImageProvider("cancel"), + new ImageProvider(CANCEL), tr("Click to skip updating the activated plugins"), null /* no specific help context */ ) @@ -542,7 +554,7 @@ public static boolean checkAndConfirmPluginUpdate(Component parent) { int ret = HelpAwareOptionPane.showOptionDialog( parent, pnlMessage, - tr("Update plugins"), + tr(UPDATE_PLUGINS), JOptionPane.WARNING_MESSAGE, null, options, @@ -551,7 +563,7 @@ public static boolean checkAndConfirmPluginUpdate(Component parent) { ); if (pnlMessage.isRememberDecision()) { - switch(ret) { + switch (ret) { case 0: Config.getPref().put(togglePreferenceKey, "always"); break; @@ -576,14 +588,14 @@ public static boolean checkAndConfirmPluginUpdate(Component parent) { */ private static void alertMissingRequiredPlugin(Component parent, String plugin, Set missingRequiredPlugin) { StringBuilder sb = new StringBuilder(48); - sb.append("") + sb.append(HTML_START) .append(trn("Plugin {0} requires a plugin which was not found. The missing plugin is:", "Plugin {0} requires {1} plugins which were not found. The missing plugins are:", missingRequiredPlugin.size(), Utils.escapeReservedCharactersHTML(plugin), missingRequiredPlugin.size())) .append(Utils.joinAsHtmlUnorderedList(missingRequiredPlugin)) - .append(""); + .append(HTML_END); ButtonSpec[] specs = { new ButtonSpec( tr("Download and restart"), @@ -635,11 +647,11 @@ private static void downloadRequiredPluginsAndRestart(final Component parent, fi // restart if some plugins have been downloaded if (!task.getDownloadedPlugins().isEmpty()) { // update plugin list in preferences - Set plugins = new HashSet<>(Config.getPref().getList("plugins")); + Set plugins = new HashSet<>(Config.getPref().getList(PLUGINS)); for (PluginInformation plugin : task.getDownloadedPlugins()) { plugins.add(plugin.name); } - Config.getPref().putList("plugins", new ArrayList<>(plugins)); + Config.getPref().putList(PLUGINS, new ArrayList<>(plugins)); // restart RestartAction.restartJOSM(); } else { @@ -666,24 +678,20 @@ private static void alertJavaUpdateRequired(Component parent, String plugin, int }; final int selected = HelpAwareOptionPane.showOptionDialog( parent, - "" + tr("Plugin {0} requires Java version {1}. The current Java version is {2}.
          " + HTML_START + tr("Plugin {0} requires Java version {1}. The current Java version is {2}.
          " + "You have to update Java in order to use this plugin.", plugin, Integer.toString(requiredVersion), Utils.getJavaVersion() - ) + "", - tr("Warning"), + ) + HTML_END, + tr(WARNING), JOptionPane.WARNING_MESSAGE, null, options, options[0], null ); - if (selected == 1) { - if (Utils.isRunningJavaWebStart()) { - OpenBrowser.displayUrl(Config.getPref().get("openwebstart.download.url", "https://openwebstart.com/download/")); - } else if (!Utils.isRunningWebStart()) { - final String javaUrl = PlatformManager.getPlatform().getJavaUrl(); - OpenBrowser.displayUrl(javaUrl); - } + if (selected == 1 && !Utils.isRunningWebStart()) { + final String javaUrl = PlatformManager.getPlatform().getJavaUrl(); + OpenBrowser.displayUrl(javaUrl); } } @@ -694,7 +702,7 @@ private static void alertJOSMUpdateRequired(Component parent, String plugin, int +"You have to update JOSM in order to use this plugin.", plugin, Integer.toString(requiredVersion), Version.getInstance().getVersionString() ), - tr("Warning"), + tr(WARNING), JOptionPane.WARNING_MESSAGE, null ); @@ -762,22 +770,8 @@ public static boolean checkRequiredPluginsPreconditions(Component parent, Collec String requires = local ? plugin.localrequires : plugin.requires; // make sure the dependencies to other plugins are not broken - // if (requires != null) { - Set pluginNames = new HashSet<>(); - for (PluginInformation pi: plugins) { - pluginNames.add(pi.name); - if (pi.provides != null) { - pluginNames.add(pi.provides); - } - } - Set missingPlugins = new HashSet<>(); - List requiredPlugins = local ? plugin.getLocalRequiredPlugins() : plugin.getRequiredPlugins(); - for (String requiredPlugin : requiredPlugins) { - if (!pluginNames.contains(requiredPlugin)) { - missingPlugins.add(requiredPlugin); - } - } + Set missingPlugins = findMissingPlugins(plugins, plugin, local); if (!missingPlugins.isEmpty()) { if (parent != null) { alertMissingRequiredPlugin(parent, plugin.name, missingPlugins); @@ -788,6 +782,31 @@ public static boolean checkRequiredPluginsPreconditions(Component parent, Collec return true; } + /** + * Find the missing plugin(s) for a specified plugin + * @param plugins The currently loaded plugins + * @param plugin The plugin to find the missing information for + * @param local Determines if the local or up-to-date plugin dependencies are to be checked. + * @return A set of missing plugins for the given plugin + */ + private static Set findMissingPlugins(Collection plugins, PluginInformation plugin, boolean local) { + Set pluginNames = new HashSet<>(); + for (PluginInformation pi: plugins) { + pluginNames.add(pi.name); + if (pi.provides != null) { + pluginNames.add(pi.provides); + } + } + Set missingPlugins = new HashSet<>(); + List requiredPlugins = local ? plugin.getLocalRequiredPlugins() : plugin.getRequiredPlugins(); + for (String requiredPlugin : requiredPlugins) { + if (!pluginNames.contains(requiredPlugin)) { + missingPlugins.add(requiredPlugin); + } + } + return missingPlugins; + } + /** * Get class loader to locate resources from plugins. *

          @@ -810,6 +829,7 @@ private static synchronized DynamicURLClassLoader getJoinedPluginResourceCL() { * * @param plugins the plugins to add */ + @SuppressWarnings("PMD.CloseResource") // NOSONAR We do *not* want to close class loaders in this method... private static void extendJoinedPluginResourceCL(Collection plugins) { // iterate all plugins and collect all libraries of all plugins: File pluginDir = Preferences.main().getPluginsDirectory(); @@ -860,7 +880,7 @@ private static void loadPlugin(Component parent, PluginInformation plugin, Plugi Logging.error(e); } if (msg != null && confirmDisablePlugin(parent, msg, plugin.name)) { - PreferencesUtils.removeFromList(Config.getPref(), "plugins", plugin.name); + PreferencesUtils.removeFromList(Config.getPref(), PLUGINS, plugin.name); } } @@ -893,35 +913,10 @@ public static void loadPlugins(Component parent, Collection p if (toLoad.isEmpty()) return; - for (PluginInformation info : toLoad) { - PluginClassLoader cl = AccessController.doPrivileged((PrivilegedAction) - () -> new PluginClassLoader( - info.libraries.toArray(new URL[0]), - PluginHandler.class.getClassLoader(), - null)); - classLoaders.put(info.name, cl); - } + generateClassloaders(toLoad); // resolve dependencies - for (PluginInformation info : toLoad) { - PluginClassLoader cl = classLoaders.get(info.name); - DEPENDENCIES: - for (String depName : info.getLocalRequiredPlugins()) { - for (PluginInformation depInfo : toLoad) { - if (isDependency(depInfo, depName)) { - cl.addDependency(classLoaders.get(depInfo.name)); - continue DEPENDENCIES; - } - } - for (PluginProxy proxy : pluginList) { - if (isDependency(proxy.getPluginInformation(), depName)) { - cl.addDependency(proxy.getClassLoader()); - continue DEPENDENCIES; - } - } - Logging.error("unable to find dependency " + depName + " for plugin " + info.getName()); - } - } + resolveDependencies(toLoad); extendJoinedPluginResourceCL(toLoad); ResourceProvider.addAdditionalClassLoaders(getResourceClassLoaders()); @@ -936,6 +931,53 @@ public static void loadPlugins(Component parent, Collection p } } + /** + * Generate classloaders for a list of plugins + * @param toLoad The plugins to generate the classloaders for + */ + @SuppressWarnings({"squid:S2095", "PMD.CloseResource"}) // NOSONAR the classloaders and put in a map which we want to keep. + private static void generateClassloaders(List toLoad) { + for (PluginInformation info : toLoad) { + PluginClassLoader cl = AccessController.doPrivileged((PrivilegedAction) + () -> new PluginClassLoader( + info.libraries.toArray(new URL[0]), + PluginHandler.class.getClassLoader(), + null)); + classLoaders.put(info.name, cl); + } + } + + /** + * Resolve dependencies for a list of plugins + * @param toLoad The plugins to resolve dependencies for + */ + @SuppressWarnings({"squid:S2095", "PMD.CloseResource"}) // NOSONAR the classloaders are from a persistent map + private static void resolveDependencies(List toLoad) { + for (PluginInformation info : toLoad) { + PluginClassLoader cl = classLoaders.get(info.name); + for (String depName : info.getLocalRequiredPlugins()) { + boolean finished = false; + for (PluginInformation depInfo : toLoad) { + if (isDependency(depInfo, depName)) { + cl.addDependency(classLoaders.get(depInfo.name)); + finished = true; + break; + } + } + if (finished) { + continue; + } + for (PluginProxy proxy : pluginList) { + if (isDependency(proxy.getPluginInformation(), depName)) { + cl.addDependency(proxy.getClassLoader()); + break; + } + } + Logging.error("unable to find dependency " + depName + " for plugin " + info.getName()); + } + } + } + private static boolean isDependency(PluginInformation pi, String depName) { return depName.equals(pi.getName()) || depName.equals(pi.provides); } @@ -991,6 +1033,8 @@ public static void loadLatePlugins(Component parent, Collection loadLocallyAvailablePluginInformation(ProgressMonitor monitor) { if (monitor == null) { monitor = NullProgressMonitor.INSTANCE; @@ -1004,6 +1048,7 @@ private static Map loadLocallyAvailablePluginInformat Logging.error(e); return null; } catch (InterruptedException e) { + Thread.currentThread().interrupt(); Logging.warn("InterruptedException in " + PluginHandler.class.getSimpleName() + " while loading locally available plugin information"); return null; @@ -1019,20 +1064,19 @@ private static Map loadLocallyAvailablePluginInformat } private static void alertMissingPluginInformation(Component parent, Collection plugins) { - StringBuilder sb = new StringBuilder(); - sb.append("") - .append(trn("JOSM could not find information about the following plugin:", - "JOSM could not find information about the following plugins:", - plugins.size())) - .append(Utils.joinAsHtmlUnorderedList(plugins)) - .append(trn("The plugin is not going to be loaded.", - "The plugins are not going to be loaded.", - plugins.size())) - .append(""); + String sb = HTML_START + + trn("JOSM could not find information about the following plugin:", + "JOSM could not find information about the following plugins:", + plugins.size()) + + Utils.joinAsHtmlUnorderedList(plugins) + + trn("The plugin is not going to be loaded.", + "The plugins are not going to be loaded.", + plugins.size()) + + HTML_END; HelpAwareOptionPane.showOptionDialog( parent, - sb.toString(), - tr("Warning"), + sb, + tr(WARNING), JOptionPane.WARNING_MESSAGE, ht("/Plugin/Loading#MissingPluginInfos") ); @@ -1053,7 +1097,7 @@ public static List buildListOfPluginsToLoad(Component parent, } try { monitor.beginTask(tr("Determining plugins to load...")); - Set plugins = new HashSet<>(Config.getPref().getList("plugins", new LinkedList<>())); + Set plugins = new HashSet<>(Config.getPref().getList(PLUGINS, new LinkedList<>())); Logging.debug("Plugins list initialized to {0}", plugins); String systemProp = Utils.getSystemProperty("josm.plugins"); if (systemProp != null) { @@ -1087,7 +1131,7 @@ public static List buildListOfPluginsToLoad(Component parent, private static void alertFailedPluginUpdate(Component parent, Collection plugins) { StringBuilder sb = new StringBuilder(128); - sb.append("") + sb.append(HTML_START) .append(trn( "Updating the following plugin has failed:", "Updating the following plugins has failed:", @@ -1101,7 +1145,7 @@ private static void alertFailedPluginUpdate(Component parent, Collection"); + .append(HTML_END); HelpAwareOptionPane.showOptionDialog( parent, sb.toString(), @@ -1183,6 +1227,7 @@ public static Collection updatePlugins(Component parent, Logging.error(e); // don't abort in case of error, continue with downloading plugins below } catch (InterruptedException e) { + Thread.currentThread().interrupt(); Logging.warn(tr("Failed to download plugin information list") + ": InterruptedException"); // don't abort in case of error, continue with downloading plugins below } @@ -1221,7 +1266,7 @@ public static Collection updatePlugins(Component parent, pluginDownloadTask = new PluginDownloadTask( monitor.createSubTaskMonitor(1, false), pluginsToDownload, - tr("Update plugins") + tr(UPDATE_PLUGINS) ); future = MainApplication.worker.submit(pluginDownloadTask); @@ -1232,6 +1277,7 @@ public static Collection updatePlugins(Component parent, alertFailedPluginUpdate(parent, pluginsToUpdate); return plugins; } catch (InterruptedException e) { + Thread.currentThread().interrupt(); Logging.warn("InterruptedException in " + PluginHandler.class.getSimpleName() + " while updating plugins"); alertFailedPluginUpdate(parent, pluginsToUpdate); @@ -1253,7 +1299,7 @@ public static Collection updatePlugins(Component parent, if (pluginsWanted == null) { // if all plugins updated, remember the update because it was successful Config.getPref().putInt("pluginmanager.version", Version.getInstance().getVersion()); - Config.getPref().put("pluginmanager.lastupdate", Long.toString(System.currentTimeMillis())); + Config.getPref().put(PLUGINMANAGER_LASTUPDATE, Long.toString(System.currentTimeMillis())); } return plugins; } @@ -1269,14 +1315,14 @@ public static Collection updatePlugins(Component parent, public static boolean confirmDisablePlugin(Component parent, String reason, String name) { ButtonSpec[] options = { new ButtonSpec( - tr("Disable plugin"), - new ImageProvider("dialogs", "delete"), + tr(DISABLE_PLUGIN), + new ImageProvider(DIALOGS, "delete"), tr("Click to delete the plugin ''{0}''", name), null /* no specific help context */ ), new ButtonSpec( tr("Keep plugin"), - new ImageProvider("cancel"), + new ImageProvider(CANCEL), tr("Click to keep the plugin ''{0}''", name), null /* no specific help context */ ) @@ -1284,7 +1330,7 @@ public static boolean confirmDisablePlugin(Component parent, String reason, Stri return 0 == HelpAwareOptionPane.showOptionDialog( parent, reason, - tr("Disable plugin"), + tr(DISABLE_PLUGIN), JOptionPane.WARNING_MESSAGE, null, options, @@ -1483,26 +1529,26 @@ private static int askUpdateDisableKeepPluginAfterException(PluginProxy plugin) final ButtonSpec[] options = { new ButtonSpec( tr("Update plugin"), - new ImageProvider("dialogs", "refresh"), + new ImageProvider(DIALOGS, "refresh"), tr("Click to update the plugin ''{0}''", plugin.getPluginInformation().name), null /* no specific help context */ ), new ButtonSpec( - tr("Disable plugin"), - new ImageProvider("dialogs", "delete"), + tr(DISABLE_PLUGIN), + new ImageProvider(DIALOGS, "delete"), tr("Click to disable the plugin ''{0}''", plugin.getPluginInformation().name), null /* no specific help context */ ), new ButtonSpec( tr("Keep plugin"), - new ImageProvider("cancel"), + new ImageProvider(CANCEL), tr("Click to keep the plugin ''{0}''", plugin.getPluginInformation().name), null /* no specific help context */ ) }; final StringBuilder msg = new StringBuilder(256); - msg.append("") + msg.append(HTML_START) .append(tr("An unexpected exception occurred that may have come from the ''{0}'' plugin.", Utils.escapeReservedCharactersHTML(plugin.getPluginInformation().name))) .append("
          "); @@ -1512,13 +1558,13 @@ private static int askUpdateDisableKeepPluginAfterException(PluginProxy plugin) .append("
          "); } msg.append(tr("Try updating to the newest version of this plugin before reporting a bug.")) - .append(""); + .append(HTML_END); try { FutureTask task = new FutureTask<>(() -> HelpAwareOptionPane.showOptionDialog( MainApplication.getMainFrame(), msg.toString(), - tr("Update plugins"), + tr(UPDATE_PLUGINS), JOptionPane.QUESTION_MESSAGE, null, options, @@ -1527,7 +1573,10 @@ private static int askUpdateDisableKeepPluginAfterException(PluginProxy plugin) )); GuiHelper.runInEDT(task); return task.get(); - } catch (InterruptedException | ExecutionException e) { + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + Logging.warn(e); + } catch (ExecutionException e) { Logging.warn(e); } return -1; @@ -1589,7 +1638,7 @@ public static PluginDownloadTask updateOrdisablePluginAfterException(Throwable e // don't know what plugin threw the exception return null; - Set plugins = new HashSet<>(Config.getPref().getList("plugins")); + Set plugins = new HashSet<>(Config.getPref().getList(PLUGINS)); final PluginInformation pluginInfo = plugin.getPluginInformation(); if (!plugins.contains(pluginInfo.name)) // plugin not activated ? strange in this context but anyway, don't bother @@ -1604,7 +1653,7 @@ public static PluginDownloadTask updateOrdisablePluginAfterException(Throwable e case 1: // deactivate the plugin plugins.remove(plugin.getPluginInformation().name); - Config.getPref().putList("plugins", new ArrayList<>(plugins)); + Config.getPref().putList(PLUGINS, new ArrayList<>(plugins)); GuiHelper.runInEDTAndWait(() -> JOptionPane.showMessageDialog( MainApplication.getMainFrame(), tr("The plugin has been removed from the configuration. Please restart JOSM to unload the plugin."), @@ -1623,7 +1672,7 @@ public static PluginDownloadTask updateOrdisablePluginAfterException(Throwable e * @return The list of loaded plugins */ public static Collection getBugReportInformation() { - final Collection pl = new TreeSet<>(Config.getPref().getList("plugins", new LinkedList<>())); + final Collection pl = new TreeSet<>(Config.getPref().getList(PLUGINS, new LinkedList<>())); for (final PluginProxy pp : pluginList) { PluginInformation pi = pp.getPluginInformation(); pl.remove(pi.name); @@ -1643,7 +1692,7 @@ public static JPanel getInfoPanel() { String name = info.name + (!Utils.isEmpty(info.localversion) ? " Version: " + info.localversion : ""); pluginTab.add(new JLabel(name), GBC.std()); - pluginTab.add(Box.createHorizontalGlue(), GBC.std().fill(GBC.HORIZONTAL)); + pluginTab.add(Box.createHorizontalGlue(), GBC.std().fill(GridBagConstraints.HORIZONTAL)); pluginTab.add(new JButton(new PluginInformationAction(info)), GBC.eol()); JosmTextArea description = new JosmTextArea(info.description == null ? tr("no description available") @@ -1656,7 +1705,7 @@ public static JPanel getInfoPanel() { description.setBackground(UIManager.getColor("Panel.background")); description.setCaretPosition(0); - pluginTab.add(description, GBC.eop().fill(GBC.HORIZONTAL)); + pluginTab.add(description, GBC.eop().fill(GridBagConstraints.HORIZONTAL)); } return pluginTab; } diff --git a/src/org/openstreetmap/josm/spi/lifecycle/Lifecycle.java b/src/org/openstreetmap/josm/spi/lifecycle/Lifecycle.java index 451e8a94070..7ae850e4900 100644 --- a/src/org/openstreetmap/josm/spi/lifecycle/Lifecycle.java +++ b/src/org/openstreetmap/josm/spi/lifecycle/Lifecycle.java @@ -66,6 +66,8 @@ public static void setShutdownSequence(Runnable sequence) { * @param initSequence Initialization sequence * @since 14139 */ + // PMD wasn't detecting that we were trying to shutdown the ExecutorService + @SuppressWarnings("PMD.CloseResource") public static void initialize(InitializationSequence initSequence) { // Initializes tasks that must be run before parallel tasks runInitializationTasks(initSequence.beforeInitializationTasks()); @@ -89,7 +91,10 @@ public static void initialize(InitializationSequence initSequence) { } catch (SecurityException e) { Logging.log(Logging.LEVEL_ERROR, "Unable to shutdown executor service", e); } - } catch (InterruptedException | ExecutionException ex) { + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + throw new JosmRuntimeException(ex); + } catch (ExecutionException ex) { throw new JosmRuntimeException(ex); } diff --git a/src/org/openstreetmap/josm/tools/AlphanumComparator.java b/src/org/openstreetmap/josm/tools/AlphanumComparator.java index b4c125bd85b..a49c7595e70 100644 --- a/src/org/openstreetmap/josm/tools/AlphanumComparator.java +++ b/src/org/openstreetmap/josm/tools/AlphanumComparator.java @@ -32,24 +32,53 @@ */ import java.io.Serializable; import java.text.Collator; +import java.util.Arrays; import java.util.Comparator; /** * The Alphanum Algorithm is an improved sorting algorithm for strings * containing numbers: Instead of sorting numbers in ASCII order like a standard * sort, this algorithm sorts numbers in numeric order. - * - * The Alphanum Algorithm is discussed at http://www.DaveKoelle.com - * + *

          + * The Alphanum Algorithm is discussed at + * DaveKoelle.com + *

          * This is an updated version with enhancements made by Daniel Migowski, Andre * Bogus, David Koelle and others. * */ public final class AlphanumComparator implements Comparator, Serializable { + /** {@code true} to use the faster ASCII sorting algorithm. Set to {@code false} when testing compatibility. */ + static boolean useFastASCIISort = true; + /** + * The sort order for the fast ASCII sort method. + */ + static final String ASCII_SORT_ORDER = + " \r\t\n\f\u000b-_,;:!?/.`^~'\"()[]{}@$*\\&#%+<=>|0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; private static final long serialVersionUID = 1L; private static final AlphanumComparator INSTANCE = new AlphanumComparator(); + /** + * A mapping from ASCII characters to the default {@link Collator} order. + * At writing, the default rules can be found in CollationRules#DEFAULTRULES. + */ + private static final byte[] ASCII_MAPPING = new byte[128]; + static { + for (int i = 0; i < ASCII_MAPPING.length; i++) { + ASCII_MAPPING[i] = (byte) i; // This is kind of pointless, but it is the default ASCII ordering. + } + // The control characters are "ignored" + Arrays.fill(ASCII_MAPPING, 0, 32, (byte) 0); + ASCII_MAPPING[127] = 0; // DEL is ignored. + // We have 37 order overrides for symbols; ASCII tables has control characters through 31. 32-47 are symbols. + // After the symbols, we have 0-9, and then aA-zZ. + // The character order + for (int i = 0; i < ASCII_SORT_ORDER.length(); i++) { + char c = ASCII_SORT_ORDER.charAt(i); + ASCII_MAPPING[c] = (byte) (i + 1); + } + } /** * Replies the unique instance. @@ -65,6 +94,41 @@ public static AlphanumComparator getInstance() { private AlphanumComparator() { } + /** + * Compare two ASCII strings in a manner compatible with the default {@link Collator} + * @param string1 The first string to compare + * @param len1 The length of the first string + * @param string2 The second string to compare + * @param len2 The length of the second string + * @return See {@link String#compareToIgnoreCase(String)} (e.g. {@code string1.compareToIgnoreCase(string2)}). + */ + private static int compareString(String string1, int len1, String string2, int len2) { + int loc1 = 0; + int loc2 = 0; + while (loc1 < len1 && loc2 < len2) { + // Ignore control symbols + while (loc1 < len1 - 1 && string1.charAt(loc1) <= 32) { + loc1++; + } + while (loc2 < len2 - 1 && string2.charAt(loc2) <= 32) { + loc2++; + } + if (loc1 >= len1 || loc2 >= len2) break; + + char lower1 = Character.toLowerCase(string1.charAt(loc1)); + char lower2 = Character.toLowerCase(string2.charAt(loc2)); + + final int c1 = ASCII_MAPPING[lower1]; + final int c2 = ASCII_MAPPING[lower2]; + if (c1 != c2) { + return c1 - c2; + } + loc1++; + loc2++; + } + return len1 - len2; + } + /** * Returns an alphanum chunk. * Length of string is passed in for improved efficiency (only need to calculate it once). @@ -74,9 +138,8 @@ private AlphanumComparator() { * @return alphanum chunk found at given position */ private static String getChunk(String s, int slength, int marker) { - StringBuilder chunk = new StringBuilder(); + final int startMarker = marker; char c = s.charAt(marker); - chunk.append(c); marker++; if (Character.isDigit(c)) { while (marker < slength) { @@ -84,7 +147,6 @@ private static String getChunk(String s, int slength, int marker) { if (!Character.isDigit(c)) { break; } - chunk.append(c); marker++; } } else { @@ -93,11 +155,62 @@ private static String getChunk(String s, int slength, int marker) { if (Character.isDigit(c)) { break; } - chunk.append(c); marker++; } } - return chunk.toString(); + return s.substring(startMarker, marker); + } + + /** + * Check if a string is ASCII only + * @param string The string to check + * @param stringLength The length of the string (for performance reasons) + * @return {@code true} if the string only contains ascii characters + */ + private static boolean isAscii(String string, int stringLength) { + for (int i = 0; i < stringLength; i++) { + char c = string.charAt(i); + if (c > ASCII_MAPPING.length) { + return false; + } + } + return true; + } + + /** + * Compare two string chunks + * @param thisChunk The first chunk to compare + * @param thisChunkLength The length of the first chunk (for performance reasons) + * @param thatChunk The second chunk to compare + * @param thatChunkLength The length of the second chunk (for performance reasons) + * @return The {@link Comparator} result + */ + private static int compareChunk(String thisChunk, int thisChunkLength, String thatChunk, int thatChunkLength) { + int result; + if (Character.isDigit(thisChunk.charAt(0)) && Character.isDigit(thatChunk.charAt(0))) { + // Simple chunk comparison by length. + result = thisChunkLength - thatChunkLength; + // If equal, the first different number counts + if (result == 0) { + for (int i = 0; i < thisChunkLength; i++) { + result = thisChunk.charAt(i) - thatChunk.charAt(i); + if (result != 0) { + return result; + } + } + } + } else { + // Check if both chunks are ascii only; if so, use a much faster sorting algorithm. + if (useFastASCIISort && isAscii(thisChunk, thisChunkLength) && isAscii(thatChunk, thatChunkLength)) { + return Utils.clamp(compareString(thisChunk, thisChunkLength, thatChunk, thatChunkLength), -1, 1); + } + // Instantiate the collator + Collator compareOperator = Collator.getInstance(); + // Compare regardless of accented letters + compareOperator.setStrength(Collator.SECONDARY); + result = compareOperator.compare(thisChunk, thatChunk); + } + return result; } @Override @@ -116,34 +229,16 @@ public int compare(String s1, String s2) { int s2Length = s2.length(); while (thisMarker < s1Length && thatMarker < s2Length) { - String thisChunk = getChunk(s1, s1Length, thisMarker); - thisMarker += thisChunk.length(); + final String thisChunk = getChunk(s1, s1Length, thisMarker); + final int thisChunkLength = thisChunk.length(); + thisMarker += thisChunkLength; String thatChunk = getChunk(s2, s2Length, thatMarker); - thatMarker += thatChunk.length(); + final int thatChunkLength = thatChunk.length(); + thatMarker += thatChunkLength; // If both chunks contain numeric characters, sort them numerically - int result; - if (Character.isDigit(thisChunk.charAt(0)) && Character.isDigit(thatChunk.charAt(0))) { - // Simple chunk comparison by length. - int thisChunkLength = thisChunk.length(); - result = thisChunkLength - thatChunk.length(); - // If equal, the first different number counts - if (result == 0) { - for (int i = 0; i < thisChunkLength; i++) { - result = thisChunk.charAt(i) - thatChunk.charAt(i); - if (result != 0) { - return result; - } - } - } - } else { - // Instantiate the collator - Collator compareOperator = Collator.getInstance(); - // Compare regardless of accented letters - compareOperator.setStrength(Collator.SECONDARY); - result = compareOperator.compare(thisChunk, thatChunk); - } + int result = compareChunk(thisChunk, thisChunkLength, thatChunk, thatChunkLength); if (result != 0) { return result; diff --git a/src/org/openstreetmap/josm/tools/CopyList.java b/src/org/openstreetmap/josm/tools/CopyList.java index 35680680651..55b2e169d8a 100644 --- a/src/org/openstreetmap/josm/tools/CopyList.java +++ b/src/org/openstreetmap/josm/tools/CopyList.java @@ -138,7 +138,7 @@ public Iterator iterator() { return new Itr(); } - private class Itr implements Iterator { + private final class Itr implements Iterator { /** * Index of element to be returned by subsequent call to next. */ @@ -197,7 +197,7 @@ public void remove() { } } - final void checkForComodification() { + void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } diff --git a/src/org/openstreetmap/josm/tools/Diff.java b/src/org/openstreetmap/josm/tools/Diff.java index da5bc5e63d8..d4f7e63e8f3 100644 --- a/src/org/openstreetmap/josm/tools/Diff.java +++ b/src/org/openstreetmap/josm/tools/Diff.java @@ -103,7 +103,9 @@ public Diff(Object[] a, Object[] b) { sibling file. */ private int equivMax = 1; - private int[] xvec, yvec; /* Vectors being compared. */ + /* Vectors being compared. */ + private int[] xvec; + private int[] yvec; private int[] fdiag; /* Vector, indexed by diagonal, containing the X coordinate of the point furthest along the given diagonal in the forward @@ -112,30 +114,31 @@ public Diff(Object[] a, Object[] b) { the X coordinate of the point furthest along the given diagonal in the backward search of the edit matrix. */ - private int fdiagoff, bdiagoff; + private int fdiagoff; + private int bdiagoff; private final FileData[] filevec; private int cost; /** * Find the midpoint of the shortest edit script for a specified * portion of the two files. - * + *

          * We scan from the beginnings of the files, and simultaneously from the ends, * doing a breadth-first search through the space of edit-sequence. * When the two searches meet, we have found the midpoint of the shortest * edit sequence. - * + *

          * The value returned is the number of the diagonal on which the midpoint lies. * The diagonal number equals the number of inserted lines minus the number * of deleted lines (counting only lines before the midpoint). * The edit cost is stored into COST; this is the total number of * lines inserted or deleted (counting only lines before the midpoint). - * + *

          * This function assumes that the first lines of the specified portions * of the two files do not match, and likewise that the last lines do not * match. The caller must trim matching lines from the beginning and end * of the portions it is going to specify. - * + *

          * Note that if we return the "wrong" diagonal value, or if * the value of bdiag at that diagonal is "wrong", * the worst this can do is cause suboptimal diff output. @@ -155,8 +158,12 @@ private int diag(int xoff, int xlim, int yoff, int ylim) { final int dmax = xlim - yoff; // Maximum valid diagonal. final int fmid = xoff - yoff; // Center diagonal of top-down search. final int bmid = xlim - ylim; // Center diagonal of bottom-up search. - int fmin = fmid, fmax = fmid; // Limits of top-down search. - int bmin = bmid, bmax = bmid; // Limits of bottom-up search. + // Limits of top-down search. + int fmin = fmid; + int fmax = fmid; + // Limits of bottom-up search. + int bmin = bmid; + int bmax = bmid; // True if southeast corner is on an odd diagonal with respect to the northwest. final boolean odd = (fmid - bmid & 1) != 0; @@ -211,7 +218,10 @@ private int diag(int xoff, int xlim, int yoff, int ylim) { --bmax; } for (d = bmax; d >= bmin; d -= 2) { - int x, y, tlo = bd[bdiagoff + d - 1], thi = bd[bdiagoff + d + 1]; + int x; + int y; + final int tlo = bd[bdiagoff + d - 1]; + final int thi = bd[bdiagoff + d + 1]; if (tlo < thi) { x = tlo; @@ -234,12 +244,12 @@ private int diag(int xoff, int xlim, int yoff, int ylim) { /** * Compare in detail contiguous subsequences of the two files * which are known, as a whole, to match each other. - * + *

          * The results are recorded in the vectors filevec[N].changed_flag, by * storing a 1 in the element for each line that is an insertion or deletion. - * + *

          * The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1. - * + *

          * Note that XLIM, YLIM are exclusive bounds. * All line numbers are origin-0 and discarded lines are not counted. * @param xoff xoff @@ -339,7 +349,8 @@ public Change buildScript( int i0 = 0, i1 = 0; while (i0 < len0 || i1 < len1) { if (changed0[1+i0] || changed1[1+i1]) { - int line0 = i0, line1 = i1; + int line0 = i0; + int line1 = i1; /* Find # lines changed here in each file. */ while (changed0[1+i0]) { @@ -373,7 +384,8 @@ public Change buildScript( while (i0 >= 0 || i1 >= 0) { if (changed0[i0] || changed1[i1]) { - int line0 = i0, line1 = i1; + int line0 = i0; + int line1 = i1; /* Find # lines changed here in each file. */ while (changed0[i0]) { @@ -479,7 +491,7 @@ public static class Change { * LINE0 and LINE1 are the first affected lines in the two files (origin 0). * DELETED is the number of lines deleted here from file 0. * INSERTED is the number of lines inserted here in file 1. - * + *

          * If DELETED is 0 then LINE0 is the number of the line before * which the insertion was done; vice versa for INSERTED and LINE1. * @param line0 first affected lines in the two files (origin 0) @@ -538,7 +550,7 @@ int[] equivCount() { /** * Discard lines that have no matches in another file. - * + *

          * A line which is discarded will not be considered by the actual comparison algorithm; * it will be as if that line were not in the file. * The file's `realindexes' table maps virtual line numbers @@ -746,14 +758,14 @@ private void discard(final byte[] discards) { equivs[i] = equivMax++; h.put(data[i], equivs[i]); } else { - equivs[i] = ir.intValue(); + equivs[i] = ir; } } } /** * Adjust inserts/deletes of blank lines to join changes as much as possible. - * + *

          * We do something when a run of changed lines include a blank line at one end and have an excluded blank line at the other. * We are free to choose which blank line is included. * `compareseq' always chooses the one at the beginning, but usually it is cleaner to consider the following blank line diff --git a/src/org/openstreetmap/josm/tools/ExceptionUtil.java b/src/org/openstreetmap/josm/tools/ExceptionUtil.java index 13e841ce7c2..11ea88e4b10 100644 --- a/src/org/openstreetmap/josm/tools/ExceptionUtil.java +++ b/src/org/openstreetmap/josm/tools/ExceptionUtil.java @@ -297,7 +297,7 @@ public static String explainFailedOAuthAuthentication(OsmApiException e) { + "Authentication at the OSM server with the OAuth token ''{0}'' failed.
          " + "Please launch the preferences dialog and retrieve another OAuth token." + "", - OAuthAccessTokenHolder.getInstance().getAccessTokenKey() + OAuthAccessTokenHolder.getInstance().getAccessToken(e.getUrl(), OsmApi.getAuthMethodVersion()) ); } @@ -343,7 +343,7 @@ public static String explainFailedOAuthAuthorisation(OsmApiException e) { + "''{1}''.
          " + "Please launch the preferences dialog and retrieve another OAuth token." + "", - OAuthAccessTokenHolder.getInstance().getAccessTokenKey(), + OAuthAccessTokenHolder.getInstance().getAccessToken(e.getUrl(), OsmApi.getAuthMethodVersion()), e.getAccessedUrl() == null ? tr("unknown") : e.getAccessedUrl() ); } @@ -460,7 +460,7 @@ private static String formatClosedOn(Instant closedOn) { */ public static String explainGeneric(Exception e) { String msg = e.getMessage(); - if (Utils.isBlank(msg)) { + if (Utils.isStripEmpty(msg)) { msg = e.toString(); } Logging.error(e); diff --git a/src/org/openstreetmap/josm/tools/ExifReader.java b/src/org/openstreetmap/josm/tools/ExifReader.java index 1ebfe908391..ad9e361c4fd 100644 --- a/src/org/openstreetmap/josm/tools/ExifReader.java +++ b/src/org/openstreetmap/josm/tools/ExifReader.java @@ -22,7 +22,6 @@ import com.drew.metadata.Tag; import com.drew.metadata.exif.ExifDirectoryBase; import com.drew.metadata.exif.ExifIFD0Directory; -import com.drew.metadata.exif.ExifSubIFDDirectory; import com.drew.metadata.exif.GpsDirectory; import com.drew.metadata.iptc.IptcDirectory; @@ -75,18 +74,18 @@ public static Instant readInstant(Metadata metadata) { continue; } for (Tag tag : dirIt.getTags()) { - if (tag.getTagType() == ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL /* 0x9003 */ && - !tag.getDescription().matches("\\[[0-9]+ .+\\]")) { + if (tag.getTagType() == ExifDirectoryBase.TAG_DATETIME_ORIGINAL /* 0x9003 */ && + !tag.getDescription().matches("\\[\\d+ .+]")) { dateTimeOrig = tag.getDescription(); - } else if (tag.getTagType() == ExifIFD0Directory.TAG_DATETIME /* 0x0132 */) { + } else if (tag.getTagType() == ExifDirectoryBase.TAG_DATETIME /* 0x0132 */) { dateTime = tag.getDescription(); - } else if (tag.getTagType() == ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED /* 0x9004 */) { + } else if (tag.getTagType() == ExifDirectoryBase.TAG_DATETIME_DIGITIZED /* 0x9004 */) { dateTimeDig = tag.getDescription(); - } else if (tag.getTagType() == ExifSubIFDDirectory.TAG_SUBSECOND_TIME_ORIGINAL /* 0x9291 */) { + } else if (tag.getTagType() == ExifDirectoryBase.TAG_SUBSECOND_TIME_ORIGINAL /* 0x9291 */) { subSecOrig = tag.getDescription(); - } else if (tag.getTagType() == ExifSubIFDDirectory.TAG_SUBSECOND_TIME /* 0x9290 */) { + } else if (tag.getTagType() == ExifDirectoryBase.TAG_SUBSECOND_TIME /* 0x9290 */) { subSec = tag.getDescription(); - } else if (tag.getTagType() == ExifSubIFDDirectory.TAG_SUBSECOND_TIME_DIGITIZED /* 0x9292 */) { + } else if (tag.getTagType() == ExifDirectoryBase.TAG_SUBSECOND_TIME_DIGITIZED /* 0x9292 */) { subSecDig = tag.getDescription(); } } @@ -144,7 +143,7 @@ public static Integer readOrientation(File filename) { try { final Metadata metadata = JpegMetadataReader.readMetadata(filename); final Directory dir = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class); - return dir == null ? null : dir.getInteger(ExifIFD0Directory.TAG_ORIENTATION); + return dir == null ? null : dir.getInteger(ExifDirectoryBase.TAG_ORIENTATION); } catch (JpegProcessingException | IOException e) { Logging.error(e); } @@ -315,7 +314,7 @@ public static Double readElevation(GpsDirectory dirGps) { Double ele = dirGps.getDoubleObject(GpsDirectory.TAG_ALTITUDE); if (ele != null) { final Integer d = dirGps.getInteger(GpsDirectory.TAG_ALTITUDE_REF); - if (d != null && d.intValue() == 1) { + if (d != null && d == 1) { ele *= -1; } return ele; @@ -366,7 +365,7 @@ public static String readObjectName(IptcDirectory dirIptc) { /** * Returns a Transform that fixes the image orientation. - * + *

          * Only orientation 1, 3, 6 and 8 are supported. Everything else is treated as 1. * @param orientation the exif-orientation of the image * @param width the original width of the image @@ -375,7 +374,8 @@ public static String readObjectName(IptcDirectory dirIptc) { */ public static AffineTransform getRestoreOrientationTransform(final int orientation, final int width, final int height) { final int q; - final double ax, ay; + final double ax; + final double ay; switch (orientation) { case 8: q = -1; @@ -403,7 +403,7 @@ public static AffineTransform getRestoreOrientationTransform(final int orientati /** * Check, if the given orientation switches width and height of the image. * E.g. 90 degree rotation - * + *

          * Only orientation 1, 3, 6 and 8 are supported. Everything else is treated * as 1. * @param orientation the exif-orientation of the image @@ -415,7 +415,7 @@ public static boolean orientationSwitchesDimensions(int orientation) { /** * Check, if the given orientation requires any correction to the image. - * + *

          * Only orientation 1, 3, 6 and 8 are supported. Everything else is treated * as 1. * @param orientation the exif-orientation of the image diff --git a/src/org/openstreetmap/josm/tools/Geometry.java b/src/org/openstreetmap/josm/tools/Geometry.java index 08c92fa4e33..5d2c995e8ed 100644 --- a/src/org/openstreetmap/josm/tools/Geometry.java +++ b/src/org/openstreetmap/josm/tools/Geometry.java @@ -693,14 +693,22 @@ public static PolygonIntersection polygonIntersection(Area a1, Area a2, double e * @since 15938 */ public static Pair polygonIntersectionResult(Area a1, Area a2, double eps) { + // Simple intersect check (if their bounds don't intersect, don't bother going further; there will be no intersection) + // This avoids the more expensive Area#intersect call some of the time (decreases CPU and memory allocation by ~95%) + // in Mesa County, CO geometry validator test runs. + final Rectangle2D a12d = a1.getBounds2D(); + final Rectangle2D a22d = a2.getBounds2D(); + if (!a12d.intersects(a22d) || !a1.intersects(a22d) || !a2.intersects(a12d)) { + return new Pair<>(PolygonIntersection.OUTSIDE, new Area()); + } Area inter = new Area(a1); inter.intersect(a2); if (inter.isEmpty() || !checkIntersection(inter, eps)) { return new Pair<>(PolygonIntersection.OUTSIDE, inter); - } else if (a2.getBounds2D().contains(a1.getBounds2D()) && inter.equals(a1)) { + } else if (a22d.contains(a12d) && inter.equals(a1)) { return new Pair<>(PolygonIntersection.FIRST_INSIDE_SECOND, inter); - } else if (a1.getBounds2D().contains(a2.getBounds2D()) && inter.equals(a2)) { + } else if (a12d.contains(a22d) && inter.equals(a2)) { return new Pair<>(PolygonIntersection.SECOND_INSIDE_FIRST, inter); } else { return new Pair<>(PolygonIntersection.CROSSING, inter); diff --git a/src/org/openstreetmap/josm/tools/HttpClient.java b/src/org/openstreetmap/josm/tools/HttpClient.java index 1b5993270e9..f3001352415 100644 --- a/src/org/openstreetmap/josm/tools/HttpClient.java +++ b/src/org/openstreetmap/josm/tools/HttpClient.java @@ -8,6 +8,7 @@ import java.io.InputStream; import java.net.CookieHandler; import java.net.CookieManager; +import java.net.CookiePolicy; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; @@ -83,7 +84,7 @@ public interface HttpClientFactory { static { try { - CookieHandler.setDefault(new CookieManager()); + CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL)); } catch (SecurityException e) { Logging.log(Logging.LEVEL_ERROR, "Unable to set default cookie handler", e); } @@ -132,6 +133,19 @@ public final Response connect() throws IOException { * @since 9179 */ public final Response connect(ProgressMonitor progressMonitor) throws IOException { + return connect(progressMonitor, null, null); + } + + /** + * Opens the HTTP connection. + * @param progressMonitor progress monitor + * @param authRedirectLocation The location where we will be redirected for authentication + * @param authRequestProperty The authorization header to set when being redirected to the auth location + * @return HTTP response + * @throws IOException if any I/O error occurs + * @since 18913 + */ + public final Response connect(ProgressMonitor progressMonitor, String authRedirectLocation, String authRequestProperty) throws IOException { if (progressMonitor == null) { progressMonitor = NullProgressMonitor.INSTANCE; } @@ -183,8 +197,10 @@ public final Response connect(ProgressMonitor progressMonitor) throws IOExceptio url = new URL(url, redirectLocation); maxRedirects--; logRequest(tr("Download redirected to ''{0}''", redirectLocation)); - // Fix JOSM #21935: Avoid leaking `Authorization` header on redirects. - if (!Objects.equals(oldUrl.getHost(), this.url.getHost()) && this.getRequestHeader("Authorization") != null) { + if (authRedirectLocation != null && authRequestProperty != null && redirectLocation.startsWith(authRedirectLocation)) { + setHeader("Authorization", authRequestProperty); + } else if (!Objects.equals(oldUrl.getHost(), this.url.getHost()) && this.getRequestHeader("Authorization") != null) { + // Fix JOSM #21935: Avoid leaking `Authorization` header on redirects. logRequest(tr("Download redirected to different host (''{0}'' -> ''{1}''), removing authorization headers", oldUrl.getHost(), url.getHost())); this.headers.remove("Authorization"); diff --git a/src/org/openstreetmap/josm/tools/I18n.java b/src/org/openstreetmap/josm/tools/I18n.java index d90e76e5891..4d55e65b41b 100644 --- a/src/org/openstreetmap/josm/tools/I18n.java +++ b/src/org/openstreetmap/josm/tools/I18n.java @@ -377,17 +377,13 @@ public static boolean hasCode(String code) { static String setupJavaLocaleProviders() { // Look up SPI providers first (for JosmDecimalFormatSymbolsProvider). - // Enable CLDR locale provider on Java 8 to get additional languages, such as Khmer. - // https://docs.oracle.com/javase/8/docs/technotes/guides/intl/enhancements.8.html#cldr - // FIXME: This must be updated after we switch to Java 9. - // See https://docs.oracle.com/javase/9/docs/api/java/util/spi/LocaleServiceProvider.html try { try { // First check we're able to open a stream to our own SPI file // Java will fail on Windows if the jar file is in a folder with a space character! I18n.class.getResourceAsStream("/META-INF/services/java.text.spi.DecimalFormatSymbolsProvider").close(); // Don't call Utils.updateSystemProperty to avoid spurious log at startup - return System.setProperty("java.locale.providers", "SPI,JRE,CLDR"); + return System.setProperty("java.locale.providers", "SPI,CLDR"); } catch (RuntimeException | IOException e) { // Don't call Logging class, it may not be fully initialized yet System.err.println("Unable to set SPI locale provider: " + e.getMessage()); @@ -397,7 +393,7 @@ static String setupJavaLocaleProviders() { System.err.println("Unable to set locale providers: " + e.getMessage()); } try { - return System.setProperty("java.locale.providers", "JRE,CLDR"); + return System.setProperty("java.locale.providers", "CLDR"); } catch (SecurityException e) { // Don't call Logging class, it may not be fully initialized yet System.err.println("Unable to set locale providers: " + e.getMessage()); @@ -649,7 +645,7 @@ public static void initializeNumberingFormat() { } private static int pluralEval(long n) { - switch(pluralMode) { + switch (pluralMode) { case MODE_NOTONE: /* bg, da, de, el, en, en_AU, en_CA, en_GB, es, et, eu, fi, gl, is, it, iw_IL, mr, nb, nl, sv */ return (n != 1) ? 1 : 0; case MODE_NONE: /* id, vi, ja, km, tr, zh_CN, zh_TW */ diff --git a/src/org/openstreetmap/josm/tools/ImageProvider.java b/src/org/openstreetmap/josm/tools/ImageProvider.java index 518cff0da6d..c07177a62b4 100644 --- a/src/org/openstreetmap/josm/tools/ImageProvider.java +++ b/src/org/openstreetmap/josm/tools/ImageProvider.java @@ -1717,27 +1717,14 @@ private static BufferedImage read(ImageInputStream stream, boolean readMetadata, reader.setInput(stream, true, !readMetadata && !enforceTransparency); ImageReadParam param = readParamFunction.apply(reader); BufferedImage bi = null; - try { // NOPMD + try (stream) { bi = reader.read(0, param); - if (bi.getTransparency() != Transparency.TRANSLUCENT && (readMetadata || enforceTransparency) && Utils.getJavaVersion() < 11) { - Color color = getTransparentColor(bi.getColorModel(), reader); - if (color != null) { - Hashtable properties = new Hashtable<>(1); - properties.put(PROP_TRANSPARENCY_COLOR, color); - bi = new BufferedImage(bi.getColorModel(), bi.getRaster(), bi.isAlphaPremultiplied(), properties); - if (enforceTransparency) { - Logging.trace("Enforcing image transparency of {0} for {1}", stream, color); - bi = makeImageTransparent(bi, color); - } - } - } } catch (LinkageError e) { // On Windows, ComponentColorModel.getRGBComponent can fail with "UnsatisfiedLinkError: no awt in java.library.path", see #13973 // Then it can leads to "NoClassDefFoundError: Could not initialize class sun.awt.image.ShortInterleavedRaster", see #15079 Logging.error(e); } finally { reader.dispose(); - stream.close(); } return bi; } diff --git a/src/org/openstreetmap/josm/tools/ImageResizeMode.java b/src/org/openstreetmap/josm/tools/ImageResizeMode.java index a6eccdcdc96..638c70f157d 100644 --- a/src/org/openstreetmap/josm/tools/ImageResizeMode.java +++ b/src/org/openstreetmap/josm/tools/ImageResizeMode.java @@ -118,6 +118,7 @@ void prepareGraphics(Dimension icon, BufferedImage image, Graphics2D g) { * @param dim the desired image dimension * @return a cache key */ + @SuppressWarnings("EnumOrdinal") int cacheKey(Dimension dim) { return (ordinal() << 28) | ((dim.width & 0xfff) << 16) | (dim.height & 0xfff); } diff --git a/src/org/openstreetmap/josm/tools/ImageWarp.java b/src/org/openstreetmap/josm/tools/ImageWarp.java index 18c7c884dd8..a4d3641ea13 100644 --- a/src/org/openstreetmap/josm/tools/ImageWarp.java +++ b/src/org/openstreetmap/josm/tools/ImageWarp.java @@ -5,6 +5,7 @@ import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -13,7 +14,7 @@ /** * Image warping algorithm. - * + *

          * Deforms an image geometrically according to a given transformation formula. * @since 11858 */ @@ -26,18 +27,20 @@ private ImageWarp() { /** * Transformation that translates the pixel coordinates. */ + @FunctionalInterface public interface PointTransform { /** * Translates pixel coordinates. - * @param pt pixel coordinates + * @param x The x coordinate + * @param y The y coordinate * @return transformed pixel coordinates */ - Point2D transform(Point2D pt); + Point2D transform(double x, double y); } /** * Wrapper that optimizes a given {@link ImageWarp.PointTransform}. - * + *

          * It does so by spanning a grid with certain step size. It will invoke the * potentially expensive master transform only at those grid points and use * bilinear interpolation to approximate transformed values in between. @@ -74,11 +77,11 @@ public GridTransform(ImageWarp.PointTransform trfm, double stride) { } @Override - public Point2D transform(Point2D pt) { - int xIdx = (int) Math.floor(pt.getX() / stride); - int yIdx = (int) Math.floor(pt.getY() / stride); - double dx = pt.getX() / stride - xIdx; - double dy = pt.getY() / stride - yIdx; + public Point2D transform(double x, double y) { + int xIdx = (int) Math.floor(x / stride); + int yIdx = (int) Math.floor(y / stride); + double dx = x / stride - xIdx; + double dy = y / stride - yIdx; Point2D value00 = getValue(xIdx, yIdx); Point2D value01 = getValue(xIdx, yIdx + 1); Point2D value10 = getValue(xIdx + 1, yIdx); @@ -91,14 +94,24 @@ public Point2D transform(Point2D pt) { } private Point2D getValue(int xIdx, int yIdx) { - return getRow(yIdx).computeIfAbsent(xIdx, k -> trfm.transform(new Point2D.Double(xIdx * stride, yIdx * stride))); + final Map rowMap = getRow(yIdx); + // This *was* computeIfAbsent. Unfortunately, it appears that it generated a ton of memory allocations. + // As in, this was ~50 GB memory allocations in a test, and converting to a non-lambda form made it 1.3GB. + // The primary culprit was LambdaForm#linkToTargetMethod + Point2D current = rowMap.get(xIdx); + if (current == null) { + current = trfm.transform(xIdx * stride, yIdx * stride); + rowMap.put(xIdx, current); + } + return current; } private Map getRow(int yIdx) { cleanUp(yIdx - 3); Map row = cache.get(yIdx); + // Note: using computeIfAbsent will drastically increase memory allocations if (row == null) { - row = new HashMap<>(); + row = new HashMap<>(256); cache.put(yIdx, row); if (consistencyTest) { // should not create a row that has been deleted before @@ -127,14 +140,14 @@ private void cleanUp(int yIdx) { public enum Interpolation { /** * Nearest neighbor. - * + *

          * Simplest possible method. Faster, but not very good quality. */ NEAREST_NEIGHBOR, /** * Bilinear. - * + *

          * Decent quality. */ BILINEAR; @@ -152,24 +165,29 @@ public enum Interpolation { public static BufferedImage warp(BufferedImage srcImg, Dimension targetDim, PointTransform invTransform, Interpolation interpolation) { BufferedImage imgTarget = new BufferedImage(targetDim.width, targetDim.height, BufferedImage.TYPE_INT_ARGB); Rectangle2D srcRect = new Rectangle2D.Double(0, 0, srcImg.getWidth(), srcImg.getHeight()); + // These arrays reduce the amount of memory allocations (getRGB and setRGB are + // collectively 40% of the memory cost, 78% if LambdaForm#linkToTargetMethod is + // ignored). We mostly want to decrease GC pauses here. + final int[] pixel = new int[1]; // Yes, this really does decrease memory allocations with TYPE_INT_ARGB. + final Object sharedArray = getSharedArray(srcImg); for (int j = 0; j < imgTarget.getHeight(); j++) { for (int i = 0; i < imgTarget.getWidth(); i++) { - Point2D srcCoord = invTransform.transform(new Point2D.Double(i, j)); + Point2D srcCoord = invTransform.transform(i, j); if (srcRect.contains(srcCoord)) { int rgba; switch (interpolation) { case NEAREST_NEIGHBOR: - rgba = getColor((int) Math.round(srcCoord.getX()), (int) Math.round(srcCoord.getY()), srcImg); + rgba = getColor((int) Math.round(srcCoord.getX()), (int) Math.round(srcCoord.getY()), srcImg, sharedArray); break; case BILINEAR: int x0 = (int) Math.floor(srcCoord.getX()); double dx = srcCoord.getX() - x0; int y0 = (int) Math.floor(srcCoord.getY()); double dy = srcCoord.getY() - y0; - int c00 = getColor(x0, y0, srcImg); - int c01 = getColor(x0, y0 + 1, srcImg); - int c10 = getColor(x0 + 1, y0, srcImg); - int c11 = getColor(x0 + 1, y0 + 1, srcImg); + int c00 = getColor(x0, y0, srcImg, sharedArray); + int c01 = getColor(x0, y0 + 1, srcImg, sharedArray); + int c10 = getColor(x0 + 1, y0, srcImg, sharedArray); + int c11 = getColor(x0 + 1, y0 + 1, srcImg, sharedArray); rgba = 0; // loop over color components: blue, green, red, alpha for (int ch = 0; ch <= 3; ch++) { @@ -183,17 +201,33 @@ public static BufferedImage warp(BufferedImage srcImg, Dimension targetDim, Poin default: throw new AssertionError(Objects.toString(interpolation)); } - imgTarget.setRGB(i, j, rgba); + imgTarget.getRaster().setDataElements(i, j, imgTarget.getColorModel().getDataElements(rgba, pixel)); } } } return imgTarget; } - private static int getColor(int x, int y, BufferedImage img) { + private static Object getSharedArray(BufferedImage srcImg) { + final int numBands = srcImg.getRaster().getNumBands(); + // Add data types as needed (shown via profiling, look for getRGB). + switch (srcImg.getRaster().getDataBuffer().getDataType()) { + case DataBuffer.TYPE_BYTE: + return new byte[numBands]; + case DataBuffer.TYPE_INT: + return new int[numBands]; + default: + return null; + } + } + + private static int getColor(int x, int y, BufferedImage img, Object sharedArray) { // border strategy: continue with the color of the outermost pixel, - return img.getRGB( - Utils.clamp(x, 0, img.getWidth() - 1), - Utils.clamp(y, 0, img.getHeight() - 1)); + final int rx = Utils.clamp(x, 0, img.getWidth() - 1); + final int ry = Utils.clamp(y, 0, img.getHeight() - 1); + if (sharedArray == null) { + return img.getRGB(rx, ry); + } + return img.getColorModel().getRGB(img.getRaster().getDataElements(rx, ry, sharedArray)); } } diff --git a/src/org/openstreetmap/josm/tools/LanguageInfo.java b/src/org/openstreetmap/josm/tools/LanguageInfo.java index 2038655f107..ac347035684 100644 --- a/src/org/openstreetmap/josm/tools/LanguageInfo.java +++ b/src/org/openstreetmap/josm/tools/LanguageInfo.java @@ -95,7 +95,7 @@ static String getWikiLanguagePrefix(Locale locale, LocaleType type) { return null; } else if (code.matches(".+@.+")) { return code.substring(0, 1).toUpperCase(Locale.ENGLISH) - + code.substring(1, 2) + + code.charAt(1) + '-' + code.substring(3, 4).toUpperCase(Locale.ENGLISH) + code.substring(4) @@ -127,11 +127,11 @@ public static String getJOSMLocaleCode() { /** * Replies the locale code used by JOSM for a given locale. - * + *

          * In most cases JOSM uses the 2-character ISO 639 language code ({@link Locale#getLanguage()} * to identify the locale of a localized resource, but in some cases it may use the * programmatic name for locales, as replied by {@link Locale#toString()}. - * + *

          * For unknown country codes and variants this function already does fallback to * internally known translations. * @@ -154,8 +154,54 @@ else if (I18n.hasCode(full)) // catch all non-single codes } /** - * Replies the locale code used by Java for a given locale. + * Replies the OSM locale codes for the default locale. + * + * @param prefix a prefix like {@code name:}. + * @return the OSM locale codes for the default locale + * @see #getOSMLocaleCodes(String, Locale) + * @since 19045 + */ + public static String[] getOSMLocaleCodes(String prefix) { + return getOSMLocaleCodes(prefix, Locale.getDefault()); + } + + /** + * Replies the locale codes used by OSM for a given locale. + *

          + * In most cases OSM uses the 2-character ISO 639 language code ({@link Locale#getLanguage()} + * to identify the locale of a localized resource, but in some cases it may use the + * programmatic name for locales, as replied by {@link Locale#toString()}. + *

          + * For unknown country codes and variants this function already does fallback to + * internally known translations. * + * @param prefix a prefix like {@code name:}. + * @param locale the locale. Replies "en" if null. + * @return the OSM codes for the given locale + * @since 19045 + */ + public static String[] getOSMLocaleCodes(String prefix, Locale locale) { + if (prefix == null) { + prefix = ""; + } + String main = getJOSMLocaleCode(locale); + switch (main) { + case "zh_CN": + return new String[]{prefix+"zh-Hans-CN", prefix+"zh-Hans", prefix+"zh"}; + case "zh_TW": + return new String[]{prefix+"zh-Hant-TW", prefix+"zh-Hant", prefix+"zh"}; + default: + ArrayList r = new ArrayList<>(); + for (String s : LanguageInfo.getLanguageCodes(null)) { + r.add(prefix + s); + } + return r.toArray(String[]::new); + } + } + + /** + * Replies the locale code used by Java for a given locale. + *

          * In most cases JOSM and Java uses the same codes, but for some exceptions this is needed. * * @param localeName the locale. Replies "en" if null. @@ -173,13 +219,14 @@ public static String getJavaLocaleCode(String localeName) { return "iw_IL"; case "id": return "in"; + default: + return localeName; } - return localeName; } /** * Replies the display string used by JOSM for a given locale. - * + *

          * In most cases returns text replied by {@link Locale#getDisplayName()}, for some * locales an override is used (i.e. when unsupported by Java). * @@ -199,7 +246,7 @@ public static String getDisplayName(Locale locale) { /** * Replies the locale used by Java for a given language code. - * + *

          * Accepts JOSM and Java codes as input. * * @param localeName the locale code. @@ -211,7 +258,7 @@ public static Locale getLocale(String localeName) { /** * Replies the locale used by Java for a given language code. - * + *

          * Accepts JOSM, Java and POSIX codes as input. * * @param localeName the locale code. diff --git a/src/org/openstreetmap/josm/tools/ListenerList.java b/src/org/openstreetmap/josm/tools/ListenerList.java index e6d4f232a63..8b1c8597dfb 100644 --- a/src/org/openstreetmap/josm/tools/ListenerList.java +++ b/src/org/openstreetmap/josm/tools/ListenerList.java @@ -39,7 +39,7 @@ private static final class WeakListener { @Override public boolean equals(Object obj) { - if (obj != null && obj.getClass() == WeakListener.class) { + if (obj instanceof WeakListener) { return Objects.equals(listener.get(), ((WeakListener) obj).listener.get()); } else { return false; @@ -71,16 +71,15 @@ protected ListenerList() { /** * Adds a listener. The listener will not prevent the object from being garbage collected. - * + *

          * This should be used with care. It is better to add good cleanup code. * @param listener The listener. */ public synchronized void addWeakListener(T listener) { if (ensureNotInList(listener)) { // clean the weak listeners, just to be sure... - while (weakListeners.remove(new WeakListener(null))) { - // continue - } + WeakListener nullListener = new WeakListener<>(null); + weakListeners.removeIf(nullListener::equals); weakListeners.add(new WeakListener<>(listener)); } } @@ -223,7 +222,7 @@ private static void dumpStack(StackTraceElement... stackTraceElements) { } } - private static class UncheckedListenerList extends ListenerList { + private static final class UncheckedListenerList extends ListenerList { @Override protected void failAdd(T listener) { Logging.warn("Listener was already added: {0}", listener); diff --git a/src/org/openstreetmap/josm/tools/ListeningCollection.java b/src/org/openstreetmap/josm/tools/ListeningCollection.java index 4361a74fcbb..73df41feb28 100644 --- a/src/org/openstreetmap/josm/tools/ListeningCollection.java +++ b/src/org/openstreetmap/josm/tools/ListeningCollection.java @@ -31,7 +31,7 @@ public ListeningCollection(List base, Runnable runOnModification) { @Override public final Iterator iterator() { Iterator it = base.iterator(); - return new Iterator() { + return new Iterator<>() { private T object; @Override diff --git a/src/org/openstreetmap/josm/tools/Logging.java b/src/org/openstreetmap/josm/tools/Logging.java index dea84df0c2b..d6fe75d80d6 100644 --- a/src/org/openstreetmap/josm/tools/Logging.java +++ b/src/org/openstreetmap/josm/tools/Logging.java @@ -129,9 +129,9 @@ public synchronized void setOutputStream(final OutputStream outputStream) { } @Override - public synchronized void publish(LogRecord record) { - if (this.prioritizedHandler == null || !this.prioritizedHandler.isLoggable(record)) { - super.publish(record); + public synchronized void publish(LogRecord logRecord) { + if (this.prioritizedHandler == null || !this.prioritizedHandler.isLoggable(logRecord)) { + super.publish(logRecord); } } } @@ -480,7 +480,7 @@ public static Logger getLogger() { return LOGGER; } - private static class RememberWarningHandler extends Handler { + private static final class RememberWarningHandler extends Handler { private final String[] log = new String[10]; private int messagesLogged; @@ -490,13 +490,13 @@ synchronized void clear() { } @Override - public synchronized void publish(LogRecord record) { + public synchronized void publish(LogRecord logRecord) { // We don't use setLevel + isLoggable to work in WebStart Sandbox mode - if (record.getLevel().intValue() < LEVEL_WARN.intValue()) { + if (logRecord.getLevel().intValue() < LEVEL_WARN.intValue()) { return; } - String msg = String.format(Locale.ROOT, "%09.3f %s%s", startup.elapsed() / 1000., getPrefix(record), record.getMessage()); + String msg = String.format(Locale.ROOT, "%09.3f %s%s", startup.elapsed() / 1000., getPrefix(logRecord), logRecord.getMessage()); // Only remember first line of message int idx = msg.indexOf('\n'); @@ -507,8 +507,8 @@ public synchronized void publish(LogRecord record) { messagesLogged++; } - private static String getPrefix(LogRecord record) { - if (record.getLevel().equals(LEVEL_WARN)) { + private static String getPrefix(LogRecord logRecord) { + if (logRecord.getLevel().equals(LEVEL_WARN)) { return "W: "; } else { // worse than warn diff --git a/src/org/openstreetmap/josm/tools/Mediawiki.java b/src/org/openstreetmap/josm/tools/Mediawiki.java index 5a5b72f31c6..29415227038 100644 --- a/src/org/openstreetmap/josm/tools/Mediawiki.java +++ b/src/org/openstreetmap/josm/tools/Mediawiki.java @@ -6,6 +6,7 @@ import java.net.URL; import java.util.List; import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import java.util.stream.Collectors; @@ -59,22 +60,33 @@ public Optional findExistingPage(List pages) ); final Document document = getDocument(url); final XPath xPath = XPathFactory.newInstance().newXPath(); - for (String page : distinctPages) { - String normalized = xPath.evaluate("/api/query/normalized/n[@from='" + page + "']/@to", document); - if (Utils.isEmpty(normalized)) { - normalized = page; + AtomicReference normalized = new AtomicReference<>(); + AtomicReference page = new AtomicReference<>(); + xPath.setXPathVariableResolver(v -> { + if ("page".equals(v.getLocalPart())) { + return page.get(); + } else if ("normalized".equals(v.getLocalPart())) { + return normalized.get(); } - final Node node = (Node) xPath.evaluate("/api/query/pages/page[@title='" + normalized + "']", document, XPathConstants.NODE); + throw new IllegalArgumentException(); + }); + for (String p : distinctPages) { + page.set(p); + normalized.set(xPath.evaluate("/api/query/normalized/n[@from=$page]/@to", document)); + if (Utils.isEmpty(normalized.get())) { + normalized.set(page.get()); + } + final Node node = (Node) xPath.evaluate("/api/query/pages/page[@title=$normalized]", document, XPathConstants.NODE); if (node != null && node.getAttributes().getNamedItem("missing") == null && node.getAttributes().getNamedItem("invalid") == null) { - return Optional.of(page); + return Optional.of(page.get()); } } return Optional.empty(); } - private Document getDocument(URL url) throws IOException, ParserConfigurationException, SAXException { + private static Document getDocument(URL url) throws IOException, ParserConfigurationException, SAXException { final HttpClient.Response conn = HttpClient.create(url).connect(); try (InputStream content = conn.getContent()) { return XmlUtils.parseSafeDOM(content); diff --git a/src/org/openstreetmap/josm/tools/MultiMap.java b/src/org/openstreetmap/josm/tools/MultiMap.java index 9472cafada1..1024d97960e 100644 --- a/src/org/openstreetmap/josm/tools/MultiMap.java +++ b/src/org/openstreetmap/josm/tools/MultiMap.java @@ -13,7 +13,7 @@ /** * MultiMap - maps keys to multiple values. - * + *

          * Corresponds to Google guava LinkedHashMultimap and Apache Collections MultiValueMap * but it is an independent (simple) implementation. * @@ -58,7 +58,7 @@ public MultiMap(Map> map0) { /** * Map a key to a value. - * + *

          * Can be called multiple times with the same key, but different value. * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key @@ -69,7 +69,7 @@ public void put(A key, B value) { /** * Put a key that maps to nothing. (Only if it is not already in the map) - * + *

          * Afterwards containsKey(key) will return true and get(key) will return * an empty Set instead of null. * @param key key with which an empty set is to be associated @@ -77,12 +77,12 @@ public void put(A key, B value) { public void putVoid(A key) { if (map.containsKey(key)) return; - map.put(key, new LinkedHashSet()); + map.put(key, new LinkedHashSet<>()); } /** * Map the key to all the given values. - * + *

          * Adds to the mappings that are already there. * @param key key with which the specified values are to be associated * @param values values to be associated with the specified key @@ -103,7 +103,7 @@ public Set keySet() { /** * Returns the Set associated with the given key. Result is null if * nothing has been mapped to this key. - * + *

          * Modifications of the returned list changes the underling map, * but you should better not do that. * @param key the key whose associated value is to be returned diff --git a/src/org/openstreetmap/josm/tools/PlatformHook.java b/src/org/openstreetmap/josm/tools/PlatformHook.java index 429a3c44242..08e191343de 100644 --- a/src/org/openstreetmap/josm/tools/PlatformHook.java +++ b/src/org/openstreetmap/josm/tools/PlatformHook.java @@ -1,19 +1,23 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.tools; +import static org.openstreetmap.josm.tools.I18n.tr; + import java.awt.GraphicsEnvironment; import java.awt.Toolkit; -import java.awt.event.KeyEvent; +import java.awt.event.InputEvent; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; +import java.lang.management.ManagementFactory; import java.nio.charset.StandardCharsets; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.text.DateFormat; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -34,7 +38,7 @@ public interface PlatformHook { /** * Visitor to construct a PlatformHook from a given {@link Platform} object. */ - PlatformVisitor CONSTRUCT_FROM_PLATFORM = new PlatformVisitor() { + PlatformVisitor CONSTRUCT_FROM_PLATFORM = new PlatformVisitor<>() { @Override public PlatformHook visitUnixoid() { return new PlatformHookUnixoid(); @@ -60,7 +64,7 @@ public PlatformHook visitOsx() { /** * The preStartupHook will be called extremely early. It is * guaranteed to be called before the GUI setup has started. - * + *

          * Reason: On OSX we need to inform the Swing libraries * that we want to be integrated with the OS before we setup our GUI. */ @@ -79,21 +83,21 @@ default void afterPrefStartupHook() { } /** - * The startupHook will be called early, but after the GUI - * setup has started. - * - * Reason: On OSX we need to register some callbacks with the - * OS, so we'll receive events from the system menu. - * @param javaCallback Java expiration callback, providing GUI feedback - * @param webStartCallback WebStart migration callback, providing GUI feedback - * @since 17679 (signature) - */ - default void startupHook(JavaExpirationCallback javaCallback, WebStartMigrationCallback webStartCallback) { - // Do nothing + * The startupHook will be called early, but after the GUI + * setup has started. + *

          + * Reason: On OSX we need to register some callbacks with the + * OS, so we'll receive events from the system menu. + * @param javaCallback Java expiration callback, providing GUI feedback + * @param sanityCheckCallback Sanity check callback, providing GUI feedback + * @since 18985 + */ + default void startupHook(JavaExpirationCallback javaCallback, SanityCheckCallback sanityCheckCallback) { + startupSanityChecks(sanityCheckCallback); } /** - * The openURL hook will be used to open an URL in the + * The openURL hook will be used to open a URL in the * default web browser. * @param url The URL to open * @throws IOException if any I/O error occurs @@ -105,17 +109,17 @@ default void startupHook(JavaExpirationCallback javaCallback, WebStartMigrationC * Shortcut class after the modifier groups have been read * from the config, but before any shortcuts are read from * it or registered from within the application. - * + *

          * Please note that you are not allowed to register any * shortcuts from this hook, but only "systemCuts"! - * + *

          * BTW: SystemCuts should be named "system:<whatever>", * and it'd be best if you'd recycle the names already used - * by the Windows and OSX hooks. Especially the later has + * by the Windows and OSX hooks. Especially the latter has * really many of them. - * + *

          * You should also register any and all shortcuts that the - * operation system handles itself to block JOSM from trying + * operating system handles itself to block JOSM from trying * to use them---as that would just not work. Call setAutomatic * on them to prevent the keyboard preferences from allowing the * user to change them. @@ -254,7 +258,7 @@ default boolean isHtmlSupportedInMenuTooltips() { */ default int getMenuShortcutKeyMaskEx() { // To remove when switching to Java 10+, and use Toolkit.getMenuShortcutKeyMaskEx instead - return KeyEvent.CTRL_DOWN_MASK; + return InputEvent.CTRL_DOWN_MASK; } /** @@ -274,16 +278,17 @@ interface JavaExpirationCallback { } /** - * Called when Oracle Java WebStart is detected at startup. - * @since 17679 + * Inform the user that a sanity check or checks failed */ @FunctionalInterface - interface WebStartMigrationCallback { + interface SanityCheckCallback { /** - * Asks user to migrate to OpenWebStart. - * @param url download URL + * Tells the user that a sanity check failed + * @param title The title of the message to show + * @param canContinue {@code true} if the failed sanity check(s) will not instantly kill JOSM when the user edits + * @param message The message parts to show the user (as a list) */ - void askMigrateWebStart(String url); + void sanityCheckFailed(String title, boolean canContinue, String... message); } /** @@ -331,7 +336,7 @@ default void warnSoonToBeUnsupportedJava(JavaExpirationCallback callback) { * @since 18580 */ default String getJavaUrl() { - StringBuilder defaultDownloadUrl = new StringBuilder("https://www.azul.com/downloads/?version=java-17-lts"); + StringBuilder defaultDownloadUrl = new StringBuilder("https://www.azul.com/downloads/?version=java-21-lts"); if (PlatformManager.isPlatformWindows()) { defaultDownloadUrl.append("&os=windows"); } else if (PlatformManager.isPlatformOsx()) { @@ -359,13 +364,53 @@ default String getJavaUrl() { } /** - * Checks if we run Oracle Web Start, proposes to user to migrate to OpenWebStart. - * @param callback WebStart migration callback - * @since 17679 + * Check startup preconditions + * @param sanityCheckCallback The callback to inform the user about failed checks */ - default void checkWebStartMigration(WebStartMigrationCallback callback) { - if (Utils.isRunningJavaWebStart()) { - callback.askMigrateWebStart(Config.getPref().get("openwebstart.download.url", "https://openwebstart.com/download/")); + default void startupSanityChecks(SanityCheckCallback sanityCheckCallback) { + final String arch = System.getProperty("os.arch"); + final List messages = new ArrayList<>(); + final String jvmArch = System.getProperty("sun.arch.data.model"); + boolean canContinue = true; + if (Utils.getJavaVersion() < 11) { + canContinue = false; + messages.add(tr("You must update Java to Java {0} or later in order to run this version of JOSM", 17)); + // Reset webstart/java update prompts + Config.getPref().put("askUpdateWebStart", null); + Config.getPref().put("askUpdateJava" + Utils.getJavaLatestVersion(), null); + Config.getPref().put("askUpdateJavalatest", null); + } + if (!"x86".equals(arch) && "32".equals(jvmArch)) { + messages.add(tr("Please use a 64 bit version of Java -- this will avoid out of memory errors")); + } + // Note: these might be able to be removed with the appropriate module-info.java settings. + final String[] expectedJvmArguments = { + "--add-exports=java.base/sun.security.action=ALL-UNNAMED", + "--add-exports=java.desktop/com.sun.imageio.plugins.jpeg=ALL-UNNAMED", + "--add-exports=java.desktop/com.sun.imageio.spi=ALL-UNNAMED" + }; + final List vmArguments = ManagementFactory.getRuntimeMXBean().getInputArguments(); + final StringBuilder missingArguments = new StringBuilder(); + for (String arg : expectedJvmArguments) { + if (vmArguments.stream().noneMatch(s -> s.contains(arg))) { + if (missingArguments.length() > 0) { + missingArguments.append("
          "); + } + missingArguments.append(arg); + } + } + if (missingArguments.length() > 0) { + final String args = missingArguments.toString(); + messages.add(tr("Missing JVM Arguments:
          {0}
          These arguments should be added in the command line or start script before the -jar parameter.", args)); + } + if (!messages.isEmpty()) { + if (canContinue) { + sanityCheckCallback.sanityCheckFailed(tr("JOSM may work improperly"), true, + messages.toArray(new String[0])); + } else { + sanityCheckCallback.sanityCheckFailed(tr("JOSM will be unable to work properly and will exit"), false, + messages.toArray(new String[0])); + } } } diff --git a/src/org/openstreetmap/josm/tools/PlatformHookOsx.java b/src/org/openstreetmap/josm/tools/PlatformHookOsx.java index 4323df057a3..48810f76c0f 100644 --- a/src/org/openstreetmap/josm/tools/PlatformHookOsx.java +++ b/src/org/openstreetmap/josm/tools/PlatformHookOsx.java @@ -1,21 +1,21 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.tools; +import static org.openstreetmap.josm.tools.I18n.marktr; import static org.openstreetmap.josm.tools.I18n.tr; import static org.openstreetmap.josm.tools.Utils.getSystemProperty; import java.awt.Desktop; import java.awt.GraphicsEnvironment; +import java.awt.HeadlessException; import java.awt.Image; import java.awt.Window; +import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; +import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -23,7 +23,6 @@ import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Arrays; -import java.util.List; import java.util.Objects; import java.util.concurrent.ExecutionException; @@ -38,7 +37,7 @@ * {@code PlatformHook} implementation for Apple macOS (formerly Mac OS X) systems. * @since 1023 */ -public class PlatformHookOsx implements PlatformHook, InvocationHandler { +public class PlatformHookOsx implements PlatformHook { private String oSBuildNumber; @@ -68,27 +67,16 @@ public void preStartupHook() { } @Override - public void startupHook(JavaExpirationCallback javaCallback, WebStartMigrationCallback webStartCallback) { + public void startupHook(JavaExpirationCallback javaCallback, SanityCheckCallback sanityCheckCallback) { // Here we register callbacks for the menu entries in the system menu and file opening through double-click // https://openjdk.java.net/jeps/272 // https://bugs.openjdk.java.net/browse/JDK-8048731 // https://cr.openjdk.java.net/~azvegint/jdk/9/8143227/10/jdk/ - // This method must be cleaned up after we switch to Java 9 try { Class eawtApplication = Class.forName("com.apple.eawt.Application"); - Class quitHandler = findHandlerClass("QuitHandler"); - Class aboutHandler = findHandlerClass("AboutHandler"); - Class openFilesHandler = findHandlerClass("OpenFilesHandler"); - Class preferencesHandler = findHandlerClass("PreferencesHandler"); - Object proxy = Proxy.newProxyInstance(PlatformHookOsx.class.getClassLoader(), new Class[] { - quitHandler, aboutHandler, openFilesHandler, preferencesHandler}, this); - Object appli = eawtApplication.getConstructor((Class[]) null).newInstance((Object[]) null); - if (Utils.getJavaVersion() < 9) { - setHandlers(eawtApplication, quitHandler, aboutHandler, openFilesHandler, preferencesHandler, proxy, appli); - // this method has been deprecated, but without replacement. To remove with Java 9 migration - eawtApplication.getDeclaredMethod("setEnabledPreferencesMenu", boolean.class).invoke(appli, Boolean.TRUE); - } else if (!GraphicsEnvironment.isHeadless()) { - setHandlers(Desktop.class, quitHandler, aboutHandler, openFilesHandler, preferencesHandler, proxy, Desktop.getDesktop()); + Object appli = eawtApplication.getConstructor((Class[]) null).newInstance((Object[]) null); + if (!GraphicsEnvironment.isHeadless()) { + setHandlers(); } // setup the dock icon. It is automatically set with application bundle and Web start but we need // to do it manually if run with `java -jar``. @@ -105,7 +93,7 @@ public void startupHook(JavaExpirationCallback javaCallback, WebStartMigrationCa } warnSoonToBeUnsupportedJava(javaCallback); checkExpiredJava(javaCallback); - checkWebStartMigration(webStartCallback); + PlatformHook.super.startupHook(javaCallback, sanityCheckCallback); } @Override @@ -119,46 +107,31 @@ public boolean isHtmlSupportedInMenuTooltips() { @Override public int getMenuShortcutKeyMaskEx() { - return KeyEvent.META_DOWN_MASK; + return InputEvent.META_DOWN_MASK; } /** * Registers Apple handlers. - * @param appClass application class - * @param quitHandler quit handler class - * @param aboutHandler about handler class - * @param openFilesHandler open file handler class - * @param preferencesHandler preferences handler class - * @param proxy proxy - * @param appInstance application instance (instance of {@code appClass}) - * @throws IllegalAccessException in case of reflection error - * @throws InvocationTargetException in case of reflection error - * @throws NoSuchMethodException if any {@code set*Handler} method cannot be found + * */ - protected void setHandlers(Class appClass, Class quitHandler, Class aboutHandler, - Class openFilesHandler, Class preferencesHandler, Object proxy, Object appInstance) - throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { - appClass.getDeclaredMethod("setQuitHandler", quitHandler).invoke(appInstance, proxy); - appClass.getDeclaredMethod("setAboutHandler", aboutHandler).invoke(appInstance, proxy); - appClass.getDeclaredMethod("setOpenFileHandler", openFilesHandler).invoke(appInstance, proxy); - appClass.getDeclaredMethod("setPreferencesHandler", preferencesHandler).invoke(appInstance, proxy); - } - - /** - * Find Apple handler class in {@code com.apple.eawt} or {@code java.awt.desktop} packages. - * @param className simple class name - * @return class - * @throws ClassNotFoundException if the handler class cannot be found - */ - protected Class findHandlerClass(String className) throws ClassNotFoundException { - try { - // Java 8 handlers - return Class.forName("com.apple.eawt."+className); - } catch (ClassNotFoundException e) { - Logging.trace(e); - // Java 9 handlers - return Class.forName("java.awt.desktop."+className); - } + protected void setHandlers() { + Desktop.getDesktop().setQuitHandler((event, response) -> { + boolean closed = osCallback.handleQuitRequest(); + if (response != null) { + if (closed) { + response.performQuit(); + } else { + response.cancelQuit(); + } + } + }); + Desktop.getDesktop().setAboutHandler(event -> osCallback.handleAbout()); + Desktop.getDesktop().setOpenFileHandler(event -> { + if (event != null) { + osCallback.openFiles(event.getFiles()); + } + }); + Desktop.getDesktop().setPreferencesHandler(event -> osCallback.handlePreferences()); } /** @@ -183,191 +156,155 @@ public void setNativeOsCallback(NativeOsCallback callback) { osCallback = Objects.requireNonNull(callback); } - @SuppressWarnings("unchecked") - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if (Logging.isDebugEnabled()) { - Logging.debug("macOS handler: {0} - {1}", method.getName(), Arrays.toString(args)); - } - switch (method.getName()) { - case "openFiles": - if (args[0] != null) { - try { - Object oFiles = args[0].getClass().getMethod("getFiles").invoke(args[0]); - if (oFiles instanceof List) { - osCallback.openFiles((List) oFiles); - } - } catch (ReflectiveOperationException | SecurityException | IllegalArgumentException ex) { - Logging.warn("Failed to access open files event: " + ex); - } - } - break; - case "handleQuitRequestWith": - boolean closed = osCallback.handleQuitRequest(); - if (args[1] != null) { - try { - args[1].getClass().getDeclaredMethod(closed ? "performQuit" : "cancelQuit").invoke(args[1]); - } catch (IllegalAccessException e) { - Logging.debug(e); - // with Java 9, module java.desktop does not export com.apple.eawt, use new Desktop API instead - Class.forName("java.awt.desktop.QuitResponse").getMethod(closed ? "performQuit" : "cancelQuit").invoke(args[1]); - } - } - break; - case "handleAbout": - osCallback.handleAbout(); - break; - case "handlePreferences": - osCallback.handlePreferences(); - break; - default: - Logging.warn("macOS unsupported method: "+method.getName()); - } - return null; - } - @Override public void openUrl(String url) throws IOException { - Runtime.getRuntime().exec(new String[]{"open", url}); + try { + Desktop.getDesktop().browse(Utils.urlToURI(url)); + } catch (HeadlessException | URISyntaxException e) { + Logging.log(Logging.LEVEL_WARN, "Desktop class failed. Platform dependent fall back for open url in browser.", e); + Runtime.getRuntime().exec(new String[]{"open", url}); + } } @Override + @SuppressWarnings("squid:S103") // NOSONAR LineLength public void initSystemShortcuts() { + final String reserved = marktr("reserved"); // CHECKSTYLE.OFF: LineLength - auto(Shortcut.registerSystemShortcut("apple-reserved-01", tr("reserved"), KeyEvent.VK_SPACE, KeyEvent.META_DOWN_MASK)); // Show or hide the Spotlight search field (when multiple languages are installed, may rotate through enabled script systems). - auto(Shortcut.registerSystemShortcut("apple-reserved-02", tr("reserved"), KeyEvent.VK_SPACE, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Apple reserved. - auto(Shortcut.registerSystemShortcut("apple-reserved-03", tr("reserved"), KeyEvent.VK_SPACE, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Show the Spotlight search results window (when multiple languages are installed, may rotate through keyboard layouts and input methods within a script). - auto(Shortcut.registerSystemShortcut("apple-reserved-04", tr("reserved"), KeyEvent.VK_SPACE, KeyEvent.META_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK)); // | Apple reserved. - auto(Shortcut.registerSystemShortcut("apple-reserved-05", tr("reserved"), KeyEvent.VK_TAB, KeyEvent.SHIFT_DOWN_MASK)); // Navigate through controls in a reverse direction. See "Keyboard Focus and Navigation." - auto(Shortcut.registerSystemShortcut("apple-reserved-06", tr("reserved"), KeyEvent.VK_TAB, KeyEvent.META_DOWN_MASK)); // Move forward to the next most recently used application in a list of open applications. - auto(Shortcut.registerSystemShortcut("apple-reserved-07", tr("reserved"), KeyEvent.VK_TAB, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Move backward through a list of open applications (sorted by recent use). - auto(Shortcut.registerSystemShortcut("apple-reserved-08", tr("reserved"), KeyEvent.VK_TAB, KeyEvent.CTRL_DOWN_MASK)); // Move focus to the next grouping of controls in a dialog or the next table (when Tab moves to the next cell). See Accessibility Overview. - auto(Shortcut.registerSystemShortcut("apple-reserved-09", tr("reserved"), KeyEvent.VK_TAB, KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Move focus to the previous grouping of controls. See Accessibility Overview. - auto(Shortcut.registerSystemShortcut("apple-reserved-10", tr("reserved"), KeyEvent.VK_ESCAPE, KeyEvent.META_DOWN_MASK)); // Open Front Row. - auto(Shortcut.registerSystemShortcut("apple-reserved-11", tr("reserved"), KeyEvent.VK_ESCAPE, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Open the Force Quit dialog. - auto(Shortcut.registerSystemShortcut("apple-reserved-12", tr("reserved"), KeyEvent.VK_F1, KeyEvent.CTRL_DOWN_MASK)); // Toggle full keyboard access on or off. See Accessibility Overview. - auto(Shortcut.registerSystemShortcut("apple-reserved-13", tr("reserved"), KeyEvent.VK_F2, KeyEvent.CTRL_DOWN_MASK)); // Move focus to the menu bar. See Accessibility Overview. - auto(Shortcut.registerSystemShortcut("apple-reserved-14", tr("reserved"), KeyEvent.VK_F3, KeyEvent.CTRL_DOWN_MASK)); // Move focus to the Dock. See Accessibility Overview. - auto(Shortcut.registerSystemShortcut("apple-reserved-15", tr("reserved"), KeyEvent.VK_F4, KeyEvent.CTRL_DOWN_MASK)); // Move focus to the active (or next) window. See Accessibility Overview. - auto(Shortcut.registerSystemShortcut("apple-reserved-16", tr("reserved"), KeyEvent.VK_F4, KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Move focus to the previously active window. See Accessibility Overview. - auto(Shortcut.registerSystemShortcut("apple-reserved-17", tr("reserved"), KeyEvent.VK_F5, KeyEvent.CTRL_DOWN_MASK)); // Move focus to the toolbar. See Accessibility Overview. - auto(Shortcut.registerSystemShortcut("apple-reserved-18", tr("reserved"), KeyEvent.VK_F5, KeyEvent.META_DOWN_MASK)); // Turn VoiceOver on or off. See Accessibility Overview. - auto(Shortcut.registerSystemShortcut("apple-reserved-19", tr("reserved"), KeyEvent.VK_F6, KeyEvent.CTRL_DOWN_MASK)); // Move focus to the first (or next) panel. See Accessibility Overview. - auto(Shortcut.registerSystemShortcut("apple-reserved-20", tr("reserved"), KeyEvent.VK_F6, KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Move focus to the previous panel. See Accessibility Overview. - auto(Shortcut.registerSystemShortcut("apple-reserved-21", tr("reserved"), KeyEvent.VK_F7, KeyEvent.CTRL_DOWN_MASK)); // Temporarily override the current keyboard access mode in windows and dialogs. See Accessibility Overview. + auto(Shortcut.registerSystemShortcut("apple-reserved-01", tr(reserved), KeyEvent.VK_SPACE, InputEvent.META_DOWN_MASK)); // Show or hide the Spotlight search field (when multiple languages are installed, may rotate through enabled script systems). + auto(Shortcut.registerSystemShortcut("apple-reserved-02", tr(reserved), KeyEvent.VK_SPACE, InputEvent.META_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); // Apple reserved. + auto(Shortcut.registerSystemShortcut("apple-reserved-03", tr(reserved), KeyEvent.VK_SPACE, InputEvent.META_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); // Show the Spotlight search results window (when multiple languages are installed, may rotate through keyboard layouts and input methods within a script). + auto(Shortcut.registerSystemShortcut("apple-reserved-04", tr(reserved), KeyEvent.VK_SPACE, InputEvent.META_DOWN_MASK | InputEvent.CTRL_DOWN_MASK)); // | Apple reserved. + auto(Shortcut.registerSystemShortcut("apple-reserved-05", tr(reserved), KeyEvent.VK_TAB, InputEvent.SHIFT_DOWN_MASK)); // Navigate through controls in a reverse direction. See "Keyboard Focus and Navigation." + auto(Shortcut.registerSystemShortcut("apple-reserved-06", tr(reserved), KeyEvent.VK_TAB, InputEvent.META_DOWN_MASK)); // Move forward to the next most recently used application in a list of open applications. + auto(Shortcut.registerSystemShortcut("apple-reserved-07", tr(reserved), KeyEvent.VK_TAB, InputEvent.META_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); // Move backward through a list of open applications (sorted by recent use). + auto(Shortcut.registerSystemShortcut("apple-reserved-08", tr(reserved), KeyEvent.VK_TAB, InputEvent.CTRL_DOWN_MASK)); // Move focus to the next grouping of controls in a dialog or the next table (when Tab moves to the next cell). See Accessibility Overview. + auto(Shortcut.registerSystemShortcut("apple-reserved-09", tr(reserved), KeyEvent.VK_TAB, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); // Move focus to the previous grouping of controls. See Accessibility Overview. + auto(Shortcut.registerSystemShortcut("apple-reserved-10", tr(reserved), KeyEvent.VK_ESCAPE, InputEvent.META_DOWN_MASK)); // Open Front Row. + auto(Shortcut.registerSystemShortcut("apple-reserved-11", tr(reserved), KeyEvent.VK_ESCAPE, InputEvent.META_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); // Open the Force Quit dialog. + auto(Shortcut.registerSystemShortcut("apple-reserved-12", tr(reserved), KeyEvent.VK_F1, InputEvent.CTRL_DOWN_MASK)); // Toggle full keyboard access on or off. See Accessibility Overview. + auto(Shortcut.registerSystemShortcut("apple-reserved-13", tr(reserved), KeyEvent.VK_F2, InputEvent.CTRL_DOWN_MASK)); // Move focus to the menu bar. See Accessibility Overview. + auto(Shortcut.registerSystemShortcut("apple-reserved-14", tr(reserved), KeyEvent.VK_F3, InputEvent.CTRL_DOWN_MASK)); // Move focus to the Dock. See Accessibility Overview. + auto(Shortcut.registerSystemShortcut("apple-reserved-15", tr(reserved), KeyEvent.VK_F4, InputEvent.CTRL_DOWN_MASK)); // Move focus to the active (or next) window. See Accessibility Overview. + auto(Shortcut.registerSystemShortcut("apple-reserved-16", tr(reserved), KeyEvent.VK_F4, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); // Move focus to the previously active window. See Accessibility Overview. + auto(Shortcut.registerSystemShortcut("apple-reserved-17", tr(reserved), KeyEvent.VK_F5, InputEvent.CTRL_DOWN_MASK)); // Move focus to the toolbar. See Accessibility Overview. + auto(Shortcut.registerSystemShortcut("apple-reserved-18", tr(reserved), KeyEvent.VK_F5, InputEvent.META_DOWN_MASK)); // Turn VoiceOver on or off. See Accessibility Overview. + auto(Shortcut.registerSystemShortcut("apple-reserved-19", tr(reserved), KeyEvent.VK_F6, InputEvent.CTRL_DOWN_MASK)); // Move focus to the first (or next) panel. See Accessibility Overview. + auto(Shortcut.registerSystemShortcut("apple-reserved-20", tr(reserved), KeyEvent.VK_F6, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); // Move focus to the previous panel. See Accessibility Overview. + auto(Shortcut.registerSystemShortcut("apple-reserved-21", tr(reserved), KeyEvent.VK_F7, InputEvent.CTRL_DOWN_MASK)); // Temporarily override the current keyboard access mode in windows and dialogs. See Accessibility Overview. //auto(Shortcut.registerSystemShortcut("apple-reserved-22", tr("reserved"), KeyEvent.VK_F9, 0)); // Tile or untile all open windows. //auto(Shortcut.registerSystemShortcut("apple-reserved-23", tr("reserved"), KeyEvent.VK_F10, 0)); // Tile or untile all open windows in the currently active application. //auto(Shortcut.registerSystemShortcut("apple-reserved-24", tr("reserved"), KeyEvent.VK_F11, 0)); // Hide or show all open windows. //auto(Shortcut.registerSystemShortcut("apple-reserved-25", tr("reserved"), KeyEvent.VK_F12, 0)); // Hide or display Dashboard. - auto(Shortcut.registerSystemShortcut("apple-reserved-26", tr("reserved"), KeyEvent.VK_DEAD_GRAVE, KeyEvent.META_DOWN_MASK)); // Activate the next open window in the frontmost application. See "Window Layering." - auto(Shortcut.registerSystemShortcut("apple-reserved-27", tr("reserved"), KeyEvent.VK_DEAD_GRAVE, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Activate the previous open window in the frontmost application. See "Window Layering." - auto(Shortcut.registerSystemShortcut("apple-reserved-28", tr("reserved"), KeyEvent.VK_DEAD_GRAVE, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Move focus to the window drawer. - //auto(Shortcut.registerSystemShortcut("apple-reserved-29", tr("reserved"), KeyEvent.VK_MINUS, KeyEvent.META_DOWN_MASK)); // Decrease the size of the selected item (equivalent to the Smaller command). See "The Format Menu." - auto(Shortcut.registerSystemShortcut("apple-reserved-30", tr("reserved"), KeyEvent.VK_MINUS, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Zoom out when screen zooming is on. See Accessibility Overview. - - //Shortcut.registerSystemShortcut("system:align-left", tr("reserved"), KeyEvent.VK_OPEN_BRACKET, KeyEvent.META_DOWN_MASK); // Left-align a selection (equivalent to the Align Left command). See "The Format Menu." - //Shortcut.registerSystemShortcut("system:align-right",tr("reserved"), KeyEvent.VK_CLOSE_BRACKET, KeyEvent.META_DOWN_MASK); // Right-align a selection (equivalent to the Align Right command). See "The Format Menu." + auto(Shortcut.registerSystemShortcut("apple-reserved-26", tr(reserved), KeyEvent.VK_DEAD_GRAVE, InputEvent.META_DOWN_MASK)); // Activate the next open window in the frontmost application. See "Window Layering." + auto(Shortcut.registerSystemShortcut("apple-reserved-27", tr(reserved), KeyEvent.VK_DEAD_GRAVE, InputEvent.META_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); // Activate the previous open window in the frontmost application. See "Window Layering." + auto(Shortcut.registerSystemShortcut("apple-reserved-28", tr(reserved), KeyEvent.VK_DEAD_GRAVE, InputEvent.META_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); // Move focus to the window drawer. + //auto(Shortcut.registerSystemShortcut("apple-reserved-29", tr(reserved), KeyEvent.VK_MINUS, KeyEvent.META_DOWN_MASK)); // Decrease the size of the selected item (equivalent to the Smaller command). See "The Format Menu." + auto(Shortcut.registerSystemShortcut("apple-reserved-30", tr(reserved), KeyEvent.VK_MINUS, InputEvent.META_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); // Zoom out when screen zooming is on. See Accessibility Overview. + + //Shortcut.registerSystemShortcut("system:align-left", tr(reserved), KeyEvent.VK_OPEN_BRACKET, KeyEvent.META_DOWN_MASK); // Left-align a selection (equivalent to the Align Left command). See "The Format Menu." + //Shortcut.registerSystemShortcut("system:align-right",tr(reserved), KeyEvent.VK_CLOSE_BRACKET, KeyEvent.META_DOWN_MASK); // Right-align a selection (equivalent to the Align Right command). See "The Format Menu." // I found no KeyEvent for | - //Shortcut.registerSystemCut("system:align-center", tr("reserved"), '|', KeyEvent.META_DOWN_MASK); // Center-align a selection (equivalent to the Align Center command). See "The Format Menu." - //Shortcut.registerSystemShortcut("system:spelling", tr("reserved"), KeyEvent.VK_COLON, KeyEvent.META_DOWN_MASK); // Display the Spelling window (equivalent to the Spelling command). See "The Edit Menu." - //Shortcut.registerSystemShortcut("system:spellcheck", tr("reserved"), KeyEvent.VK_SEMICOLON, KeyEvent.META_DOWN_MASK); // Find misspelled words in the document (equivalent to the Check Spelling command). See "The Edit Menu." - auto(Shortcut.registerSystemShortcut("system:preferences", tr("reserved"), KeyEvent.VK_COMMA, KeyEvent.META_DOWN_MASK)); // Open the application's preferences window (equivalent to the Preferences command). See "The Application Menu." + //Shortcut.registerSystemCut("system:align-center", tr(reserved), '|', KeyEvent.META_DOWN_MASK); // Center-align a selection (equivalent to the Align Center command). See "The Format Menu." + //Shortcut.registerSystemShortcut("system:spelling", tr(reserved), KeyEvent.VK_COLON, KeyEvent.META_DOWN_MASK); // Display the Spelling window (equivalent to the Spelling command). See "The Edit Menu." + //Shortcut.registerSystemShortcut("system:spellcheck", tr(reserved), KeyEvent.VK_SEMICOLON, KeyEvent.META_DOWN_MASK); // Find misspelled words in the document (equivalent to the Check Spelling command). See "The Edit Menu." + auto(Shortcut.registerSystemShortcut("system:preferences", tr(reserved), KeyEvent.VK_COMMA, InputEvent.META_DOWN_MASK)); // Open the application's preferences window (equivalent to the Preferences command). See "The Application Menu." - auto(Shortcut.registerSystemShortcut("apple-reserved-31", tr("reserved"), KeyEvent.VK_COMMA, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Decrease screen contrast. See Accessibility Overview. - auto(Shortcut.registerSystemShortcut("apple-reserved-32", tr("reserved"), KeyEvent.VK_PERIOD, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Increase screen contrast. See Accessibility Overview. + auto(Shortcut.registerSystemShortcut("apple-reserved-31", tr(reserved), KeyEvent.VK_COMMA, InputEvent.META_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); // Decrease screen contrast. See Accessibility Overview. + auto(Shortcut.registerSystemShortcut("apple-reserved-32", tr(reserved), KeyEvent.VK_PERIOD, InputEvent.META_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); // Increase screen contrast. See Accessibility Overview. // I found no KeyEvent for ? - //auto(Shortcut.registerSystemCut("system:help", tr("reserved"), '?', KeyEvent.META_DOWN_MASK)); // Open the application's help in Help Viewer. See "The Help Menu." - - auto(Shortcut.registerSystemShortcut("apple-reserved-33", tr("reserved"), KeyEvent.VK_SLASH, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Turn font smoothing on or off. - auto(Shortcut.registerSystemShortcut("apple-reserved-34", tr("reserved"), KeyEvent.VK_EQUALS, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Increase the size of the selected item (equivalent to the Bigger command). See "The Format Menu." - auto(Shortcut.registerSystemShortcut("apple-reserved-35", tr("reserved"), KeyEvent.VK_EQUALS, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Zoom in when screen zooming is on. See Accessibility Overview. - auto(Shortcut.registerSystemShortcut("apple-reserved-36", tr("reserved"), KeyEvent.VK_3, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Capture the screen to a file. - auto(Shortcut.registerSystemShortcut("apple-reserved-37", tr("reserved"), KeyEvent.VK_3, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK)); // Capture the screen to the Clipboard. - auto(Shortcut.registerSystemShortcut("apple-reserved-38", tr("reserved"), KeyEvent.VK_4, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Capture a selection to a file. - auto(Shortcut.registerSystemShortcut("apple-reserved-39", tr("reserved"), KeyEvent.VK_4, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK)); // Capture a selection to the Clipboard. - auto(Shortcut.registerSystemShortcut("apple-reserved-40", tr("reserved"), KeyEvent.VK_8, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Turn screen zooming on or off. See Accessibility Overview. - auto(Shortcut.registerSystemShortcut("apple-reserved-41", tr("reserved"), KeyEvent.VK_8, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK)); // Invert the screen colors. See Accessibility Overview. - - Shortcut.registerSystemShortcut("system:selectall", tr("reserved"), KeyEvent.VK_A, KeyEvent.META_DOWN_MASK); // Highlight every item in a document or window, or all characters in a text field (equivalent to the Select All command). See "The Edit Menu." - //Shortcut.registerSystemShortcut("system:bold", tr("reserved"), KeyEvent.VK_B, KeyEvent.META_DOWN_MASK); // Boldface the selected text or toggle boldfaced text on and off (equivalent to the Bold command). See "The Edit Menu." - Shortcut.registerSystemShortcut("system:copy", tr("reserved"), KeyEvent.VK_C, KeyEvent.META_DOWN_MASK); // Duplicate the selected data and store on the Clipboard (equivalent to the Copy command). See "The Edit Menu." - //Shortcut.registerSystemShortcut("system:colors", tr("reserved"), KeyEvent.VK_C, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); // Display the Colors window (equivalent to the Show Colors command). See "The Format Menu." - //Shortcut.registerSystemShortcut("system:copystyle", tr("reserved"), KeyEvent.VK_C, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Copy the style of the selected text (equivalent to the Copy Style command). See "The Format Menu." - //Shortcut.registerSystemShortcut("system:copyformat", tr("reserved"), KeyEvent.VK_C, KeyEvent.META_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK)); // Copy the formatting settings of the selected item and store on the Clipboard (equivalent to the Copy Ruler command). See "The Format Menu." - - auto(Shortcut.registerSystemShortcut("apple-reserved-42", tr("reserved"), KeyEvent.VK_D, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Show or hide the Dock. See "The Dock." - - Shortcut.registerSystemShortcut("system:dictionarylookup", tr("reserved"), KeyEvent.VK_D, KeyEvent.META_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK); // Display the definition of the selected word in the Dictionary application. - //Shortcut.registerSystemShortcut("system:findselected", tr("reserved"), KeyEvent.VK_E, KeyEvent.META_DOWN_MASK); // Use the selection for a find operation. See "Find Windows." - Shortcut.registerSystemShortcut("system:find", tr("reserved"), KeyEvent.VK_F, KeyEvent.META_DOWN_MASK); // Open a Find window (equivalent to the Find command). See "The Edit Menu." - Shortcut.registerSystemShortcut("system:search", tr("reserved"), KeyEvent.VK_F, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Jump to the search field control. See "Search Fields." - //Shortcut.registerSystemShortcut("system:findnext", tr("reserved"), KeyEvent.VK_G, KeyEvent.META_DOWN_MASK); // Find the next occurrence of the selection (equivalent to the Find Next command). See "The Edit Menu." - //Shortcut.registerSystemShortcut("system:findprev", tr("reserved"), KeyEvent.VK_G, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); // Find the previous occurrence of the selection (equivalent to the Find Previous command). See "The Edit Menu." - auto(Shortcut.registerSystemShortcut("system:hide", tr("reserved"), KeyEvent.VK_H, KeyEvent.META_DOWN_MASK)); // Hide the windows of the currently running application (equivalent to the Hide ApplicationName command). See "The Application Menu." - auto(Shortcut.registerSystemShortcut("system:hideothers", tr("reserved"), KeyEvent.VK_H, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Hide the windows of all other running applications (equivalent to the Hide Others command). See "The Application Menu." + //auto(Shortcut.registerSystemCut("system:help", tr(reserved), '?', KeyEvent.META_DOWN_MASK)); // Open the application's help in Help Viewer. See "The Help Menu." + + auto(Shortcut.registerSystemShortcut("apple-reserved-33", tr(reserved), KeyEvent.VK_SLASH, InputEvent.META_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); // Turn font smoothing on or off. + auto(Shortcut.registerSystemShortcut("apple-reserved-34", tr(reserved), KeyEvent.VK_EQUALS, InputEvent.META_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); // Increase the size of the selected item (equivalent to the Bigger command). See "The Format Menu." + auto(Shortcut.registerSystemShortcut("apple-reserved-35", tr(reserved), KeyEvent.VK_EQUALS, InputEvent.META_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); // Zoom in when screen zooming is on. See Accessibility Overview. + auto(Shortcut.registerSystemShortcut("apple-reserved-36", tr(reserved), KeyEvent.VK_3, InputEvent.META_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); // Capture the screen to a file. + auto(Shortcut.registerSystemShortcut("apple-reserved-37", tr(reserved), KeyEvent.VK_3, InputEvent.META_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK)); // Capture the screen to the Clipboard. + auto(Shortcut.registerSystemShortcut("apple-reserved-38", tr(reserved), KeyEvent.VK_4, InputEvent.META_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); // Capture a selection to a file. + auto(Shortcut.registerSystemShortcut("apple-reserved-39", tr(reserved), KeyEvent.VK_4, InputEvent.META_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK)); // Capture a selection to the Clipboard. + auto(Shortcut.registerSystemShortcut("apple-reserved-40", tr(reserved), KeyEvent.VK_8, InputEvent.META_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); // Turn screen zooming on or off. See Accessibility Overview. + auto(Shortcut.registerSystemShortcut("apple-reserved-41", tr(reserved), KeyEvent.VK_8, InputEvent.META_DOWN_MASK | InputEvent.ALT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK)); // Invert the screen colors. See Accessibility Overview. + + Shortcut.registerSystemShortcut("system:selectall", tr(reserved), KeyEvent.VK_A, InputEvent.META_DOWN_MASK); // Highlight every item in a document or window, or all characters in a text field (equivalent to the Select All command). See "The Edit Menu." + //Shortcut.registerSystemShortcut("system:bold", tr(reserved), KeyEvent.VK_B, KeyEvent.META_DOWN_MASK); // Boldface the selected text or toggle boldfaced text on and off (equivalent to the Bold command). See "The Edit Menu." + Shortcut.registerSystemShortcut("system:copy", tr(reserved), KeyEvent.VK_C, InputEvent.META_DOWN_MASK); // Duplicate the selected data and store on the Clipboard (equivalent to the Copy command). See "The Edit Menu." + //Shortcut.registerSystemShortcut("system:colors", tr(reserved), KeyEvent.VK_C, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); // Display the Colors window (equivalent to the Show Colors command). See "The Format Menu." + //Shortcut.registerSystemShortcut("system:copystyle", tr(reserved), KeyEvent.VK_C, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Copy the style of the selected text (equivalent to the Copy Style command). See "The Format Menu." + //Shortcut.registerSystemShortcut("system:copyformat", tr(reserved), KeyEvent.VK_C, KeyEvent.META_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK)); // Copy the formatting settings of the selected item and store on the Clipboard (equivalent to the Copy Ruler command). See "The Format Menu." + + auto(Shortcut.registerSystemShortcut("apple-reserved-42", tr(reserved), KeyEvent.VK_D, InputEvent.META_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); // Show or hide the Dock. See "The Dock." + + Shortcut.registerSystemShortcut("system:dictionarylookup", tr(reserved), KeyEvent.VK_D, InputEvent.META_DOWN_MASK | InputEvent.CTRL_DOWN_MASK); // Display the definition of the selected word in the Dictionary application. + //Shortcut.registerSystemShortcut("system:findselected", tr(reserved), KeyEvent.VK_E, KeyEvent.META_DOWN_MASK); // Use the selection for a find operation. See "Find Windows." + Shortcut.registerSystemShortcut("system:find", tr(reserved), KeyEvent.VK_F, InputEvent.META_DOWN_MASK); // Open a Find window (equivalent to the Find command). See "The Edit Menu." + Shortcut.registerSystemShortcut("system:search", tr(reserved), KeyEvent.VK_F, InputEvent.META_DOWN_MASK | InputEvent.ALT_DOWN_MASK); // Jump to the search field control. See "Search Fields." + //Shortcut.registerSystemShortcut("system:findnext", tr(reserved), KeyEvent.VK_G, KeyEvent.META_DOWN_MASK); // Find the next occurrence of the selection (equivalent to the Find Next command). See "The Edit Menu." + //Shortcut.registerSystemShortcut("system:findprev", tr(reserved), KeyEvent.VK_G, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); // Find the previous occurrence of the selection (equivalent to the Find Previous command). See "The Edit Menu." + auto(Shortcut.registerSystemShortcut("system:hide", tr(reserved), KeyEvent.VK_H, InputEvent.META_DOWN_MASK)); // Hide the windows of the currently running application (equivalent to the Hide ApplicationName command). See "The Application Menu." + auto(Shortcut.registerSystemShortcut("system:hideothers", tr(reserved), KeyEvent.VK_H, InputEvent.META_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); // Hide the windows of all other running applications (equivalent to the Hide Others command). See "The Application Menu." // What about applications that have italic text AND info windows? - //Shortcut.registerSystemCut("system:italic", tr("reserved"), KeyEvent.VK_I, KeyEvent.META_DOWN_MASK); // Italicize the selected text or toggle italic text on or off (equivalent to the Italic command). See "The Format Menu." - //Shortcut.registerSystemShortcut("system:info", tr("reserved"), KeyEvent.VK_I, KeyEvent.META_DOWN_MASK); // Display an Info window. See "Inspector Windows." - //Shortcut.registerSystemShortcut("system:inspector", tr("reserved"), KeyEvent.VK_I, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Display an inspector window. See "Inspector Windows." - //Shortcut.registerSystemShortcut("system:toselection", tr("reserved"), KeyEvent.VK_J, KeyEvent.META_DOWN_MASK); // Scroll to a selection. - //Shortcut.registerSystemShortcut("system:minimize", tr("reserved"), KeyEvent.VK_M, KeyEvent.META_DOWN_MASK); // Minimize the active window to the Dock (equivalent to the Minimize command). See "The Window Menu." - //Shortcut.registerSystemShortcut("system:minimizeall", tr("reserved"), KeyEvent.VK_M, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Minimize all windows of the active application to the Dock (equivalent to the Minimize All command). See "The Window Menu." - Shortcut.registerSystemShortcut("system:new", tr("reserved"), KeyEvent.VK_N, KeyEvent.META_DOWN_MASK); // Open a new document (equivalent to the New command). See "The File Menu." - Shortcut.registerSystemShortcut("system:open", tr("reserved"), KeyEvent.VK_O, KeyEvent.META_DOWN_MASK); // Display a dialog for choosing a document to open (equivalent to the Open command). See "The File Menu." - Shortcut.registerSystemShortcut("system:print", tr("reserved"), KeyEvent.VK_P, KeyEvent.META_DOWN_MASK); // Display the Print dialog (equivalent to the Print command). See "The File Menu." - //Shortcut.registerSystemShortcut("system:printsetup", tr("reserved"), KeyEvent.VK_P, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); // Display a dialog for specifying printing parameters (equivalent to the Page Setup command). See "The File Menu." - auto(Shortcut.registerSystemShortcut("system:menuexit", tr("reserved"), KeyEvent.VK_Q, KeyEvent.META_DOWN_MASK)); // Quit the application (equivalent to the Quit command). See "The Application Menu." - - auto(Shortcut.registerSystemShortcut("apple-reserved-43", tr("reserved"), KeyEvent.VK_Q, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Log out the current user (equivalent to the Log Out command). - auto(Shortcut.registerSystemShortcut("apple-reserved-44", tr("reserved"), KeyEvent.VK_Q, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)); // Log out the current user without confirmation. - - Shortcut.registerSystemShortcut("system:save", tr("reserved"), KeyEvent.VK_S, KeyEvent.META_DOWN_MASK); // Save the active document (equivalent to the Save command). See "The File Menu." - Shortcut.registerSystemShortcut("system:saveas", tr("reserved"), KeyEvent.VK_S, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); // Display the Save dialog (equivalent to the Save As command). See "The File Menu." - //Shortcut.registerSystemShortcut("system:fonts", tr("reserved"), KeyEvent.VK_T, KeyEvent.META_DOWN_MASK); // Display the Fonts window (equivalent to the Show Fonts command). See "The Format Menu." - Shortcut.registerSystemShortcut("system:toggletoolbar", tr("reserved"), KeyEvent.VK_T, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Show or hide a toolbar (equivalent to the Show/Hide Toolbar command). See "The View Menu" and "Toolbars." - //Shortcut.registerSystemShortcut("system:underline", tr("reserved"), KeyEvent.VK_U, KeyEvent.META_DOWN_MASK); // Underline the selected text or turn underlining on or off (equivalent to the Underline command). See "The Format Menu." - Shortcut.registerSystemShortcut("system:paste", tr("reserved"), KeyEvent.VK_V, KeyEvent.META_DOWN_MASK); // Insert the Clipboard contents at the insertion point (equivalent to the Paste command). See "The File Menu." - //Shortcut.registerSystemShortcut("system:pastestyle", tr("reserved"), KeyEvent.VK_V, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Apply the style of one object to the selected object (equivalent to the Paste Style command). See "The Format Menu." - //Shortcut.registerSystemShortcut("system:pastemwithoutstyle", tr("reserved"), KeyEvent.VK_V, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Apply the style of the surrounding text to the inserted object (equivalent to the Paste and Match Style command). See "The Edit Menu." - //Shortcut.registerSystemShortcut("system:pasteformatting", tr("reserved"), KeyEvent.VK_V, KeyEvent.META_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK); // Apply formatting settings to the selected object (equivalent to the Paste Ruler command). See "The Format Menu." - //Shortcut.registerSystemShortcut("system:closewindow", tr("reserved"), KeyEvent.VK_W, KeyEvent.META_DOWN_MASK); // Close the active window (equivalent to the Close command). See "The File Menu." - Shortcut.registerSystemShortcut("system:closefile", tr("reserved"), KeyEvent.VK_W, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); // Close a file and its associated windows (equivalent to the Close File command). See "The File Menu." - Shortcut.registerSystemShortcut("system:closeallwindows", tr("reserved"), KeyEvent.VK_W, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Close all windows in the application (equivalent to the Close All command). See "The File Menu." - Shortcut.registerSystemShortcut("system:cut", tr("reserved"), KeyEvent.VK_X, KeyEvent.META_DOWN_MASK); // Remove the selection and store on the Clipboard (equivalent to the Cut command). See "The Edit Menu." - Shortcut.registerSystemShortcut("system:undo", tr("reserved"), KeyEvent.VK_Z, KeyEvent.META_DOWN_MASK); // Reverse the effect of the user's previous operation (equivalent to the Undo command). See "The Edit Menu." - Shortcut.registerSystemShortcut("system:redo", tr("reserved"), KeyEvent.VK_Z, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); // Reverse the effect of the last Undo command (equivalent to the Redo command). See "The Edit Menu." - - auto(Shortcut.registerSystemShortcut("apple-reserved-45", tr("reserved"), KeyEvent.VK_RIGHT, KeyEvent.META_DOWN_MASK)); // Change the keyboard layout to current layout of Roman script. - //auto(Shortcut.registerSystemCut("apple-reserved-46", tr("reserved"), KeyEvent.VK_RIGHT, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the next semantic unit, typically the end of the current line. - //auto(Shortcut.registerSystemCut("apple-reserved-47", tr("reserved"), KeyEvent.VK_RIGHT, KeyEvent.SHIFT_DOWN_MASK)); // Extend selection one character to the right. - //auto(Shortcut.registerSystemCut("apple-reserved-48", tr("reserved"), KeyEvent.VK_RIGHT, KeyEvent.ALT_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the end of the current word, then to the end of the next word. - - Shortcut.registerSystemShortcut("system:movefocusright", tr("reserved"), KeyEvent.VK_RIGHT, KeyEvent.CTRL_DOWN_MASK); // Move focus to another value or cell within a view, such as a table. See Accessibility Overview. - - auto(Shortcut.registerSystemShortcut("apple-reserved-49", tr("reserved"), KeyEvent.VK_LEFT, KeyEvent.META_DOWN_MASK)); // Change the keyboard layout to current layout of system script. - //auto(Shortcut.registerSystemCut("apple-reserved-50", tr("reserved"), KeyEvent.VK_LEFT, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the previous semantic unit, typically the beginning of the current line. - //auto(Shortcut.registerSystemCut("apple-reserved-51", tr("reserved"), KeyEvent.VK_LEFT, KeyEvent.SHIFT_DOWN_MASK)); // Extend selection one character to the left. - //auto(Shortcut.registerSystemCut("apple-reserved-52", tr("reserved"), KeyEvent.VK_LEFT, KeyEvent.ALT_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the beginning of the current word, then to the beginning of the previous word. - - Shortcut.registerSystemShortcut("system:movefocusleft", tr("reserved"), KeyEvent.VK_LEFT, KeyEvent.CTRL_DOWN_MASK); // Move focus to another value or cell within a view, such as a table. See Accessibility Overview. - - //auto(Shortcut.registerSystemCut("apple-reserved-53", tr("reserved"), KeyEvent.VK_UP, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection upward in the next semantic unit, typically the beginning of the document. - //auto(Shortcut.registerSystemCut("apple-reserved-54", tr("reserved"), KeyEvent.VK_UP, KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the line above, to the nearest character boundary at the same horizontal location. - //auto(Shortcut.registerSystemCut("apple-reserved-55", tr("reserved"), KeyEvent.VK_UP, KeyEvent.ALT_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the beginning of the current paragraph, then to the beginning of the next paragraph. - - Shortcut.registerSystemShortcut("system:movefocusup", tr("reserved"), KeyEvent.VK_UP, KeyEvent.CTRL_DOWN_MASK); // Move focus to another value or cell within a view, such as a table. See Accessibility Overview. - - //auto(Shortcut.registerSystemCut("apple-reserved-56", tr("reserved"), KeyEvent.VK_DOWN, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection downward in the next semantic unit, typically the end of the document. - //auto(Shortcut.registerSystemCut("apple-reserved-57", tr("reserved"), KeyEvent.VK_DOWN, KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the line below, to the nearest character boundary at the same horizontal location. - //auto(Shortcut.registerSystemCut("apple-reserved-58", tr("reserved"), KeyEvent.VK_DOWN, KeyEvent.ALT_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the end of the current paragraph, then to the end of the next paragraph (include the blank line between paragraphs in cut, copy, and paste operations). - - Shortcut.registerSystemShortcut("system:movefocusdown", tr("reserved"), KeyEvent.VK_DOWN, KeyEvent.CTRL_DOWN_MASK); // Move focus to another value or cell within a view, such as a table. See Accessibility Overview. - - auto(Shortcut.registerSystemShortcut("system:about", tr("reserved"), 0, -1)); // About - - //Shortcut.registerSystemShortcut("view:zoomin", tr("reserved"), KeyEvent.VK_ADD, KeyEvent.META_DOWN_MASK); // Zoom in - //Shortcut.registerSystemShortcut("view:zoomout", tr("reserved"), KeyEvent.VK_SUBTRACT, KeyEvent.META_DOWN_MASK); // Zoom out + //Shortcut.registerSystemCut("system:italic", tr(reserved), KeyEvent.VK_I, KeyEvent.META_DOWN_MASK); // Italicize the selected text or toggle italic text on or off (equivalent to the Italic command). See "The Format Menu." + //Shortcut.registerSystemShortcut("system:info", tr(reserved), KeyEvent.VK_I, KeyEvent.META_DOWN_MASK); // Display an Info window. See "Inspector Windows." + //Shortcut.registerSystemShortcut("system:inspector", tr(reserved), KeyEvent.VK_I, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Display an inspector window. See "Inspector Windows." + //Shortcut.registerSystemShortcut("system:toselection", tr(reserved), KeyEvent.VK_J, KeyEvent.META_DOWN_MASK); // Scroll to a selection. + //Shortcut.registerSystemShortcut("system:minimize", tr(reserved), KeyEvent.VK_M, KeyEvent.META_DOWN_MASK); // Minimize the active window to the Dock (equivalent to the Minimize command). See "The Window Menu." + //Shortcut.registerSystemShortcut("system:minimizeall", tr(reserved), KeyEvent.VK_M, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Minimize all windows of the active application to the Dock (equivalent to the Minimize All command). See "The Window Menu." + Shortcut.registerSystemShortcut("system:new", tr(reserved), KeyEvent.VK_N, InputEvent.META_DOWN_MASK); // Open a new document (equivalent to the New command). See "The File Menu." + Shortcut.registerSystemShortcut("system:open", tr(reserved), KeyEvent.VK_O, InputEvent.META_DOWN_MASK); // Display a dialog for choosing a document to open (equivalent to the Open command). See "The File Menu." + Shortcut.registerSystemShortcut("system:print", tr(reserved), KeyEvent.VK_P, InputEvent.META_DOWN_MASK); // Display the Print dialog (equivalent to the Print command). See "The File Menu." + //Shortcut.registerSystemShortcut("system:printsetup", tr(reserved), KeyEvent.VK_P, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); // Display a dialog for specifying printing parameters (equivalent to the Page Setup command). See "The File Menu." + auto(Shortcut.registerSystemShortcut("system:menuexit", tr(reserved), KeyEvent.VK_Q, InputEvent.META_DOWN_MASK)); // Quit the application (equivalent to the Quit command). See "The Application Menu." + + auto(Shortcut.registerSystemShortcut("apple-reserved-43", tr(reserved), KeyEvent.VK_Q, InputEvent.META_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); // Log out the current user (equivalent to the Log Out command). + auto(Shortcut.registerSystemShortcut("apple-reserved-44", tr(reserved), KeyEvent.VK_Q, InputEvent.META_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); // Log out the current user without confirmation. + + Shortcut.registerSystemShortcut("system:save", tr(reserved), KeyEvent.VK_S, InputEvent.META_DOWN_MASK); // Save the active document (equivalent to the Save command). See "The File Menu." + Shortcut.registerSystemShortcut("system:saveas", tr(reserved), KeyEvent.VK_S, InputEvent.META_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK); // Display the Save dialog (equivalent to the Save As command). See "The File Menu." + //Shortcut.registerSystemShortcut("system:fonts", tr(reserved), KeyEvent.VK_T, KeyEvent.META_DOWN_MASK); // Display the Fonts window (equivalent to the Show Fonts command). See "The Format Menu." + Shortcut.registerSystemShortcut("system:toggletoolbar", tr(reserved), KeyEvent.VK_T, InputEvent.META_DOWN_MASK | InputEvent.ALT_DOWN_MASK); // Show or hide a toolbar (equivalent to the Show/Hide Toolbar command). See "The View Menu" and "Toolbars." + //Shortcut.registerSystemShortcut("system:underline", tr(reserved), KeyEvent.VK_U, KeyEvent.META_DOWN_MASK); // Underline the selected text or turn underlining on or off (equivalent to the Underline command). See "The Format Menu." + Shortcut.registerSystemShortcut("system:paste", tr(reserved), KeyEvent.VK_V, InputEvent.META_DOWN_MASK); // Insert the Clipboard contents at the insertion point (equivalent to the Paste command). See "The File Menu." + //Shortcut.registerSystemShortcut("system:pastestyle", tr(reserved), KeyEvent.VK_V, KeyEvent.META_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Apply the style of one object to the selected object (equivalent to the Paste Style command). See "The Format Menu." + //Shortcut.registerSystemShortcut("system:pastemwithoutstyle", tr(reserved), KeyEvent.VK_V, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK | KeyEvent.ALT_DOWN_MASK); // Apply the style of the surrounding text to the inserted object (equivalent to the Paste and Match Style command). See "The Edit Menu." + //Shortcut.registerSystemShortcut("system:pasteformatting", tr(reserved), KeyEvent.VK_V, KeyEvent.META_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK); // Apply formatting settings to the selected object (equivalent to the Paste Ruler command). See "The Format Menu." + //Shortcut.registerSystemShortcut("system:closewindow", tr(reserved), KeyEvent.VK_W, KeyEvent.META_DOWN_MASK); // Close the active window (equivalent to the Close command). See "The File Menu." + Shortcut.registerSystemShortcut("system:closefile", tr(reserved), KeyEvent.VK_W, InputEvent.META_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK); // Close a file and its associated windows (equivalent to the Close File command). See "The File Menu." + Shortcut.registerSystemShortcut("system:closeallwindows", tr(reserved), KeyEvent.VK_W, InputEvent.META_DOWN_MASK | InputEvent.ALT_DOWN_MASK); // Close all windows in the application (equivalent to the Close All command). See "The File Menu." + Shortcut.registerSystemShortcut("system:cut", tr(reserved), KeyEvent.VK_X, InputEvent.META_DOWN_MASK); // Remove the selection and store on the Clipboard (equivalent to the Cut command). See "The Edit Menu." + Shortcut.registerSystemShortcut("system:undo", tr(reserved), KeyEvent.VK_Z, InputEvent.META_DOWN_MASK); // Reverse the effect of the user's previous operation (equivalent to the Undo command). See "The Edit Menu." + Shortcut.registerSystemShortcut("system:redo", tr(reserved), KeyEvent.VK_Z, InputEvent.META_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK); // Reverse the effect of the last Undo command (equivalent to the Redo command). See "The Edit Menu." + + auto(Shortcut.registerSystemShortcut("apple-reserved-45", tr(reserved), KeyEvent.VK_RIGHT, InputEvent.META_DOWN_MASK)); // Change the keyboard layout to current layout of Roman script. + //auto(Shortcut.registerSystemCut("apple-reserved-46", tr(reserved), KeyEvent.VK_RIGHT, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the next semantic unit, typically the end of the current line. + //auto(Shortcut.registerSystemCut("apple-reserved-47", tr(reserved), KeyEvent.VK_RIGHT, KeyEvent.SHIFT_DOWN_MASK)); // Extend selection one character to the right. + //auto(Shortcut.registerSystemCut("apple-reserved-48", tr(reserved), KeyEvent.VK_RIGHT, KeyEvent.ALT_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the end of the current word, then to the end of the next word. + + Shortcut.registerSystemShortcut("system:movefocusright", tr(reserved), KeyEvent.VK_RIGHT, InputEvent.CTRL_DOWN_MASK); // Move focus to another value or cell within a view, such as a table. See Accessibility Overview. + + auto(Shortcut.registerSystemShortcut("apple-reserved-49", tr(reserved), KeyEvent.VK_LEFT, InputEvent.META_DOWN_MASK)); // Change the keyboard layout to current layout of system script. + //auto(Shortcut.registerSystemCut("apple-reserved-50", tr(reserved), KeyEvent.VK_LEFT, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the previous semantic unit, typically the beginning of the current line. + //auto(Shortcut.registerSystemCut("apple-reserved-51", tr(reserved), KeyEvent.VK_LEFT, KeyEvent.SHIFT_DOWN_MASK)); // Extend selection one character to the left. + //auto(Shortcut.registerSystemCut("apple-reserved-52", tr(reserved), KeyEvent.VK_LEFT, KeyEvent.ALT_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the beginning of the current word, then to the beginning of the previous word. + + Shortcut.registerSystemShortcut("system:movefocusleft", tr(reserved), KeyEvent.VK_LEFT, InputEvent.CTRL_DOWN_MASK); // Move focus to another value or cell within a view, such as a table. See Accessibility Overview. + + //auto(Shortcut.registerSystemCut("apple-reserved-53", tr(reserved), KeyEvent.VK_UP, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection upward in the next semantic unit, typically the beginning of the document. + //auto(Shortcut.registerSystemCut("apple-reserved-54", tr(reserved), KeyEvent.VK_UP, KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the line above, to the nearest character boundary at the same horizontal location. + //auto(Shortcut.registerSystemCut("apple-reserved-55", tr(reserved), KeyEvent.VK_UP, KeyEvent.ALT_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the beginning of the current paragraph, then to the beginning of the next paragraph. + + Shortcut.registerSystemShortcut("system:movefocusup", tr(reserved), KeyEvent.VK_UP, InputEvent.CTRL_DOWN_MASK); // Move focus to another value or cell within a view, such as a table. See Accessibility Overview. + + //auto(Shortcut.registerSystemCut("apple-reserved-56", tr(reserved), KeyEvent.VK_DOWN, KeyEvent.META_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection downward in the next semantic unit, typically the end of the document. + //auto(Shortcut.registerSystemCut("apple-reserved-57", tr(reserved), KeyEvent.VK_DOWN, KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the line below, to the nearest character boundary at the same horizontal location. + //auto(Shortcut.registerSystemCut("apple-reserved-58", tr(reserved), KeyEvent.VK_DOWN, KeyEvent.ALT_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK)); // Extend selection to the end of the current paragraph, then to the end of the next paragraph (include the blank line between paragraphs in cut, copy, and paste operations). + + Shortcut.registerSystemShortcut("system:movefocusdown", tr(reserved), KeyEvent.VK_DOWN, InputEvent.CTRL_DOWN_MASK); // Move focus to another value or cell within a view, such as a table. See Accessibility Overview. + + auto(Shortcut.registerSystemShortcut("system:about", tr(reserved), 0, -1)); // About + + //Shortcut.registerSystemShortcut("view:zoomin", tr(reserved), KeyEvent.VK_ADD, KeyEvent.META_DOWN_MASK); // Zoom in + //Shortcut.registerSystemShortcut("view:zoomout", tr(reserved), KeyEvent.VK_SUBTRACT, KeyEvent.META_DOWN_MASK); // Zoom out // CHECKSTYLE.ON: LineLength } @@ -377,6 +314,10 @@ private static void auto(Shortcut sc) { } } + private static String getHome() { + return getSystemProperty("user.home"); + } + @Override public String getDefaultStyle() { return "com.apple.laf.AquaLookAndFeel"; @@ -396,11 +337,12 @@ public String getOSDescription() { private String buildOSBuildNumber() { StringBuilder sb = new StringBuilder(); try { - sb.append(exec("sw_vers", "-productName")) + String swVers = "sw_vers"; + sb.append(exec(swVers, "-productName")) .append(' ') - .append(exec("sw_vers", "-productVersion")) + .append(exec(swVers, "-productVersion")) .append(" (") - .append(exec("sw_vers", "-buildVersion")) + .append(exec(swVers, "-buildVersion")) .append(')'); } catch (IOException e) { Logging.error(e); @@ -418,19 +360,19 @@ public String getOSBuildNumber() { @Override public File getDefaultCacheDirectory() { - return new File(getSystemProperty("user.home")+"/Library/Caches", + return new File(getHome() + "/Library/Caches", Preferences.getJOSMDirectoryBaseName()); } @Override public File getDefaultPrefDirectory() { - return new File(getSystemProperty("user.home")+"/Library/Preferences", + return new File(getHome() + "/Library/Preferences", Preferences.getJOSMDirectoryBaseName()); } @Override public File getDefaultUserDataDirectory() { - return new File(getSystemProperty("user.home")+"/Library", + return new File(getHome() + "/Library", Preferences.getJOSMDirectoryBaseName()); } diff --git a/src/org/openstreetmap/josm/tools/PlatformHookUnixoid.java b/src/org/openstreetmap/josm/tools/PlatformHookUnixoid.java index eb7ec6ac0b0..57dbfe483cd 100644 --- a/src/org/openstreetmap/josm/tools/PlatformHookUnixoid.java +++ b/src/org/openstreetmap/josm/tools/PlatformHookUnixoid.java @@ -1,11 +1,13 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.tools; +import static org.openstreetmap.josm.tools.I18n.marktr; import static org.openstreetmap.josm.tools.I18n.tr; import static org.openstreetmap.josm.tools.Utils.getSystemEnv; import static org.openstreetmap.josm.tools.Utils.getSystemProperty; import java.awt.Desktop; +import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.io.BufferedReader; import java.io.File; @@ -28,6 +30,8 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutionException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.openstreetmap.josm.data.Preferences; import org.openstreetmap.josm.io.CertificateAmendment.NativeCertAmend; @@ -50,21 +54,16 @@ public Platform getPlatform() { public void preStartupHook() { // See #12022, #16666 - Disable GNOME ATK Java wrapper as it causes a lot of serious trouble if (isDebianOrUbuntu()) { - if (Utils.getJavaVersion() >= 9) { - // TODO: find a way to disable ATK wrapper on Java >= 9 - // We should probably be able to do that by embedding a no-op AccessibilityProvider in our jar - // so that it is loaded by ServiceLoader without error - // But this require to compile at least one class with Java 9 - } else { - // Java 8 does a simple Class.newInstance() from system classloader - Utils.updateSystemProperty("javax.accessibility.assistive_technologies", "java.lang.Object"); - } + // TODO: find a way to disable ATK wrapper on Java >= 9 + // We should probably be able to do that by embedding a no-op AccessibilityProvider in our jar + // so that it is loaded by ServiceLoader without error + // But this require to compile at least one class with Java 9 } } @Override - public void startupHook(JavaExpirationCallback javaCallback, WebStartMigrationCallback webStartCallback) { - checkWebStartMigration(webStartCallback); + public void startupHook(JavaExpirationCallback javaCallback, SanityCheckCallback sanityCheckCallback) { + PlatformHook.super.startupHook(javaCallback, sanityCheckCallback); } @Override @@ -88,16 +87,18 @@ public void openUrl(String url) throws IOException { } @Override + @SuppressWarnings("squid:S103") // NOSONAR LineLength public void initSystemShortcuts() { + final String reserved = marktr("reserved"); // CHECKSTYLE.OFF: LineLength // TODO: Insert system shortcuts here. See Windows and especially OSX to see how to. for (int i = KeyEvent.VK_F1; i <= KeyEvent.VK_F12; ++i) { - Shortcut.registerSystemShortcut("screen:toogle"+i, tr("reserved"), i, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK) + Shortcut.registerSystemShortcut("screen:toggle"+i, tr(reserved), i, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK) .setAutomatic(); } - Shortcut.registerSystemShortcut("system:reset", tr("reserved"), KeyEvent.VK_DELETE, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK) + Shortcut.registerSystemShortcut("system:reset", tr(reserved), KeyEvent.VK_DELETE, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK) .setAutomatic(); - Shortcut.registerSystemShortcut("system:resetX", tr("reserved"), KeyEvent.VK_BACK_SPACE, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK) + Shortcut.registerSystemShortcut("system:resetX", tr(reserved), KeyEvent.VK_BACK_SPACE, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK) .setAutomatic(); // CHECKSTYLE.ON: LineLength } @@ -172,7 +173,7 @@ public static String getPackageDetails(String... packageNames) { /** * Get the Java package name including detailed version. - * + *

          * Some Java bugs are specific to a certain security update, so in addition * to the Java version, we also need the exact package version. * @@ -180,16 +181,13 @@ public static String getPackageDetails(String... packageNames) { */ public String getJavaPackageDetails() { String home = getSystemProperty("java.home"); - if (home.contains("java-8-openjdk") || home.contains("java-1.8.0-openjdk")) { - return getPackageDetails("openjdk-8-jre", "java-1_8_0-openjdk", "java-1.8.0-openjdk"); - } else if (home.contains("java-9-openjdk") || home.contains("java-1.9.0-openjdk")) { - return getPackageDetails("openjdk-9-jre", "java-1_9_0-openjdk", "java-1.9.0-openjdk", "java-9-openjdk"); - } else if (home.contains("java-10-openjdk")) { - return getPackageDetails("openjdk-10-jre", "java-10-openjdk"); - } else if (home.contains("java-11-openjdk")) { - return getPackageDetails("openjdk-11-jre", "java-11-openjdk"); - } else if (home.contains("java-17-openjdk")) { - return getPackageDetails("openjdk-17-jre", "java-17-openjdk"); + if (home == null) { + return null; + } + Matcher matcher = Pattern.compile("java-(\\d+)-openjdk").matcher(home); + if (matcher.find()) { + String version = matcher.group(1); + return getPackageDetails("openjdk-" + version + "-jre", "java-" + version + "-openjdk"); } else if (home.contains("java-openjdk")) { return getPackageDetails("java-openjdk"); } else if (home.contains("icedtea")) { @@ -202,10 +200,10 @@ public String getJavaPackageDetails() { /** * Get the Web Start package name including detailed version. - * + *

          * OpenJDK packages are shipped with icedtea-web package, * but its version generally does not match main java package version. - * + *

          * Simply return {@code null} if there's no separate package for Java WebStart. * * @return The package name and package version if it can be identified, null otherwise @@ -219,10 +217,10 @@ public String getWebStartPackageDetails() { /** * Get the Gnome ATK wrapper package name including detailed version. - * + *

          * Debian and Ubuntu derivatives come with a pre-enabled accessibility software * completely buggy that makes Swing crash in a lot of different ways. - * + *

          * Simply return {@code null} if it's not found. * * @return The package name and package version if it can be identified, null otherwise diff --git a/src/org/openstreetmap/josm/tools/PlatformHookWindows.java b/src/org/openstreetmap/josm/tools/PlatformHookWindows.java index d60d4151ca9..a2956fe706c 100644 --- a/src/org/openstreetmap/josm/tools/PlatformHookWindows.java +++ b/src/org/openstreetmap/josm/tools/PlatformHookWindows.java @@ -25,6 +25,7 @@ import static java.awt.event.KeyEvent.VK_X; import static java.awt.event.KeyEvent.VK_Y; import static java.awt.event.KeyEvent.VK_Z; +import static org.openstreetmap.josm.tools.I18n.marktr; import static org.openstreetmap.josm.tools.I18n.tr; import static org.openstreetmap.josm.tools.Utils.getSystemEnv; import static org.openstreetmap.josm.tools.Utils.getSystemProperty; @@ -92,7 +93,7 @@ public class PlatformHookWindows implements PlatformHook { /** * Simple data class to hold information about a font. - * + *

          * Used for fontconfig.properties files. */ public static class FontEntry { @@ -153,10 +154,10 @@ public void afterPrefStartupHook() { } @Override - public void startupHook(JavaExpirationCallback javaCallback, WebStartMigrationCallback webStartCallback) { + public void startupHook(JavaExpirationCallback javaCallback, SanityCheckCallback sanityCheckCallback) { warnSoonToBeUnsupportedJava(javaCallback); checkExpiredJava(javaCallback); - checkWebStartMigration(webStartCallback); + PlatformHook.super.startupHook(javaCallback, sanityCheckCallback); } @Override @@ -178,74 +179,76 @@ public void openUrl(String url) throws IOException { } @Override + @SuppressWarnings("squid:S103") // NOSONAR LineLength public void initSystemShortcuts() { + final String reserved = marktr("reserved"); // CHECKSTYLE.OFF: LineLength - //Shortcut.registerSystemCut("system:menuexit", tr("reserved"), VK_Q, CTRL_DOWN_MASK); - Shortcut.registerSystemShortcut("system:duplicate", tr("reserved"), VK_D, CTRL_DOWN_MASK); // not really system, but to avoid odd results + //Shortcut.registerSystemCut("system:menuexit", tr(reserved), VK_Q, CTRL_DOWN_MASK); + Shortcut.registerSystemShortcut("system:duplicate", tr(reserved), VK_D, CTRL_DOWN_MASK); // not really system, but to avoid odd results // Windows 7 shortcuts: http://windows.microsoft.com/en-US/windows7/Keyboard-shortcuts // Shortcuts with setAutomatic(): items with automatic shortcuts will not be added to the menu bar at all // Don't know why Ctrl-Alt-Del isn't even listed on official Microsoft support page - Shortcut.registerSystemShortcut("system:reset", tr("reserved"), VK_DELETE, CTRL_DOWN_MASK | ALT_DOWN_MASK).setAutomatic(); + Shortcut.registerSystemShortcut("system:reset", tr(reserved), VK_DELETE, CTRL_DOWN_MASK | ALT_DOWN_MASK).setAutomatic(); // Ease of Access keyboard shortcuts - Shortcut.registerSystemShortcut("microsoft-reserved-01", tr("reserved"), VK_PRINTSCREEN, ALT_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Turn High Contrast on or off - Shortcut.registerSystemShortcut("microsoft-reserved-02", tr("reserved"), VK_NUM_LOCK, ALT_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Turn Mouse Keys on or off - //Shortcut.registerSystemCut("microsoft-reserved-03", tr("reserved"), VK_U, );// Open the Ease of Access Center (TODO: Windows-U, how to handle it in Java ?) + Shortcut.registerSystemShortcut("microsoft-reserved-01", tr(reserved), VK_PRINTSCREEN, ALT_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Turn High Contrast on or off + Shortcut.registerSystemShortcut("microsoft-reserved-02", tr(reserved), VK_NUM_LOCK, ALT_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Turn Mouse Keys on or off + //Shortcut.registerSystemCut("microsoft-reserved-03", tr(reserved), VK_U, );// Open the Ease of Access Center (TODO: Windows-U, how to handle it in Java ?) // General keyboard shortcuts - //Shortcut.registerSystemShortcut("system:help", tr("reserved"), VK_F1, 0); // Display Help - Shortcut.registerSystemShortcut("system:copy", tr("reserved"), VK_C, CTRL_DOWN_MASK); // Copy the selected item - Shortcut.registerSystemShortcut("system:cut", tr("reserved"), VK_X, CTRL_DOWN_MASK); // Cut the selected item - Shortcut.registerSystemShortcut("system:paste", tr("reserved"), VK_V, CTRL_DOWN_MASK); // Paste the selected item - Shortcut.registerSystemShortcut("system:undo", tr("reserved"), VK_Z, CTRL_DOWN_MASK); // Undo an action - Shortcut.registerSystemShortcut("system:redo", tr("reserved"), VK_Y, CTRL_DOWN_MASK); // Redo an action - //Shortcut.registerSystemCut("microsoft-reserved-10", tr("reserved"), VK_DELETE, 0); // Delete the selected item and move it to the Recycle Bin - //Shortcut.registerSystemCut("microsoft-reserved-11", tr("reserved"), VK_DELETE, SHIFT_DOWN_MASK); // Delete the selected item without moving it to the Recycle Bin first - //Shortcut.registerSystemCut("system:rename", tr("reserved"), VK_F2, 0); // Rename the selected item - Shortcut.registerSystemShortcut("system:movefocusright", tr("reserved"), VK_RIGHT, CTRL_DOWN_MASK); // Move the cursor to the beginning of the next word - Shortcut.registerSystemShortcut("system:movefocusleft", tr("reserved"), VK_LEFT, CTRL_DOWN_MASK); // Move the cursor to the beginning of the previous word - Shortcut.registerSystemShortcut("system:movefocusdown", tr("reserved"), VK_DOWN, CTRL_DOWN_MASK); // Move the cursor to the beginning of the next paragraph - Shortcut.registerSystemShortcut("system:movefocusup", tr("reserved"), VK_UP, CTRL_DOWN_MASK); // Move the cursor to the beginning of the previous paragraph - //Shortcut.registerSystemCut("microsoft-reserved-17", tr("reserved"), VK_RIGHT, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text - //Shortcut.registerSystemCut("microsoft-reserved-18", tr("reserved"), VK_LEFT, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text - //Shortcut.registerSystemCut("microsoft-reserved-19", tr("reserved"), VK_DOWN, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text - //Shortcut.registerSystemCut("microsoft-reserved-20", tr("reserved"), VK_UP, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text - //Shortcut.registerSystemCut("microsoft-reserved-21", tr("reserved"), VK_RIGHT, SHIFT_DOWN_MASK); // Select more than one item in a window or on the desktop, or select text within a document - //Shortcut.registerSystemCut("microsoft-reserved-22", tr("reserved"), VK_LEFT, SHIFT_DOWN_MASK); // Select more than one item in a window or on the desktop, or select text within a document - //Shortcut.registerSystemCut("microsoft-reserved-23", tr("reserved"), VK_DOWN, SHIFT_DOWN_MASK); // Select more than one item in a window or on the desktop, or select text within a document - //Shortcut.registerSystemCut("microsoft-reserved-24", tr("reserved"), VK_UP, SHIFT_DOWN_MASK); // Select more than one item in a window or on the desktop, or select text within a document - //Shortcut.registerSystemCut("microsoft-reserved-25", tr("reserved"), VK_RIGHT+, CTRL_DOWN_MASK); // Select multiple individual items in a window or on the desktop (TODO: ctrl+arrow+spacebar, how to handle it in Java ?) - //Shortcut.registerSystemCut("microsoft-reserved-26", tr("reserved"), VK_LEFT+, CTRL_DOWN_MASK); // Select multiple individual items in a window or on the desktop (TODO: ctrl+arrow+spacebar, how to handle it in Java ?) - //Shortcut.registerSystemCut("microsoft-reserved-27", tr("reserved"), VK_DOWN+, CTRL_DOWN_MASK); // Select multiple individual items in a window or on the desktop (TODO: ctrl+arrow+spacebar, how to handle it in Java ?) - //Shortcut.registerSystemCut("microsoft-reserved-28", tr("reserved"), VK_UP+, CTRL_DOWN_MASK); // Select multiple individual items in a window or on the desktop (TODO: ctrl+arrow+spacebar, how to handle it in Java ?) - Shortcut.registerSystemShortcut("system:selectall", tr("reserved"), VK_A, CTRL_DOWN_MASK); // Select all items in a document or window - //Shortcut.registerSystemCut("system:search", tr("reserved"), VK_F3, 0); // Search for a file or folder - Shortcut.registerSystemShortcut("microsoft-reserved-31", tr("reserved"), VK_ENTER, ALT_DOWN_MASK).setAutomatic(); // Display properties for the selected item - Shortcut.registerSystemShortcut("system:exit", tr("reserved"), VK_F4, ALT_DOWN_MASK).setAutomatic(); // Close the active item, or exit the active program - Shortcut.registerSystemShortcut("microsoft-reserved-33", tr("reserved"), VK_SPACE, ALT_DOWN_MASK).setAutomatic(); // Open the shortcut menu for the active window - //Shortcut.registerSystemCut("microsoft-reserved-34", tr("reserved"), VK_F4, CTRL_DOWN_MASK); // Close the active document (in programs that allow you to have multiple documents open simultaneously) - Shortcut.registerSystemShortcut("microsoft-reserved-35", tr("reserved"), VK_TAB, ALT_DOWN_MASK).setAutomatic(); // Switch between open items - Shortcut.registerSystemShortcut("microsoft-reserved-36", tr("reserved"), VK_TAB, CTRL_DOWN_MASK | ALT_DOWN_MASK).setAutomatic(); // Use the arrow keys to switch between open items - //Shortcut.registerSystemCut("microsoft-reserved-37", tr("reserved"), VK_TAB, ); // Cycle through programs on the taskbar by using Aero Flip 3-D (TODO: Windows-Tab, how to handle it in Java ?) - //Shortcut.registerSystemCut("microsoft-reserved-38", tr("reserved"), VK_TAB, CTRL_DOWN_MASK | ); // Use the arrow keys to cycle through programs on the taskbar by using Aero Flip 3-D (TODO: Ctrl-Windows-Tab, how to handle it in Java ?) - Shortcut.registerSystemShortcut("microsoft-reserved-39", tr("reserved"), VK_ESCAPE, ALT_DOWN_MASK).setAutomatic(); // Cycle through items in the order in which they were opened - //Shortcut.registerSystemCut("microsoft-reserved-40", tr("reserved"), VK_F6, 0); // Cycle through screen elements in a window or on the desktop - //Shortcut.registerSystemCut("microsoft-reserved-41", tr("reserved"), VK_F4, 0); // Display the address bar list in Windows Explorer - Shortcut.registerSystemShortcut("microsoft-reserved-42", tr("reserved"), VK_F10, SHIFT_DOWN_MASK); // Display the shortcut menu for the selected item - Shortcut.registerSystemShortcut("microsoft-reserved-43", tr("reserved"), VK_ESCAPE, CTRL_DOWN_MASK).setAutomatic(); // Open the Start menu - //Shortcut.registerSystemShortcut("microsoft-reserved-44", tr("reserved"), VK_F10, 0); // Activate the menu bar in the active program - //Shortcut.registerSystemCut("microsoft-reserved-45", tr("reserved"), VK_RIGHT, 0); // Open the next menu to the right, or open a submenu - //Shortcut.registerSystemCut("microsoft-reserved-46", tr("reserved"), VK_LEFT, 0); // Open the next menu to the left, or close a submenu - //Shortcut.registerSystemCut("microsoft-reserved-47", tr("reserved"), VK_F5, 0); // Refresh the active window - //Shortcut.registerSystemCut("microsoft-reserved-48", tr("reserved"), VK_UP, ALT_DOWN_MASK); // View the folder one level up in Windows Explorer - //Shortcut.registerSystemCut("microsoft-reserved-49", tr("reserved"), VK_ESCAPE, 0); // Cancel the current task - Shortcut.registerSystemShortcut("microsoft-reserved-50", tr("reserved"), VK_ESCAPE, CTRL_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Open Task Manager - Shortcut.registerSystemShortcut("microsoft-reserved-51", tr("reserved"), VK_SHIFT, ALT_DOWN_MASK).setAutomatic(); // Switch the input language when multiple input languages are enabled - Shortcut.registerSystemShortcut("microsoft-reserved-52", tr("reserved"), VK_SHIFT, CTRL_DOWN_MASK).setAutomatic(); // Switch the keyboard layout when multiple keyboard layouts are enabled - //Shortcut.registerSystemCut("microsoft-reserved-53", tr("reserved"), ); // Change the reading direction of text in right-to-left reading languages (TODO: unclear) + //Shortcut.registerSystemShortcut("system:help", tr(reserved), VK_F1, 0); // Display Help + Shortcut.registerSystemShortcut("system:copy", tr(reserved), VK_C, CTRL_DOWN_MASK); // Copy the selected item + Shortcut.registerSystemShortcut("system:cut", tr(reserved), VK_X, CTRL_DOWN_MASK); // Cut the selected item + Shortcut.registerSystemShortcut("system:paste", tr(reserved), VK_V, CTRL_DOWN_MASK); // Paste the selected item + Shortcut.registerSystemShortcut("system:undo", tr(reserved), VK_Z, CTRL_DOWN_MASK); // Undo an action + Shortcut.registerSystemShortcut("system:redo", tr(reserved), VK_Y, CTRL_DOWN_MASK); // Redo an action + //Shortcut.registerSystemCut("microsoft-reserved-10", tr(reserved), VK_DELETE, 0); // Delete the selected item and move it to the Recycle Bin + //Shortcut.registerSystemCut("microsoft-reserved-11", tr(reserved), VK_DELETE, SHIFT_DOWN_MASK); // Delete the selected item without moving it to the Recycle Bin first + //Shortcut.registerSystemCut("system:rename", tr(reserved), VK_F2, 0); // Rename the selected item + Shortcut.registerSystemShortcut("system:movefocusright", tr(reserved), VK_RIGHT, CTRL_DOWN_MASK); // Move the cursor to the beginning of the next word + Shortcut.registerSystemShortcut("system:movefocusleft", tr(reserved), VK_LEFT, CTRL_DOWN_MASK); // Move the cursor to the beginning of the previous word + Shortcut.registerSystemShortcut("system:movefocusdown", tr(reserved), VK_DOWN, CTRL_DOWN_MASK); // Move the cursor to the beginning of the next paragraph + Shortcut.registerSystemShortcut("system:movefocusup", tr(reserved), VK_UP, CTRL_DOWN_MASK); // Move the cursor to the beginning of the previous paragraph + //Shortcut.registerSystemCut("microsoft-reserved-17", tr(reserved), VK_RIGHT, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text + //Shortcut.registerSystemCut("microsoft-reserved-18", tr(reserved), VK_LEFT, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text + //Shortcut.registerSystemCut("microsoft-reserved-19", tr(reserved), VK_DOWN, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text + //Shortcut.registerSystemCut("microsoft-reserved-20", tr(reserved), VK_UP, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); // Select a block of text + //Shortcut.registerSystemCut("microsoft-reserved-21", tr(reserved), VK_RIGHT, SHIFT_DOWN_MASK); // Select more than one item in a window or on the desktop, or select text within a document + //Shortcut.registerSystemCut("microsoft-reserved-22", tr(reserved), VK_LEFT, SHIFT_DOWN_MASK); // Select more than one item in a window or on the desktop, or select text within a document + //Shortcut.registerSystemCut("microsoft-reserved-23", tr(reserved), VK_DOWN, SHIFT_DOWN_MASK); // Select more than one item in a window or on the desktop, or select text within a document + //Shortcut.registerSystemCut("microsoft-reserved-24", tr(reserved), VK_UP, SHIFT_DOWN_MASK); // Select more than one item in a window or on the desktop, or select text within a document + //Shortcut.registerSystemCut("microsoft-reserved-25", tr(reserved), VK_RIGHT+, CTRL_DOWN_MASK); // Select multiple individual items in a window or on the desktop (TODO: ctrl+arrow+spacebar, how to handle it in Java ?) + //Shortcut.registerSystemCut("microsoft-reserved-26", tr(reserved), VK_LEFT+, CTRL_DOWN_MASK); // Select multiple individual items in a window or on the desktop (TODO: ctrl+arrow+spacebar, how to handle it in Java ?) + //Shortcut.registerSystemCut("microsoft-reserved-27", tr(reserved), VK_DOWN+, CTRL_DOWN_MASK); // Select multiple individual items in a window or on the desktop (TODO: ctrl+arrow+spacebar, how to handle it in Java ?) + //Shortcut.registerSystemCut("microsoft-reserved-28", tr(reserved), VK_UP+, CTRL_DOWN_MASK); // Select multiple individual items in a window or on the desktop (TODO: ctrl+arrow+spacebar, how to handle it in Java ?) + Shortcut.registerSystemShortcut("system:selectall", tr(reserved), VK_A, CTRL_DOWN_MASK); // Select all items in a document or window + //Shortcut.registerSystemCut("system:search", tr(reserved), VK_F3, 0); // Search for a file or folder + Shortcut.registerSystemShortcut("microsoft-reserved-31", tr(reserved), VK_ENTER, ALT_DOWN_MASK).setAutomatic(); // Display properties for the selected item + Shortcut.registerSystemShortcut("system:exit", tr(reserved), VK_F4, ALT_DOWN_MASK).setAutomatic(); // Close the active item, or exit the active program + Shortcut.registerSystemShortcut("microsoft-reserved-33", tr(reserved), VK_SPACE, ALT_DOWN_MASK).setAutomatic(); // Open the shortcut menu for the active window + //Shortcut.registerSystemCut("microsoft-reserved-34", tr(reserved), VK_F4, CTRL_DOWN_MASK); // Close the active document (in programs that allow you to have multiple documents open simultaneously) + Shortcut.registerSystemShortcut("microsoft-reserved-35", tr(reserved), VK_TAB, ALT_DOWN_MASK).setAutomatic(); // Switch between open items + Shortcut.registerSystemShortcut("microsoft-reserved-36", tr(reserved), VK_TAB, CTRL_DOWN_MASK | ALT_DOWN_MASK).setAutomatic(); // Use the arrow keys to switch between open items + //Shortcut.registerSystemCut("microsoft-reserved-37", tr(reserved), VK_TAB, ); // Cycle through programs on the taskbar by using Aero Flip 3-D (TODO: Windows-Tab, how to handle it in Java ?) + //Shortcut.registerSystemCut("microsoft-reserved-38", tr(reserved), VK_TAB, CTRL_DOWN_MASK | ); // Use the arrow keys to cycle through programs on the taskbar by using Aero Flip 3-D (TODO: Ctrl-Windows-Tab, how to handle it in Java ?) + Shortcut.registerSystemShortcut("microsoft-reserved-39", tr(reserved), VK_ESCAPE, ALT_DOWN_MASK).setAutomatic(); // Cycle through items in the order in which they were opened + //Shortcut.registerSystemCut("microsoft-reserved-40", tr(reserved), VK_F6, 0); // Cycle through screen elements in a window or on the desktop + //Shortcut.registerSystemCut("microsoft-reserved-41", tr(reserved), VK_F4, 0); // Display the address bar list in Windows Explorer + Shortcut.registerSystemShortcut("microsoft-reserved-42", tr(reserved), VK_F10, SHIFT_DOWN_MASK); // Display the shortcut menu for the selected item + Shortcut.registerSystemShortcut("microsoft-reserved-43", tr(reserved), VK_ESCAPE, CTRL_DOWN_MASK).setAutomatic(); // Open the Start menu + //Shortcut.registerSystemShortcut("microsoft-reserved-44", tr(reserved), VK_F10, 0); // Activate the menu bar in the active program + //Shortcut.registerSystemCut("microsoft-reserved-45", tr(reserved), VK_RIGHT, 0); // Open the next menu to the right, or open a submenu + //Shortcut.registerSystemCut("microsoft-reserved-46", tr(reserved), VK_LEFT, 0); // Open the next menu to the left, or close a submenu + //Shortcut.registerSystemCut("microsoft-reserved-47", tr(reserved), VK_F5, 0); // Refresh the active window + //Shortcut.registerSystemCut("microsoft-reserved-48", tr(reserved), VK_UP, ALT_DOWN_MASK); // View the folder one level up in Windows Explorer + //Shortcut.registerSystemCut("microsoft-reserved-49", tr(reserved), VK_ESCAPE, 0); // Cancel the current task + Shortcut.registerSystemShortcut("microsoft-reserved-50", tr(reserved), VK_ESCAPE, CTRL_DOWN_MASK | SHIFT_DOWN_MASK).setAutomatic(); // Open Task Manager + Shortcut.registerSystemShortcut("microsoft-reserved-51", tr(reserved), VK_SHIFT, ALT_DOWN_MASK).setAutomatic(); // Switch the input language when multiple input languages are enabled + Shortcut.registerSystemShortcut("microsoft-reserved-52", tr(reserved), VK_SHIFT, CTRL_DOWN_MASK).setAutomatic(); // Switch the keyboard layout when multiple keyboard layouts are enabled + //Shortcut.registerSystemCut("microsoft-reserved-53", tr(reserved), ); // Change the reading direction of text in right-to-left reading languages (TODO: unclear) // CHECKSTYLE.ON: LineLength } @@ -289,6 +292,17 @@ public static String getReleaseId() throws IllegalAccessException, InvocationTar return WinRegistry.readString(HKEY_LOCAL_MACHINE, CURRENT_VERSION, "ReleaseId"); } + /** + * Returns the Windows display version from registry (example: "22H2") + * @return the Windows display version from registry + * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible + * @throws InvocationTargetException if the underlying method throws an exception + * @since 19041 + */ + public static String getDisplayVersion() throws IllegalAccessException, InvocationTargetException { + return WinRegistry.readString(HKEY_LOCAL_MACHINE, CURRENT_VERSION, "DisplayVersion"); + } + /** * Returns the Windows current build number from registry (example: "15063") * @return the Windows current build number from registry @@ -304,9 +318,14 @@ private static String buildOSBuildNumber() { StringBuilder sb = new StringBuilder(); try { sb.append(getProductName()); - String releaseId = getReleaseId(); - if (releaseId != null) { - sb.append(' ').append(releaseId); + String displayVersion = getDisplayVersion(); + if (displayVersion != null) { + sb.append(' ').append(displayVersion); + } else { + String releaseId = getReleaseId(); + if (releaseId != null) { + sb.append(' ').append(releaseId); + } } sb.append(" (").append(getCurrentBuild()).append(')'); } catch (ReflectiveOperationException | JosmRuntimeException | NoClassDefFoundError e) { diff --git a/src/org/openstreetmap/josm/tools/Shortcut.java b/src/org/openstreetmap/josm/tools/Shortcut.java index 3adf77cf689..3b38043b465 100644 --- a/src/org/openstreetmap/josm/tools/Shortcut.java +++ b/src/org/openstreetmap/josm/tools/Shortcut.java @@ -3,6 +3,7 @@ import static org.openstreetmap.josm.tools.I18n.tr; +import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.Arrays; @@ -245,7 +246,7 @@ public void setFocusAccelerator(JTextComponent component) { */ public void setAccelerator(AbstractAction action) { if (getKeyStroke() != null) { - action.putValue(AbstractAction.ACCELERATOR_KEY, getKeyStroke()); + action.putValue(Action.ACCELERATOR_KEY, getKeyStroke()); } } @@ -265,7 +266,7 @@ public String getKeyText() { */ public static String getKeyText(KeyStroke keyStroke) { if (keyStroke == null) return ""; - String modifText = KeyEvent.getModifiersExText(keyStroke.getModifiers()); + String modifText = InputEvent.getModifiersExText(keyStroke.getModifiers()); if (modifText.isEmpty()) return KeyEvent.getKeyText(keyStroke.getKeyCode()); return modifText + '+' + KeyEvent.getKeyText(keyStroke.getKeyCode()); } @@ -306,7 +307,7 @@ public String toString() { // here we store our shortcuts private static final ShortcutCollection shortcuts = new ShortcutCollection(); - private static class ShortcutCollection extends CopyOnWriteArrayList { + private static final class ShortcutCollection extends CopyOnWriteArrayList { private static final long serialVersionUID = 1L; @Override public boolean add(Shortcut shortcut) { @@ -397,15 +398,15 @@ private static void doInit() { initdone = true; int commandDownMask = PlatformManager.getPlatform().getMenuShortcutKeyMaskEx(); groups.put(NONE, -1); - groups.put(MNEMONIC, KeyEvent.ALT_DOWN_MASK); + groups.put(MNEMONIC, InputEvent.ALT_DOWN_MASK); groups.put(DIRECT, 0); - groups.put(ALT, KeyEvent.ALT_DOWN_MASK); - groups.put(SHIFT, KeyEvent.SHIFT_DOWN_MASK); + groups.put(ALT, InputEvent.ALT_DOWN_MASK); + groups.put(SHIFT, InputEvent.SHIFT_DOWN_MASK); groups.put(CTRL, commandDownMask); - groups.put(ALT_SHIFT, KeyEvent.ALT_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK); - groups.put(ALT_CTRL, KeyEvent.ALT_DOWN_MASK | commandDownMask); - groups.put(CTRL_SHIFT, commandDownMask | KeyEvent.SHIFT_DOWN_MASK); - groups.put(ALT_CTRL_SHIFT, KeyEvent.ALT_DOWN_MASK | commandDownMask | KeyEvent.SHIFT_DOWN_MASK); + groups.put(ALT_SHIFT, InputEvent.ALT_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK); + groups.put(ALT_CTRL, InputEvent.ALT_DOWN_MASK | commandDownMask); + groups.put(CTRL_SHIFT, commandDownMask | InputEvent.SHIFT_DOWN_MASK); + groups.put(ALT_CTRL_SHIFT, InputEvent.ALT_DOWN_MASK | commandDownMask | InputEvent.SHIFT_DOWN_MASK); // (1) System reserved shortcuts PlatformManager.getPlatform().initSystemShortcuts(); @@ -566,10 +567,10 @@ private static Shortcut registerShortcut(String shortText, String longText, int private static int findNewOsxModifier(int requestedGroup) { switch (requestedGroup) { - case CTRL: return KeyEvent.CTRL_DOWN_MASK; - case ALT_CTRL: return KeyEvent.ALT_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK; - case CTRL_SHIFT: return KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK; - case ALT_CTRL_SHIFT: return KeyEvent.ALT_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK | KeyEvent.SHIFT_DOWN_MASK; + case CTRL: return InputEvent.CTRL_DOWN_MASK; + case ALT_CTRL: return InputEvent.ALT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK; + case CTRL_SHIFT: return InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK; + case ALT_CTRL_SHIFT: return InputEvent.ALT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK; default: return 0; } } diff --git a/src/org/openstreetmap/josm/tools/Tag2Link.java b/src/org/openstreetmap/josm/tools/Tag2Link.java index ceb4d8f6f0c..831a9da3341 100644 --- a/src/org/openstreetmap/josm/tools/Tag2Link.java +++ b/src/org/openstreetmap/josm/tools/Tag2Link.java @@ -64,7 +64,7 @@ public final class Tag2Link { .collect(Collectors.joining("|")); static final ListProperty PREF_SOURCE = new ListProperty("tag2link.source", - Collections.singletonList("resource://META-INF/resources/webjars/tag2link/2022.11.28/index.json")); + Collections.singletonList("resource://META-INF/resources/webjars/tag2link/2024.2.8/index.json")); static final CachingProperty> PREF_SEARCH_ENGINES = new ListProperty("tag2link.search", Arrays.asList("https://duckduckgo.com/?q=$1", "https://www.google.com/search?q=$1")).cached(); diff --git a/src/org/openstreetmap/josm/tools/TextTagParser.java b/src/org/openstreetmap/josm/tools/TextTagParser.java index fc37aa2b27e..744b0e01190 100644 --- a/src/org/openstreetmap/josm/tools/TextTagParser.java +++ b/src/org/openstreetmap/josm/tools/TextTagParser.java @@ -20,7 +20,7 @@ public final class TextTagParser { // properties need JOSM restart to apply, modified rarely enough private static final int MAX_KEY_LENGTH = Config.getPref().getInt("tags.paste.max-key-length", 50); private static final int MAX_KEY_COUNT = Config.getPref().getInt("tags.paste.max-key-count", 30); - private static final String KEY_PATTERN = Config.getPref().get("tags.paste.tag-pattern", "[0-9a-zA-Z:_]*"); + private static final String KEY_PATTERN = Config.getPref().get("tags.paste.tag-pattern", "[0-9a-zA-Z:_-]*"); private static final int MAX_VALUE_LENGTH = 255; private TextTagParser() { @@ -128,7 +128,7 @@ public static Map readTagsFromText(String buf) { /** * Check tags for correctness and display warnings if needed - * @param tags - map key->value to check + * @param tags - map key → value to check * @param callback warning callback * @return true if the tags should be pasted * @since 12683 diff --git a/src/org/openstreetmap/josm/tools/Utils.java b/src/org/openstreetmap/josm/tools/Utils.java index 9d29c970906..dd9230bfd22 100644 --- a/src/org/openstreetmap/josm/tools/Utils.java +++ b/src/org/openstreetmap/josm/tools/Utils.java @@ -1,6 +1,7 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.tools; +import static java.util.function.Predicate.not; import static org.openstreetmap.josm.tools.I18n.marktr; import static org.openstreetmap.josm.tools.I18n.tr; import static org.openstreetmap.josm.tools.I18n.trn; @@ -8,14 +9,12 @@ import java.awt.Font; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; -import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; -import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; @@ -90,6 +89,9 @@ public final class Utils { private static final Pattern REMOVE_DIACRITICS = Pattern.compile("\\p{InCombiningDiacriticalMarks}+"); + private static final Pattern PATTERN_LENGTH = Pattern.compile("^(-?\\d+(?:\\.\\d+)?)(cm|mi|mm|m|ft|km|nmi|in|'|\")?$"); + private static final Pattern PATTERN_LENGTH2 = Pattern.compile("^(-?)(\\d+(?:\\.\\d+)?)(ft|')(\\d+(?:\\.\\d+)?)(in|\")?$"); + private static final String DEFAULT_STRIP = "\uFEFF\u200B"; private static final String[] SIZE_UNITS = {"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}; @@ -98,22 +100,6 @@ public final class Utils { private static final double TO_DEGREES = 180.0 / Math.PI; private static final double TO_RADIANS = Math.PI / 180.0; - /** - * A reference to {@code Map.ofEntries()} available since Java 9 - */ - static final Method mapOfEntries = mapOfEntriesMethod(); - - private static Method mapOfEntriesMethod() { - if (getJavaVersion() >= 9) { - try { - return Map.class.getMethod("ofEntries", Map.Entry[].class); - } catch (NoSuchMethodException noSuchMethodException) { - Logging.trace(noSuchMethodException); - } - } - return null; - } - private Utils() { // Hide default constructor for utils classes } @@ -168,7 +154,7 @@ public static int indexOf(Iterable collection, Predicate values) { CheckParameterUtil.ensureParameterNotNull(sep, "sep"); if (values == null) @@ -345,11 +331,7 @@ public static boolean deleteDirectory(File path) { * @since 10569 */ public static boolean deleteFileIfExists(File file) { - if (file.exists()) { - return deleteFile(file); - } else { - return true; - } + return !file.exists() || deleteFile(file); } /** @@ -535,7 +517,7 @@ public static String toHexString(byte[] bytes) { * Topological sort. * @param type of items * - * @param dependencies contains mappings (key -> value). In the final list of sorted objects, the key will come + * @param dependencies contains mappings (key → value). In the final list of sorted objects, the key will come * after the value. (In other words, the key depends on the value(s).) * There must not be cyclic dependencies. * @return the list of sorted objects @@ -586,7 +568,7 @@ public static String escapeReservedCharactersHTML(String s) { * @return the transformed unmodifiable collection */ public static Collection transform(final Collection c, final Function f) { - return new AbstractCollection() { + return new AbstractCollection<>() { @Override public int size() { @@ -595,7 +577,7 @@ public int size() { @Override public Iterator iterator() { - return new Iterator() { + return new Iterator<>() { private final Iterator it = c.iterator(); @@ -628,7 +610,7 @@ public void remove() { * @return the transformed unmodifiable list */ public static List transform(final List l, final Function f) { - return new AbstractList() { + return new AbstractList<>() { @Override public int size() { @@ -653,6 +635,8 @@ public B get(int index) { */ @SuppressWarnings("unchecked") public static List toUnmodifiableList(Collection collection) { + // Note: Windows does a `null` check on startup on these lists. See #23717. + // Only change this once that is fixed. // Java 9: use List.of(...) if (isEmpty(collection)) { return Collections.emptyList(); @@ -681,15 +665,9 @@ public static Map toUnmodifiableMap(Map map) { } else if (map.size() == 1) { final Map.Entry entry = map.entrySet().iterator().next(); return Collections.singletonMap(entry.getKey(), entry.getValue()); - } else if (mapOfEntries != null) { - try { - // Java 9: use Map.ofEntries(...) - return (Map) mapOfEntries.invoke(null, (Object) map.entrySet().toArray(new Map.Entry[0])); - } catch (ReflectiveOperationException toLog) { - Logging.trace(toLog); - } } - return Collections.unmodifiableMap(map); + // Map.copyOf would also work, but if the original map is immutable, it just returns the original map. + return Map.ofEntries(map.entrySet().toArray(new Map.Entry[0])); } /** @@ -737,9 +715,11 @@ public static boolean isEmpty(String string) { * @param string string * @return {@code true} if string is null or blank * @since 18208 + * @deprecated use {@link #isStripEmpty(String)} or {@link String#isBlank()} instead */ + @Deprecated(since = "19080", forRemoval = true) public static boolean isBlank(String string) { - return string == null || strip(string).isEmpty(); + return isStripEmpty(string); } /** @@ -751,7 +731,7 @@ public static boolean isBlank(String string) { */ public static String firstNotEmptyString(String defaultString, String... candidates) { return Arrays.stream(candidates) - .filter(candidate -> !Utils.isStripEmpty(candidate)) + .filter(not(Utils::isStripEmpty)) .findFirst().orElse(defaultString); } @@ -763,7 +743,14 @@ public static String firstNotEmptyString(String defaultString, String... candida * @since 11435 */ public static boolean isStripEmpty(String str) { - return str == null || IntStream.range(0, str.length()).allMatch(i -> isStrippedChar(str.charAt(i), null)); + if (str != null && !str.isBlank()) { + for (int i = 0; i < str.length(); i++) { + if (!isStrippedChar(str.charAt(i), null)) { + return false; + } + } + } + return true; } /** @@ -836,7 +823,7 @@ public static String removeWhiteSpaces(String s) { /** * Runs an external command and returns the standard output. - * + *

          * The program is expected to execute fast, as this call waits 10 seconds at most. * * @param command the command with arguments @@ -892,7 +879,7 @@ public static File getJosmTempDir() { if (tmpDir == null) { return null; } - File josmTmpDir = new File(tmpDir, "JOSM"); + final File josmTmpDir = new File(tmpDir, "JOSM"); if (!josmTmpDir.exists() && !josmTmpDir.mkdirs()) { Logging.warn("Unable to create temp directory " + josmTmpDir); } @@ -978,8 +965,8 @@ public static String getPositionListString(List positionList) { } else if (cnt == 0) { sb.append(',').append(cur); } else { - sb.append('-').append(last); - sb.append(',').append(cur); + sb.append('-').append(last) + .append(',').append(cur); cnt = 0; } last = cur; @@ -1015,10 +1002,9 @@ public static List getMatches(final Matcher m) { * @return null if o is null or the type o is not * a subclass of klass. The casted value otherwise. */ - @SuppressWarnings("unchecked") public static T cast(Object o, Class klass) { if (klass.isInstance(o)) { - return (T) o; + return klass.cast(o); } return null; } @@ -1116,7 +1102,7 @@ public static Collection limit(Collection elements, int maxElements, T /** * Fixes URL with illegal characters in the query (and fragment) part by * percent encoding those characters. - * + *

          * special characters like & and # are not encoded * * @param url the URL that should be fixed @@ -1126,12 +1112,12 @@ public static String fixURLQuery(String url) { if (url == null || url.indexOf('?') == -1) return url; - String query = url.substring(url.indexOf('?') + 1); + final String query = url.substring(url.indexOf('?') + 1); - StringBuilder sb = new StringBuilder(url.substring(0, url.indexOf('?') + 1)); + final StringBuilder sb = new StringBuilder(url.substring(0, url.indexOf('?') + 1)); for (int i = 0; i < query.length(); i++) { - String c = query.substring(i, i + 1); + final String c = query.substring(i, i + 1); if (URL_CHARS.contains(c)) { sb.append(c); } else { @@ -1397,7 +1383,7 @@ static final class JosmForkJoinWorkerThread extends ForkJoinWorkerThread { */ @SuppressWarnings("ThreadPriorityCheck") public static ForkJoinPool newForkJoinPool(String pref, final String nameFormat, final int threadPriority) { - int noThreads = Config.getPref().getInt(pref, Runtime.getRuntime().availableProcessors()); + final int noThreads = Config.getPref().getInt(pref, Runtime.getRuntime().availableProcessors()); return new ForkJoinPool(noThreads, new ForkJoinPool.ForkJoinWorkerThreadFactory() { final AtomicLong count = new AtomicLong(0); @Override @@ -1516,29 +1502,14 @@ public static boolean hasExtension(File file, String... extensions) { * @param stream input stream * @return byte array of data in input stream (empty if stream is null) * @throws IOException if any I/O error occurs + * @deprecated since 19050 -- use {@link InputStream#readAllBytes()} instead */ + @Deprecated(since = "19050", forRemoval = true) public static byte[] readBytesFromStream(InputStream stream) throws IOException { - // TODO: remove this method when switching to Java 11 and use InputStream.readAllBytes if (stream == null) { return new byte[0]; } - try (ByteArrayOutputStream bout = new ByteArrayOutputStream(stream.available())) { - byte[] buffer = new byte[8192]; - boolean finished = false; - do { - int read = stream.read(buffer); - if (read >= 0) { - bout.write(buffer, 0, read); - } else { - finished = true; - } - } while (!finished); - if (bout.size() == 0) - return new byte[0]; - return bout.toByteArray(); - } finally { - stream.close(); - } + return stream.readAllBytes(); } /** @@ -1602,19 +1573,19 @@ private static class DirectionString { * @return a list of GlyphVectors */ public static List getGlyphVectorsBidi(String string, Font font, FontRenderContext frc) { - List gvs = new ArrayList<>(); - Bidi bidi = new Bidi(string, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT); - byte[] levels = new byte[bidi.getRunCount()]; - DirectionString[] dirStrings = new DirectionString[levels.length]; + final List gvs = new ArrayList<>(); + final Bidi bidi = new Bidi(string, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT); + final byte[] levels = new byte[bidi.getRunCount()]; + final DirectionString[] dirStrings = new DirectionString[levels.length]; for (int i = 0; i < levels.length; ++i) { levels[i] = (byte) bidi.getRunLevel(i); - String substr = string.substring(bidi.getRunStart(i), bidi.getRunLimit(i)); - int dir = levels[i] % 2 == 0 ? Bidi.DIRECTION_LEFT_TO_RIGHT : Bidi.DIRECTION_RIGHT_TO_LEFT; + final String substr = string.substring(bidi.getRunStart(i), bidi.getRunLimit(i)); + final int dir = levels[i] % 2 == 0 ? Bidi.DIRECTION_LEFT_TO_RIGHT : Bidi.DIRECTION_RIGHT_TO_LEFT; dirStrings[i] = new DirectionString(dir, substr); } Bidi.reorderVisually(levels, 0, dirStrings, 0, levels.length); for (DirectionString dirString : dirStrings) { - char[] chars = dirString.str.toCharArray(); + final char[] chars = dirString.str.toCharArray(); gvs.add(font.layoutGlyphVector(frc, chars, 0, chars.length, dirString.direction)); } return gvs; @@ -1646,11 +1617,8 @@ public static double clamp(double val, double min, double max) { throw new IllegalArgumentException(MessageFormat.format("Parameter min ({0}) cannot be greater than max ({1})", min, max)); } else if (val < min) { return min; - } else if (val > max) { - return max; - } else { - return val; } + return Math.min(val, max); } /** @@ -1814,15 +1782,10 @@ public static String getJavaLatestVersion() { "java.baseline.version.url", Config.getUrls().getJOSMWebsite() + "/remote/oracle-java-update-baseline.version"))) .connect().fetchContent().split("\n", -1); - if (getJavaVersion() <= 11 && isRunningWebStart()) { // OpenWebStart currently only has Java 11 + // OpenWebStart currently only has Java 21 + if (getJavaVersion() <= 21) { for (String version : versions) { - if (version.startsWith("11")) { - return version; - } - } - } else if (getJavaVersion() <= 17) { - for (String version : versions) { - if (version.startsWith("17")) { // Use current Java LTS + if (version.startsWith("21")) { // Use current Java LTS return version; } } @@ -1862,7 +1825,9 @@ public static boolean isRunningWebStart() { * Determines whether JOSM has been started via Oracle Java Web Start. * @return true if JOSM has been started via Oracle Java Web Start * @since 15740 + * @deprecated JOSM no longer supports Oracle Java Webstart since Oracle Java Webstart doesn't support Java 9+. */ + @Deprecated(since = "19101", forRemoval = true) public static boolean isRunningJavaWebStart() { return isRunningWebStart() && isClassFound("com.sun.javaws.Main"); } @@ -1880,7 +1845,7 @@ public static boolean isRunningOpenWebStart() { /** * Get a function that converts an object to a singleton stream of a certain * class (or null if the object cannot be cast to that class). - * + *

          * Can be useful in relation with streams, but be aware of the performance * implications of creating a stream for each element. * @param type of the objects to convert @@ -1905,10 +1870,9 @@ public static Function> castToStream(Class klass) { * @param consumer action to take when o is and instance of T * @since 12604 */ - @SuppressWarnings("unchecked") public static void instanceOfThen(Object o, Class klass, Consumer consumer) { if (klass.isInstance(o)) { - consumer.accept((T) o); + consumer.accept(klass.cast(o)); } } @@ -1921,10 +1885,9 @@ public static void instanceOfThen(Object o, Class klass, Consumer Optional instanceOfAndCast(Object o, Class klass) { if (klass.isInstance(o)) - return Optional.of((T) o); + return Optional.of(klass.cast(o)); return Optional.empty(); } @@ -1944,7 +1907,7 @@ public static InputStream openStream(URL url) throws IOException { try { return url.openStream(); } catch (FileNotFoundException | InvalidPathException e) { - URL betterUrl = betterJarUrl(url); + final URL betterUrl = betterJarUrl(url); if (betterUrl != null) { try { return betterUrl.openStream(); @@ -1985,11 +1948,11 @@ public static URL betterJarUrl(URL jarUrl, URL defaultUrl) throws IOException { if (urlPath.startsWith("file:/") && urlPath.split("!", -1).length > 2) { // Locate jar file int index = urlPath.lastIndexOf("!/"); - Path jarFile = Paths.get(urlPath.substring("file:/".length(), index)); + final Path jarFile = Paths.get(urlPath.substring("file:/".length(), index)); Path filename = jarFile.getFileName(); FileTime jarTime = Files.readAttributes(jarFile, BasicFileAttributes.class).lastModifiedTime(); // Copy it to temp directory (hopefully free of exclamation mark) if needed (missing or older jar) - Path jarCopy = Paths.get(getSystemProperty("java.io.tmpdir")).resolve(filename); + final Path jarCopy = Paths.get(getSystemProperty("java.io.tmpdir")).resolve(filename); if (!jarCopy.toFile().exists() || Files.readAttributes(jarCopy, BasicFileAttributes.class).lastModifiedTime().compareTo(jarTime) < 0) { Files.copy(jarFile, jarCopy, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES); @@ -2028,7 +1991,7 @@ public static InputStream getResourceAsStream(ClassLoader cl, String path) { Logging.error("Cannot open {0}: {1}", path, e.getMessage()); Logging.trace(e); try { - URL betterUrl = betterJarUrl(cl.getResource(path)); + final URL betterUrl = betterJarUrl(cl.getResource(path)); if (betterUrl != null) { return betterUrl.openStream(); } @@ -2064,4 +2027,54 @@ public static String stripHtml(String rawString) { public static String intern(String string) { return string == null ? null : string.intern(); } + + /** + * Convert a length unit to meters + * @param s arbitrary string representing a length + * @return the length converted to meters + * @throws IllegalArgumentException if input is no valid length + * @since 19089 + */ + public static Double unitToMeter(String s) throws IllegalArgumentException { + s = s.replace(" ", "").replace(",", "."); + Matcher m = PATTERN_LENGTH.matcher(s); + if (m.matches()) { + return Double.parseDouble(m.group(1)) * unitToMeterConversion(m.group(2)); + } else { + m = PATTERN_LENGTH2.matcher(s); + if (m.matches()) { + /* NOTE: we assume -a'b" means -(a'+b") and not (-a')+b" - because of such issues SI units have been invented + and have been adopted by the majority of the world */ + return (Double.parseDouble(m.group(2))*0.3048+Double.parseDouble(m.group(4))*0.0254)*(m.group(1).isEmpty() ? 1.0 : -1.0); + } + } + throw new IllegalArgumentException("Invalid length value: " + s); + } + + /** + * Get the conversion factor for a specified unit to meters + * @param unit The unit to convert to meters + * @return The conversion factor or 1. + * @throws IllegalArgumentException if the unit does not currently have a conversion + */ + private static double unitToMeterConversion(String unit) throws IllegalArgumentException { + if (unit == null) { + return 1; + } + switch (unit) { + case "cm": return 0.01; + case "mm": return 0.001; + case "m": return 1; + case "km": return 1000.0; + case "nmi": return 1852.0; + case "mi": return 1609.344; + case "ft": + case "'": + return 0.3048; + case "in": + case "\"": + return 0.0254; + default: throw new IllegalArgumentException("Invalid length unit: " + unit); + } + } } diff --git a/src/org/openstreetmap/josm/tools/WikiReader.java b/src/org/openstreetmap/josm/tools/WikiReader.java index 3af81511a20..effc009ee74 100644 --- a/src/org/openstreetmap/josm/tools/WikiReader.java +++ b/src/org/openstreetmap/josm/tools/WikiReader.java @@ -151,7 +151,7 @@ protected String readFromTrac(BufferedReader in, URL url) throws IOException { } } if (b.indexOf(" Describe ") >= 0 - || b.indexOf(" does not exist. You can create it here.

          ") >= 0) + || b.indexOf(" does not exist. You can create it here.") >= 0) return ""; if (b.length() == 0) b = full; diff --git a/src/org/openstreetmap/josm/tools/WinRegistry.java b/src/org/openstreetmap/josm/tools/WinRegistry.java index e803c605644..3b2126115eb 100644 --- a/src/org/openstreetmap/josm/tools/WinRegistry.java +++ b/src/org/openstreetmap/josm/tools/WinRegistry.java @@ -28,7 +28,7 @@ public final class WinRegistry { * colors, printers, network connections, and application preferences. * See Predefined Keys */ - public static final int HKEY_CURRENT_USER = 0x80000001; + public static final long HKEY_CURRENT_USER = 0x80000001L; /** * Registry entries subordinate to this key define the physical state of the computer, including data about the bus type, @@ -38,44 +38,62 @@ public final class WinRegistry { * names and the location of the server), and other system information. * See Predefined Keys */ - public static final int HKEY_LOCAL_MACHINE = 0x80000002; + public static final long HKEY_LOCAL_MACHINE = 0x80000002L; private static final long REG_SUCCESS = 0L; private static final int KEY_READ = 0x20019; - private static final Preferences userRoot = Preferences.userRoot(); - private static final Preferences systemRoot = Preferences.systemRoot(); - private static final Class userClass = userRoot.getClass(); - private static final Method regOpenKey; - private static final Method regCloseKey; - private static final Method regQueryValueEx; - private static final Method regEnumValue; - private static final Method regQueryInfoKey; - private static final Method regEnumKeyEx; - - private static boolean java11; + private static final Preferences USER_ROOT = Preferences.userRoot(); + private static final Preferences SYSTEM_ROOT = Preferences.systemRoot(); + private static final Class USER_CLASS = USER_ROOT.getClass(); + private static final String HKEY_EQ = "hkey="; + + /** + * Wrapper for Windows registry API RegOpenKey(long, byte[], int) + * Returns {@code long[]} + */ + private static final Method REG_OPEN_KEY; + /** + * Wrapper for Windows registry API RegCloseKey(long) + * Returns {@code int} + */ + private static final Method REG_CLOSE_KEY; + /** + * Wrapper for Windows registry API RegQueryValueEx(long, byte[]) + * Returns {@code byte[]} + */ + private static final Method REG_QUERY_VALUE_EX; + /** + * Wrapper for Windows registry API RegEnumValue(long, int, int) + * Returns {@code byte[]} + */ + private static final Method REG_ENUM_VALUE; + /** + * Wrapper for RegQueryInfoKey(long) + * Returns {@code long[]} + */ + private static final Method REG_QUERY_INFO_KEY; + /** + * Wrapper for RegEnumKeyEx(long, int, int) + * Returns {@code byte[]} + */ + private static final Method REG_ENUM_KEY_EX; static { - regOpenKey = getDeclaredMethod("WindowsRegOpenKey", int.class, byte[].class, int.class); - regCloseKey = getDeclaredMethod("WindowsRegCloseKey", int.class); - regQueryValueEx = getDeclaredMethod("WindowsRegQueryValueEx", int.class, byte[].class); - regEnumValue = getDeclaredMethod("WindowsRegEnumValue", int.class, int.class, int.class); - regQueryInfoKey = getDeclaredMethod("WindowsRegQueryInfoKey1", int.class); - regEnumKeyEx = getDeclaredMethod("WindowsRegEnumKeyEx", int.class, int.class, int.class); - ReflectionUtils.setObjectsAccessible(regOpenKey, regCloseKey, regQueryValueEx, regEnumValue, regQueryInfoKey, regEnumKeyEx); + REG_OPEN_KEY = getDeclaredMethod("WindowsRegOpenKey", long.class, byte[].class, int.class); + REG_CLOSE_KEY = getDeclaredMethod("WindowsRegCloseKey", long.class); + REG_QUERY_VALUE_EX = getDeclaredMethod("WindowsRegQueryValueEx", long.class, byte[].class); + REG_ENUM_VALUE = getDeclaredMethod("WindowsRegEnumValue", long.class, int.class, int.class); + REG_QUERY_INFO_KEY = getDeclaredMethod("WindowsRegQueryInfoKey1", long.class); + REG_ENUM_KEY_EX = getDeclaredMethod("WindowsRegEnumKeyEx", long.class, int.class, int.class); + ReflectionUtils.setObjectsAccessible(REG_OPEN_KEY, REG_CLOSE_KEY, REG_QUERY_VALUE_EX, REG_ENUM_VALUE, + REG_QUERY_INFO_KEY, REG_ENUM_KEY_EX); } private static Method getDeclaredMethod(String name, Class... parameterTypes) { try { - return userClass.getDeclaredMethod(name, parameterTypes); + return USER_CLASS.getDeclaredMethod(name, parameterTypes); } catch (NoSuchMethodException e) { - if (parameterTypes.length > 0 && parameterTypes[0] == int.class) { - // JDK-8198899: change of signature in Java 11. Old signature to drop when we switch to Java 11 - Class[] parameterTypesCopy = Utils.copyArray(parameterTypes); - parameterTypesCopy[0] = long.class; - java11 = true; - return getDeclaredMethod(name, parameterTypesCopy); - } Logging.log(Logging.LEVEL_ERROR, "Unable to find WindowsReg method", e); return null; } catch (RuntimeException e) { @@ -84,10 +102,6 @@ private static Method getDeclaredMethod(String name, Class... parameterTypes) } } - private static Number hkey(int key) { - return java11 ? ((Number) Long.valueOf(key)) : ((Number) Integer.valueOf(key)); - } - private WinRegistry() { // Hide default constructor for utilities classes } @@ -101,15 +115,16 @@ private WinRegistry() { * @throws IllegalArgumentException if hkey not HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible * @throws InvocationTargetException if the underlying method throws an exception + * @since 19100 (method definition) */ - public static String readString(int hkey, String key, String valueName) + public static String readString(long hkey, String key, String valueName) throws IllegalAccessException, InvocationTargetException { if (hkey == HKEY_LOCAL_MACHINE) { - return readString(systemRoot, hkey, key, valueName); + return readString(SYSTEM_ROOT, hkey, key, valueName); } else if (hkey == HKEY_CURRENT_USER) { - return readString(userRoot, hkey, key, valueName); + return readString(USER_ROOT, hkey, key, valueName); } else { - throw new IllegalArgumentException("hkey=" + hkey); + throw new IllegalArgumentException(HKEY_EQ + hkey); } } @@ -122,14 +137,14 @@ public static String readString(int hkey, String key, String valueName) * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible * @throws InvocationTargetException if the underlying method throws an exception */ - public static Map readStringValues(int hkey, String key) + public static Map readStringValues(long hkey, String key) throws IllegalAccessException, InvocationTargetException { if (hkey == HKEY_LOCAL_MACHINE) { - return readStringValues(systemRoot, hkey, key); + return readStringValues(SYSTEM_ROOT, hkey, key); } else if (hkey == HKEY_CURRENT_USER) { - return readStringValues(userRoot, hkey, key); + return readStringValues(USER_ROOT, hkey, key); } else { - throw new IllegalArgumentException("hkey=" + hkey); + throw new IllegalArgumentException(HKEY_EQ + hkey); } } @@ -142,89 +157,82 @@ public static Map readStringValues(int hkey, String key) * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible * @throws InvocationTargetException if the underlying method throws an exception */ - public static List readStringSubKeys(int hkey, String key) + public static List readStringSubKeys(long hkey, String key) throws IllegalAccessException, InvocationTargetException { if (hkey == HKEY_LOCAL_MACHINE) { - return readStringSubKeys(systemRoot, hkey, key); + return readStringSubKeys(SYSTEM_ROOT, hkey, key); } else if (hkey == HKEY_CURRENT_USER) { - return readStringSubKeys(userRoot, hkey, key); + return readStringSubKeys(USER_ROOT, hkey, key); } else { - throw new IllegalArgumentException("hkey=" + hkey); + throw new IllegalArgumentException(HKEY_EQ + hkey); } } // ===================== - private static Number getNumber(Object array, int index) { - if (array instanceof int[]) { - return ((int[]) array)[index]; - } else if (array instanceof long[]) { + private static long getNumber(Object array, int index) { + if (array instanceof long[]) { return ((long[]) array)[index]; } throw new IllegalArgumentException(); } - private static String readString(Preferences root, int hkey, String key, String value) + private static String readString(Preferences root, long hkey, String key, String value) throws IllegalAccessException, InvocationTargetException { - if (regOpenKey == null || regQueryValueEx == null || regCloseKey == null) { + if (REG_OPEN_KEY == null || REG_QUERY_VALUE_EX == null || REG_CLOSE_KEY == null) { return null; } - // Need to capture both int[] (Java 8-10) and long[] (Java 11+) - Object handles = regOpenKey.invoke(root, hkey(hkey), toCstr(key), Integer.valueOf(KEY_READ)); - if (getNumber(handles, 1).longValue() != REG_SUCCESS) { + Object handles = REG_OPEN_KEY.invoke(root, hkey, toCstr(key), KEY_READ); + if (getNumber(handles, 1) != REG_SUCCESS) { return null; } - byte[] valb = (byte[]) regQueryValueEx.invoke(root, getNumber(handles, 0), toCstr(value)); - regCloseKey.invoke(root, getNumber(handles, 0)); + byte[] valb = (byte[]) REG_QUERY_VALUE_EX.invoke(root, getNumber(handles, 0), toCstr(value)); + REG_CLOSE_KEY.invoke(root, getNumber(handles, 0)); return (valb != null ? new String(valb, StandardCharsets.UTF_8).trim() : null); } - private static Map readStringValues(Preferences root, int hkey, String key) + private static Map readStringValues(Preferences root, long hkey, String key) throws IllegalAccessException, InvocationTargetException { - if (regOpenKey == null || regQueryInfoKey == null || regEnumValue == null || regCloseKey == null) { + if (REG_OPEN_KEY == null || REG_QUERY_INFO_KEY == null || REG_ENUM_VALUE == null || REG_CLOSE_KEY == null) { return Collections.emptyMap(); } HashMap results = new HashMap<>(); - // Need to capture both int[] (Java 8-10) and long[] (Java 11+) - Object handles = regOpenKey.invoke(root, hkey(hkey), toCstr(key), Integer.valueOf(KEY_READ)); - if (getNumber(handles, 1).longValue() != REG_SUCCESS) { + Object handles = REG_OPEN_KEY.invoke(root, hkey, toCstr(key), KEY_READ); + if (getNumber(handles, 1) != REG_SUCCESS) { return Collections.emptyMap(); } - // Need to capture both int[] (Java 8-10) and long[] (Java 11+) - Object info = regQueryInfoKey.invoke(root, getNumber(handles, 0)); + Object info = REG_QUERY_INFO_KEY.invoke(root, getNumber(handles, 0)); - int count = getNumber(info, 0).intValue(); - int maxlen = getNumber(info, 3).intValue(); + int count = Math.toIntExact(getNumber(info, 0)); + int maxlen = Math.toIntExact(getNumber(info, 3)); for (int index = 0; index < count; index++) { - byte[] name = (byte[]) regEnumValue.invoke(root, getNumber(handles, 0), Integer.valueOf(index), Integer.valueOf(maxlen + 1)); + byte[] name = (byte[]) REG_ENUM_VALUE.invoke(root, getNumber(handles, 0), index, maxlen + 1); String value = readString(hkey, key, new String(name, StandardCharsets.UTF_8)); results.put(new String(name, StandardCharsets.UTF_8).trim(), value); } - regCloseKey.invoke(root, getNumber(handles, 0)); + REG_CLOSE_KEY.invoke(root, getNumber(handles, 0)); return Collections.unmodifiableMap(results); } - private static List readStringSubKeys(Preferences root, int hkey, String key) + private static List readStringSubKeys(Preferences root, long hkey, String key) throws IllegalAccessException, InvocationTargetException { - if (regOpenKey == null || regQueryInfoKey == null || regEnumKeyEx == null || regCloseKey == null) { + if (REG_OPEN_KEY == null || REG_QUERY_INFO_KEY == null || REG_ENUM_KEY_EX == null || REG_CLOSE_KEY == null) { return Collections.emptyList(); } List results = new ArrayList<>(); - // Need to capture both int[] (Java 8-10) and long[] (Java 11+) - Object handles = regOpenKey.invoke(root, hkey(hkey), toCstr(key), Integer.valueOf(KEY_READ)); - if (getNumber(handles, 1).longValue() != REG_SUCCESS) { + Object handles = REG_OPEN_KEY.invoke(root, hkey, toCstr(key), KEY_READ); + if (getNumber(handles, 1) != REG_SUCCESS) { return Collections.emptyList(); } - // Need to capture both int[] (Java 8-10) and long[] (Java 11+) - Object info = regQueryInfoKey.invoke(root, getNumber(handles, 0)); + Object info = REG_QUERY_INFO_KEY.invoke(root, getNumber(handles, 0)); - int count = getNumber(info, 0).intValue(); - int maxlen = getNumber(info, 3).intValue(); + int count = Math.toIntExact(getNumber(info, 0)); + int maxlen = Math.toIntExact(getNumber(info, 3)); for (int index = 0; index < count; index++) { - byte[] name = (byte[]) regEnumKeyEx.invoke(root, getNumber(handles, 0), Integer.valueOf(index), Integer.valueOf(maxlen + 1)); + byte[] name = (byte[]) REG_ENUM_KEY_EX.invoke(root, getNumber(handles, 0), index, maxlen + 1); results.add(new String(name, StandardCharsets.UTF_8).trim()); } - regCloseKey.invoke(root, getNumber(handles, 0)); + REG_CLOSE_KEY.invoke(root, getNumber(handles, 0)); return Collections.unmodifiableList(results); } diff --git a/src/org/openstreetmap/josm/tools/XmlObjectParser.java b/src/org/openstreetmap/josm/tools/XmlObjectParser.java index 36eeb0cd7f8..6cc1f9c155e 100644 --- a/src/org/openstreetmap/josm/tools/XmlObjectParser.java +++ b/src/org/openstreetmap/josm/tools/XmlObjectParser.java @@ -64,7 +64,7 @@ public void startElement(String uri, String localName, String qName, Attributes } } - private class Parser extends DefaultHandler { + private final class Parser extends DefaultHandler { private final Stack current = new Stack<>(); private StringBuilder characters = new StringBuilder(64); private Locator locator; @@ -77,7 +77,7 @@ public void setDocumentLocator(Locator locator) { this.locator = locator; } - protected void throwException(Exception e) throws XmlParsingException { + void throwException(Exception e) throws XmlParsingException { throw new XmlParsingException(e).rememberLocation(locator); } diff --git a/src/org/openstreetmap/josm/tools/bugreport/BugReport.java b/src/org/openstreetmap/josm/tools/bugreport/BugReport.java index b85dcc99a2e..36b376e3b71 100644 --- a/src/org/openstreetmap/josm/tools/bugreport/BugReport.java +++ b/src/org/openstreetmap/josm/tools/bugreport/BugReport.java @@ -32,7 +32,7 @@ * try { * ... your code ... * } catch (RuntimeException t) { - * throw BugReport.intercept(t).put("id", id).put("tag", () -> x.getTag()); + * throw BugReport.intercept(t).put("id", id).put("tag", () → x.getTag()); * } * * diff --git a/src/org/openstreetmap/josm/tools/bugreport/BugReportQueue.java b/src/org/openstreetmap/josm/tools/bugreport/BugReportQueue.java index b2490e3eb29..1dfb20c3fd6 100644 --- a/src/org/openstreetmap/josm/tools/bugreport/BugReportQueue.java +++ b/src/org/openstreetmap/josm/tools/bugreport/BugReportQueue.java @@ -88,7 +88,7 @@ public synchronized void submit(ReportedException report) { } } - private class BugReportDisplayRunnable implements Runnable { + private final class BugReportDisplayRunnable implements Runnable { private volatile boolean running = true; diff --git a/src/org/openstreetmap/josm/tools/bugreport/ReportedException.java b/src/org/openstreetmap/josm/tools/bugreport/ReportedException.java index 59f16e4685d..76d5f6f4136 100644 --- a/src/org/openstreetmap/josm/tools/bugreport/ReportedException.java +++ b/src/org/openstreetmap/josm/tools/bugreport/ReportedException.java @@ -31,7 +31,6 @@ * @see BugReport * @since 10285 */ -@SuppressWarnings("OverrideThrowableToString") public class ReportedException extends RuntimeException { /** * How many entries of a collection to include in the bug report. @@ -167,11 +166,7 @@ private static String niceThreadName(Thread thread) { * @return true if they are considered the same. */ public boolean isSame(ReportedException e) { - if (!getMessage().equals(e.getMessage())) { - return false; - } - - return hasSameStackTrace(new CauseTraceIterator(), e.getCause()); + return getMessage().equals(e.getMessage()) && hasSameStackTrace(new CauseTraceIterator(), e.getCause()); } private static boolean hasSameStackTrace(CauseTraceIterator causeTraceIterator, Throwable e2) { @@ -191,11 +186,8 @@ private static boolean hasSameStackTrace(CauseTraceIterator causeTraceIterator, Throwable c2 = e2.getCause(); if ((c1 == null) != (c2 == null)) { return false; - } else if (c1 != null) { - return hasSameStackTrace(causeTraceIterator, c2); - } else { - return true; } + return c1 == null || hasSameStackTrace(causeTraceIterator, c2); } /** @@ -230,7 +222,7 @@ public ReportedException put(String key, Supplier valueSupplier) { } else if (value instanceof Collection) { string = makeCollectionNice((Collection) value); } else if (value.getClass().isArray()) { - string = makeCollectionNice(Arrays.asList(value)); + string = makeCollectionNice(Collections.singleton(value)); } else { string = value.toString(); } @@ -282,7 +274,7 @@ public boolean mayHaveConcurrentSource() { * @since 10819 */ public boolean isOutOfMemory() { - return StreamUtils.toStream(CauseTraceIterator::new).anyMatch(t -> t instanceof OutOfMemoryError); + return StreamUtils.toStream(CauseTraceIterator::new).anyMatch(OutOfMemoryError.class::isInstance); } /** @@ -292,7 +284,7 @@ public boolean isOutOfMemory() { */ private final class CauseTraceIterator implements Iterator { private Throwable current = getCause(); - private final Set dejaVu = Collections.newSetFromMap(new IdentityHashMap()); + private final Set dejaVu = Collections.newSetFromMap(new IdentityHashMap<>()); @Override public boolean hasNext() { diff --git a/src/org/openstreetmap/josm/tools/template_engine/Tokenizer.java b/src/org/openstreetmap/josm/tools/template_engine/Tokenizer.java index 867dd25e113..b0eccf8b292 100644 --- a/src/org/openstreetmap/josm/tools/template_engine/Tokenizer.java +++ b/src/org/openstreetmap/josm/tools/template_engine/Tokenizer.java @@ -7,21 +7,35 @@ /** * This class converts a template string (stream of characters) into a stream of tokens. - * + *

          * The result of the tokenization (also called lexical analysis) serves as input for the * parser {@link TemplateParser}. */ public class Tokenizer { + /** + * A token for the parser + */ public static class Token { private final TokenType type; private final int position; private final String text; + /** + * A token + * @param type The token type + * @param position The position of the token + */ public Token(TokenType type, int position) { this(type, position, null); } + /** + * A token + * @param type The token type + * @param position The position of the token + * @param text The text for the token + */ public Token(TokenType type, int position, String text) { this.type = type; this.position = position; @@ -46,6 +60,9 @@ public String toString() { } } + /** + * The token type + */ public enum TokenType { CONDITION_START, VARIABLE_START, CONTEXT_SWITCH_START, END, PIPE, APOSTROPHE, TEXT, EOF } private final Set specialCharacters = new HashSet<>(Arrays.asList('$', '?', '{', '}', '|', '\'', '!')); @@ -74,6 +91,11 @@ private void getChar() { } } + /** + * Get the next token + * @return The next token + * @throws ParseError if there is an error getting the next token + */ public Token nextToken() throws ParseError { if (currentToken != null) { Token result = currentToken; @@ -127,6 +149,11 @@ public Token nextToken() throws ParseError { } } + /** + * Look at the next token + * @return The next token + * @throws ParseError if there is an error getting the next token + */ public Token lookAhead() throws ParseError { if (currentToken == null) { currentToken = nextToken(); @@ -134,6 +161,11 @@ public Token lookAhead() throws ParseError { return currentToken; } + /** + * Skip until we hit a character + * @param lastChar The last character to skip + * @return A token with the skipped characters + */ public Token skip(char lastChar) { currentToken = null; int position = index; diff --git a/src/org/openstreetmap/josm/tools/template_engine/Variable.java b/src/org/openstreetmap/josm/tools/template_engine/Variable.java index 78abd24a76e..8d152b00901 100644 --- a/src/org/openstreetmap/josm/tools/template_engine/Variable.java +++ b/src/org/openstreetmap/josm/tools/template_engine/Variable.java @@ -61,10 +61,8 @@ public void appendText(StringBuilder result, TemplateEngineDataProvider dataProv @Override public boolean isValid(TemplateEngineDataProvider dataProvider) { - if (special && SPECIAL_VALUE_EVERYTHING.equals(variableName)) - return true; - else - return dataProvider.getTemplateValue(variableName, special) != null; + return (special && SPECIAL_VALUE_EVERYTHING.equals(variableName)) + || dataProvider.getTemplateValue(variableName, special) != null; } @Override @@ -93,13 +91,6 @@ public boolean equals(Object obj) { if (obj == null || getClass() != obj.getClass()) return false; Variable other = (Variable) obj; - if (special != other.special) - return false; - if (variableName == null) { - if (other.variableName != null) - return false; - } else if (!variableName.equals(other.variableName)) - return false; - return true; + return this.special == other.special && Objects.equals(this.variableName, other.variableName); } } diff --git a/test/data/ImageProviderTest-java21/housenumber_small-AUTO-08x08.png b/test/data/ImageProviderTest-java21/housenumber_small-AUTO-08x08.png index 411c1d6b1c3..5109cb74325 100644 Binary files a/test/data/ImageProviderTest-java21/housenumber_small-AUTO-08x08.png and b/test/data/ImageProviderTest-java21/housenumber_small-AUTO-08x08.png differ diff --git a/test/data/ImageProviderTest-java21/housenumber_small-AUTO-16x16.png b/test/data/ImageProviderTest-java21/housenumber_small-AUTO-16x16.png index 1e46b67f583..a37514cfe3c 100644 Binary files a/test/data/ImageProviderTest-java21/housenumber_small-AUTO-16x16.png and b/test/data/ImageProviderTest-java21/housenumber_small-AUTO-16x16.png differ diff --git a/test/data/ImageProviderTest-java21/housenumber_small-AUTO-24x24.png b/test/data/ImageProviderTest-java21/housenumber_small-AUTO-24x24.png index a06f3d7c381..7dce9fb4201 100644 Binary files a/test/data/ImageProviderTest-java21/housenumber_small-AUTO-24x24.png and b/test/data/ImageProviderTest-java21/housenumber_small-AUTO-24x24.png differ diff --git a/test/data/ImageProviderTest-java21/housenumber_small-AUTO-36x27.png b/test/data/ImageProviderTest-java21/housenumber_small-AUTO-36x27.png index ea98fce9f15..b71f68927e3 100644 Binary files a/test/data/ImageProviderTest-java21/housenumber_small-AUTO-36x27.png and b/test/data/ImageProviderTest-java21/housenumber_small-AUTO-36x27.png differ diff --git a/test/data/ImageProviderTest-java21/housenumber_small-AUTO-default.png b/test/data/ImageProviderTest-java21/housenumber_small-AUTO-default.png index 0fca0130951..7ecd1919d18 100644 Binary files a/test/data/ImageProviderTest-java21/housenumber_small-AUTO-default.png and b/test/data/ImageProviderTest-java21/housenumber_small-AUTO-default.png differ diff --git a/test/data/ImageProviderTest-java21/housenumber_small-AUTO-null.png b/test/data/ImageProviderTest-java21/housenumber_small-AUTO-null.png index 0fca0130951..7ecd1919d18 100644 Binary files a/test/data/ImageProviderTest-java21/housenumber_small-AUTO-null.png and b/test/data/ImageProviderTest-java21/housenumber_small-AUTO-null.png differ diff --git a/test/data/ImageProviderTest-java21/housenumber_small-BOUNDED-08x08.png b/test/data/ImageProviderTest-java21/housenumber_small-BOUNDED-08x08.png index 411c1d6b1c3..5109cb74325 100644 Binary files a/test/data/ImageProviderTest-java21/housenumber_small-BOUNDED-08x08.png and b/test/data/ImageProviderTest-java21/housenumber_small-BOUNDED-08x08.png differ diff --git a/test/data/ImageProviderTest-java21/housenumber_small-BOUNDED-16x16.png b/test/data/ImageProviderTest-java21/housenumber_small-BOUNDED-16x16.png index 0fca0130951..7ecd1919d18 100644 Binary files a/test/data/ImageProviderTest-java21/housenumber_small-BOUNDED-16x16.png and b/test/data/ImageProviderTest-java21/housenumber_small-BOUNDED-16x16.png differ diff --git a/test/data/ImageProviderTest-java21/housenumber_small-BOUNDED-24x24.png b/test/data/ImageProviderTest-java21/housenumber_small-BOUNDED-24x24.png index 0fca0130951..7ecd1919d18 100644 Binary files a/test/data/ImageProviderTest-java21/housenumber_small-BOUNDED-24x24.png and b/test/data/ImageProviderTest-java21/housenumber_small-BOUNDED-24x24.png differ diff --git a/test/data/ImageProviderTest-java21/housenumber_small-PADDED-08x08.png b/test/data/ImageProviderTest-java21/housenumber_small-PADDED-08x08.png index 6b000e0d4b7..72386398d57 100644 Binary files a/test/data/ImageProviderTest-java21/housenumber_small-PADDED-08x08.png and b/test/data/ImageProviderTest-java21/housenumber_small-PADDED-08x08.png differ diff --git a/test/data/ImageProviderTest-java21/housenumber_small-PADDED-16x16.png b/test/data/ImageProviderTest-java21/housenumber_small-PADDED-16x16.png index d2e1eaf4650..247d5d5fb39 100644 Binary files a/test/data/ImageProviderTest-java21/housenumber_small-PADDED-16x16.png and b/test/data/ImageProviderTest-java21/housenumber_small-PADDED-16x16.png differ diff --git a/test/data/ImageProviderTest-java21/housenumber_small-PADDED-24x24.png b/test/data/ImageProviderTest-java21/housenumber_small-PADDED-24x24.png index c3dabb042ed..50fecf02c59 100644 Binary files a/test/data/ImageProviderTest-java21/housenumber_small-PADDED-24x24.png and b/test/data/ImageProviderTest-java21/housenumber_small-PADDED-24x24.png differ diff --git a/test/data/ImageProviderTest-java8/cursor-crosshair-10.png b/test/data/ImageProviderTest-java8/cursor-crosshair-10.png deleted file mode 100644 index 70d35226940..00000000000 Binary files a/test/data/ImageProviderTest-java8/cursor-crosshair-10.png and /dev/null differ diff --git a/test/data/ImageProviderTest-java8/cursor-crosshair-15.png b/test/data/ImageProviderTest-java8/cursor-crosshair-15.png deleted file mode 100644 index 70d35226940..00000000000 Binary files a/test/data/ImageProviderTest-java8/cursor-crosshair-15.png and /dev/null differ diff --git a/test/data/ImageProviderTest-java8/cursor-crosshair-30.png b/test/data/ImageProviderTest-java8/cursor-crosshair-30.png deleted file mode 100644 index 70d35226940..00000000000 Binary files a/test/data/ImageProviderTest-java8/cursor-crosshair-30.png and /dev/null differ diff --git a/test/data/ImageProviderTest-java8/cursor-normal-selection-10.png b/test/data/ImageProviderTest-java8/cursor-normal-selection-10.png deleted file mode 100644 index 0258c66ceef..00000000000 Binary files a/test/data/ImageProviderTest-java8/cursor-normal-selection-10.png and /dev/null differ diff --git a/test/data/ImageProviderTest-java8/cursor-normal-selection-15.png b/test/data/ImageProviderTest-java8/cursor-normal-selection-15.png deleted file mode 100644 index fbfd5fb1f8d..00000000000 Binary files a/test/data/ImageProviderTest-java8/cursor-normal-selection-15.png and /dev/null differ diff --git a/test/data/ImageProviderTest-java8/cursor-normal-selection-30.png b/test/data/ImageProviderTest-java8/cursor-normal-selection-30.png deleted file mode 100644 index b57180e49e6..00000000000 Binary files a/test/data/ImageProviderTest-java8/cursor-normal-selection-30.png and /dev/null differ diff --git a/test/data/ImageProviderTest-java8/housenumber_small-AUTO-08x08.png b/test/data/ImageProviderTest-java8/housenumber_small-AUTO-08x08.png deleted file mode 100644 index a232f79ca46..00000000000 Binary files a/test/data/ImageProviderTest-java8/housenumber_small-AUTO-08x08.png and /dev/null differ diff --git a/test/data/ImageProviderTest-java8/housenumber_small-AUTO-16x16.png b/test/data/ImageProviderTest-java8/housenumber_small-AUTO-16x16.png deleted file mode 100644 index 2f03d996a13..00000000000 Binary files a/test/data/ImageProviderTest-java8/housenumber_small-AUTO-16x16.png and /dev/null differ diff --git a/test/data/ImageProviderTest-java8/housenumber_small-AUTO-24x24.png b/test/data/ImageProviderTest-java8/housenumber_small-AUTO-24x24.png deleted file mode 100644 index 7510034099c..00000000000 Binary files a/test/data/ImageProviderTest-java8/housenumber_small-AUTO-24x24.png and /dev/null differ diff --git a/test/data/ImageProviderTest-java8/housenumber_small-AUTO-36x27.png b/test/data/ImageProviderTest-java8/housenumber_small-AUTO-36x27.png deleted file mode 100644 index 9e08351158e..00000000000 Binary files a/test/data/ImageProviderTest-java8/housenumber_small-AUTO-36x27.png and /dev/null differ diff --git a/test/data/ImageProviderTest-java8/housenumber_small-AUTO-default.png b/test/data/ImageProviderTest-java8/housenumber_small-AUTO-default.png deleted file mode 100644 index 8a64dcefc13..00000000000 Binary files a/test/data/ImageProviderTest-java8/housenumber_small-AUTO-default.png and /dev/null differ diff --git a/test/data/ImageProviderTest-java8/housenumber_small-AUTO-null.png b/test/data/ImageProviderTest-java8/housenumber_small-AUTO-null.png deleted file mode 100644 index 8a64dcefc13..00000000000 Binary files a/test/data/ImageProviderTest-java8/housenumber_small-AUTO-null.png and /dev/null differ diff --git a/test/data/ImageProviderTest-java8/housenumber_small-BOUNDED-08x08.png b/test/data/ImageProviderTest-java8/housenumber_small-BOUNDED-08x08.png deleted file mode 100644 index a232f79ca46..00000000000 Binary files a/test/data/ImageProviderTest-java8/housenumber_small-BOUNDED-08x08.png and /dev/null differ diff --git a/test/data/ImageProviderTest-java8/housenumber_small-BOUNDED-16x16.png b/test/data/ImageProviderTest-java8/housenumber_small-BOUNDED-16x16.png deleted file mode 100644 index 8a64dcefc13..00000000000 Binary files a/test/data/ImageProviderTest-java8/housenumber_small-BOUNDED-16x16.png and /dev/null differ diff --git a/test/data/ImageProviderTest-java8/housenumber_small-BOUNDED-24x24.png b/test/data/ImageProviderTest-java8/housenumber_small-BOUNDED-24x24.png deleted file mode 100644 index 8a64dcefc13..00000000000 Binary files a/test/data/ImageProviderTest-java8/housenumber_small-BOUNDED-24x24.png and /dev/null differ diff --git a/test/data/ImageProviderTest-java8/housenumber_small-PADDED-08x08.png b/test/data/ImageProviderTest-java8/housenumber_small-PADDED-08x08.png deleted file mode 100644 index 56255d0e45b..00000000000 Binary files a/test/data/ImageProviderTest-java8/housenumber_small-PADDED-08x08.png and /dev/null differ diff --git a/test/data/ImageProviderTest-java8/housenumber_small-PADDED-16x16.png b/test/data/ImageProviderTest-java8/housenumber_small-PADDED-16x16.png deleted file mode 100644 index 73201a8dd6c..00000000000 Binary files a/test/data/ImageProviderTest-java8/housenumber_small-PADDED-16x16.png and /dev/null differ diff --git a/test/data/ImageProviderTest-java8/housenumber_small-PADDED-24x24.png b/test/data/ImageProviderTest-java8/housenumber_small-PADDED-24x24.png deleted file mode 100644 index ce84113b962..00000000000 Binary files a/test/data/ImageProviderTest-java8/housenumber_small-PADDED-24x24.png and /dev/null differ diff --git a/test/data/regress/21881/CycleDetector_test_wikipedia.osm b/test/data/regress/21881/CycleDetector_test_wikipedia.osm new file mode 100644 index 00000000000..1759548de4b --- /dev/null +++ b/test/data/regress/21881/CycleDetector_test_wikipedia.osm @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/data/regress/21881/not_consecutive.osm b/test/data/regress/21881/not_consecutive.osm new file mode 100644 index 00000000000..6cfc0ed967d --- /dev/null +++ b/test/data/regress/21881/not_consecutive.osm @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/data/regress/22817/data.osm b/test/data/regress/22817/data.osm new file mode 100644 index 00000000000..70e778b2857 --- /dev/null +++ b/test/data/regress/22817/data.osm @@ -0,0 +1,4 @@ + + + + diff --git a/test/data/regress/23599/visible.osm.pbf b/test/data/regress/23599/visible.osm.pbf new file mode 100644 index 00000000000..20f75f4d3c5 Binary files /dev/null and b/test/data/regress/23599/visible.osm.pbf differ diff --git a/test/data/regress/23599/w1194668585.drop-author.osm.pbf b/test/data/regress/23599/w1194668585.drop-author.osm.pbf new file mode 100644 index 00000000000..adfdf458a80 Binary files /dev/null and b/test/data/regress/23599/w1194668585.drop-author.osm.pbf differ diff --git a/test/data/regress/23599/w1194668585.drop-version.osm.pbf b/test/data/regress/23599/w1194668585.drop-version.osm.pbf new file mode 100644 index 00000000000..782b9aa2699 Binary files /dev/null and b/test/data/regress/23599/w1194668585.drop-version.osm.pbf differ diff --git a/test/data/regress/23599/w1194668585.full.osm.pbf b/test/data/regress/23599/w1194668585.full.osm.pbf new file mode 100644 index 00000000000..9cc5cca63e7 Binary files /dev/null and b/test/data/regress/23599/w1194668585.full.osm.pbf differ diff --git a/test/data/regress/23599/w1194668585.orig.osm b/test/data/regress/23599/w1194668585.orig.osm new file mode 100644 index 00000000000..d93875eba1f --- /dev/null +++ b/test/data/regress/23599/w1194668585.orig.osm @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/test/data/regress/23641/data.osm b/test/data/regress/23641/data.osm new file mode 100644 index 00000000000..e8c84501507 --- /dev/null +++ b/test/data/regress/23641/data.osm @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/data/renderer/area-fill-color/reference-java8.png b/test/data/renderer/area-fill-color/reference-java8.png deleted file mode 100644 index fb395e3f734..00000000000 Binary files a/test/data/renderer/area-fill-color/reference-java8.png and /dev/null differ diff --git a/test/data/renderer/area-fill-image/reference-java8.png b/test/data/renderer/area-fill-image/reference-java8.png deleted file mode 100644 index a6d5fc2bdc3..00000000000 Binary files a/test/data/renderer/area-fill-image/reference-java8.png and /dev/null differ diff --git a/test/data/renderer/area-icon/reference-java8.png b/test/data/renderer/area-icon/reference-java8.png deleted file mode 100644 index cb6f6d02e88..00000000000 Binary files a/test/data/renderer/area-icon/reference-java8.png and /dev/null differ diff --git a/test/data/renderer/area-text/reference-java8.png b/test/data/renderer/area-text/reference-java8.png deleted file mode 100644 index d9998930ccb..00000000000 Binary files a/test/data/renderer/area-text/reference-java8.png and /dev/null differ diff --git a/test/data/renderer/eval/reference-java8.png b/test/data/renderer/eval/reference-java8.png deleted file mode 100644 index b547d259c8e..00000000000 Binary files a/test/data/renderer/eval/reference-java8.png and /dev/null differ diff --git a/test/data/renderer/node-shapes-combined/reference-java21.png b/test/data/renderer/node-shapes-combined/reference-java21.png new file mode 100644 index 00000000000..e3f31a25870 Binary files /dev/null and b/test/data/renderer/node-shapes-combined/reference-java21.png differ diff --git a/test/data/renderer/node-shapes-combined/reference-java8.png b/test/data/renderer/node-shapes-combined/reference-java8.png deleted file mode 100644 index 86ea9e590af..00000000000 Binary files a/test/data/renderer/node-shapes-combined/reference-java8.png and /dev/null differ diff --git a/test/data/renderer/node-shapes-default/reference-java8.png b/test/data/renderer/node-shapes-default/reference-java8.png deleted file mode 100644 index bd957124c5c..00000000000 Binary files a/test/data/renderer/node-shapes-default/reference-java8.png and /dev/null differ diff --git a/test/data/renderer/node-shapes/reference-java8.png b/test/data/renderer/node-shapes/reference-java8.png deleted file mode 100644 index 36c6869d1dd..00000000000 Binary files a/test/data/renderer/node-shapes/reference-java8.png and /dev/null differ diff --git a/test/data/renderer/node-shapes2/reference-java8.png b/test/data/renderer/node-shapes2/reference-java8.png deleted file mode 100644 index 197b8ef1e20..00000000000 Binary files a/test/data/renderer/node-shapes2/reference-java8.png and /dev/null differ diff --git a/test/data/renderer/node-text/reference-java8.png b/test/data/renderer/node-text/reference-java8.png deleted file mode 100644 index ab6826eb56d..00000000000 Binary files a/test/data/renderer/node-text/reference-java8.png and /dev/null differ diff --git a/test/data/renderer/node-text2/reference-java8.png b/test/data/renderer/node-text2/reference-java8.png deleted file mode 100644 index dc267f1ec17..00000000000 Binary files a/test/data/renderer/node-text2/reference-java8.png and /dev/null differ diff --git a/test/data/renderer/order/reference-java8.png b/test/data/renderer/order/reference-java8.png deleted file mode 100644 index 03566823a3d..00000000000 Binary files a/test/data/renderer/order/reference-java8.png and /dev/null differ diff --git a/test/data/renderer/relation-linkselector/reference-java8.png b/test/data/renderer/relation-linkselector/reference-java8.png deleted file mode 100644 index 0e3466e3c90..00000000000 Binary files a/test/data/renderer/relation-linkselector/reference-java8.png and /dev/null differ diff --git a/test/data/renderer/relation-parentselector/reference-java8.png b/test/data/renderer/relation-parentselector/reference-java8.png deleted file mode 100644 index b9438956e97..00000000000 Binary files a/test/data/renderer/relation-parentselector/reference-java8.png and /dev/null differ diff --git a/test/data/renderer/way-color/reference-java8.png b/test/data/renderer/way-color/reference-java8.png deleted file mode 100644 index 4f66d862d1c..00000000000 Binary files a/test/data/renderer/way-color/reference-java8.png and /dev/null differ diff --git a/test/data/renderer/way-dashes-clamp/reference-java8.png b/test/data/renderer/way-dashes-clamp/reference-java8.png deleted file mode 100644 index 5394abde960..00000000000 Binary files a/test/data/renderer/way-dashes-clamp/reference-java8.png and /dev/null differ diff --git a/test/data/renderer/way-dashes/reference-java8.png b/test/data/renderer/way-dashes/reference-java8.png deleted file mode 100644 index 5f84ad598d9..00000000000 Binary files a/test/data/renderer/way-dashes/reference-java8.png and /dev/null differ diff --git a/test/data/renderer/way-dashes2/reference-java8.png b/test/data/renderer/way-dashes2/reference-java8.png deleted file mode 100644 index 5e301f90349..00000000000 Binary files a/test/data/renderer/way-dashes2/reference-java8.png and /dev/null differ diff --git a/test/data/renderer/way-repeat-image-clamp/reference-java8.png b/test/data/renderer/way-repeat-image-clamp/reference-java8.png deleted file mode 100644 index 9766541a75e..00000000000 Binary files a/test/data/renderer/way-repeat-image-clamp/reference-java8.png and /dev/null differ diff --git a/test/data/renderer/way-repeat-image/reference-java8.png b/test/data/renderer/way-repeat-image/reference-java8.png deleted file mode 100644 index c2f86f7b945..00000000000 Binary files a/test/data/renderer/way-repeat-image/reference-java8.png and /dev/null differ diff --git a/test/data/renderer/way-text/reference-java8.png b/test/data/renderer/way-text/reference-java8.png deleted file mode 100644 index 35fc5cbdff6..00000000000 Binary files a/test/data/renderer/way-text/reference-java8.png and /dev/null differ diff --git a/test/data/renderer/way-width/reference-java8.png b/test/data/renderer/way-width/reference-java8.png deleted file mode 100644 index f231011da9e..00000000000 Binary files a/test/data/renderer/way-width/reference-java8.png and /dev/null differ diff --git a/test/functional/org/openstreetmap/josm/data/osm/TaginfoTestIT.java b/test/functional/org/openstreetmap/josm/data/osm/TaginfoTestIT.java index 303e62e1d2c..8774881cf8e 100644 --- a/test/functional/org/openstreetmap/josm/data/osm/TaginfoTestIT.java +++ b/test/functional/org/openstreetmap/josm/data/osm/TaginfoTestIT.java @@ -17,6 +17,7 @@ import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException; import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; import org.openstreetmap.josm.testutils.annotations.BasicPreferences; +import org.openstreetmap.josm.testutils.annotations.Territories; import org.openstreetmap.josm.tools.HttpClient; import jakarta.json.Json; @@ -29,6 +30,7 @@ */ @BasicPreferences @Timeout(20) +@Territories class TaginfoTestIT { /** * Checks that popular tags are known (i.e included in internal presets, or deprecated, or explicitely ignored) diff --git a/test/functional/org/openstreetmap/josm/gui/mappaint/MapCSSRendererTest.java b/test/functional/org/openstreetmap/josm/gui/mappaint/MapCSSRendererTest.java index 58177f77853..375ea58b377 100644 --- a/test/functional/org/openstreetmap/josm/gui/mappaint/MapCSSRendererTest.java +++ b/test/functional/org/openstreetmap/josm/gui/mappaint/MapCSSRendererTest.java @@ -20,7 +20,6 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -69,83 +68,83 @@ public class MapCSSRendererTest { */ public static Collection runs() { return Stream.of( - /** Tests for StyledMapRenderer#drawNodeSymbol */ + /* Tests for StyledMapRenderer#drawNodeSymbol */ new TestConfig("node-shapes", AREA_DEFAULT) .setThresholdPixels(0).setThresholdTotalColorDiff(0), - /** Text for nodes */ + /* Text for nodes */ new TestConfig("node-text", AREA_DEFAULT).usesFont("DejaVu Sans") .setThresholdPixels(0).setThresholdTotalColorDiff(0), - /** Tests that StyledMapRenderer#drawWay respects width */ + /* Tests that StyledMapRenderer#drawWay respects width */ new TestConfig("way-width", AREA_DEFAULT) .setThresholdPixels(0).setThresholdTotalColorDiff(0), - /** Tests the way color property, including alpha */ + /* Tests the way color property, including alpha */ new TestConfig("way-color", AREA_DEFAULT) .setThresholdPixels(0).setThresholdTotalColorDiff(0), - /** Tests dashed ways. */ + /* Tests dashed ways. */ new TestConfig("way-dashes", AREA_DEFAULT) .setThresholdPixels(0).setThresholdTotalColorDiff(0), - /** Tests dashed way clamping algorithm */ + /* Tests dashed way clamping algorithm */ new TestConfig("way-dashes-clamp", AREA_DEFAULT) .setThresholdPixels(0).setThresholdTotalColorDiff(0), - /** Tests fill-color property */ + /* Tests fill-color property */ new TestConfig("area-fill-color", AREA_DEFAULT), - /** Tests the fill-image property. */ + /* Tests the fill-image property. */ new TestConfig("area-fill-image", AREA_DEFAULT) .setThresholdPixels(0).setThresholdTotalColorDiff(0), - /** Tests area label drawing/placement */ + /* Tests area label drawing/placement */ new TestConfig("area-text", AREA_DEFAULT) .setThresholdPixels(0).setThresholdTotalColorDiff(0), - /** Tests area icon drawing/placement */ + /* Tests area icon drawing/placement */ new TestConfig("area-icon", AREA_DEFAULT) .setThresholdPixels(0).setThresholdTotalColorDiff(0), - /** Tests if all styles are sorted correctly. Tests {@link StyleRecord#compareTo(StyleRecord)} */ + /* Tests if all styles are sorted correctly. Tests {@link StyleRecord#compareTo(StyleRecord)} */ new TestConfig("order", AREA_DEFAULT) .setThresholdPixels(0).setThresholdTotalColorDiff(0), - /** Tests repeat-image feature for ways */ + /* Tests repeat-image feature for ways */ new TestConfig("way-repeat-image", AREA_DEFAULT) .setThresholdPixels(2100).setThresholdTotalColorDiff(93_000), - /** Tests the clamping for repeat-images and repeat-image-phase */ + /* Tests the clamping for repeat-images and repeat-image-phase */ new TestConfig("way-repeat-image-clamp", AREA_DEFAULT) .setThresholdPixels(0).setThresholdTotalColorDiff(0), - /** Tests text along a way */ + /* Tests text along a way */ new TestConfig("way-text", AREA_DEFAULT) .setThresholdPixels(3400).setThresholdTotalColorDiff(0), - /** Another test for node shapes */ + /* Another test for node shapes */ new TestConfig("node-shapes2").setImageWidth(600) .setThresholdPixels(0).setThresholdTotalColorDiff(0), - /** Tests default values for node shapes */ + /* Tests default values for node shapes */ new TestConfig("node-shapes-default") .setThresholdPixels(0).setThresholdTotalColorDiff(0), - /** Tests node shapes with both fill and stroke combined */ + /* Tests node shapes with both fill and stroke combined */ new TestConfig("node-shapes-combined") .setThresholdPixels(0).setThresholdTotalColorDiff(0), - /** Another test for dashed ways */ + /* Another test for dashed ways */ new TestConfig("way-dashes2") .setThresholdPixels(0).setThresholdTotalColorDiff(0), - /** Tests node text placement */ + /* Tests node text placement */ new TestConfig("node-text2") .setThresholdPixels(1020).setThresholdTotalColorDiff(0), - /** Tests relation link selector */ + /* Tests relation link selector */ new TestConfig("relation-linkselector") .setThresholdPixels(0).setThresholdTotalColorDiff(0), - /** Tests parent selector on relation */ + /* Tests parent selector on relation */ new TestConfig("relation-parentselector") .setThresholdPixels(0).setThresholdTotalColorDiff(0), - /** Tests evaluation of expressions */ + /* Tests evaluation of expressions */ new TestConfig("eval").setImageWidth(600) .setThresholdPixels(6610).setThresholdTotalColorDiff(0) @@ -162,11 +161,6 @@ public static Collection runs() { @ParameterizedTest(name = "{1}") @MethodSource("runs") void testRender(TestConfig testConfig, String ignored) throws Exception { - // This test only runs on OpenJDK. - // It is ignored for other Java versions since they differ slightly in their rendering engine. - String javaHome = System.getProperty("java.home"); - assumeTrue(javaHome != null && javaHome.toLowerCase(Locale.ENGLISH).contains("openjdk"), "Test requires openJDK"); - List fonts = Arrays.asList(GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames()); for (String font : testConfig.fonts) { assumeTrue(fonts.contains(font), "Test requires font: " + font); @@ -227,8 +221,8 @@ public static void assertImageEquals( return; } final BufferedImage reference = ImageIO.read(referenceImageFile); - assertEquals(image.getWidth(), reference.getWidth()); - assertEquals(image.getHeight(), reference.getHeight()); + assertEquals(reference.getWidth(), image.getWidth()); + assertEquals(reference.getHeight(), image.getHeight()); StringBuilder differences = new StringBuilder(); ArrayList differencePoints = new ArrayList<>(); @@ -330,7 +324,7 @@ public TestConfig setImageWidth(int imageWidth) { /** * Set the number of pixels that can differ. - * + *

          * Needed due to somewhat platform dependent font rendering. * @param thresholdPixels the number of pixels that can differ * @return this object, for convenience @@ -358,9 +352,13 @@ public TestConfig usesFont(String string) { } public File getReference() { - // Java 8 renders SVG images differently, thus, use separate reference files - final String javaSuffix = Utils.getJavaVersion() == 8 ? "-java8" : ""; - return new File(getTestDirectory() + "/reference" + javaSuffix + ".png"); + // Sometimes Java changes how things are rendered. When that happens, use separate reference files. It is + // usually "reference" + javaSuffix + ".png". + final File customReferenceFile = new File(getTestDirectory() + "/reference-java" + Utils.getJavaVersion() + ".png"); + if (customReferenceFile.isFile()) { + return customReferenceFile; + } + return new File(getTestDirectory() + "/reference.png"); } private String getTestDirectory() { diff --git a/test/functional/org/openstreetmap/josm/tools/ImageProviderTest.java b/test/functional/org/openstreetmap/josm/tools/ImageProviderTest.java index 4b90b01d6ae..a5b1d7df4a3 100644 --- a/test/functional/org/openstreetmap/josm/tools/ImageProviderTest.java +++ b/test/functional/org/openstreetmap/josm/tools/ImageProviderTest.java @@ -188,15 +188,8 @@ private static void testImage(int width, int height, String reference, ImageIcon } private static File getReferenceFile(String reference) { - // Java 8 and Java 21 render SVG images differently, thus, use separate reference files - final String javaSuffix; - switch (Utils.getJavaVersion()) { - case 8: javaSuffix = "-java8"; - break; - case 21: javaSuffix = "-java21"; - break; - default: javaSuffix = ""; - } + // Java 11-17 and Java 21 render SVG images differently, thus, use separate reference files + final String javaSuffix = Utils.getJavaVersion() == 21 ? "-java21" : ""; return new File(TestUtils.getTestDataRoot() + "/" + ImageProviderTest.class.getSimpleName() + javaSuffix + "/" + reference + ".png"); } diff --git a/test/performance/org/openstreetmap/josm/gui/mappaint/MapRendererPerformanceTest.java b/test/performance/org/openstreetmap/josm/gui/mappaint/MapRendererPerformanceTest.java index fbbb6eadafa..0279c539ba4 100644 --- a/test/performance/org/openstreetmap/josm/gui/mappaint/MapRendererPerformanceTest.java +++ b/test/performance/org/openstreetmap/josm/gui/mappaint/MapRendererPerformanceTest.java @@ -179,7 +179,7 @@ public static void cleanUp() { MapPaintStyleLoader.reloadStyles(defaultStyleIdx); } - private static class PerformanceTester { + private static final class PerformanceTester { public double scale = 0; public LatLon center = LL_CITY; public Bounds bounds; diff --git a/test/pom.xml b/test/pom.xml new file mode 100644 index 00000000000..8bd8ac51cbf --- /dev/null +++ b/test/pom.xml @@ -0,0 +1,113 @@ + + + 4.0.0 + josm-unittest + 1.0-SNAPSHOT + + + org.openstreetmap.josm + josm-parent + 1.0-SNAPSHOT + ../nodist/pom.xml + + + UTF-8 + + + + org.openstreetmap.josm + josm + 1.0-SNAPSHOT + provided + + + org.jmockit + jmockit + compile + + + com.github.spotbugs + spotbugs-annotations + compile + + + com.ginsberg + junit5-system-exit + compile + + + org.wiremock + wiremock + compile + + + io.github.classgraph + classgraph + compile + + + org.junit.platform + junit-platform-launcher + compile + + + org.junit.platform + junit-platform-suite + compile + + + org.junit.vintage + junit-vintage-engine + compile + + + org.junit.jupiter + junit-jupiter-params + compile + + + org.junit.jupiter + junit-jupiter-api + compile + + + org.junit.jupiter + junit-jupiter-engine + compile + + + org.junit.jupiter + junit-jupiter-migrationsupport + compile + + + net.trajano.commons + commons-testing + compile + + + nl.jqno.equalsverifier + equalsverifier + compile + + + org.apache.commons + commons-lang3 + compile + + + org.awaitility + awaitility + compile + + + + + ${project.basedir}/unit + + + ${project.basedir}/data + + + + \ No newline at end of file diff --git a/test/unit/org/openstreetmap/josm/TestUtils.java b/test/unit/org/openstreetmap/josm/TestUtils.java index 1733b4693b9..d481d6afd30 100644 --- a/test/unit/org/openstreetmap/josm/TestUtils.java +++ b/test/unit/org/openstreetmap/josm/TestUtils.java @@ -495,7 +495,7 @@ public static Component getComponentByName(Component root, String name) { public static void assumeWorkingEqualsVerifier() { // See https://github.com/raphw/byte-buddy/blob/master/byte-buddy-dep/src/main/java/net/bytebuddy/ClassFileVersion.java // for currently supported Java versions. - if (Utils.getJavaVersion() >= 19) { + if (Utils.getJavaVersion() >= 22) { // Byte Buddy often supports new class file versions for current EA releases if its experimental flag is set to true System.setProperty("net.bytebuddy.experimental", "true"); } else { @@ -591,8 +591,8 @@ public static void assertFileContentsEqual(final File fileA, final File fileB) { FileInputStream streamB = new FileInputStream(fileB); ) { assertArrayEquals( - Utils.readBytesFromStream(streamA), - Utils.readBytesFromStream(streamB) + streamA.readAllBytes(), + streamB.readAllBytes() ); } } catch (IOException e) { @@ -658,10 +658,10 @@ public static Set> getJosmSubtypes(Class superClass) { /** * Determines if OSM DEV_API credential have been provided. Required for functional tests. - * @return {@code true} if {@code osm.username} and {@code osm.password} have been defined on the command line + * @return {@code true} if {@code osm.oauth2} have been defined on the command line */ public static boolean areCredentialsProvided() { - return Utils.getSystemProperty("osm.username") != null && Utils.getSystemProperty("osm.password") != null; + return Utils.getSystemProperty("osm.oauth2") != null; } /** diff --git a/test/unit/org/openstreetmap/josm/actions/AlignInCircleActionTest.java b/test/unit/org/openstreetmap/josm/actions/AlignInCircleActionTest.java index 8f93992a5c8..ceb8152ad51 100644 --- a/test/unit/org/openstreetmap/josm/actions/AlignInCircleActionTest.java +++ b/test/unit/org/openstreetmap/josm/actions/AlignInCircleActionTest.java @@ -44,6 +44,8 @@ final class AlignInCircleActionTest { void testWaySelected() throws Exception { DataSet ds = OsmReader.parseDataSet(Files.newInputStream(Paths.get(TestUtils.getTestDataRoot(), "alignCircleBefore.osm")), null); DataSet ds2 = OsmReader.parseDataSet(Files.newInputStream(Paths.get(TestUtils.getTestDataRoot(), "alignCircleAfter1.osm")), null); + ds.allPrimitives().forEach(p -> p.setReferrersDownloaded(true)); + ds2.allPrimitives().forEach(p -> p.setReferrersDownloaded(true)); Way roundabout = null; for (Way w : ds.getWays()) { @@ -75,6 +77,7 @@ void testWaySelected() throws Exception { @Test void testTicket20041() throws Exception { DataSet ds = OsmReader.parseDataSet(Files.newInputStream(Paths.get(TestUtils.getTestDataRoot(), "alignCircleAfter1.osm")), null); + ds.allPrimitives().forEach(p -> p.setReferrersDownloaded(true)); Way roundabout = null; for (Way w : ds.getWays()) { @@ -96,6 +99,8 @@ void testTicket20041() throws Exception { void testNodesSelected() throws Exception { DataSet ds = OsmReader.parseDataSet(Files.newInputStream(Paths.get(TestUtils.getTestDataRoot(), "alignCircleBefore.osm")), null); DataSet ds2 = OsmReader.parseDataSet(Files.newInputStream(Paths.get(TestUtils.getTestDataRoot(), "alignCircleAfter2.osm")), null); + ds.allPrimitives().forEach(p -> p.setReferrersDownloaded(true)); + ds2.allPrimitives().forEach(p -> p.setReferrersDownloaded(true)); Way circularWay = null; for (Way w : ds.getWays()) { @@ -128,6 +133,8 @@ void testNodesSelected() throws Exception { void testOpenWaysSelected() throws Exception { DataSet ds = OsmReader.parseDataSet(Files.newInputStream(Paths.get(TestUtils.getTestDataRoot(), "alignCircleTwoWaysBefore.osm")), null); DataSet ds2 = OsmReader.parseDataSet(Files.newInputStream(Paths.get(TestUtils.getTestDataRoot(), "alignCircleTwoWaysAfter.osm")), null); + ds.allPrimitives().forEach(p -> p.setReferrersDownloaded(true)); + ds2.allPrimitives().forEach(p -> p.setReferrersDownloaded(true)); Set junctions = ds.getWays().stream().filter(w -> "roundabout".equals(w.get("junction"))).collect(Collectors.toSet()); assertEquals(2, junctions.size()); diff --git a/test/unit/org/openstreetmap/josm/actions/CreateMultipolygonActionTest.java b/test/unit/org/openstreetmap/josm/actions/CreateMultipolygonActionTest.java index d9472941ca2..ff2314fe2fa 100644 --- a/test/unit/org/openstreetmap/josm/actions/CreateMultipolygonActionTest.java +++ b/test/unit/org/openstreetmap/josm/actions/CreateMultipolygonActionTest.java @@ -290,4 +290,27 @@ void testTicket20325NoUpdateWarning() throws Exception { assertNull(cmd); } + /** + * Non-regression test for Bug #23641. + * @throws Exception if an error occurs + */ + @Test + void testTicket23642() throws Exception { + DataSet ds = OsmReader.parseDataSet(TestUtils.getRegressionDataStream(23641, "data.osm"), null); + assertEquals(0, ds.getRelations().size()); + Pair cmd = CreateMultipolygonAction.createMultipolygonCommand(ds.getWays(), null); + assertNotNull(cmd); + cmd.a.executeCommand(); + assertEquals(1, ds.getRelations().size()); + Relation mp = ds.getRelations().iterator().next(); + assertTrue(mp.hasTag("landuse", "forest")); + assertTrue(mp.hasTag("leaf_type", "needleleaved")); + assertEquals(0, ds.getWays().stream().filter(w -> w.hasTag("leaf_type", "needleleaved")).count()); + assertEquals(1, ds.getWays().stream().filter(w -> w.hasTag("leaf_type", "broadleaved")).count()); + assertEquals(1, mp.getMembers().stream() + .filter(m -> "inner".equals(m.getRole()) && m.getMember().hasTag("landuse", "forest")).count()); + Pair updateCmd = CreateMultipolygonAction.createMultipolygonCommand(ds.getWays(), mp); + assertNull(updateCmd); + } + } diff --git a/test/unit/org/openstreetmap/josm/actions/JoinAreasActionTest.java b/test/unit/org/openstreetmap/josm/actions/JoinAreasActionTest.java index 3023913ebb4..80934397b28 100644 --- a/test/unit/org/openstreetmap/josm/actions/JoinAreasActionTest.java +++ b/test/unit/org/openstreetmap/josm/actions/JoinAreasActionTest.java @@ -56,6 +56,7 @@ class JoinAreasActionTest { void testTicket9599() throws IOException, IllegalDataException { try (InputStream is = TestUtils.getRegressionDataStream(9599, "ex5.osm")) { DataSet ds = OsmReader.parseDataSet(is, null); + ds.allPrimitives().forEach(p -> p.setReferrersDownloaded(true)); Layer layer = new OsmDataLayer(ds, null, null); MainApplication.getLayerManager().addLayer(layer); try { @@ -80,6 +81,7 @@ void testTicket9599Simple() throws IOException, IllegalDataException { try (InputStream is = TestUtils.getRegressionDataStream(9599, "three_old.osm")) { DataSet ds = OsmReader.parseDataSet(is, null); ds.addDataSource(new DataSource(new Bounds(-90, -180, 90, 180), "Everywhere")); + ds.allPrimitives().forEach(p -> p.setReferrersDownloaded(true)); Layer layer = new OsmDataLayer(ds, null, null); MainApplication.getLayerManager().addLayer(layer); try { @@ -152,6 +154,7 @@ void testTicket18744() throws IOException, IllegalDataException { try (InputStream is = TestUtils.getRegressionDataStream(18744, "18744-sample.osm")) { DataSet ds = OsmReader.parseDataSet(is, null); ds.addDataSource(new DataSource(new Bounds(-90, -180, 90, 180), "Everywhere")); + ds.allPrimitives().forEach(p -> p.setReferrersDownloaded(true)); Layer layer = new OsmDataLayer(ds, null, null); MainApplication.getLayerManager().addLayer(layer); try { diff --git a/test/unit/org/openstreetmap/josm/actions/SessionSaveActionTest.java b/test/unit/org/openstreetmap/josm/actions/SessionSaveActionTest.java index 90e35f5f57e..7b321cf3b1b 100644 --- a/test/unit/org/openstreetmap/josm/actions/SessionSaveActionTest.java +++ b/test/unit/org/openstreetmap/josm/actions/SessionSaveActionTest.java @@ -54,7 +54,7 @@ void testSaveAction() throws IOException { GpxLayer gpx = SessionWriterTest.createGpxLayer(); JOptionPaneSimpleMocker mocker = new JOptionPaneSimpleMocker(Collections.singletonMap(overrideStr, 0)); - SessionSaveAction.setCurrentSession(jos, false, Arrays.asList(gpx, osm)); //gpx and OSM layer + SessionSaveAction.setCurrentSession(jos, Arrays.asList(gpx, osm)); //gpx and OSM layer MainApplication.getLayerManager().addLayer(gpx); //only gpx layer saveAction.actionPerformed(null); //Complain that OSM layer was removed assertEquals(1, mocker.getInvocationLog().size()); diff --git a/test/unit/org/openstreetmap/josm/actions/SimplifyWayActionTest.java b/test/unit/org/openstreetmap/josm/actions/SimplifyWayActionTest.java index 211ac6b5cf6..6803214e0b2 100644 --- a/test/unit/org/openstreetmap/josm/actions/SimplifyWayActionTest.java +++ b/test/unit/org/openstreetmap/josm/actions/SimplifyWayActionTest.java @@ -1,8 +1,12 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.actions; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.openstreetmap.josm.tools.I18n.tr; import java.io.IOException; import java.nio.file.Files; @@ -24,11 +28,16 @@ import org.openstreetmap.josm.data.osm.DataSet; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.Way; +import org.openstreetmap.josm.gui.ExtendedDialog; import org.openstreetmap.josm.gui.MainApplication; +import org.openstreetmap.josm.gui.layer.OsmDataLayer; +import org.openstreetmap.josm.gui.util.GuiHelper; import org.openstreetmap.josm.io.IllegalDataException; import org.openstreetmap.josm.io.OsmReader; import org.openstreetmap.josm.testutils.annotations.Main; import org.openstreetmap.josm.testutils.annotations.Projection; +import org.openstreetmap.josm.testutils.mockers.ExtendedDialogMocker; +import org.openstreetmap.josm.testutils.mockers.HelpAwareOptionPaneMocker; import org.openstreetmap.josm.tools.Utils; /** @@ -99,4 +108,35 @@ void testSimplifyFirstNode() { assertEquals(1, deleteCommands.size()); assertEquals(Collections.singleton(n1), deleteCommands.iterator().next().getParticipatingPrimitives()); } + + /** + * Non-regression test for #23399 + */ + @Test + void testNonRegression23399() { + TestUtils.assumeWorkingJMockit(); + new ExtendedDialogMocker(Collections.singletonMap("Simplify way", "Simplify")) { + @Override + protected String getString(ExtendedDialog instance) { + return instance.getTitle(); + } + }; + new HelpAwareOptionPaneMocker(Collections.singletonMap( + tr("The selection contains {0} ways. Are you sure you want to simplify them all?", 1000), "Yes")); + final ArrayList ways = new ArrayList<>(1000); + final DataSet ds = new DataSet(); + for (int i = 0; i < 1000; i++) { + final Way way = TestUtils.newWay("", new Node(new LatLon(0, 0)), new Node(new LatLon(0, 0.001)), + new Node(new LatLon(0, 0.002))); + ways.add(way); + ds.addPrimitiveRecursive(way); + } + MainApplication.getLayerManager().addLayer(new OsmDataLayer(ds, "SimplifyWayActionTest#testNonRegression23399", null)); + GuiHelper.runInEDTAndWait(() -> ds.setSelected(ds.allPrimitives())); + assertEquals(ds.allPrimitives().size(), ds.getAllSelected().size()); + assertDoesNotThrow(() -> GuiHelper.runInEDTAndWaitWithException(() -> action.actionPerformed(null))); + assertAll(ways.stream().map(way -> () -> assertEquals(2, way.getNodesCount()))); + assertAll(ds.getAllSelected().stream().map(p -> () -> assertFalse(p.isDeleted()))); + assertEquals(3000, ds.getAllSelected().size()); + } } diff --git a/test/unit/org/openstreetmap/josm/actions/UploadActionTest.java b/test/unit/org/openstreetmap/josm/actions/UploadActionTest.java index bbbe9405877..9559fe251ee 100644 --- a/test/unit/org/openstreetmap/josm/actions/UploadActionTest.java +++ b/test/unit/org/openstreetmap/josm/actions/UploadActionTest.java @@ -84,7 +84,7 @@ void testNonRegression21476() throws ExecutionException, InterruptedException, T } } - private static class UploadDialogMock extends MockUp { + private static final class UploadDialogMock extends MockUp { @Mock public void pack(final Invocation invocation) { if (!GraphicsEnvironment.isHeadless()) { @@ -100,7 +100,7 @@ public void setVisible(final Invocation invocation, final boolean visible) { } @Mock - public final boolean isCanceled(final Invocation invocation) { + public boolean isCanceled(final Invocation invocation) { if (!GraphicsEnvironment.isHeadless()) { return Boolean.TRUE.equals(invocation.proceed()); } diff --git a/test/unit/org/openstreetmap/josm/actions/downloadtasks/PluginDownloadTaskTest.java b/test/unit/org/openstreetmap/josm/actions/downloadtasks/PluginDownloadTaskTest.java index 758fdf0430c..15a3f9550c8 100644 --- a/test/unit/org/openstreetmap/josm/actions/downloadtasks/PluginDownloadTaskTest.java +++ b/test/unit/org/openstreetmap/josm/actions/downloadtasks/PluginDownloadTaskTest.java @@ -18,7 +18,6 @@ import org.openstreetmap.josm.plugins.PluginDownloadTask; import org.openstreetmap.josm.plugins.PluginInformation; import org.openstreetmap.josm.testutils.annotations.AssumeRevision; -import org.openstreetmap.josm.tools.Utils; /** * Unit tests for class {@link PluginDownloadTask}. @@ -128,7 +127,7 @@ void testUpdatePluginCorrupt() throws Exception { // the ".jar" file's contents should be as before assertArrayEquals( existingPluginContents, - Utils.readBytesFromStream(pluginDirPluginStream) + pluginDirPluginStream.readAllBytes() ); } } diff --git a/test/unit/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyActionTest.java b/test/unit/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyActionTest.java index 83e5192a6dd..39e59e451a0 100644 --- a/test/unit/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyActionTest.java +++ b/test/unit/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyActionTest.java @@ -1,16 +1,43 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.actions.mapmode; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.InputEvent; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; +import java.util.Collection; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.commons.lang3.function.TriConsumer; import org.junit.jupiter.api.Test; import org.openstreetmap.josm.TestUtils; import org.openstreetmap.josm.actions.mapmode.ImproveWayAccuracyAction.State; +import org.openstreetmap.josm.command.DeleteCommand; +import org.openstreetmap.josm.data.Bounds; +import org.openstreetmap.josm.data.coor.ILatLon; +import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.DataSet; +import org.openstreetmap.josm.data.osm.Node; +import org.openstreetmap.josm.data.osm.OsmPrimitive; +import org.openstreetmap.josm.data.osm.Relation; +import org.openstreetmap.josm.data.osm.RelationToChildReference; +import org.openstreetmap.josm.data.osm.Way; +import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; import org.openstreetmap.josm.gui.MainApplication; import org.openstreetmap.josm.gui.MapFrame; +import org.openstreetmap.josm.gui.MapView; +import org.openstreetmap.josm.gui.layer.Layer; import org.openstreetmap.josm.gui.layer.OsmDataLayer; +import org.openstreetmap.josm.gui.util.GuiHelper; import org.openstreetmap.josm.testutils.annotations.Main; import org.openstreetmap.josm.testutils.annotations.Projection; @@ -20,6 +47,52 @@ @Main @Projection class ImproveWayAccuracyActionTest { + private static final int WIDTH = 800; + private static final int HEIGHT = 600; + + private static final class AlwaysDeleteCallback implements DeleteCommand.DeletionCallback { + @Override + public boolean checkAndConfirmOutlyingDelete(Collection primitives, Collection ignore) { + return true; + } + + @Override + public boolean confirmRelationDeletion(Collection relations) { + return true; + } + + @Override + public boolean confirmDeletionFromRelation(Collection references) { + return true; + } + } + + private static void setupMapView(DataSet ds) { + // setup a reasonable size for the edit window + MainApplication.getMap().mapView.setBounds(new Rectangle(WIDTH, HEIGHT)); + if (ds.getDataSourceBoundingBox() != null) { + MainApplication.getMap().mapView.zoomTo(ds.getDataSourceBoundingBox()); + } else { + BoundingXYVisitor v = new BoundingXYVisitor(); + for (Layer l : MainApplication.getLayerManager().getLayers()) { + l.visitBoundingBox(v); + } + MainApplication.getMap().mapView.zoomTo(v); + } + } + + /** + * Generate a mouse event + * @param mapView The current map view + * @param location The location to generate the event for + * @param modifiers The modifiers for {@link MouseEvent} (see {@link InputEvent#getModifiersEx()}) + * @return The generated event + */ + private static MouseEvent generateEvent(MapView mapView, ILatLon location, int modifiers) { + final Point p = mapView.getPoint(location); + return new MouseEvent(mapView, 0, 0, modifiers, p.x, p.y, p.x, p.y, 1, false, MouseEvent.BUTTON1); + } + /** * Unit test of {@link ImproveWayAccuracyAction#enterMode} and {@link ImproveWayAccuracyAction#exitMode}. */ @@ -44,6 +117,209 @@ void testMode() { */ @Test void testEnumState() { - TestUtils.superficialEnumCodeCoverage(State.class); + assertDoesNotThrow(() -> TestUtils.superficialEnumCodeCoverage(State.class)); + } + + @Test + void testNonRegression23444Selection() { + final DataSet dataSet = new DataSet(); + final OsmDataLayer layer = new OsmDataLayer(dataSet, "ImproveWayAccuracyActionTest#testNonRegression23444Selection", null); + MainApplication.getLayerManager().addLayer(layer); + final ImproveWayAccuracyAction mapMode = new ImproveWayAccuracyAction(); + final MapFrame map = MainApplication.getMap(); + final Way testWay = TestUtils.newWay("", new Node(1, 1), new Node(2, 1), + new Node(3), new Node(4, 1), new Node(5, 1)); + testWay.firstNode().setCoor(new LatLon(0, 0)); + testWay.lastNode().setCoor(new LatLon(0.001, 0.001)); + testWay.getNode(1).setCoor(new LatLon(0.0001, 0.0001)); + testWay.getNode(3).setCoor(new LatLon(0.0009, 0.0009)); + dataSet.addPrimitiveRecursive(testWay); + assertFalse(testWay.getNode(2).isLatLonKnown(), "The second node should not have valid coordinates"); + dataSet.setSelected(testWay.firstNode()); + assertTrue(map.selectMapMode(mapMode)); + assertEquals(mapMode, map.mapMode); + // This is where the exception occurs; we shouldn't be setting the incomplete way as the target when we enter the mode. + setupMapView(dataSet); + assertDoesNotThrow(() -> GuiHelper.runInEDTAndWaitWithException(() -> { + mapMode.mouseMoved(generateEvent(map.mapView, new LatLon(0.0001, 0.0001), 0)); + })); + } + + @Test + void testNonRegression23444() { + testSimplifyWayAction((mapMode, map, testWay) -> { + testWay.getNode(2).setCoor(null); + assertFalse(testWay.getNode(2).isLatLonKnown(), "The second node should not have valid coordinates"); + mapMode.startSelecting(); + mapMode.mouseMoved(generateEvent(map.mapView, testWay.getNode(1), 0)); + }); + } + + @Test + void testAdd() { + AtomicReference referenceWay = new AtomicReference<>(); + testSimplifyWayAction((mapMode, map, testWay) -> { + // Add a node at 0.0001, 0.0005 (not on the direct line) + mapMode.mouseMoved(generateEvent(map.mapView, new LatLon(0.0001, 0.0005), InputEvent.CTRL_DOWN_MASK)); + mapMode.mouseReleased(generateEvent(map.mapView, new LatLon(0.0001, 0.0005), InputEvent.CTRL_DOWN_MASK)); + referenceWay.set(testWay); + }); + final Way testWay = referenceWay.get(); + // There should be a new node between nodes 1 and 2 (old node 2 is now node 3) + assertAll(() -> assertEquals(6, testWay.getNodesCount()), + () -> assertFalse(testWay.getNode(1).isNew()), + () -> assertTrue(testWay.getNode(2).isNew()), + () -> assertFalse(testWay.getNode(3).isNew())); + // These aren't expected to be 0.0001 and 0.0005 exactly, due zoom and conversions between point and latlon. + assertAll(() -> assertEquals(0.0001, testWay.getNode(2).lat(), 1e-5), + () -> assertEquals(0.0005, testWay.getNode(2).lon(), 1e-5)); + } + + @Test + void testAddLock() { + final AtomicReference referenceWay = new AtomicReference<>(); + testSimplifyWayAction((mapMode, map, testWay) -> { + // Add a node at 0.0009, 0.0005 (not on the direct line) that is between nodes 1 and 2 but not 2 and 3. + // First get the waysegment selected + mapMode.mouseMoved(generateEvent(map.mapView, new LatLon(0.0001, 0.0005), InputEvent.CTRL_DOWN_MASK)); + // Then move to another location with ctrl+shift + mapMode.mouseMoved(generateEvent(map.mapView, new LatLon(0.0009, 0.0005), InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); + // Finally, release the mouse with ctrl+shift + mapMode.mouseReleased(generateEvent(map.mapView, new LatLon(0.0009, 0.0005), + InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); + referenceWay.set(testWay); + }); + final Way testWay = referenceWay.get(); + // There should be a new node between nodes 1 and 2 (old node 2 is now node 3) + assertAll(() -> assertEquals(6, testWay.getNodesCount()), + () -> assertFalse(testWay.getNode(1).isNew()), + () -> assertTrue(testWay.getNode(2).isNew()), + () -> assertFalse(testWay.getNode(3).isNew())); + // These aren't expected to be 0.0009 and 0.0005 exactly, due zoom and conversions between point and latlon. + assertAll(() -> assertEquals(0.0009, testWay.getNode(2).lat(), 1e-5), + () -> assertEquals(0.0005, testWay.getNode(2).lon(), 1e-5)); + } + + @Test + void testMove() { + final AtomicReference referenceWay = new AtomicReference<>(); + testSimplifyWayAction((mapMode, map, testWay) -> { + // Move node to 0.0001, 0.0005 (not on the direct line) + // First get the waysegment selected + mapMode.mouseMoved(generateEvent(map.mapView, new LatLon(0.0001, 0.0005), 0)); + // Finally, release the mouse + mapMode.mouseReleased(generateEvent(map.mapView, new LatLon(0.0001, 0.0005), 0)); + referenceWay.set(testWay); + }); + final Way testWay = referenceWay.get(); + assertEquals(5, testWay.getNodesCount()); + // These aren't expected to be 0.0001 and 0.0005 exactly, due zoom and conversions between point and latlon. + assertAll(() -> assertEquals(0.0001, testWay.getNode(2).lat(), 1e-5), + () -> assertEquals(0.0005, testWay.getNode(2).lon(), 1e-5)); + } + + @Test + void testMoveLock() { + final AtomicReference referenceWay = new AtomicReference<>(); + testSimplifyWayAction((mapMode, map, testWay) -> { + // Move node to 0.0001, 0.0005 (not on the direct line) + // First get the waysegment selected + mapMode.mouseMoved(generateEvent(map.mapView, new LatLon(0.0001, 0.0005), 0)); + // Then move to another location + mapMode.mouseMoved(generateEvent(map.mapView, new LatLon(0.0009, 0.0005), InputEvent.SHIFT_DOWN_MASK)); + // Finally, release the mouse + mapMode.mouseReleased(generateEvent(map.mapView, new LatLon(0.0009, 0.0005), InputEvent.SHIFT_DOWN_MASK)); + referenceWay.set(testWay); + }); + final Way testWay = referenceWay.get(); + assertEquals(5, testWay.getNodesCount()); + // These aren't expected to be 0.0009 and 0.0005 exactly, due zoom and conversions between point and latlon. + assertAll(() -> assertEquals(0.0009, testWay.getNode(2).lat(), 1e-5), + () -> assertEquals(0.0005, testWay.getNode(2).lon(), 1e-5)); + } + + @Test + void testDelete() { + DeleteCommand.setDeletionCallback(new AlwaysDeleteCallback()); + final AtomicReference referenceWay = new AtomicReference<>(); + testSimplifyWayAction((mapMode, map, testWay) -> { + // Move node to 0.0001, 0.0005 (not on the direct line) + // First get the waysegment selected + mapMode.mouseMoved(generateEvent(map.mapView, new LatLon(0.0001, 0.0005), InputEvent.ALT_DOWN_MASK)); + // Finally, release the mouse + mapMode.mouseReleased(generateEvent(map.mapView, new LatLon(0.0001, 0.0005), InputEvent.ALT_DOWN_MASK)); + referenceWay.set(testWay); + }); + final Way testWay = referenceWay.get(); + assertEquals(4, testWay.getNodesCount()); + assertAll(testWay.getNodes().stream().map(n -> () -> assertNotEquals(3, n.getUniqueId()))); + } + + @Test + void testDeleteLock() { + DeleteCommand.setDeletionCallback(new AlwaysDeleteCallback()); + final AtomicReference referenceWay = new AtomicReference<>(); + testSimplifyWayAction((mapMode, map, testWay) -> { + // Move node to 0.0001, 0.0005 (not on the direct line) + // First get the waysegment selected + mapMode.mouseMoved(generateEvent(map.mapView, new LatLon(0.0001, 0.0005), 0)); + // Then move to another location + mapMode.mouseMoved(generateEvent(map.mapView, new LatLon(0.0009, 0.0005), InputEvent.SHIFT_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); + // Finally, release the mouse + mapMode.mouseReleased(generateEvent(map.mapView, new LatLon(0.0009, 0.0005), InputEvent.SHIFT_DOWN_MASK | InputEvent.ALT_DOWN_MASK)); + referenceWay.set(testWay); + }); + final Way testWay = referenceWay.get(); + assertEquals(4, testWay.getNodesCount()); + assertAll(testWay.getNodes().stream().map(n -> () -> assertNotEquals(3, n.getUniqueId()))); + } + + private void testSimplifyWayAction(TriConsumer runnable) { + final DataSet dataSet = new DataSet(); + final OsmDataLayer layer = new OsmDataLayer(dataSet, "ImproveWayAccuracyActionT", null); + MainApplication.getLayerManager().addLayer(layer); + final ImproveWayAccuracyAction mapMode = new ImproveWayAccuracyAction(); + final MapFrame map = MainApplication.getMap(); + assertTrue(map.selectMapMode(mapMode)); + assertEquals(mapMode, map.mapMode); + final Way testWay = TestUtils.newWay("", new Node(1, 1), new Node(2, 1), + new Node(3), new Node(4, 1), new Node(5, 1)); + testWay.firstNode().setCoor(new LatLon(0, 0)); + testWay.lastNode().setCoor(new LatLon(0.001, 0.001)); + testWay.getNode(1).setCoor(new LatLon(0.0001, 0.0001)); + testWay.getNode(2).setCoor(new LatLon(0.0005, 0.0005)); + testWay.getNode(3).setCoor(new LatLon(0.0009, 0.0009)); + dataSet.addPrimitiveRecursive(testWay); + setupMapView(dataSet); + final Graphics2D g2d = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB).createGraphics(); + g2d.setClip(0, 0, WIDTH, HEIGHT); + try { + // If this fails, something else is wrong + assertDoesNotThrow(() -> map.mapView.paint(g2d), "The mapview should be able to handle a null coordinate node"); + // Ensure that the test way is selected (and use the methods from the action to do so) + assertDoesNotThrow(() -> GuiHelper.runInEDTAndWaitWithException(() -> { + // Set the way as selected + mapMode.mouseMoved(generateEvent(map.mapView, testWay.getNode(1), 0)); + mapMode.mouseReleased(generateEvent(map.mapView, testWay.getNode(1), 0)); + // And then run the test case + runnable.accept(mapMode, map, testWay); + })); + + // Now check painting (where the problem should occur; the mapMode.paint call should be called as part of the map.mapView.paint call) + assertDoesNotThrow(() -> map.mapView.paint(g2d)); + assertDoesNotThrow(() -> mapMode.paint(g2d, map.mapView, new Bounds(0, 0, 0.001, 0.001))); + + // Then perform the action(s) + GuiHelper.runInEDTAndWaitWithException(() -> { + // Set the mouse location (unset in mouseReleased call) + // This is required by testNonRegression23444, and it doesn't hurt during the other tests + mapMode.mouseMoved(generateEvent(map.mapView, new LatLon(0.0001, 0.0001), 0)); + }); + // Now check painting again (just in case) + assertDoesNotThrow(() -> map.paint(g2d)); + assertDoesNotThrow(() -> mapMode.paint(g2d, map.mapView, new Bounds(0, 0, 0.001, 0.001))); + } finally { + g2d.dispose(); + } } } diff --git a/test/unit/org/openstreetmap/josm/actions/upload/UploadNotesTaskTest.java b/test/unit/org/openstreetmap/josm/actions/upload/UploadNotesTaskTest.java index 54157ea48ef..b32b7c90a48 100644 --- a/test/unit/org/openstreetmap/josm/actions/upload/UploadNotesTaskTest.java +++ b/test/unit/org/openstreetmap/josm/actions/upload/UploadNotesTaskTest.java @@ -156,7 +156,7 @@ void testUpload(final NoteData noteData, final Collection shouldBeUploaded assertTrue(Logging.getLastErrorAndWarnings().isEmpty()); } - private static class FakeOsmApiMocker extends MockUp { + private static final class FakeOsmApiMocker extends MockUp { Collection closed = new ArrayList<>(); Collection commented = new ArrayList<>(); Collection created = new ArrayList<>(); diff --git a/test/unit/org/openstreetmap/josm/command/CommandTest.java b/test/unit/org/openstreetmap/josm/command/CommandTest.java index 302b8195343..22754764e49 100644 --- a/test/unit/org/openstreetmap/josm/command/CommandTest.java +++ b/test/unit/org/openstreetmap/josm/command/CommandTest.java @@ -3,6 +3,7 @@ import java.util.Arrays; +import org.junit.jupiter.api.Test; import org.openstreetmap.josm.TestUtils; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.DataSet; @@ -18,7 +19,6 @@ import nl.jqno.equalsverifier.EqualsVerifier; import nl.jqno.equalsverifier.Warning; -import org.junit.jupiter.api.Test; /** * Unit tests of {@link Command} class. @@ -89,6 +89,7 @@ public Node createNode(long id) { node.setOsmId(id, 1); node.setCoor(LatLon.ZERO); node.put("existing", "existing"); + node.setReferrersDownloaded(true); layer.data.addPrimitive(node); return node; } @@ -99,11 +100,12 @@ public Node createNode(long id) { * @param nodes The nodes * @return The way. */ - public Way createWay(int id, Node...nodes) { + public Way createWay(int id, Node... nodes) { Way way = new Way(); way.setOsmId(id, 1); way.setNodes(Arrays.asList(nodes)); way.put("existing", "existing"); + way.setReferrersDownloaded(true); layer.data.addPrimitive(way); return way; } @@ -114,12 +116,13 @@ public Way createWay(int id, Node...nodes) { * @param members The members * @return The relation. */ - public Relation createRelation(int id, RelationMember...members) { + public Relation createRelation(int id, RelationMember... members) { Relation relation = new Relation(id, 1); for (RelationMember member : members) { relation.addMember(member); } relation.put("existing", "existing"); + relation.setReferrersDownloaded(true); layer.data.addPrimitive(relation); return relation; } diff --git a/test/unit/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJobTest.java b/test/unit/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJobTest.java index ba5aff2bae3..5cec059204c 100644 --- a/test/unit/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJobTest.java +++ b/test/unit/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJobTest.java @@ -84,7 +84,7 @@ protected CacheEntry createCacheEntry(byte[] content) { } } - private static class Listener implements ICachedLoaderListener { + private static final class Listener implements ICachedLoaderListener { private CacheEntryAttributes attributes; private boolean ready; private LoadResult result; @@ -512,7 +512,7 @@ void testCheckUsingHead() throws IOException { * @throws IOException exception */ @Test - public void testCheckUsing304() throws IOException { + void testCheckUsing304() throws IOException { ICacheAccess cache = getCache(); long expires = TimeUnit.DAYS.toMillis(1); long testStart = System.currentTimeMillis(); diff --git a/test/unit/org/openstreetmap/josm/data/gpx/GpxDataTest.java b/test/unit/org/openstreetmap/josm/data/gpx/GpxDataTest.java index 66039b18c11..386939569ca 100644 --- a/test/unit/org/openstreetmap/josm/data/gpx/GpxDataTest.java +++ b/test/unit/org/openstreetmap/josm/data/gpx/GpxDataTest.java @@ -456,7 +456,7 @@ void testChangeListener() { assertNull(cl2.lastEvent); } - private static class TestChangeListener implements GpxDataChangeListener { + private static final class TestChangeListener implements GpxDataChangeListener { private GpxDataChangeEvent lastEvent; diff --git a/test/unit/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJobTest.java b/test/unit/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJobTest.java index e42d4d97f83..e366e80d10f 100644 --- a/test/unit/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJobTest.java +++ b/test/unit/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJobTest.java @@ -98,7 +98,7 @@ public boolean isObjectLoadable() { } } - private static class Listener implements TileLoaderListener { + private static final class Listener implements TileLoaderListener { private CacheEntryAttributes attributes; private boolean ready; private byte[] data; diff --git a/test/unit/org/openstreetmap/josm/data/imagery/WMSEndpointTileSourceTest.java b/test/unit/org/openstreetmap/josm/data/imagery/WMSEndpointTileSourceTest.java index 1e4120449df..047a00d543d 100644 --- a/test/unit/org/openstreetmap/josm/data/imagery/WMSEndpointTileSourceTest.java +++ b/test/unit/org/openstreetmap/josm/data/imagery/WMSEndpointTileSourceTest.java @@ -67,13 +67,14 @@ void testDefaultLayerSetInMaps() throws Exception { ) ); - tileServer.stubFor(WireMock.get(WireMock.urlEqualTo("//maps")).willReturn(WireMock.aResponse().withBody( + tileServer.stubFor(WireMock.get(WireMock.urlEqualTo("/other/maps")).willReturn(WireMock.aResponse().withBody( "\n" + "\n" + "\n" + "OSM Inspector: Geometry\n" + "OSM_Inspector-Geometry\n" + "wms_endpoint\n" + + "qa\n" + "\n" + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsSAAALEgHS3X78AAAB5UlEQVQ4y4WTwWsTURDG" + "fy8W1yYmXZOqtGJJFyGw6KF7CEigwYuS0kthrYUi4i0iORS9BU9hQdA/ILcixVBrwENKLz1FUBB0wWOwYFAqxUNYTZq6BfM8yC5d05iBObz3vfnmm3kz4sqDh/zP" + @@ -93,7 +94,7 @@ void testDefaultLayerSetInMaps() throws Exception { "" ))); - Config.getPref().putList("imagery.layers.sites", Collections.singletonList(tileServer.url("//maps"))); + Config.getPref().putList("imagery.layers.sites", Collections.singletonList(tileServer.url("/other/maps"))); ImageryLayerInfo.instance.loadDefaults(true, null, false); assertEquals(1, ImageryLayerInfo.instance.getDefaultLayers().size()); ImageryInfo wmsImageryInfo = ImageryLayerInfo.instance.getDefaultLayers().get(0); @@ -116,7 +117,7 @@ void testCustomHeadersServerSide() throws IOException { ) ); - tileServer.stubFor(WireMock.get(WireMock.urlEqualTo("//maps")).willReturn(WireMock.aResponse().withBody( + tileServer.stubFor(WireMock.get(WireMock.urlEqualTo("/other/maps")).willReturn(WireMock.aResponse().withBody( "\n" + "\n" + " \n" + @@ -124,6 +125,7 @@ void testCustomHeadersServerSide() throws IOException { " Norge i Bilder (historisk)\n" + " geovekst-nib-historic\n" + " wms_endpoint\n" + + " photo\n" + " NO\n" + " Historic Norwegian orthophotos and maps, courtesy of Geovekst and Norkart.\n" + " \n" + @@ -138,7 +140,7 @@ void testCustomHeadersServerSide() throws IOException { "" ))); - Config.getPref().putList("imagery.layers.sites", Collections.singletonList(tileServer.url("//maps"))); + Config.getPref().putList("imagery.layers.sites", Collections.singletonList(tileServer.url("/other/maps"))); ImageryLayerInfo.instance.loadDefaults(true, null, false); ImageryInfo wmsImageryInfo = ImageryLayerInfo.instance.getDefaultLayers().get(0); wmsImageryInfo.setDefaultLayers(Collections.singletonList(new DefaultLayer(ImageryType.WMS_ENDPOINT, "historiske-ortofoto", "", ""))); diff --git a/test/unit/org/openstreetmap/josm/data/imagery/WMTSTileSourceTest.java b/test/unit/org/openstreetmap/josm/data/imagery/WMTSTileSourceTest.java index b1ebdfc1dbb..f94b0f86a6a 100644 --- a/test/unit/org/openstreetmap/josm/data/imagery/WMTSTileSourceTest.java +++ b/test/unit/org/openstreetmap/josm/data/imagery/WMTSTileSourceTest.java @@ -15,16 +15,13 @@ import java.net.MalformedURLException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; -import com.github.tomakehurst.wiremock.WireMockServer; -import com.github.tomakehurst.wiremock.client.WireMock; -import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; @@ -47,6 +44,9 @@ import org.openstreetmap.josm.testutils.annotations.Projection; import org.openstreetmap.josm.tools.ReflectionUtils; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; + /** * Unit tests for class {@link WMTSTileSource}. */ @@ -275,9 +275,7 @@ void testProjectionWithENUAxis() throws IOException, WMTSGetCapabilitiesExceptio void testTwoTileSetsForOneProjection() throws Exception { ProjectionRegistry.setProjection(Projections.getProjectionByCode("EPSG:3857")); ImageryInfo ontario = getImagery(TestUtils.getTestDataRoot() + "wmts/WMTSCapabilities-Ontario.xml"); - ontario.setDefaultLayers(Arrays.asList(new DefaultLayer[] { - new DefaultLayer(ImageryType.WMTS, "Basemap_Imagery_2014", null, "default028mm") - })); + ontario.setDefaultLayers(Collections.singletonList(new DefaultLayer(ImageryType.WMTS, "Basemap_Imagery_2014", null, "default028mm"))); WMTSTileSource testSource = new WMTSTileSource(ontario); testSource.initProjection(ProjectionRegistry.getProjection()); assertEquals( @@ -292,9 +290,9 @@ void testTwoTileSetsForOneProjection() throws Exception { void testTwoTileSetsForOneProjectionSecondLayer() throws Exception { ProjectionRegistry.setProjection(Projections.getProjectionByCode("EPSG:3857")); ImageryInfo ontario = getImagery(TestUtils.getTestDataRoot() + "wmts/WMTSCapabilities-Ontario.xml"); - ontario.setDefaultLayers(Arrays.asList(new DefaultLayer[] { + ontario.setDefaultLayers(Collections.singletonList( new DefaultLayer(ImageryType.WMTS, "Basemap_Imagery_2014", null, "GoogleMapsCompatible") - })); + )); WMTSTileSource testSource = new WMTSTileSource(ontario); testSource.initProjection(ProjectionRegistry.getProjection()); assertEquals( @@ -383,7 +381,7 @@ void testDefaultLayer() throws Exception { ); tileServer.stubFor( - WireMock.get("//maps") + WireMock.get("/other/maps") .willReturn( WireMock.aResponse().withBody( "\n" + @@ -392,6 +390,7 @@ void testDefaultLayer() throws Exception { "Landsat\n" + "landsat\n" + "wmts\n" + + "photo\n" + "\n" + "" + "" + @@ -400,7 +399,7 @@ void testDefaultLayer() throws Exception { "" ))); - Config.getPref().putList("imagery.layers.sites", Collections.singletonList(tileServer.url("//maps"))); + Config.getPref().putList("imagery.layers.sites", Collections.singletonList(tileServer.url("/other/maps"))); ImageryLayerInfo.instance.loadDefaults(true, null, false); assertEquals(1, ImageryLayerInfo.instance.getDefaultLayers().size()); @@ -488,12 +487,12 @@ void testApiKeyInvalid() { @ValueSource(strings = {"image/jpgpng", "image/png8", "image/png; mode=8bit", "image/jpeg", "image/jpg"}) void testSupportedMimeTypesUrlEncode(String mimeType, @TempDir File temporaryDirectory) throws IOException, WMTSGetCapabilitiesException, ReflectiveOperationException { - final String data = FileUtils.readFileToString(new File(TestUtils.getTestDataRoot() + - "wmts/bug13975-multiple-tile-matrices-for-one-layer-projection.xml"), StandardCharsets.UTF_8) + final String data = Files.readString(Paths.get(TestUtils.getTestDataRoot() + + "wmts", "bug13975-multiple-tile-matrices-for-one-layer-projection.xml"), StandardCharsets.UTF_8) .replace("image/jpgpng", mimeType); - File file = new File(temporaryDirectory, "testSupportedMimeTypes.xml"); - FileUtils.writeStringToFile(file, data, StandardCharsets.UTF_8); - WMTSCapabilities capabilities = WMTSTileSource.getCapabilities(file.toURI().toURL().toExternalForm(), Collections.emptyMap()); + Path file = temporaryDirectory.toPath().resolve("testSupportedMimeTypes.xml"); + Files.writeString(file, data, StandardCharsets.UTF_8); + WMTSCapabilities capabilities = WMTSTileSource.getCapabilities(file.toUri().toURL().toExternalForm(), Collections.emptyMap()); assertEquals(2, capabilities.getLayers().size()); Field format = WMTSTileSource.Layer.class.getDeclaredField("format"); ReflectionUtils.setObjectsAccessible(format); diff --git a/test/unit/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MapboxVectorTileSourceTest.java b/test/unit/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MapboxVectorTileSourceTest.java index 7c688576b7a..34233ddfe8e 100644 --- a/test/unit/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MapboxVectorTileSourceTest.java +++ b/test/unit/org/openstreetmap/josm/data/imagery/vectortile/mapbox/MapboxVectorTileSourceTest.java @@ -26,7 +26,7 @@ * @since 17862 */ class MapboxVectorTileSourceTest implements TileSourceTest { - private static class SelectLayerDialogMocker extends ExtendedDialogMocker { + private static final class SelectLayerDialogMocker extends ExtendedDialogMocker { int index; @Override protected void act(final ExtendedDialog instance) { diff --git a/test/unit/org/openstreetmap/josm/data/oauth/OAuth20AuthorizationTest.java b/test/unit/org/openstreetmap/josm/data/oauth/OAuth20AuthorizationTest.java index 18456f22c2e..091f7739f23 100644 --- a/test/unit/org/openstreetmap/josm/data/oauth/OAuth20AuthorizationTest.java +++ b/test/unit/org/openstreetmap/josm/data/oauth/OAuth20AuthorizationTest.java @@ -16,11 +16,24 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.openstreetmap.josm.data.oauth.osm.OsmScopes; +import org.openstreetmap.josm.data.preferences.JosmUrls; +import org.openstreetmap.josm.io.OsmApi; +import org.openstreetmap.josm.io.remotecontrol.RemoteControl; +import org.openstreetmap.josm.spi.preferences.Config; +import org.openstreetmap.josm.testutils.annotations.BasicPreferences; +import org.openstreetmap.josm.testutils.annotations.HTTP; +import org.openstreetmap.josm.testutils.mockers.OpenBrowserMocker; +import org.openstreetmap.josm.tools.HttpClient; +import org.openstreetmap.josm.tools.Logging; + import com.github.tomakehurst.wiremock.client.WireMock; -import com.github.tomakehurst.wiremock.common.FileSource; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; -import com.github.tomakehurst.wiremock.extension.Parameters; -import com.github.tomakehurst.wiremock.extension.ResponseTransformer; +import com.github.tomakehurst.wiremock.extension.ResponseTransformerV2; import com.github.tomakehurst.wiremock.http.FixedDelayDistribution; import com.github.tomakehurst.wiremock.http.HttpHeader; import com.github.tomakehurst.wiremock.http.HttpHeaders; @@ -32,22 +45,9 @@ import com.github.tomakehurst.wiremock.matching.AnythingPattern; import com.github.tomakehurst.wiremock.matching.EqualToPattern; import com.github.tomakehurst.wiremock.matching.StringValuePattern; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import mockit.Mock; import mockit.MockUp; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.openstreetmap.josm.data.oauth.osm.OsmScopes; -import org.openstreetmap.josm.data.preferences.JosmUrls; -import org.openstreetmap.josm.io.OsmApi; -import org.openstreetmap.josm.io.remotecontrol.RemoteControl; -import org.openstreetmap.josm.spi.preferences.Config; -import org.openstreetmap.josm.testutils.annotations.BasicPreferences; -import org.openstreetmap.josm.testutils.annotations.HTTP; -import org.openstreetmap.josm.testutils.mockers.OpenBrowserMocker; -import org.openstreetmap.josm.tools.HttpClient; -import org.openstreetmap.josm.tools.Logging; @BasicPreferences @HTTP @@ -69,11 +69,13 @@ private enum ConnectionProblems { SOCKET_TIMEOUT } - private static class OAuthServerWireMock extends ResponseTransformer { + private static final class OAuthServerWireMock implements ResponseTransformerV2 { String stateToReturn; ConnectionProblems connectionProblems = ConnectionProblems.NONE; + @Override - public Response transform(Request request, Response response, FileSource files, Parameters parameters) { + public Response transform(Response response, ServeEvent serveEvent) { + final var request = serveEvent.getRequest(); try { if (request.getUrl().startsWith("/oauth2/authorize")) { return authorizationRequest(request, response); @@ -157,10 +159,10 @@ void setup() { /** * Set up the default wiremock information - * @param wireMockRuntimeInfo The info to set up */ @BeforeEach - void setupWireMock(WireMockRuntimeInfo wireMockRuntimeInfo) { + void setupWireMock() { + final WireMockRuntimeInfo wireMockRuntimeInfo = wml.getRuntimeInfo(); Config.getPref().put("osm-server.url", wireMockRuntimeInfo.getHttpBaseUrl() + "/api/"); new MockUp() { @Mock @@ -185,7 +187,7 @@ private HttpClient generateClient(WireMockRuntimeInfo wireMockRuntimeInfo, Atomi final OAuth20Authorization authorization = new OAuth20Authorization(); OAuth20Parameters parameters = (OAuth20Parameters) OAuthParameters.createDefault(OsmApi.getOsmApi().getBaseUrl(), OAuthVersion.OAuth20); RemoteControl.start(); - authorization.authorize(new OAuth20Parameters(parameters.getClientId(), parameters.getClientSecret(), + authorization.authorize(new OAuth20Parameters(CLIENT_ID_VALUE, parameters.getClientSecret(), wireMockRuntimeInfo.getHttpBaseUrl() + "/oauth2", wireMockRuntimeInfo.getHttpBaseUrl() + "/api", parameters.getRedirectUri()), consumer::set, OsmScopes.read_gpx); assertEquals(1, OpenBrowserMocker.getCalledURIs().size()); @@ -194,9 +196,9 @@ private HttpClient generateClient(WireMockRuntimeInfo wireMockRuntimeInfo, Atomi } @Test - void testAuthorize(WireMockRuntimeInfo wireMockRuntimeInfo) throws IOException { + void testAuthorize() throws IOException { final AtomicReference> consumer = new AtomicReference<>(); - final HttpClient client = generateClient(wireMockRuntimeInfo, consumer); + final HttpClient client = generateClient(wml.getRuntimeInfo(), consumer); try { HttpClient.Response response = client.connect(); assertEquals(200, response.getResponseCode()); @@ -211,10 +213,10 @@ void testAuthorize(WireMockRuntimeInfo wireMockRuntimeInfo) throws IOException { } @Test - void testAuthorizeBadState(WireMockRuntimeInfo wireMockRuntimeInfo) throws IOException { + void testAuthorizeBadState() throws IOException { oauthServer.stateToReturn = "Bad_State"; final AtomicReference> consumer = new AtomicReference<>(); - final HttpClient client = generateClient(wireMockRuntimeInfo, consumer); + final HttpClient client = generateClient(wml.getRuntimeInfo(), consumer); try { HttpClient.Response response = client.connect(); assertEquals(400, response.getResponseCode()); @@ -227,14 +229,14 @@ void testAuthorizeBadState(WireMockRuntimeInfo wireMockRuntimeInfo) throws IOExc } @Test - void testSocketTimeout(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception { + void testSocketTimeout() throws Exception { // 1s before timeout Config.getPref().putInt("socket.timeout.connect", 1); Config.getPref().putInt("socket.timeout.read", 1); oauthServer.connectionProblems = ConnectionProblems.SOCKET_TIMEOUT; final AtomicReference> consumer = new AtomicReference<>(); - final HttpClient client = generateClient(wireMockRuntimeInfo, consumer) + final HttpClient client = generateClient(wml.getRuntimeInfo(), consumer) .setConnectTimeout(15_000).setReadTimeout(30_000); try { HttpClient.Response response = client.connect(); diff --git a/test/unit/org/openstreetmap/josm/data/oauth/OAuthParametersTest.java b/test/unit/org/openstreetmap/josm/data/oauth/OAuthParametersTest.java index 16f12b4b853..a5ab49d8e92 100644 --- a/test/unit/org/openstreetmap/josm/data/oauth/OAuthParametersTest.java +++ b/test/unit/org/openstreetmap/josm/data/oauth/OAuthParametersTest.java @@ -23,16 +23,14 @@ class OAuthParametersTest { @Test @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD") void testCreateDefault() { - OAuthParameters def = OAuthParameters.createDefault(); + IOAuthParameters def = OAuthParameters.createDefault(); assertNotNull(def); - assertEquals(def, OAuthParameters.createDefault(Config.getUrls().getDefaultOsmApiUrl())); - OAuthParameters dev = OAuthParameters.createDefault("https://api06.dev.openstreetmap.org/api"); + assertEquals(def, OAuthParameters.createDefault(Config.getUrls().getDefaultOsmApiUrl(), OAuthVersion.OAuth20)); + IOAuthParameters dev = OAuthParameters.createDefault("https://api06.dev.openstreetmap.org/api", OAuthVersion.OAuth20); assertNotNull(dev); assertNotEquals(def, dev); Logging.setLogLevel(Logging.LEVEL_TRACE); // enable trace for line coverage - assertEquals(def, OAuthParameters.createDefault("wrong_url")); - OAuthParameters dev2 = new OAuthParameters(dev); - assertEquals(dev, dev2); + assertEquals(def, OAuthParameters.createDefault("wrong_url", OAuthVersion.OAuth20)); } /** @@ -41,6 +39,6 @@ void testCreateDefault() { @Test void testEqualsContract() { TestUtils.assumeWorkingEqualsVerifier(); - EqualsVerifier.forClass(OAuthParameters.class).usingGetClass().verify(); + EqualsVerifier.forClass(OAuth20Parameters.class).usingGetClass().verify(); } } diff --git a/test/unit/org/openstreetmap/josm/data/oauth/OAuthTokenTest.java b/test/unit/org/openstreetmap/josm/data/oauth/OAuthTokenTest.java deleted file mode 100644 index 1c2683f2ca7..00000000000 --- a/test/unit/org/openstreetmap/josm/data/oauth/OAuthTokenTest.java +++ /dev/null @@ -1,40 +0,0 @@ -// License: GPL. For details, see LICENSE file. -package org.openstreetmap.josm.data.oauth; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import org.junit.jupiter.api.Test; -import org.openstreetmap.josm.TestUtils; - -import nl.jqno.equalsverifier.EqualsVerifier; -import oauth.signpost.OAuthConsumer; - -/** - * Unit tests for class {@link OAuthToken}. - */ -class OAuthTokenTest { - - /** - * Unit test of method {@link OAuthToken#createToken}. - */ - @Test - void testCreateToken() { - OAuthConsumer defCon = OAuthParameters.createDefault().buildConsumer(); - assertNotNull(defCon); - OAuthToken defTok = OAuthToken.createToken(defCon); - assertNotNull(defTok); - assertEquals(defCon.getToken(), defTok.getKey()); - assertEquals(defCon.getTokenSecret(), defTok.getSecret()); - assertEquals(defTok, new OAuthToken(defTok)); - } - - /** - * Unit test of methods {@link OAuthToken#equals} and {@link OAuthToken#hashCode}. - */ - @Test - void testEqualsContract() { - TestUtils.assumeWorkingEqualsVerifier(); - EqualsVerifier.forClass(OAuthToken.class).usingGetClass().verify(); - } -} diff --git a/test/unit/org/openstreetmap/josm/data/oauth/SignpostAdaptersTest.java b/test/unit/org/openstreetmap/josm/data/oauth/SignpostAdaptersTest.java deleted file mode 100644 index 5c1375a37e7..00000000000 --- a/test/unit/org/openstreetmap/josm/data/oauth/SignpostAdaptersTest.java +++ /dev/null @@ -1,99 +0,0 @@ -// License: GPL. For details, see LICENSE file. -package org.openstreetmap.josm.data.oauth; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; - -import org.junit.jupiter.api.Test; -import org.openstreetmap.josm.data.oauth.SignpostAdapters.HttpRequest; -import org.openstreetmap.josm.data.oauth.SignpostAdapters.HttpResponse; -import org.openstreetmap.josm.data.oauth.SignpostAdapters.OAuthConsumer; -import org.openstreetmap.josm.testutils.annotations.HTTPS; -import org.openstreetmap.josm.tools.HttpClient; - -import net.trajano.commons.testing.UtilityClassTestUtil; - -/** - * Unit tests for class {@link SignpostAdapters}. - */ -@HTTPS -class SignpostAdaptersTest { - - private static HttpClient newClient() throws MalformedURLException { - return HttpClient.create(new URL("https://www.openstreetmap.org")); - } - - /** - * Tests that {@code SignpostAdapters} satisfies utility class criteria. - * @throws ReflectiveOperationException if an error occurs - */ - @Test - void testUtilityClass() throws ReflectiveOperationException { - UtilityClassTestUtil.assertUtilityClassWellDefined(SignpostAdapters.class); - } - - /** - * Unit test of method {@link SignpostAdapters.OAuthConsumer#wrap}. - * @throws MalformedURLException never - */ - @Test - void testOAuthConsumerWrap() throws MalformedURLException { - assertNotNull(new OAuthConsumer("", "").wrap(newClient())); - } - - /** - * Unit test of method {@link SignpostAdapters.HttpRequest#getMessagePayload}. - * @throws IOException never - */ - @Test - void testHttpRequestGetMessagePayload() throws IOException { - assertNull(new HttpRequest(newClient()).getMessagePayload()); - } - - /** - * Unit test of method {@link SignpostAdapters.HttpRequest#setRequestUrl}. - */ - @Test - void testHttpRequestSetRequestUrl() { - assertThrows(IllegalStateException.class, () -> new HttpRequest(newClient()).setRequestUrl(null)); - } - - /** - * Unit test of method {@link SignpostAdapters.HttpRequest#getAllHeaders}. - */ - @Test - void testHttpRequestGetAllHeaders() { - assertThrows(IllegalStateException.class, () -> new HttpRequest(newClient()).getAllHeaders()); - } - - /** - * Unit test of method {@link SignpostAdapters.HttpRequest#unwrap}. - */ - @Test - void testHttpRequestUnwrap() { - assertThrows(IllegalStateException.class, () -> new HttpRequest(newClient()).unwrap()); - } - - /** - * Unit test of method {@link SignpostAdapters.HttpResponse#getReasonPhrase()}. - * @throws Exception never - */ - @Test - void testHttpResponseGetReasonPhrase() throws Exception { - assertEquals("OK", new HttpResponse(new HttpRequest(newClient()).request.connect()).getReasonPhrase()); - } - - /** - * Unit test of method {@link SignpostAdapters.HttpResponse#unwrap}. - */ - @Test - void testHttpResponseUnwrap() { - assertThrows(IllegalStateException.class, () -> new HttpResponse(new HttpRequest(newClient()).request.connect()).unwrap()); - } -} diff --git a/test/unit/org/openstreetmap/josm/data/osm/DefaultNameFormatterTest.java b/test/unit/org/openstreetmap/josm/data/osm/DefaultNameFormatterTest.java index 3c1ae6e431c..8ac052aa3ea 100644 --- a/test/unit/org/openstreetmap/josm/data/osm/DefaultNameFormatterTest.java +++ b/test/unit/org/openstreetmap/josm/data/osm/DefaultNameFormatterTest.java @@ -78,13 +78,13 @@ void testTicket9632() throws IllegalDataException, IOException, SAXException { System.out.println("p3: "+DefaultNameFormatter.getInstance().format(p3)+" - "+p3); // CHECKSTYLE.OFF: SingleSpaceSeparator - assertEquals(comparator.compare(p1, p2), -1); // p1 < p2 - assertEquals(comparator.compare(p2, p1), 1); // p2 > p1 + assertEquals(-1, comparator.compare(p1, p2)); // p1 < p2 + assertEquals(1, comparator.compare(p2, p1)); // p2 > p1 - assertEquals(comparator.compare(p1, p3), -1); // p1 < p3 - assertEquals(comparator.compare(p3, p1), 1); // p3 > p1 - assertEquals(comparator.compare(p2, p3), 1); // p2 > p3 - assertEquals(comparator.compare(p3, p2), -1); // p3 < p2 + assertEquals(-1, comparator.compare(p1, p3)); // p1 < p3 + assertEquals(1, comparator.compare(p3, p1)); // p3 > p1 + assertEquals(1, comparator.compare(p2, p3)); // p2 > p3 + assertEquals(-1, comparator.compare(p3, p2)); // p3 < p2 // CHECKSTYLE.ON: SingleSpaceSeparator Relation[] relations = new ArrayList<>(ds.getRelations()).toArray(new Relation[0]); diff --git a/test/unit/org/openstreetmap/josm/data/osm/WaySegmentTest.java b/test/unit/org/openstreetmap/josm/data/osm/WaySegmentTest.java index ab0da6e26cb..8386ea4d1cb 100644 --- a/test/unit/org/openstreetmap/josm/data/osm/WaySegmentTest.java +++ b/test/unit/org/openstreetmap/josm/data/osm/WaySegmentTest.java @@ -20,22 +20,35 @@ void testForNodePair() { final Node n2 = new Node(new LatLon(1, 0)); final Node n3 = new Node(new LatLon(2, 0)); final Node n4 = new Node(new LatLon(3, 0)); - final Way w = new Way(); - for (OsmPrimitive p : Arrays.asList(n1, n2, n3, n4, w)) { + final Way w1 = new Way(); + final Way w2 = new Way(); + for (OsmPrimitive p : Arrays.asList(n1, n2, n3, n4, w1, w2)) { ds.addPrimitive(p); } - w.addNode(n1); - w.addNode(n2); - w.addNode(n1); - w.addNode(n3); - w.addNode(n1); - w.addNode(n4); - w.addNode(n1); - assertEquals(WaySegment.forNodePair(w, n1, n2).getLowerIndex(), 0); - assertEquals(WaySegment.forNodePair(w, n1, n3).getLowerIndex(), 2); - assertEquals(WaySegment.forNodePair(w, n1, n4).getLowerIndex(), 4); - assertEquals(WaySegment.forNodePair(w, n4, n1).getLowerIndex(), 5); - IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> WaySegment.forNodePair(w, n3, n4)); - assertEquals("Node pair is not part of way!", iae.getMessage()); + w1.addNode(n1); + w1.addNode(n2); + w1.addNode(n1); + w1.addNode(n3); + w1.addNode(n1); + w1.addNode(n4); + w1.addNode(n1); + + w2.addNode(n1); + w2.addNode(n2); + w2.addNode(n3); + + assertEquals(0, WaySegment.forNodePair(w1, n1, n2).getLowerIndex()); + assertEquals(2, WaySegment.forNodePair(w1, n1, n3).getLowerIndex()); + assertEquals(4, WaySegment.forNodePair(w1, n1, n4).getLowerIndex()); + assertEquals(5, WaySegment.forNodePair(w1, n4, n1).getLowerIndex()); + // two segments between n3 and n4 + IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> WaySegment.forNodePair(w1, n3, n4)); + assertEquals(IWaySegment.NOT_A_SEGMENT, iae.getMessage()); + // wrong order + iae = assertThrows(IllegalArgumentException.class, () -> WaySegment.forNodePair(w2, n2, n1)); + assertEquals(IWaySegment.NOT_A_SEGMENT, iae.getMessage()); + // node is not in way + iae = assertThrows(IllegalArgumentException.class, () -> WaySegment.forNodePair(w2, n1, n4)); + assertEquals(IWaySegment.NOT_A_SEGMENT, iae.getMessage()); } } diff --git a/test/unit/org/openstreetmap/josm/data/projection/ProjectionRegressionTest.java b/test/unit/org/openstreetmap/josm/data/projection/ProjectionRegressionTest.java index beb00e441c7..5d93daca11d 100644 --- a/test/unit/org/openstreetmap/josm/data/projection/ProjectionRegressionTest.java +++ b/test/unit/org/openstreetmap/josm/data/projection/ProjectionRegressionTest.java @@ -1,6 +1,8 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.data.projection; +import static org.junit.jupiter.api.Assertions.fail; + import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; @@ -18,7 +20,6 @@ import java.util.TreeSet; import java.util.stream.Collectors; -import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; import org.openstreetmap.josm.JOSMFixture; import org.openstreetmap.josm.data.Bounds; @@ -26,8 +27,6 @@ import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.testutils.annotations.ProjectionNadGrids; import org.openstreetmap.josm.tools.Pair; -import org.openstreetmap.josm.tools.Platform; -import org.openstreetmap.josm.tools.Utils; /** * This test is used to monitor changes in projection code. @@ -44,7 +43,7 @@ class ProjectionRegressionTest { private static final String PROJECTION_DATA_FILE = "nodist/data/projection/projection-regression-test-data"; - private static class TestData { + private static final class TestData { public String code; public LatLon ll; public EastNorth en; @@ -141,11 +140,6 @@ private static Pair readLine(String expectedName, String input) @ProjectionNadGrids @Test void testNonRegression() throws IOException { - // Disable on Github Windows runners + Java 8, minor differences appeared around 2021-07-20 - Assumptions.assumeFalse( - Utils.getJavaVersion() == 8 - && Platform.determinePlatform() == Platform.WINDOWS - && System.getenv("GITHUB_WORKFLOW") != null); List allData = readData(); Set dataCodes = allData.stream().map(data -> data.code).collect(Collectors.toSet()); @@ -157,7 +151,6 @@ void testNonRegression() throws IOException { } } - final boolean java9 = Utils.getJavaVersion() >= 9; for (TestData data : allData) { Projection proj = Projections.getProjectionByCode(data.code); if (proj == null) { @@ -166,14 +159,14 @@ void testNonRegression() throws IOException { } EastNorth en = proj.latlon2eastNorth(data.ll); LatLon ll2 = proj.eastNorth2latlon(data.en); - if (!(java9 ? equalsJava9(en, data.en) : en.equals(data.en))) { + if (!equalsJava9(en, data.en)) { String error = String.format("%s (%s): Projecting latlon(%s,%s):%n" + " expected: eastnorth(%s,%s),%n" + " but got: eastnorth(%s,%s)!%n", proj, data.code, data.ll.lat(), data.ll.lon(), data.en.east(), data.en.north(), en.east(), en.north()); fail.append(error); } - if (!(java9 ? equalsJava9(ll2, data.ll2) : ll2.equals(data.ll2))) { + if (!equalsJava9(ll2, data.ll2)) { String error = String.format("%s (%s): Inverse projecting eastnorth(%s,%s):%n" + " expected: latlon(%s,%s),%n" + " but got: latlon(%s,%s)!%n", @@ -184,7 +177,7 @@ void testNonRegression() throws IOException { if (fail.length() > 0) { System.err.println(fail); - throw new AssertionError(fail.toString()); + fail(fail.toString()); } } diff --git a/test/unit/org/openstreetmap/josm/data/validation/tests/AddressesTest.java b/test/unit/org/openstreetmap/josm/data/validation/tests/AddressesTest.java index 0f022cf4b57..e8f1b4266c0 100644 --- a/test/unit/org/openstreetmap/josm/data/validation/tests/AddressesTest.java +++ b/test/unit/org/openstreetmap/josm/data/validation/tests/AddressesTest.java @@ -2,22 +2,29 @@ package org.openstreetmap.josm.data.validation.tests; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.openstreetmap.josm.data.coor.LatLon.NORTH_POLE; import static org.openstreetmap.josm.data.coor.LatLon.SOUTH_POLE; import static org.openstreetmap.josm.data.coor.LatLon.ZERO; import java.util.List; +import javax.swing.JCheckBox; +import javax.swing.JPanel; + import org.junit.jupiter.api.Test; import org.openstreetmap.josm.TestUtils; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.DataSet; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.RelationMember; +import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.data.validation.Severity; import org.openstreetmap.josm.data.validation.TestError; +import org.openstreetmap.josm.gui.progress.NullProgressMonitor; /** * JUnit Test of {@link Addresses} validation test. @@ -119,4 +126,64 @@ void testMultiAddressDuplicates() { doTestDuplicateHouseNumber(num1, ZERO, num3, ZERO, Severity.WARNING); doTestDuplicateHouseNumber(num1, ZERO, num4, ZERO, null); } + + /** + * See #23302 + */ + @Test + void testCheckForDuplicatePOIBuildingAddresses() { + final Addresses test = new Addresses(); + final JPanel panel = new JPanel(); + final Node poi = TestUtils.newNode("addr:housenumber=1 addr:street=Foo"); + final Way building = TestUtils.newWay("addr:housenumber=1 addr:street=Foo building=yes", + TestUtils.newNode(""), TestUtils.newNode(""), TestUtils.newNode("")); + final DataSet ds = new DataSet(); + // Ensure that we are checking for building-poi duplicates + test.addGui(panel); + JCheckBox checkboxIncludeBldgPOI = assertInstanceOf(JCheckBox.class, panel.getComponent(panel.getComponentCount() - 1)); + checkboxIncludeBldgPOI.setSelected(true); + test.ok(); + // Set up the dataset + ds.addPrimitive(poi); + ds.addPrimitiveRecursive(building); + building.addNode(building.firstNode()); + + // Duplicate addresses with no additional information should always have warnings + test.startTest(NullProgressMonitor.INSTANCE); + test.visit(ds.allPrimitives()); + test.endTest(); + assertEquals(1, test.getErrors().size()); + + // Do the first test checking for building-poi duplicates + poi.put("name", "FooBar"); + test.startTest(NullProgressMonitor.INSTANCE); + test.visit(ds.allPrimitives()); + test.endTest(); + assertEquals(1, test.getErrors().size()); + assertEquals(Severity.OTHER, test.getErrors().get(0).getSeverity()); + + // Now check if they have the same name + building.put("name", "FooBar"); + test.startTest(NullProgressMonitor.INSTANCE); + test.visit(ds.allPrimitives()); + test.endTest(); + assertEquals(1, test.getErrors().size()); + assertEquals(Severity.WARNING, test.getErrors().get(0).getSeverity()); + + // Now check if they have a different name + building.put("name", "FooBar2"); + test.startTest(NullProgressMonitor.INSTANCE); + test.visit(ds.allPrimitives()); + test.endTest(); + assertEquals(1, test.getErrors().size()); + assertEquals(Severity.OTHER, test.getErrors().get(0).getSeverity()); + + // Now ensure that it doesn't get errors when disabled + checkboxIncludeBldgPOI.setSelected(false); + test.ok(); + test.startTest(NullProgressMonitor.INSTANCE); + test.visit(ds.allPrimitives()); + test.endTest(); + assertTrue(test.getErrors().isEmpty()); + } } diff --git a/test/unit/org/openstreetmap/josm/data/validation/tests/CrossingWaysTest.java b/test/unit/org/openstreetmap/josm/data/validation/tests/CrossingWaysTest.java index 5904812cf26..cb57cf897b8 100644 --- a/test/unit/org/openstreetmap/josm/data/validation/tests/CrossingWaysTest.java +++ b/test/unit/org/openstreetmap/josm/data/validation/tests/CrossingWaysTest.java @@ -9,6 +9,7 @@ import java.nio.file.Paths; import java.util.HashMap; import java.util.List; +import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import org.openstreetmap.josm.TestUtils; @@ -16,12 +17,15 @@ import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.DataSet; import org.openstreetmap.josm.data.osm.Node; +import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.data.osm.WaySegment; import org.openstreetmap.josm.data.validation.TestError; import org.openstreetmap.josm.data.validation.tests.CrossingWays.Boundaries; import org.openstreetmap.josm.data.validation.tests.CrossingWays.SelfCrossing; import org.openstreetmap.josm.data.validation.tests.CrossingWays.Ways; +import org.openstreetmap.josm.gui.MainApplication; +import org.openstreetmap.josm.gui.layer.OsmDataLayer; import org.openstreetmap.josm.io.OsmReader; import org.openstreetmap.josm.testutils.annotations.BasicPreferences; import org.openstreetmap.josm.testutils.annotations.Projection; @@ -199,6 +203,7 @@ void testCoverage() throws Exception { crossingWays.visit(ds.allPrimitives()); crossingWays.endTest(); + assertEquals(109, crossingWays.getErrors().size()); for (TestError e : crossingWays.getErrors()) { // we don't report self crossing ways in this test assertEquals(2, e.getPrimitives().size(), e.getPrimitives().toString()); @@ -219,4 +224,39 @@ void testCoverage() throws Exception { assertEquals(2, crossingBoundaries.getErrors().size()); } + /** + * Check if partial selection find crossings with unselected objects. + * @throws Exception if an error occurs + */ + @Test + void testPartial() throws Exception { + + DataSet ds = OsmReader.parseDataSet( + Files.newInputStream(Paths.get(TestUtils.getTestDataRoot(), "crossingWays.osm")), null); + MainApplication.getLayerManager().addLayer(new OsmDataLayer(ds, null, null)); + + CrossingWays crossingWays = new CrossingWays.Ways(); + List partialSelection = ds.getWays().stream().filter(w -> w.hasTag("testsel", "horizontal")) + .collect(Collectors.toList()); + + crossingWays.setPartialSelection(true); + crossingWays.startTest(null); + crossingWays.visit(partialSelection); + crossingWays.endTest(); + + assertEquals(109, crossingWays.getErrors().size()); + for (TestError e : crossingWays.getErrors()) { + // we don't report self crossing ways in this test + assertEquals(2, e.getPrimitives().size(), e.getPrimitives().toString()); + // see #20121: crossing water areas should not be reported + assertFalse(e.getPrimitives().stream().filter(Way.class::isInstance).allMatch(CrossingWays::isWaterArea)); + } + + CrossingWays crossingBoundaries = new CrossingWays.Boundaries(); + crossingBoundaries.setPartialSelection(true); + crossingBoundaries.startTest(null); + crossingBoundaries.visit(ds.allPrimitives()); + crossingBoundaries.endTest(); + assertEquals(2, crossingBoundaries.getErrors().size()); + } } diff --git a/test/unit/org/openstreetmap/josm/data/validation/tests/CycleDetectorTest.java b/test/unit/org/openstreetmap/josm/data/validation/tests/CycleDetectorTest.java new file mode 100644 index 00000000000..9cea7c9ac76 --- /dev/null +++ b/test/unit/org/openstreetmap/josm/data/validation/tests/CycleDetectorTest.java @@ -0,0 +1,41 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.data.validation.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.openstreetmap.josm.TestUtils; +import org.openstreetmap.josm.data.osm.DataSet; +import org.openstreetmap.josm.io.OsmReader; +import org.openstreetmap.josm.testutils.annotations.BasicPreferences; + +/** + * JUnit test for {@link CycleDetector} validation test. + */ +@BasicPreferences +class CycleDetectorTest { + + @Test + void testCycleDetection() throws Exception { + CycleDetector cycleDetector = new CycleDetector(); + DataSet ds = OsmReader.parseDataSet(TestUtils.getRegressionDataStream(21881, "CycleDetector_test_wikipedia.osm"), null); + cycleDetector.startTest(null); + cycleDetector.visit(ds.allPrimitives()); + cycleDetector.endTest(); + + // we have 4 cycles in the test file + assertEquals(4, cycleDetector.getErrors().size()); + } + + @Test + void testNotConsecutive() throws Exception { + CycleDetector cycleDetector = new CycleDetector(); + DataSet ds = OsmReader.parseDataSet(TestUtils.getRegressionDataStream(21881, "not_consecutive.osm"), null); + cycleDetector.startTest(null); + cycleDetector.visit(ds.allPrimitives()); + cycleDetector.endTest(); + + // we have 1 cycles in the test file + assertEquals(1, cycleDetector.getErrors().size()); + } +} diff --git a/test/unit/org/openstreetmap/josm/data/validation/tests/DuplicateRelationTest.java b/test/unit/org/openstreetmap/josm/data/validation/tests/DuplicateRelationTest.java index c6676b105e8..fa6e3034155 100644 --- a/test/unit/org/openstreetmap/josm/data/validation/tests/DuplicateRelationTest.java +++ b/test/unit/org/openstreetmap/josm/data/validation/tests/DuplicateRelationTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; import org.openstreetmap.josm.TestUtils; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.DataSet; @@ -12,8 +13,6 @@ import org.openstreetmap.josm.gui.progress.NullProgressMonitor; import org.openstreetmap.josm.testutils.annotations.BasicPreferences; -import org.junit.jupiter.api.Test; - /** * JUnit Test of "Duplicate relation" validation test. */ @@ -44,6 +43,8 @@ private void performTest(DataSet ds, ExpectedResult... expectations) { ExpectedResult expected = expectations[i++]; assertEquals(expected.code, error.getCode()); assertEquals(expected.fixable, error.isFixable()); + if (error.isFixable()) + error.getFix(); } } @@ -63,8 +64,8 @@ private static DataSet buildDataSet(String tags1, String tags2) { @Test void testDuplicateRelationNoTags() { doTest("", "", - new ExpectedResult(DuplicateRelation.DUPLICATE_RELATION, true), - new ExpectedResult(DuplicateRelation.SAME_RELATION, false)); + new ExpectedResult(DuplicateRelation.DUPLICATE_RELATION, true) + ); } /** @@ -73,8 +74,8 @@ void testDuplicateRelationNoTags() { @Test void testDuplicateRelationSameTags() { doTest("type=boundary", "type=boundary", - new ExpectedResult(DuplicateRelation.DUPLICATE_RELATION, true), - new ExpectedResult(DuplicateRelation.SAME_RELATION, false)); + new ExpectedResult(DuplicateRelation.DUPLICATE_RELATION, true) + ); } /** @@ -83,6 +84,122 @@ void testDuplicateRelationSameTags() { @Test void testDuplicateRelationDifferentTags() { doTest("type=boundary", "type=multipolygon", - new ExpectedResult(DuplicateRelation.SAME_RELATION, false)); + new ExpectedResult(DuplicateRelation.IDENTICAL_MEMBERLIST, false)); + } + + /** + * Test of duplicate "tmc" relation, should not be ignored + */ + @Test + void testTMCRelation1() { + doTest("type=tmc t1=v1", "type=tmc t1=v1", + new ExpectedResult(DuplicateRelation.DUPLICATE_RELATION, true)); + } + + /** + * Test of "tmc" relation with equal members but different tags, should be ignored + */ + @Test + void testTMCRelation2() { + doTest("type=tmc t1=v1", "type=tmc t1=v2"); + } + + /** + * Test with incomplete members + */ + @Test + void testIncomplete() { + DataSet ds = new DataSet(); + + Node a = new Node(1234); + ds.addPrimitive(a); + ds.addPrimitive(TestUtils.newRelation("type=multipolygon", new RelationMember(null, a))); + ds.addPrimitive(TestUtils.newRelation("type=multipolygon", new RelationMember(null, a))); + performTest(ds, new ExpectedResult(DuplicateRelation.DUPLICATE_RELATION, true)); + } + + /** + * Test with different order of members, order doesn't count + */ + @Test + void testMemberOrder1() { + DataSet ds = new DataSet(); + + Node a = new Node(1); + Node b = new Node(2); + ds.addPrimitive(a); + ds.addPrimitive(b); + ds.addPrimitive(TestUtils.newRelation("type=multipolygon", new RelationMember(null, a), new RelationMember(null, b))); + ds.addPrimitive(TestUtils.newRelation("type=multipolygon", new RelationMember(null, b), new RelationMember(null, a))); + performTest(ds, new ExpectedResult(DuplicateRelation.DUPLICATE_RELATION, true)); + } + + /** + * Test with different order of members, order counts + */ + @Test + void testMemberOrder2() { + DataSet ds = new DataSet(); + + Node a = new Node(1); + a.setCoor(new LatLon(10.0, 5.0)); + Node b = new Node(2); + b.setCoor(new LatLon(10.0, 6.0)); + ds.addPrimitive(a); + ds.addPrimitive(b); + ds.addPrimitive(TestUtils.newRelation("type=route", new RelationMember(null, a), new RelationMember(null, b))); + ds.addPrimitive(TestUtils.newRelation("type=route", new RelationMember(null, b), new RelationMember(null, a))); + performTest(ds, new ExpectedResult(DuplicateRelation.SAME_RELATION, false)); + } + + /** + * Test with different order of members, one is duplicated, order doesn't matter + */ + @Test + void testMemberOrder3() { + DataSet ds = new DataSet(); + + Node a = new Node(1); + Node b = new Node(2); + ds.addPrimitive(a); + ds.addPrimitive(b); + ds.addPrimitive(TestUtils.newRelation("type=restriction", new RelationMember(null, a), + new RelationMember(null, b), new RelationMember(null, a))); + ds.addPrimitive(TestUtils.newRelation("type=restriction", new RelationMember(null, b), + new RelationMember(null, a), new RelationMember(null, a))); + performTest(ds, new ExpectedResult(DuplicateRelation.DUPLICATE_RELATION, true)); + } + + /** + * Test with different order of members, one is duplicated in one of the relations + */ + @Test + void testMemberOrder4() { + DataSet ds = new DataSet(); + Node a = new Node(new LatLon(10.0, 5.0)); + Node b = new Node(new LatLon(10.0, 6.0)); + ds.addPrimitive(a); + ds.addPrimitive(b); + ds.addPrimitive(TestUtils.newRelation("", new RelationMember(null, a), new RelationMember(null, b))); + ds.addPrimitive(TestUtils.newRelation("", new RelationMember(null, b), new RelationMember(null, a), new RelationMember(null, b))); + performTest(ds); + } + + /** + * Test with two relations where members are different but geometry is equal. + */ + @Test + void testImport() { + DataSet ds = new DataSet(); + Node a = new Node(1234, 1); + a.setCoor(new LatLon(10.0, 5.0)); + + Node b = new Node(new LatLon(10.0, 5.0)); + + ds.addPrimitive(a); + ds.addPrimitive(b); + ds.addPrimitive(TestUtils.newRelation("type=multipolygon", new RelationMember(null, a))); + ds.addPrimitive(TestUtils.newRelation("type=multipolygon", new RelationMember(null, b))); + performTest(ds, new ExpectedResult(DuplicateRelation.DUPLICATE_RELATION, true)); } } diff --git a/test/unit/org/openstreetmap/josm/data/validation/tests/DuplicateWayTest.java b/test/unit/org/openstreetmap/josm/data/validation/tests/DuplicateWayTest.java index 8966642e5a6..b46e61cd037 100644 --- a/test/unit/org/openstreetmap/josm/data/validation/tests/DuplicateWayTest.java +++ b/test/unit/org/openstreetmap/josm/data/validation/tests/DuplicateWayTest.java @@ -34,9 +34,11 @@ private static void doTest(int code, String tags) { private static void doTest(int code, String tags1, String tags2, boolean fixable) { performTest(code, buildDataSet(tags1, tags2), fixable); + performPartialTest(code, buildDataSet(tags1, tags2), fixable); } private static void performTest(int code, DataSet ds, boolean fixable) { + TEST.setPartialSelection(false); TEST.startTest(NullProgressMonitor.INSTANCE); TEST.visit(ds.allPrimitives()); TEST.endTest(); @@ -47,6 +49,19 @@ private static void performTest(int code, DataSet ds, boolean fixable) { assertEquals(fixable, error.isFixable()); } + private static void performPartialTest(int code, DataSet ds, boolean fixable) { + ds.setSelected(ds.getWays().iterator().next()); + TEST.setPartialSelection(true); + TEST.startTest(NullProgressMonitor.INSTANCE); + TEST.visit(ds.getSelectedWays().iterator().next()); + TEST.endTest(); + + assertEquals(1, TEST.getErrors().size()); + TestError error = TEST.getErrors().iterator().next(); + assertEquals(code, error.getCode()); + assertEquals(fixable, error.isFixable()); + } + private static DataSet buildDataSet(String tags1, String tags2) { DataSet ds = new DataSet(); diff --git a/test/unit/org/openstreetmap/josm/data/validation/tests/HighwaysTest.java b/test/unit/org/openstreetmap/josm/data/validation/tests/HighwaysTest.java index 6cb10a5d5d9..8827501eccb 100644 --- a/test/unit/org/openstreetmap/josm/data/validation/tests/HighwaysTest.java +++ b/test/unit/org/openstreetmap/josm/data/validation/tests/HighwaysTest.java @@ -114,4 +114,15 @@ void testTicket14891() throws Exception { assertEquals(2, test.getErrors().size()); } } + + /** + * Test all error cases manually created in data.osm. + * @throws Exception in case of error + */ + @Test + void testTicket9304() throws Exception { + final Highways test = new Highways(); + ValidatorTestUtils.testSampleFile("nodist/data/9304-examples.osm", DataSet::getNodes, null, test); + } + } diff --git a/test/unit/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerTest.java b/test/unit/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerTest.java index 8c44a6c97c3..f5ae4d7458d 100644 --- a/test/unit/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerTest.java +++ b/test/unit/org/openstreetmap/josm/data/validation/tests/MapCSSTagCheckerTest.java @@ -1,6 +1,7 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.data.validation.tests; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; @@ -30,6 +31,7 @@ import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.OsmUtils; +import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.data.preferences.sources.ExtendedSourceEntry; import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper; import org.openstreetmap.josm.data.validation.Severity; @@ -40,6 +42,7 @@ import org.openstreetmap.josm.gui.mappaint.MapPaintStyles; import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource; import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException; +import org.openstreetmap.josm.gui.progress.NullProgressMonitor; import org.openstreetmap.josm.io.OsmReader; import org.openstreetmap.josm.testutils.annotations.BasicPreferences; import org.openstreetmap.josm.testutils.annotations.Projection; @@ -446,4 +449,32 @@ void testTicket19053() throws ParseException { assertEquals("12.12", p.get("ele")); } + /** + * A water area inside a coastline, where the coastline way is oriented away from the water area + * (the water area is not inside the ocean). + */ + @Test + void testTicket23308() { + final MapCSSTagChecker test = new MapCSSTagChecker(); + final Way innerWay = TestUtils.newWay("natural=water", + new Node(new LatLon(32.775, -117.238)), + new Node(new LatLon(32.774, -117.238)), + new Node(new LatLon(32.774, -117.237)), + new Node(new LatLon(32.775, -117.237))); + final Way outerWay = TestUtils.newWay("natural=coastline", + new Node(new LatLon(32.779, -117.232)), + new Node(new LatLon(32.777, -117.241)), + new Node(new LatLon(32.771, -117.240)), + new Node(new LatLon(32.771, -117.235))); + final DataSet ds = new DataSet(); + ds.addPrimitiveRecursive(innerWay); + ds.addPrimitiveRecursive(outerWay); + innerWay.addNode(innerWay.firstNode()); + outerWay.addNode(outerWay.firstNode()); + assertDoesNotThrow(test::initialize); + test.startTest(NullProgressMonitor.INSTANCE); + test.visit(ds.allPrimitives()); + test.endTest(); + assertTrue(test.getErrors().isEmpty()); + } } diff --git a/test/unit/org/openstreetmap/josm/data/validation/tests/TagCheckerTest.java b/test/unit/org/openstreetmap/josm/data/validation/tests/TagCheckerTest.java index 5b75659046c..fe3f00e6b2d 100644 --- a/test/unit/org/openstreetmap/josm/data/validation/tests/TagCheckerTest.java +++ b/test/unit/org/openstreetmap/josm/data/validation/tests/TagCheckerTest.java @@ -19,11 +19,13 @@ import org.openstreetmap.josm.data.osm.Tag; import org.openstreetmap.josm.data.validation.Severity; import org.openstreetmap.josm.data.validation.TestError; +import org.openstreetmap.josm.testutils.annotations.I18n; import org.openstreetmap.josm.testutils.annotations.TaggingPresets; /** * JUnit Test of {@link TagChecker}. */ +@I18n @TaggingPresets class TagCheckerTest { List test(OsmPrimitive primitive) throws IOException { @@ -228,6 +230,27 @@ void testValueDifferentCase() throws IOException { assertFalse(errors.get(0).isFixable()); } + @Test + void testRegionKey() throws IOException { + final List errors = test(OsmUtils.createPrimitive("node highway=crossing crossing_ref=zebra")); + assertEquals(1, errors.size()); + assertEquals("Key from a preset is invalid in this region", errors.get(0).getMessage()); + assertEquals("Preset Pedestrian Crossing should not have the key crossing_ref", errors.get(0).getDescription()); + assertEquals(Severity.WARNING, errors.get(0).getSeverity()); + assertFalse(errors.get(0).isFixable()); + + } + + @Test + void testRegionTag() throws IOException { + final List errors = test(OsmUtils.createPrimitive("relation type=waterway gnis:feature_id=123456")); + assertEquals(1, errors.size()); + assertEquals("Key from a preset is invalid in this region", errors.get(0).getMessage()); + assertEquals("Preset Waterway should not have the key gnis:feature_id", errors.get(0).getDescription()); + assertEquals(Severity.WARNING, errors.get(0).getSeverity()); + assertFalse(errors.get(0).isFixable()); + } + /** * Key in presets but not in ignored.cfg. Caused a NPE with r14727. * @throws IOException if any I/O error occurs diff --git a/test/unit/org/openstreetmap/josm/gui/dialogs/InspectPrimitiveDialogTest.java b/test/unit/org/openstreetmap/josm/gui/dialogs/InspectPrimitiveDialogTest.java index 86539abb3ae..30a3c168d60 100644 --- a/test/unit/org/openstreetmap/josm/gui/dialogs/InspectPrimitiveDialogTest.java +++ b/test/unit/org/openstreetmap/josm/gui/dialogs/InspectPrimitiveDialogTest.java @@ -77,6 +77,7 @@ void testBuildDataText() { way.addNode(way.firstNode()); // close way assertEqualsNewline( "Way: 1\n" + + " State: referrers-not-all-downloaded\n" + " Data Set: "+Integer.toHexString(ds.hashCode())+"\n" + " Edited at: \n" + " Edited by: \n" + diff --git a/test/unit/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditorTest.java b/test/unit/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditorTest.java index 7e1176dd173..ad4bf39b113 100644 --- a/test/unit/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditorTest.java +++ b/test/unit/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditorTest.java @@ -323,9 +323,9 @@ private static T getComponent(Container parent, int... tre return (T) current; } - private static class PasteMembersActionMock extends MockUp { + private static final class PasteMembersActionMock extends MockUp { @Mock - protected void updateEnabledState() { + public void updateEnabledState() { // Do nothing } } diff --git a/test/unit/org/openstreetmap/josm/gui/dialogs/relation/actions/RelationEditorActionsTest.java b/test/unit/org/openstreetmap/josm/gui/dialogs/relation/actions/RelationEditorActionsTest.java index 2eda4d69413..ae6a7e687af 100644 --- a/test/unit/org/openstreetmap/josm/gui/dialogs/relation/actions/RelationEditorActionsTest.java +++ b/test/unit/org/openstreetmap/josm/gui/dialogs/relation/actions/RelationEditorActionsTest.java @@ -16,6 +16,7 @@ import org.openstreetmap.josm.TestUtils; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.DataSet; +import org.openstreetmap.josm.data.osm.IRelation; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.Relation; import org.openstreetmap.josm.data.osm.RelationMember; @@ -168,6 +169,11 @@ void testNonRegression22024() { relationEditorAccess.getMemberTableModel().populate(relation); relationEditorAccess.getTagModel().initFromPrimitive(relation); relationEditorAccess.getEditor().reloadDataFromRelation(); - assertDoesNotThrow(relationEditorAccess::getChangedRelation); + + assertDoesNotThrow(() -> { + IRelation tempRelation = relationEditorAccess.getChangedRelation(); + if (tempRelation.getDataSet() == null) + tempRelation.setMembers(null); // see #19885 + }); } } diff --git a/test/unit/org/openstreetmap/josm/gui/io/DownloadOpenChangesetsTaskTest.java b/test/unit/org/openstreetmap/josm/gui/io/DownloadOpenChangesetsTaskTest.java index 0a5bf56213e..8ce5e668b92 100644 --- a/test/unit/org/openstreetmap/josm/gui/io/DownloadOpenChangesetsTaskTest.java +++ b/test/unit/org/openstreetmap/josm/gui/io/DownloadOpenChangesetsTaskTest.java @@ -8,17 +8,16 @@ import java.awt.GraphicsEnvironment; import java.net.URL; -import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import javax.swing.JOptionPane; import javax.swing.JPanel; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.openstreetmap.josm.TestUtils; import org.openstreetmap.josm.data.UserIdentityManager; import org.openstreetmap.josm.gui.oauth.OAuthAuthorizationWizard; -import org.openstreetmap.josm.spi.preferences.Config; import org.openstreetmap.josm.testutils.annotations.BasicPreferences; import org.openstreetmap.josm.testutils.annotations.OsmApi; import org.openstreetmap.josm.testutils.mockers.JOptionPaneSimpleMocker; @@ -62,14 +61,6 @@ void obtainAccessToken(final Invocation invocation, final URL serverUrl) { } } - /** - * These tests were written with {@link org.openstreetmap.josm.data.oauth.OAuthVersion#OAuth10a} as the default auth method. - */ - @BeforeEach - void setup() { - Config.getPref().put("osm-server.auth-method", "oauth"); - } - /** * Test of {@link DownloadOpenChangesetsTask} class when anonymous. */ @@ -79,14 +70,12 @@ void testAnonymous() { if (GraphicsEnvironment.isHeadless()) { new WindowMocker(); } - final OAuthWizardMocker oaWizardMocker = new OAuthWizardMocker(); - final JOptionPaneSimpleMocker jopsMocker = new JOptionPaneSimpleMocker( - Collections.singletonMap( - "Could not retrieve the list of your open changesets because
          JOSM does not know " + final Map optionPaneMock = new HashMap<>(2); + optionPaneMock.put("Could not retrieve the list of your open changesets because
          JOSM does not know " + "your identity.
          You have either chosen to work anonymously or you are not " - + "entitled
          to know the identity of the user on whose behalf you are working.", JOptionPane.OK_OPTION - ) - ); + + "entitled
          to know the identity of the user on whose behalf you are working.", JOptionPane.OK_OPTION); + optionPaneMock.put("Obtain OAuth 2.0 token for authentication?", JOptionPane.NO_OPTION); + final JOptionPaneSimpleMocker jopsMocker = new JOptionPaneSimpleMocker(optionPaneMock); DownloadOpenChangesetsTask task = new DownloadOpenChangesetsTask(new JPanel()); assertNull(task.getChangesets()); @@ -95,12 +84,14 @@ void testAnonymous() { task.run(); assertNull(task.getChangesets()); - assertEquals(1, jopsMocker.getInvocationLog().size()); - Object[] invocationLogEntry = jopsMocker.getInvocationLog().get(0); + assertEquals(2, jopsMocker.getInvocationLog().size()); + Object[] invocationLogEntry = jopsMocker.getInvocationLog().get(1); assertEquals(JOptionPane.OK_OPTION, (int) invocationLogEntry[0]); assertEquals("Missing user identity", invocationLogEntry[2]); - assertTrue(oaWizardMocker.called); + invocationLogEntry = jopsMocker.getInvocationLog().get(0); + assertEquals(JOptionPane.NO_OPTION, (int) invocationLogEntry[0]); + assertEquals("Obtain authentication to OSM servers", invocationLogEntry[2]); } /** @@ -112,10 +103,10 @@ void testPartiallyIdentified() { if (GraphicsEnvironment.isHeadless()) { new WindowMocker(); } - final OAuthWizardMocker oaWizardMocker = new OAuthWizardMocker(); - final JOptionPaneSimpleMocker jopsMocker = new JOptionPaneSimpleMocker( - Collections.singletonMap("There are no open changesets", JOptionPane.OK_OPTION) - ); + final Map optionPaneMock = new HashMap<>(2); + optionPaneMock.put("There are no open changesets", JOptionPane.OK_OPTION); + optionPaneMock.put("Obtain OAuth 2.0 token for authentication?", JOptionPane.NO_OPTION); + final JOptionPaneSimpleMocker jopsMocker = new JOptionPaneSimpleMocker(optionPaneMock); DownloadOpenChangesetsTask task = new DownloadOpenChangesetsTask(new JPanel()); UserIdentityManager.getInstance().setPartiallyIdentified(System.getProperty("osm.username", "josm_test")); @@ -123,11 +114,13 @@ void testPartiallyIdentified() { task.run(); assertNotNull(task.getChangesets()); - assertEquals(1, jopsMocker.getInvocationLog().size()); - Object[] invocationLogEntry = jopsMocker.getInvocationLog().get(0); + assertEquals(2, jopsMocker.getInvocationLog().size()); + Object[] invocationLogEntry = jopsMocker.getInvocationLog().get(1); assertEquals(JOptionPane.OK_OPTION, (int) invocationLogEntry[0]); assertEquals("No open changesets", invocationLogEntry[2]); - assertTrue(oaWizardMocker.called); + invocationLogEntry = jopsMocker.getInvocationLog().get(0); + assertEquals(JOptionPane.NO_OPTION, (int) invocationLogEntry[0]); + assertEquals("Obtain authentication to OSM servers", invocationLogEntry[2]); } } diff --git a/test/unit/org/openstreetmap/josm/gui/io/SaveLayersDialogTest.java b/test/unit/org/openstreetmap/josm/gui/io/SaveLayersDialogTest.java index 76a1214c037..db1c196dfcf 100644 --- a/test/unit/org/openstreetmap/josm/gui/io/SaveLayersDialogTest.java +++ b/test/unit/org/openstreetmap/josm/gui/io/SaveLayersDialogTest.java @@ -8,6 +8,9 @@ import java.awt.Component; import java.awt.GraphicsEnvironment; import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.Collections; import java.util.List; @@ -16,22 +19,23 @@ import javax.swing.JList; import javax.swing.JOptionPane; -import mockit.Invocation; -import mockit.Mock; -import mockit.MockUp; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; -import org.openstreetmap.josm.command.AddPrimitivesCommand; -import org.openstreetmap.josm.data.coor.LatLon; +import org.openstreetmap.josm.TestUtils; import org.openstreetmap.josm.data.osm.DataSet; -import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.UploadPolicy; import org.openstreetmap.josm.gui.layer.OsmDataLayer; +import org.openstreetmap.josm.io.IllegalDataException; +import org.openstreetmap.josm.io.OsmReader; import org.openstreetmap.josm.testutils.annotations.BasicPreferences; import org.openstreetmap.josm.testutils.mockers.JOptionPaneSimpleMocker; import org.openstreetmap.josm.testutils.mockers.WindowMocker; +import mockit.Invocation; +import mockit.Mock; +import mockit.MockUp; + /** * Unit tests of {@link SaveLayersDialog} class. */ @@ -125,37 +129,46 @@ public List getLayersWithIllegalFilesAndSaveRequest() { /** * Non-regression test for #22817: No warning when deleting a layer with changes and discourages upload * @param policy The upload policy to test + * @throws IOException if an error occurs + * @throws IllegalDataException if an error occurs */ @ParameterizedTest @EnumSource(value = UploadPolicy.class) - void testNonRegression22817(UploadPolicy policy) { - final OsmDataLayer osmDataLayer = new OsmDataLayer(new DataSet(), null, null); + void testNonRegression22817(UploadPolicy policy) throws IOException, IllegalDataException { + File file = new File(TestUtils.getRegressionDataFile(22817, "data.osm")); + InputStream is = new FileInputStream(file); + final OsmDataLayer osmDataLayer = new OsmDataLayer(OsmReader.parseDataSet(is, null), null, null); + osmDataLayer.onPostLoadFromFile(); osmDataLayer.getDataSet().setUploadPolicy(policy); - // BLOCKED files don't have a way to become blocked via the UI, so they must be loaded from disk. - if (policy == UploadPolicy.BLOCKED) { - osmDataLayer.setAssociatedFile(new File("/dev/null")); - } - new AddPrimitivesCommand(Collections.singletonList(new Node(LatLon.ZERO).save()), Collections.emptyList(), osmDataLayer.getDataSet()) - .executeCommand(); + osmDataLayer.setAssociatedFile(file); assertTrue(osmDataLayer.getDataSet().isModified()); + assertFalse(osmDataLayer.requiresSaveToFile()); + assertTrue(osmDataLayer.getDataSet().requiresUploadToServer()); + assertEquals(policy != UploadPolicy.BLOCKED, osmDataLayer.requiresUploadToServer()); + assertEquals(policy != UploadPolicy.BLOCKED, osmDataLayer.isUploadable()); new WindowMocker(); // Needed since the *first call* is to check whether we are in a headless environment new GraphicsEnvironmentMock(); // Needed since we need to mock out the UI SaveLayersDialogMock saveLayersDialogMock = new SaveLayersDialogMock(); - assertTrue(SaveLayersDialog.saveUnsavedModifications(Collections.singleton(osmDataLayer), SaveLayersDialog.Reason.DELETE)); - assertEquals(1, saveLayersDialogMock.getUserActionCalled, "The user should have been asked for an action on the layer"); + int res = saveLayersDialogMock.getUserActionCalled; + if (policy == UploadPolicy.NORMAL) { + assertEquals(1, res, "The user should have been asked for an action on the layer"); + } else { + assertEquals(0, res, "The user should not have been asked for an action on the layer"); + + } } - private static class GraphicsEnvironmentMock extends MockUp { + private static final class GraphicsEnvironmentMock extends MockUp { @Mock public static boolean isHeadless(Invocation invocation) { return false; } } - private static class SaveLayersDialogMock extends MockUp { + private static final class SaveLayersDialogMock extends MockUp { private final SaveLayersModel model = new SaveLayersModel(); private int getUserActionCalled = 0; @Mock diff --git a/test/unit/org/openstreetmap/josm/gui/io/importexport/OsmPbfImporterTest.java b/test/unit/org/openstreetmap/josm/gui/io/importexport/OsmPbfImporterTest.java index 988a9e49b9c..b6b8163e697 100644 --- a/test/unit/org/openstreetmap/josm/gui/io/importexport/OsmPbfImporterTest.java +++ b/test/unit/org/openstreetmap/josm/gui/io/importexport/OsmPbfImporterTest.java @@ -23,8 +23,10 @@ import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.AbstractPrimitive; import org.openstreetmap.josm.data.osm.DataSet; +import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.OsmPrimitiveType; import org.openstreetmap.josm.data.osm.Relation; +import org.openstreetmap.josm.data.osm.UploadPolicy; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.data.protobuf.ProtobufTest; import org.openstreetmap.josm.gui.progress.NullProgressMonitor; @@ -142,4 +144,72 @@ void testIdParsing() throws IOException, IllegalDataException { assertNotNull(dataSet.getPrimitiveById(9223372036854775806L, OsmPrimitiveType.WAY)); assertNotNull(dataSet.getPrimitiveById(9223372036854775806L, OsmPrimitiveType.RELATION)); } + + /** + * Non-regression test for #23550: Error when deserializing PBF blob when generator writes the blob then + * the compression type. + */ + @Test + void testNonRegression23550() { + final byte[] badData = HEADER_DATA.clone(); + final byte[] sizeInfo = Arrays.copyOfRange(badData, 18, 21); + for (int i = 18; i < badData.length - sizeInfo.length; i++) { + badData[i] = badData[i + sizeInfo.length]; + } + System.arraycopy(sizeInfo, 0, badData, badData.length - 3, 3); + // the data doesn't include any "real" data, but the problematic code path is exercised by the header parsing code. + assertDoesNotThrow(() -> importer.parseDataSet(new ByteArrayInputStream(badData), NullProgressMonitor.INSTANCE)); + } + + @Test + void testNonRegression23599a() throws IOException, IllegalDataException { + final DataSet dataSet; + try (InputStream inputStream = TestUtils.getRegressionDataStream(23599, "visible.osm.pbf")) { + dataSet = importer.parseDataSet(inputStream, NullProgressMonitor.INSTANCE); + } + assertTrue(dataSet.getNodes().stream().allMatch(OsmPrimitive::isVisible)); + assertTrue(dataSet.getWays().stream().allMatch(OsmPrimitive::isVisible)); + assertTrue(dataSet.getRelations().stream().allMatch(OsmPrimitive::isVisible)); + + } + + @Test + void testNonRegression23599b() throws IOException, IllegalDataException { + final DataSet dataSet; + // osmconvert w1194668585.orig.osm -o=w1194668585.full.osm.pbf + try (InputStream inputStream = TestUtils.getRegressionDataStream(23599, "w1194668585.full.osm.pbf")) { + dataSet = importer.parseDataSet(inputStream, NullProgressMonitor.INSTANCE); + } + assertNotNull(dataSet.getPrimitiveById(1194668585L, OsmPrimitiveType.WAY)); + assertEquals(145987123, dataSet.getPrimitiveById(1194668585L, OsmPrimitiveType.WAY).getChangesetId()); + assertEquals(4, dataSet.getPrimitiveById(1194668585L, OsmPrimitiveType.WAY).getVersion()); + assertEquals(UploadPolicy.NORMAL, dataSet.getUploadPolicy()); + } + + @Test + void testNonRegression23599c() throws IOException, IllegalDataException { + final DataSet dataSet; + // osmconvert --drop-author w1194668585.orig.osm -o=w1194668585.full.osm.pbf + try (InputStream inputStream = TestUtils.getRegressionDataStream(23599, "w1194668585.drop-author.osm.pbf")) { + dataSet = importer.parseDataSet(inputStream, NullProgressMonitor.INSTANCE); + } + assertNotNull(dataSet.getPrimitiveById(1194668585L, OsmPrimitiveType.WAY)); + assertEquals(0, dataSet.getPrimitiveById(1194668585L, OsmPrimitiveType.WAY).getChangesetId()); + assertEquals(4, dataSet.getPrimitiveById(1194668585L, OsmPrimitiveType.WAY).getVersion()); + assertEquals(UploadPolicy.NORMAL, dataSet.getUploadPolicy()); + } + + @Test + void testNonRegression23599d() throws IOException, IllegalDataException { + final DataSet dataSet; + // osmconvert --drop-version w1194668585.orig.osm -o=w1194668585.full.osm.pbf + try (InputStream inputStream = TestUtils.getRegressionDataStream(23599, "w1194668585.drop-version.osm.pbf")) { + dataSet = importer.parseDataSet(inputStream, NullProgressMonitor.INSTANCE); + } + assertNotNull(dataSet.getPrimitiveById(1194668585L, OsmPrimitiveType.WAY)); + assertEquals(0, dataSet.getPrimitiveById(1194668585L, OsmPrimitiveType.WAY).getChangesetId()); + assertEquals(0, dataSet.getPrimitiveById(1194668585L, OsmPrimitiveType.WAY).getVersion()); + assertEquals(UploadPolicy.DISCOURAGED, dataSet.getUploadPolicy()); + } + } diff --git a/test/unit/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayerTest.java b/test/unit/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayerTest.java index 84a28f183c8..549de9c44b9 100644 --- a/test/unit/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayerTest.java +++ b/test/unit/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayerTest.java @@ -11,6 +11,7 @@ import org.openstreetmap.josm.gui.layer.OsmDataLayer; import org.openstreetmap.josm.testutils.annotations.BasicPreferences; import org.openstreetmap.josm.testutils.annotations.Main; +import org.openstreetmap.josm.testutils.annotations.ThreadSync; /** * Unit tests of {@link GeoImageLayer} class. @@ -21,6 +22,11 @@ class GeoImageLayerTest { @AfterEach void tearDown() { + // We need to ensure that all threads are "done" before continuing. + // Otherwise, other tests may have an ImageViewerDialog that causes issues... + // Note: we cannot (currently) use the ThreadSync annotation since it runs + // *after* local AfterEach and AfterAll methods. + new ThreadSync.ThreadSyncExtension().threadSync(); if (ImageViewerDialog.hasInstance()) { ImageViewerDialog.getInstance().destroy(); } diff --git a/test/unit/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayerTest.java b/test/unit/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayerTest.java index fc579f4a3cb..0852b7a0618 100644 --- a/test/unit/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayerTest.java +++ b/test/unit/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayerTest.java @@ -6,6 +6,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.net.MalformedURLException; +import java.net.URI; import java.util.Collections; import org.junit.jupiter.api.BeforeEach; @@ -21,6 +23,7 @@ import org.openstreetmap.josm.gui.layer.OsmDataLayer; import org.openstreetmap.josm.spi.preferences.Config; import org.openstreetmap.josm.testutils.annotations.BasicPreferences; +import org.openstreetmap.josm.testutils.annotations.I18n; import org.openstreetmap.josm.testutils.annotations.Main; import org.openstreetmap.josm.testutils.annotations.Projection; @@ -30,6 +33,7 @@ @BasicPreferences @Main @Projection +@I18n class MarkerLayerTest { /** * Setup tests @@ -91,4 +95,15 @@ void testPlayHeadMarker() { } } } + + /** + * Ensure that if a file is unable to be read, we return an empty list instead of a list with {@code null} in it. + */ + @Test + void testNonRegression23316() throws MalformedURLException { + MarkerLayer layer = new MarkerLayer(new GpxData(), null, null, null); + layer.setCurrentMarker(new ImageMarker(LatLon.ZERO, URI.create("file:/not_a_real_file_123456789.jpg").toURL(), + layer, 0, 0)); + assertEquals(Collections.emptyList(), layer.getSelection()); + } } diff --git a/test/unit/org/openstreetmap/josm/gui/mappaint/RenderingCLIAreaTest.java b/test/unit/org/openstreetmap/josm/gui/mappaint/RenderingCLIAreaTest.java index 2a119cb4250..90f2c071c2f 100644 --- a/test/unit/org/openstreetmap/josm/gui/mappaint/RenderingCLIAreaTest.java +++ b/test/unit/org/openstreetmap/josm/gui/mappaint/RenderingCLIAreaTest.java @@ -44,7 +44,7 @@ public static Collection runs() { // 1 runs.add(new Object[] {"--scale 4000 --bounds " + param(bFeldberg), - CoreMatchers.is(scaleFeldberg4000), + isFP(scaleFeldberg4000, ErrorMode.ABSOLUTE, 0.000000000001d), CoreMatchers.is(bFeldberg)}); // 2 diff --git a/test/unit/org/openstreetmap/josm/gui/oauth/FullyAutomaticAuthorizationUITest.java b/test/unit/org/openstreetmap/josm/gui/oauth/FullyAutomaticAuthorizationUITest.java index da21cc9368f..4194c3e830c 100644 --- a/test/unit/org/openstreetmap/josm/gui/oauth/FullyAutomaticAuthorizationUITest.java +++ b/test/unit/org/openstreetmap/josm/gui/oauth/FullyAutomaticAuthorizationUITest.java @@ -1,13 +1,14 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.oauth; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.openstreetmap.josm.data.oauth.OAuthVersion; import org.openstreetmap.josm.gui.MainApplication; import org.openstreetmap.josm.testutils.annotations.BasicPreferences; -import org.junit.jupiter.api.Test; - /** * Unit tests of {@link FullyAutomaticAuthorizationUI} class. */ @@ -16,8 +17,9 @@ class FullyAutomaticAuthorizationUITest { /** * Unit test of {@link FullyAutomaticAuthorizationUI#FullyAutomaticAuthorizationUI}. */ - @Test - void testFullyAutomaticAuthorizationUI() { - assertNotNull(new FullyAutomaticAuthorizationUI("", MainApplication.worker)); + @ParameterizedTest + @EnumSource(OAuthVersion.class) + void testFullyAutomaticAuthorizationUI(OAuthVersion version) { + assertDoesNotThrow(() -> new FullyAutomaticAuthorizationUI("", MainApplication.worker, version)); } } diff --git a/test/unit/org/openstreetmap/josm/gui/oauth/ManualAuthorizationUITest.java b/test/unit/org/openstreetmap/josm/gui/oauth/ManualAuthorizationUITest.java index dc12267d787..9f702c29e89 100644 --- a/test/unit/org/openstreetmap/josm/gui/oauth/ManualAuthorizationUITest.java +++ b/test/unit/org/openstreetmap/josm/gui/oauth/ManualAuthorizationUITest.java @@ -1,9 +1,10 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.oauth; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import org.junit.jupiter.api.Test; +import org.openstreetmap.josm.data.oauth.OAuthVersion; import org.openstreetmap.josm.gui.MainApplication; import org.openstreetmap.josm.testutils.annotations.BasicPreferences; @@ -17,6 +18,6 @@ class ManualAuthorizationUITest { */ @Test void testManualAuthorizationUI() { - assertNotNull(new ManualAuthorizationUI("", MainApplication.worker)); + assertDoesNotThrow(() -> new ManualAuthorizationUI("", MainApplication.worker, OAuthVersion.OAuth20)); } } diff --git a/test/unit/org/openstreetmap/josm/gui/oauth/OsmOAuthAuthorizationClientTest.java b/test/unit/org/openstreetmap/josm/gui/oauth/OsmOAuthAuthorizationClientTest.java deleted file mode 100644 index bc39d73d2eb..00000000000 --- a/test/unit/org/openstreetmap/josm/gui/oauth/OsmOAuthAuthorizationClientTest.java +++ /dev/null @@ -1,116 +0,0 @@ -// License: GPL. For details, see LICENSE file. -package org.openstreetmap.josm.gui.oauth; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; - -import java.net.CookieHandler; -import java.net.CookieManager; -import java.net.URI; -import java.util.Collections; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout; -import org.openstreetmap.josm.data.oauth.OAuthParameters; -import org.openstreetmap.josm.data.oauth.OAuthToken; -import org.openstreetmap.josm.io.OsmTransferCanceledException; -import org.openstreetmap.josm.testutils.annotations.BasicPreferences; -import org.openstreetmap.josm.testutils.annotations.BasicWiremock; -import org.openstreetmap.josm.testutils.annotations.HTTP; - -import com.github.tomakehurst.wiremock.WireMockServer; - -/** - * Unit tests of {@link OsmOAuthAuthorizationClient} class. - */ -@Timeout(20) -@BasicWiremock -// Needed for OAuthParameters -@BasicPreferences -@HTTP -class OsmOAuthAuthorizationClientTest { - /** - * HTTP mock. - */ - @BasicWiremock - WireMockServer wireMockServer; - - /** - * Unit test of {@link OsmOAuthAuthorizationClient}. - * @throws OsmOAuthAuthorizationException if OAuth authorization error occurs - * @throws OsmTransferCanceledException if OSM transfer error occurs - */ - @Test - void testOsmOAuthAuthorizationClient() throws OsmTransferCanceledException, OsmOAuthAuthorizationException { - // request token - wireMockServer.stubFor(get(urlEqualTo("/oauth/request_token")) - .willReturn(aResponse().withStatus(200).withBody(String.join("&", - "oauth_token=entxUGuwRKV6KyVDF0OWScdGhbqXGMGmosXuiChR", - "oauth_token_secret=nsBD2Hr5lLGDUeNoh3SnLaGsUV1TiPYM4qUr7tPB")))); - OsmOAuthAuthorizationClient client = new OsmOAuthAuthorizationClient(OAuthParameters.createDefault( - wireMockServer.url("/api"))); - - OAuthToken requestToken = client.getRequestToken(null); - assertEquals("entxUGuwRKV6KyVDF0OWScdGhbqXGMGmosXuiChR", requestToken.getKey(), "requestToken.key"); - assertEquals("nsBD2Hr5lLGDUeNoh3SnLaGsUV1TiPYM4qUr7tPB", requestToken.getSecret(), "requestToken.secret"); - String url = client.getAuthoriseUrl(requestToken); - assertEquals(wireMockServer.url("/oauth/authorize?oauth_token=entxUGuwRKV6KyVDF0OWScdGhbqXGMGmosXuiChR"), url, "url"); - - // access token - wireMockServer.stubFor(get(urlEqualTo("/oauth/access_token")) - .willReturn(aResponse().withStatus(200).withBody(String.join("&", - "oauth_token=eGMGmosXuiChRntxUGuwRKV6KyVDF0OWScdGhbqX", - "oauth_token_secret=nsBUeNor7tPh3SHr5lLaGsGDUD2PYMV1TinL4qUB")))); - - OAuthToken accessToken = client.getAccessToken(null); - assertEquals("eGMGmosXuiChRntxUGuwRKV6KyVDF0OWScdGhbqX", accessToken.getKey(), "accessToken.key"); - assertEquals("nsBUeNor7tPh3SHr5lLaGsGDUD2PYMV1TinL4qUB", accessToken.getSecret(), "accessToken.secret"); - } - - /** - * Unit test for correct cookie handling when logging in to the OSM website. - * - * https://josm.openstreetmap.de/ticket/12584 - * @throws Exception if any error occurs - */ - @Test - void testCookieHandlingMock() throws Exception { - wireMockServer.stubFor(get(urlEqualTo("/login?cookie_test=true")) - .willReturn(aResponse() - .withStatus(200) - .withHeader("Set-Cookie", "_osm_session=7fe8e2ea36c6b803cb902301b28e0a; path=/; HttpOnly; SameSite=Lax") - .withBody(""))); - final OAuthParameters parameters = OAuthParameters.createDefault(wireMockServer.url("/api")); - final OsmOAuthAuthorizationClient client = new OsmOAuthAuthorizationClient(parameters); - final OsmOAuthAuthorizationClient.SessionId sessionId = client.fetchOsmWebsiteSessionId(); - assertNotNull(sessionId); - assertEquals("7fe8e2ea36c6b803cb902301b28e0a", sessionId.id, "sessionId.id"); - assertEquals("fzp6CWJhp6Vns09re3s2Tw==", sessionId.token, "sessionId.token"); - assertNull(sessionId.userName, "sessionId.userName"); - } - - /** - * Unit test for correct cookie handling when logging in to the OSM website. - * - * https://josm.openstreetmap.de/ticket/12584 - * @throws Exception if any error occurs - */ - @Test - void testCookieHandlingCookieManager() throws Exception { - // emulate Java Web Start behaviour - // see https://docs.oracle.com/javase/tutorial/deployment/doingMoreWithRIA/accessingCookies.html - final OAuthParameters parameters = OAuthParameters.createDefault(); - final OsmOAuthAuthorizationClient client = new OsmOAuthAuthorizationClient(parameters); - final CookieManager cm = new CookieManager(); - cm.put(new URI(parameters.getOsmLoginUrl()), - Collections.singletonMap("Cookie", Collections.singletonList("_osm_session=" + String.valueOf(Math.PI).substring(2)))); - CookieHandler.setDefault(cm); - assertNotNull(client.fetchOsmWebsiteSessionId()); - } -} diff --git a/test/unit/org/openstreetmap/josm/gui/oauth/SemiAutomaticAuthorizationUITest.java b/test/unit/org/openstreetmap/josm/gui/oauth/SemiAutomaticAuthorizationUITest.java deleted file mode 100644 index b72d8cd4863..00000000000 --- a/test/unit/org/openstreetmap/josm/gui/oauth/SemiAutomaticAuthorizationUITest.java +++ /dev/null @@ -1,22 +0,0 @@ -// License: GPL. For details, see LICENSE file. -package org.openstreetmap.josm.gui.oauth; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import org.junit.jupiter.api.Test; -import org.openstreetmap.josm.gui.MainApplication; -import org.openstreetmap.josm.testutils.annotations.BasicPreferences; - -/** - * Unit tests of {@link SemiAutomaticAuthorizationUI} class. - */ -@BasicPreferences -class SemiAutomaticAuthorizationUITest { - /** - * Unit test of {@link SemiAutomaticAuthorizationUI#SemiAutomaticAuthorizationUI}. - */ - @Test - void testSemiAutomaticAuthorizationUI() { - assertNotNull(new SemiAutomaticAuthorizationUI("", MainApplication.worker)); - } -} diff --git a/test/unit/org/openstreetmap/josm/gui/preferences/imagery/ImageryPreferenceTestIT.java b/test/unit/org/openstreetmap/josm/gui/preferences/imagery/ImageryPreferenceTestIT.java index a3cbf1cbe80..4583801da9e 100644 --- a/test/unit/org/openstreetmap/josm/gui/preferences/imagery/ImageryPreferenceTestIT.java +++ b/test/unit/org/openstreetmap/josm/gui/preferences/imagery/ImageryPreferenceTestIT.java @@ -169,7 +169,7 @@ private Optional checkUrl(ImageryInfo info, String url) { Logging.warn(url + " -> HTTP " + response.getResponseCode()); } try { - byte[] data = Utils.readBytesFromStream(response.getContent()); + byte[] data = response.getContent().readAllBytes(); if (response.getResponseCode() < 300) { workingURLs.put(url, data); } diff --git a/test/unit/org/openstreetmap/josm/gui/preferences/map/TaggingPresetPreferenceTestIT.java b/test/unit/org/openstreetmap/josm/gui/preferences/map/TaggingPresetPreferenceTestIT.java index 5621029c780..a91b6f749e5 100644 --- a/test/unit/org/openstreetmap/josm/gui/preferences/map/TaggingPresetPreferenceTestIT.java +++ b/test/unit/org/openstreetmap/josm/gui/preferences/map/TaggingPresetPreferenceTestIT.java @@ -29,6 +29,7 @@ import org.openstreetmap.josm.gui.tagging.presets.items.Link; import org.openstreetmap.josm.spi.preferences.Config; import org.openstreetmap.josm.testutils.annotations.HTTPS; +import org.openstreetmap.josm.testutils.annotations.Territories; import org.openstreetmap.josm.tools.HttpClient; import org.openstreetmap.josm.tools.HttpClient.Response; import org.openstreetmap.josm.tools.ImageProvider; @@ -39,6 +40,7 @@ * Integration tests of {@link TaggingPresetPreference} class. */ @HTTPS +@Territories @Timeout(value = 20, unit = TimeUnit.MINUTES) class TaggingPresetPreferenceTestIT extends AbstractExtendedSourceEntryTestCase { /** diff --git a/test/unit/org/openstreetmap/josm/gui/preferences/plugin/PluginPreferenceHighLevelTest.java b/test/unit/org/openstreetmap/josm/gui/preferences/plugin/PluginPreferenceHighLevelTest.java index ced126c98b8..11931fd9610 100644 --- a/test/unit/org/openstreetmap/josm/gui/preferences/plugin/PluginPreferenceHighLevelTest.java +++ b/test/unit/org/openstreetmap/josm/gui/preferences/plugin/PluginPreferenceHighLevelTest.java @@ -18,10 +18,6 @@ import javax.swing.JOptionPane; -import com.github.tomakehurst.wiremock.client.WireMock; -import com.github.tomakehurst.wiremock.junit5.WireMockExtension; -import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; -import mockit.MockUp; import org.awaitility.Awaitility; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -43,6 +39,10 @@ import org.openstreetmap.josm.testutils.mockers.HelpAwareOptionPaneMocker; import org.openstreetmap.josm.testutils.mockers.JOptionPaneSimpleMocker; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import mockit.MockUp; + /** * Higher level tests of {@link PluginPreference} class. */ @@ -131,13 +131,13 @@ public void tearDown() throws ReflectiveOperationException { * @throws Exception never */ @Test - void testInstallWithoutUpdate(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception { + void testInstallWithoutUpdate() throws Exception { final PluginServer pluginServer = new PluginServer( new PluginServer.RemotePlugin(this.referenceDummyJarNew), new PluginServer.RemotePlugin(this.referenceBazJarOld), new PluginServer.RemotePlugin(null, Collections.singletonMap("Plugin-Version", "2"), "irrelevant_plugin") ); - pluginServer.applyToWireMockServer(wireMockRuntimeInfo); + pluginServer.applyToWireMockServer(pluginServerRule.getRuntimeInfo()); Config.getPref().putList("plugins", Collections.singletonList("dummy_plugin")); final HelpAwareOptionPaneMocker haMocker = new HelpAwareOptionPaneMocker( @@ -237,13 +237,13 @@ void testInstallWithoutUpdate(WireMockRuntimeInfo wireMockRuntimeInfo) throws Ex * @throws Exception never */ @Test - void testDisablePluginWithUpdatesAvailable(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception { + void testDisablePluginWithUpdatesAvailable() throws Exception { final PluginServer pluginServer = new PluginServer( new PluginServer.RemotePlugin(this.referenceDummyJarNew), new PluginServer.RemotePlugin(this.referenceBazJarNew), new PluginServer.RemotePlugin(null, null, "irrelevant_plugin") ); - pluginServer.applyToWireMockServer(wireMockRuntimeInfo); + pluginServer.applyToWireMockServer(pluginServerRule.getRuntimeInfo()); Config.getPref().putList("plugins", Arrays.asList("baz_plugin", "dummy_plugin")); final HelpAwareOptionPaneMocker haMocker = new HelpAwareOptionPaneMocker( @@ -345,13 +345,13 @@ void testDisablePluginWithUpdatesAvailable(WireMockRuntimeInfo wireMockRuntimeIn * @throws Exception never */ @Test - void testUpdateOnlySelectedPlugin(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception { + void testUpdateOnlySelectedPlugin() throws Exception { TestUtils.assumeWorkingJMockit(); final PluginServer pluginServer = new PluginServer( new PluginServer.RemotePlugin(this.referenceDummyJarNew), new PluginServer.RemotePlugin(this.referenceBazJarNew) ); - pluginServer.applyToWireMockServer(wireMockRuntimeInfo); + pluginServer.applyToWireMockServer(pluginServerRule.getRuntimeInfo()); Config.getPref().putList("plugins", Arrays.asList("baz_plugin", "dummy_plugin")); final HelpAwareOptionPaneMocker haMocker = new HelpAwareOptionPaneMocker(); @@ -515,14 +515,14 @@ void testUpdateOnlySelectedPlugin(WireMockRuntimeInfo wireMockRuntimeInfo) throw * @throws Exception never */ @Test - void testUpdateWithNoAvailableUpdates(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception { + void testUpdateWithNoAvailableUpdates() throws Exception { TestUtils.assumeWorkingJMockit(); final PluginServer pluginServer = new PluginServer( new PluginServer.RemotePlugin(this.referenceDummyJarOld), new PluginServer.RemotePlugin(this.referenceBazJarOld), new PluginServer.RemotePlugin(null, Collections.singletonMap("Plugin-Version", "123"), "irrelevant_plugin") ); - pluginServer.applyToWireMockServer(wireMockRuntimeInfo); + pluginServer.applyToWireMockServer(pluginServerRule.getRuntimeInfo()); Config.getPref().putList("plugins", Arrays.asList("baz_plugin", "dummy_plugin")); final HelpAwareOptionPaneMocker haMocker = new HelpAwareOptionPaneMocker( @@ -643,7 +643,7 @@ void testUpdateWithNoAvailableUpdates(WireMockRuntimeInfo wireMockRuntimeInfo) t * @throws Exception never */ @Test - void testInstallWithoutRestartRequired(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception { + void testInstallWithoutRestartRequired() throws Exception { TestUtils.assumeWorkingJMockit(); final boolean[] loadPluginsCalled = new boolean[] {false}; new MockUp() { @@ -664,7 +664,7 @@ private void loadPlugins( new PluginServer.RemotePlugin(this.referenceDummyJarNew), new PluginServer.RemotePlugin(this.referenceBazJarNew) ); - pluginServer.applyToWireMockServer(wireMockRuntimeInfo); + pluginServer.applyToWireMockServer(pluginServerRule.getRuntimeInfo()); Config.getPref().putList("plugins", Collections.emptyList()); final HelpAwareOptionPaneMocker haMocker = new HelpAwareOptionPaneMocker(); @@ -755,7 +755,7 @@ private void loadPlugins( */ @AssumeRevision("Revision: 7000\n") @Test - void testInstallMultiVersion(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception { + void testInstallMultiVersion() throws Exception { TestUtils.assumeWorkingJMockit(); final String bazOldServePath = "/baz/old.jar"; @@ -765,7 +765,7 @@ void testInstallMultiVersion(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exc "6800_Plugin-Url", "6;" + pluginServerRule.url(bazOldServePath) )) ); - pluginServer.applyToWireMockServer(wireMockRuntimeInfo); + pluginServer.applyToWireMockServer(pluginServerRule.getRuntimeInfo()); // need to actually serve this older jar from somewhere pluginServerRule.stubFor( WireMock.get(WireMock.urlEqualTo(bazOldServePath)).willReturn( diff --git a/test/unit/org/openstreetmap/josm/gui/preferences/server/OAuthAuthenticationPreferencesPanelTest.java b/test/unit/org/openstreetmap/josm/gui/preferences/server/OAuthAuthenticationPreferencesPanelTest.java index 1f6cc5f60b2..f9afe52a5d4 100644 --- a/test/unit/org/openstreetmap/josm/gui/preferences/server/OAuthAuthenticationPreferencesPanelTest.java +++ b/test/unit/org/openstreetmap/josm/gui/preferences/server/OAuthAuthenticationPreferencesPanelTest.java @@ -20,12 +20,10 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; -import org.openstreetmap.josm.data.oauth.IOAuthToken; import org.openstreetmap.josm.data.oauth.OAuth20Exception; import org.openstreetmap.josm.data.oauth.OAuth20Parameters; import org.openstreetmap.josm.data.oauth.OAuth20Token; import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder; -import org.openstreetmap.josm.data.oauth.OAuthToken; import org.openstreetmap.josm.data.oauth.OAuthVersion; import org.openstreetmap.josm.io.OsmApi; import org.openstreetmap.josm.io.auth.CredentialsAgentException; @@ -46,7 +44,7 @@ void tearDown() { } List exceptionList = new ArrayList<>(); try { - CredentialsManager.getInstance().storeOAuthAccessToken(null); + CredentialsManager.getInstance().storeOAuthAccessToken(OsmApi.getOsmApi().getServerUrl(), null); } catch (CredentialsAgentException exception) { exceptionList.add(exception); } @@ -60,7 +58,7 @@ void tearDown() { } @ParameterizedTest - @EnumSource(value = OAuthVersion.class, names = {"OAuth10a", "OAuth20"}) + @EnumSource(value = OAuthVersion.class, names = "OAuth20") void testRemoveToken(OAuthVersion oAuthVersion) throws ReflectiveOperationException, CredentialsAgentException, OAuth20Exception { final OAuthAuthenticationPreferencesPanel panel = new OAuthAuthenticationPreferencesPanel(oAuthVersion); final Field pnlNotYetAuthorised = OAuthAuthenticationPreferencesPanel.class.getDeclaredField("pnlNotYetAuthorised"); @@ -75,8 +73,8 @@ void testRemoveToken(OAuthVersion oAuthVersion) throws ReflectiveOperationExcept panel.initFromPreferences(); assertSame(pnlAlreadyAuthorised.get(panel), holder.getComponent(0), "Authentication should now be set"); assertNotNull(getAuthorization(oAuthVersion)); - final JPanel buttons = (JPanel) ((JPanel) pnlAlreadyAuthorised.get(panel)).getComponent(6); - final JButton action = (JButton) buttons.getComponent(oAuthVersion == OAuthVersion.OAuth10a ? 2 : 1); + final JPanel buttons = (JPanel) ((JPanel) pnlAlreadyAuthorised.get(panel)).getComponent(5); + final JButton action = (JButton) buttons.getComponent(2); assertEquals(tr("Remove token"), action.getText(), "The selected button should be for removing the token"); action.getAction().actionPerformed(null); panel.saveToPreferences(); // Save to preferences should make the removal permanent @@ -96,9 +94,6 @@ void testRemoveToken(OAuthVersion oAuthVersion) throws ReflectiveOperationExcept */ private static void addAuthorization(OAuthVersion oAuthVersion) throws CredentialsAgentException, OAuth20Exception { switch (oAuthVersion) { - case OAuth10a: - CredentialsManager.getInstance().storeOAuthAccessToken(new OAuthToken("fake_key", "fake_secret")); - break; case OAuth20: case OAuth21: CredentialsManager.getInstance().storeOAuthAccessToken(OsmApi.getOsmApi().getHost(), @@ -116,17 +111,9 @@ private static void addAuthorization(OAuthVersion oAuthVersion) throws Credentia */ private static Object getAuthorization(OAuthVersion oAuthVersion) { OAuthAccessTokenHolder.getInstance().clear(); - OAuthAccessTokenHolder.getInstance().setAccessToken(OsmApi.getOsmApi().getHost(), (IOAuthToken) null); + OAuthAccessTokenHolder.getInstance().setAccessToken(OsmApi.getOsmApi().getServerUrl(), null); OAuthAccessTokenHolder.getInstance().init(CredentialsManager.getInstance()); // Ensure that we are not saving authorization data - switch (oAuthVersion) { - case OAuth10a: - return OAuthAccessTokenHolder.getInstance().getAccessToken(); - case OAuth20: - case OAuth21: - return OAuthAccessTokenHolder.getInstance().getAccessToken(OsmApi.getOsmApi().getHost(), oAuthVersion); - default: - throw new AssertionError("OAuth version not understood"); - } + return OAuthAccessTokenHolder.getInstance().getAccessToken(OsmApi.getOsmApi().getServerUrl(), oAuthVersion); } } diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/PresetClassificationsTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/PresetClassificationsTest.java index 910c568eb2a..fc0125dabb6 100644 --- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/PresetClassificationsTest.java +++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/PresetClassificationsTest.java @@ -19,11 +19,13 @@ import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSelector.PresetClassification; import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSelector.PresetClassifications; +import org.openstreetmap.josm.testutils.annotations.Territories; import org.xml.sax.SAXException; /** * Unit tests of {@link PresetClassifications} class. */ +@Territories class PresetClassificationsTest { static final PresetClassifications classifications = new PresetClassifications(); diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemTest.java new file mode 100644 index 00000000000..ba82814421d --- /dev/null +++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemTest.java @@ -0,0 +1,34 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.gui.tagging.presets; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import javax.swing.JPanel; + +import org.junit.jupiter.api.Test; + +/** + * Test class for {@link TaggingPresetItem} + */ +public interface TaggingPresetItemTest { + /** + * Get the instance to test + * + * @return The item to test + */ + TaggingPresetItem getInstance(); + + /** + * Test method for {@link TaggingPresetItem#addToPanel(JPanel, TaggingPresetItemGuiSupport)} + */ + @Test + default void testAddToPanel() { + TaggingPresetItem item = getInstance(); + JPanel p = new JPanel(); + assertEquals(0, p.getComponentCount()); + assertFalse(item.addToPanel(p, TaggingPresetItemGuiSupport.create(false))); + assertNotEquals(0, p.getComponentCount()); + } +} diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReaderTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReaderTest.java index fa414d81c82..391e282bb5d 100644 --- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReaderTest.java +++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetReaderTest.java @@ -17,6 +17,7 @@ import org.openstreetmap.josm.TestUtils; import org.openstreetmap.josm.gui.tagging.presets.items.Check; import org.openstreetmap.josm.gui.tagging.presets.items.Key; +import org.openstreetmap.josm.testutils.annotations.Territories; import org.xml.sax.SAXException; /** @@ -76,6 +77,7 @@ void testExternalEntityResolving() throws IOException { * @throws SAXException if any XML error occurs * @throws IOException if any I/O error occurs */ + @Territories @Test void testReadDefaultPresets() throws SAXException, IOException { String presetfile = "resource://data/defaultpresets.xml"; diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckGroupTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckGroupTest.java index 9be864204f6..60f9d959bfd 100644 --- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckGroupTest.java +++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckGroupTest.java @@ -1,28 +1,15 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.tagging.presets.items; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import javax.swing.JPanel; - -import org.junit.jupiter.api.Test; -import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest; /** * Unit tests of {@link CheckGroup} class. */ -class CheckGroupTest { - /** - * Unit test for {@link CheckGroup#addToPanel}. - */ - @Test - void testAddToPanel() { - CheckGroup cg = new CheckGroup(); - JPanel p = new JPanel(); - assertEquals(0, p.getComponentCount()); - assertFalse(cg.addToPanel(p, TaggingPresetItemGuiSupport.create(false))); - assertTrue(p.getComponentCount() > 0); +class CheckGroupTest implements TaggingPresetItemTest { + @Override + public TaggingPresetItem getInstance() { + return new CheckGroup(); } } diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckTest.java index 741fc167ad5..d296e5b4d48 100644 --- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckTest.java +++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/CheckTest.java @@ -8,21 +8,30 @@ import org.junit.jupiter.api.Test; import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest; import org.openstreetmap.josm.testutils.annotations.Main; /** * Unit tests of {@link Check} class. */ @Main -class CheckTest { +class CheckTest implements RegionSpecificTest, TaggingPresetItemTest { + @Override + public Check getInstance() { + final Check check = new Check(); + check.key = "crossing:island"; + return check; + } + /** * Unit test for {@link Check#addToPanel}. */ + @Override @Test - void testAddToPanel() { + public void testAddToPanel() { JPanel p = new JPanel(); assertEquals(0, p.getComponentCount()); - assertTrue(new Check().addToPanel(p, TaggingPresetItemGuiSupport.create(false))); + assertTrue(getInstance().addToPanel(p, TaggingPresetItemGuiSupport.create(false))); assertTrue(p.getComponentCount() > 0); } } diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ComboTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ComboTest.java index a186d697d44..408adc4605b 100644 --- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ComboTest.java +++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ComboTest.java @@ -12,6 +12,7 @@ import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.OsmUtils; import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest; import org.openstreetmap.josm.testutils.annotations.BasicPreferences; import org.openstreetmap.josm.testutils.annotations.I18n; import org.openstreetmap.josm.testutils.annotations.Main; @@ -22,15 +23,21 @@ @BasicPreferences @I18n("de") @Main -class ComboTest { +class ComboTest implements TaggingPresetItemTest { + @Override + public Combo getInstance() { + return new Combo(); + } + /** - * Unit test for {@link Combo#addToPanel}. + * Unit test for {@link Check#addToPanel}. */ + @Override @Test - void testAddToPanel() { + public void testAddToPanel() { JPanel p = new JPanel(); assertEquals(0, p.getComponentCount()); - assertTrue(new Combo().addToPanel(p, TaggingPresetItemGuiSupport.create(false))); + assertTrue(getInstance().addToPanel(p, TaggingPresetItemGuiSupport.create(false))); assertTrue(p.getComponentCount() > 0); } @@ -39,7 +46,7 @@ void testAddToPanel() { */ @Test void testUseLastAsDefault() { - Combo combo = new Combo(); + final Combo combo = getInstance(); combo.key = "addr:country"; combo.values_from = "java.util.Locale#getISOCountries"; OsmPrimitive way = OsmUtils.createPrimitive("way"); @@ -118,7 +125,7 @@ void testUseLastAsDefault() { @Test void testColor() { - Combo combo = new Combo(); + final Combo combo = getInstance(); combo.key = "colour"; combo.values = "red;green;blue;black"; combo.values_context = "color"; diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ItemSeparatorTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ItemSeparatorTest.java index 9528b989dc7..99722099237 100644 --- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ItemSeparatorTest.java +++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/ItemSeparatorTest.java @@ -1,27 +1,14 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.tagging.presets.items; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import javax.swing.JPanel; - -import org.junit.jupiter.api.Test; -import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest; /** * Unit tests of {@link ItemSeparator} class. */ -class ItemSeparatorTest { - /** - * Unit test for {@link ItemSeparator#addToPanel}. - */ - @Test - void testAddToPanel() { - JPanel p = new JPanel(); - assertEquals(0, p.getComponentCount()); - assertFalse(new ItemSeparator().addToPanel(p, TaggingPresetItemGuiSupport.create(false))); - assertTrue(p.getComponentCount() > 0); +class ItemSeparatorTest implements TaggingPresetItemTest { + @Override + public ItemSeparator getInstance() { + return new ItemSeparator(); } } diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/KeyTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/KeyTest.java index 2b2480a3e6a..37e08ce3e63 100644 --- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/KeyTest.java +++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/KeyTest.java @@ -8,16 +8,26 @@ import org.junit.jupiter.api.Test; import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest; /** * Unit tests of {@link Key} class. */ -class KeyTest { +class KeyTest implements RegionSpecificTest, TaggingPresetItemTest { + @Override + public Key getInstance() { + final Key key = new Key(); + key.key = "highway"; + key.value = "residential"; + return key; + } + /** * Unit test for {@link Key#addToPanel}. */ @Test - void testAddToPanel() { + @Override + public void testAddToPanel() { JPanel p = new JPanel(); assertEquals(0, p.getComponentCount()); assertFalse(new Key().addToPanel(p, TaggingPresetItemGuiSupport.create(false))); diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LabelTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LabelTest.java index 3a3f64a16ae..f6821ecf305 100644 --- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LabelTest.java +++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LabelTest.java @@ -8,19 +8,26 @@ import org.junit.jupiter.api.Test; import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest; /** * Unit tests of {@link Label} class. */ -class LabelTest { +class LabelTest implements TaggingPresetItemTest { + @Override + public Label getInstance() { + return new Label(); + } + /** - * Unit test for {@link Label#addToPanel}. + * Unit test for {@link Check#addToPanel}. */ + @Override @Test - void testAddToPanel() { + public void testAddToPanel() { JPanel p = new JPanel(); assertEquals(0, p.getComponentCount()); - assertTrue(new Label().addToPanel(p, TaggingPresetItemGuiSupport.create(false))); + assertTrue(getInstance().addToPanel(p, TaggingPresetItemGuiSupport.create(false))); assertTrue(p.getComponentCount() > 0); } } diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LinkTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LinkTest.java index 766ef5d09fa..0df1b6479a6 100644 --- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LinkTest.java +++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/LinkTest.java @@ -9,18 +9,25 @@ import org.junit.jupiter.api.Test; import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest; import org.openstreetmap.josm.spi.preferences.Config; /** * Unit tests of {@link Link} class. */ -class LinkTest { +class LinkTest implements TaggingPresetItemTest { + @Override + public Link getInstance() { + return new Link(); + } + /** * Unit test for {@link Link#addToPanel}. */ + @Override @Test - void testAddToPanel() { - Link l = new Link(); + public void testAddToPanel() { + Link l = getInstance(); JPanel p = new JPanel(); assertEquals(0, p.getComponentCount()); assertFalse(l.addToPanel(p, TaggingPresetItemGuiSupport.create(false))); diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelectTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelectTest.java index dee4757bd8a..91096b84bcb 100644 --- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelectTest.java +++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelectTest.java @@ -8,21 +8,28 @@ import org.junit.jupiter.api.Test; import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest; import org.openstreetmap.josm.testutils.annotations.Main; /** * Unit tests of {@link MultiSelect} class. */ @Main -class MultiSelectTest { +class MultiSelectTest implements TaggingPresetItemTest { + @Override + public MultiSelect getInstance() { + return new MultiSelect(); + } + /** - * Unit test for {@link MultiSelect#addToPanel}. + * Unit test for {@link Check#addToPanel}. */ + @Override @Test - void testAddToPanel() { + public void testAddToPanel() { JPanel p = new JPanel(); assertEquals(0, p.getComponentCount()); - assertTrue(new MultiSelect().addToPanel(p, TaggingPresetItemGuiSupport.create(false))); + assertTrue(getInstance().addToPanel(p, TaggingPresetItemGuiSupport.create(false))); assertTrue(p.getComponentCount() > 0); } } diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/OptionalTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/OptionalTest.java index 9b8490e87ab..e1015222588 100644 --- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/OptionalTest.java +++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/OptionalTest.java @@ -1,27 +1,14 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.tagging.presets.items; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import javax.swing.JPanel; - -import org.junit.jupiter.api.Test; -import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest; /** * Unit tests of {@link Optional} class. */ -class OptionalTest { - /** - * Unit test for {@link Optional#addToPanel}. - */ - @Test - void testAddToPanel() { - JPanel p = new JPanel(); - assertEquals(0, p.getComponentCount()); - assertFalse(new Optional().addToPanel(p, TaggingPresetItemGuiSupport.create(false))); - assertTrue(p.getComponentCount() > 0); +class OptionalTest implements TaggingPresetItemTest { + @Override + public Optional getInstance() { + return new Optional(); } } diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/PresetLinkTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/PresetLinkTest.java index 9bb420ecd47..c38f5fa9683 100644 --- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/PresetLinkTest.java +++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/PresetLinkTest.java @@ -1,31 +1,18 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.tagging.presets.items; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import javax.swing.JPanel; - -import org.junit.jupiter.api.Test; -import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest; import org.openstreetmap.josm.testutils.annotations.TaggingPresets; /** * Unit tests of {@link PresetLink} class. */ @TaggingPresets -class PresetLinkTest { - /** - * Unit test for {@link PresetLink#addToPanel}. - */ - @Test - void testAddToPanel() { - PresetLink l = new PresetLink(); - l.preset_name = "River"; - JPanel p = new JPanel(); - assertEquals(0, p.getComponentCount()); - assertFalse(l.addToPanel(p, TaggingPresetItemGuiSupport.create(false))); - assertTrue(p.getComponentCount() > 0); +class PresetLinkTest implements TaggingPresetItemTest { + @Override + public PresetLink getInstance() { + PresetLink presetLink = new PresetLink(); + presetLink.preset_name = "River"; + return presetLink; } } diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/RegionSpecificTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/RegionSpecificTest.java new file mode 100644 index 00000000000..07afc937908 --- /dev/null +++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/RegionSpecificTest.java @@ -0,0 +1,50 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.gui.tagging.presets.items; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.openstreetmap.josm.testutils.annotations.Territories; +import org.xml.sax.SAXException; + +/** + * Test class for {@link RegionSpecific} + */ +@Territories +interface RegionSpecificTest { + /** + * Get the test instance + * @return The instance to test + */ + RegionSpecific getInstance(); + + @Test + default void testSetRegions() throws SAXException { + final RegionSpecific regionSpecific = getInstance(); + if ("java.lang.Record".equals(regionSpecific.getClass().getSuperclass().getCanonicalName())) { + assertThrows(UnsupportedOperationException.class, () -> regionSpecific.setRegions("US")); + } else { + assertFalse(regionSpecific.regions() != null && regionSpecific.regions().contains("US"), + "Using US as the test region for setting regions"); + regionSpecific.setRegions("US"); + assertAll(() -> assertEquals(1, regionSpecific.regions().size()), + () -> assertEquals("US", regionSpecific.regions().iterator().next())); + } + } + + @Test + default void testSetExcludeRegions() { + final RegionSpecific regionSpecific = getInstance(); + if ("java.lang.Record".equals(regionSpecific.getClass().getSuperclass().getCanonicalName())) { + assertThrows(UnsupportedOperationException.class, () -> regionSpecific.setExclude_regions(true)); + } else { + final boolean oldExclude = regionSpecific.exclude_regions(); + regionSpecific.setExclude_regions(!oldExclude); + assertNotEquals(oldExclude, regionSpecific.exclude_regions()); + } + } +} diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/RolesTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/RolesTest.java index 5477d1b1e3c..076e2238ad3 100644 --- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/RolesTest.java +++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/RolesTest.java @@ -1,27 +1,15 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.tagging.presets.items; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import javax.swing.JPanel; - -import org.junit.jupiter.api.Test; -import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest; /** * Unit tests of {@link Roles} class. */ -class RolesTest { - /** - * Unit test for {@link Roles#addToPanel}. - */ - @Test - void testAddToPanel() { - JPanel p = new JPanel(); - assertEquals(0, p.getComponentCount()); - assertFalse(new Roles().addToPanel(p, TaggingPresetItemGuiSupport.create(false))); - assertTrue(p.getComponentCount() > 0); +class RolesTest implements TaggingPresetItemTest { + @Override + public TaggingPresetItem getInstance() { + return new Roles(); } } diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/SpaceTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/SpaceTest.java index 06030c64cd4..78da5e7e1ba 100644 --- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/SpaceTest.java +++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/SpaceTest.java @@ -1,27 +1,14 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.tagging.presets.items; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import javax.swing.JPanel; - -import org.junit.jupiter.api.Test; -import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest; /** * Unit tests of {@link Space} class. */ -class SpaceTest { - /** - * Unit test for {@link Space#addToPanel}. - */ - @Test - void testAddToPanel() { - JPanel p = new JPanel(); - assertEquals(0, p.getComponentCount()); - assertFalse(new Space().addToPanel(p, TaggingPresetItemGuiSupport.create(false))); - assertTrue(p.getComponentCount() > 0); +class SpaceTest implements TaggingPresetItemTest { + @Override + public Space getInstance() { + return new Space(); } } diff --git a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/TextTest.java b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/TextTest.java index dc39e1d43af..84dc79195c3 100644 --- a/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/TextTest.java +++ b/test/unit/org/openstreetmap/josm/gui/tagging/presets/items/TextTest.java @@ -8,21 +8,28 @@ import org.junit.jupiter.api.Test; import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport; +import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest; import org.openstreetmap.josm.testutils.annotations.Main; /** * Unit tests of {@link Text} class. */ @Main -class TextTest { +class TextTest implements TaggingPresetItemTest { + @Override + public Text getInstance() { + return new Text(); + } + /** - * Unit test for {@link Text#addToPanel}. + * Unit test for {@link Check#addToPanel}. */ + @Override @Test - void testAddToPanel() { + public void testAddToPanel() { JPanel p = new JPanel(); assertEquals(0, p.getComponentCount()); - assertTrue(new Text().addToPanel(p, TaggingPresetItemGuiSupport.create(false))); + assertTrue(getInstance().addToPanel(p, TaggingPresetItemGuiSupport.create(false))); assertTrue(p.getComponentCount() > 0); } } diff --git a/test/unit/org/openstreetmap/josm/io/auth/CredentialsAgentTest.java b/test/unit/org/openstreetmap/josm/io/auth/CredentialsAgentTest.java index 8345450454b..654497b31d9 100644 --- a/test/unit/org/openstreetmap/josm/io/auth/CredentialsAgentTest.java +++ b/test/unit/org/openstreetmap/josm/io/auth/CredentialsAgentTest.java @@ -18,7 +18,6 @@ import org.openstreetmap.josm.data.oauth.OAuth20Exception; import org.openstreetmap.josm.data.oauth.OAuth20Parameters; import org.openstreetmap.josm.data.oauth.OAuth20Token; -import org.openstreetmap.josm.data.oauth.OAuthToken; import org.openstreetmap.josm.io.OsmApi; import org.openstreetmap.josm.testutils.annotations.BasicPreferences; @@ -75,18 +74,6 @@ default void testLookUpAndStorePasswordAuthenticationNull() throws CredentialsAg assertNull(agent.lookup(null, null)); } - @Test - default void testLookUpAndStoreOAuth10() throws CredentialsAgentException { - final T agent = createAgent(); - assertNull(agent.lookupOAuthAccessToken()); - final OAuthToken token = new OAuthToken("foo", "bar"); - agent.storeOAuthAccessToken(token); - final OAuthToken actual = agent.lookupOAuthAccessToken(); - assertEquals(token, actual); - agent.storeOAuthAccessToken(null); - assertNull(agent.lookupOAuthAccessToken()); - } - @ParameterizedTest @MethodSource("getHosts") default void testLookupAndStoreOAuthTokens(final String host) throws CredentialsAgentException, OAuth20Exception { diff --git a/test/unit/org/openstreetmap/josm/io/remotecontrol/RemoteControlTest.java b/test/unit/org/openstreetmap/josm/io/remotecontrol/RemoteControlTest.java index ff41eb694ec..304ff15206d 100644 --- a/test/unit/org/openstreetmap/josm/io/remotecontrol/RemoteControlTest.java +++ b/test/unit/org/openstreetmap/josm/io/remotecontrol/RemoteControlTest.java @@ -16,7 +16,6 @@ import org.openstreetmap.josm.spi.preferences.Config; import org.openstreetmap.josm.testutils.annotations.AssertionsInEDT; import org.openstreetmap.josm.testutils.annotations.HTTPS; -import org.openstreetmap.josm.tools.Utils; /** * Unit tests for Remote Control @@ -59,7 +58,7 @@ private void testListOfCommands(String url) throws IOException, ReflectiveOperat connection.connect(); assertEquals(HttpURLConnection.HTTP_BAD_REQUEST, connection.getResponseCode()); try (InputStream is = connection.getErrorStream()) { - String responseBody = new String(Utils.readBytesFromStream(is), StandardCharsets.UTF_8); + String responseBody = new String(is.readAllBytes(), StandardCharsets.UTF_8); assert responseBody.contains(RequestProcessor.getUsageAsHtml()); } } diff --git a/test/unit/org/openstreetmap/josm/io/remotecontrol/handler/AuthorizationHandlerTest.java b/test/unit/org/openstreetmap/josm/io/remotecontrol/handler/AuthorizationHandlerTest.java index 92394ad1d96..3ff0b17e30a 100644 --- a/test/unit/org/openstreetmap/josm/io/remotecontrol/handler/AuthorizationHandlerTest.java +++ b/test/unit/org/openstreetmap/josm/io/remotecontrol/handler/AuthorizationHandlerTest.java @@ -19,7 +19,7 @@ * Test class for {@link AuthorizationHandler} */ class AuthorizationHandlerTest { - private static class TestAuthorizationConsumer implements AuthorizationHandler.AuthorizationConsumer { + private static final class TestAuthorizationConsumer implements AuthorizationHandler.AuthorizationConsumer { boolean validated; boolean handled; @Override diff --git a/test/unit/org/openstreetmap/josm/io/session/SessionWriterTest.java b/test/unit/org/openstreetmap/josm/io/session/SessionWriterTest.java index 9a5638f23b8..a580bd077f1 100644 --- a/test/unit/org/openstreetmap/josm/io/session/SessionWriterTest.java +++ b/test/unit/org/openstreetmap/josm/io/session/SessionWriterTest.java @@ -2,11 +2,11 @@ package org.openstreetmap.josm.io.session; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; import java.awt.Color; import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -133,11 +133,10 @@ private Map testWrite(List layers, final boolean zip) thr try (ZipFile zipFile = new ZipFile(file)) { return Collections.list(zipFile.entries()).stream().collect(Collectors.toMap(ZipEntry::getName, e -> { try { - return Utils.readBytesFromStream(zipFile.getInputStream(e)); + return zipFile.getInputStream(e).readAllBytes(); } catch (IOException ex) { - fail(ex); + throw new UncheckedIOException(ex); } - return null; })); } } finally { @@ -274,12 +273,12 @@ void testWriteGpxAndMarkerJoz() throws IOException { Map bytes = testWrite(Arrays.asList(gpx, markers), true); Path path = Paths.get(TestUtils.getTestDataRoot() + "/sessions/gpx_markers_combined.jos"); - String expected = new String(Files.readAllBytes(path), StandardCharsets.UTF_8).replace("\r", ""); + String expected = Files.readString(path).replace("\r", ""); String actual = new String(bytes.get("session.jos"), StandardCharsets.UTF_8).replace("\r", ""); assertEquals(expected, actual); path = Paths.get(TestUtils.getTestDataRoot() + "/sessions/data_export.gpx"); - expected = new String(Files.readAllBytes(path), StandardCharsets.UTF_8).replace("\r", ""); + expected = Files.readString(path).replace("\r", ""); actual = new String(bytes.get("layers/01/data.gpx"), StandardCharsets.UTF_8).replace("\r", ""); assertEquals(expected, actual); @@ -290,17 +289,17 @@ void testWriteGpxAndMarkerJoz() throws IOException { bytes = testWrite(Arrays.asList(gpx, markers), true); path = Paths.get(TestUtils.getTestDataRoot() + "/sessions/gpx_markers.jos"); - expected = new String(Files.readAllBytes(path), StandardCharsets.UTF_8).replace("\r", ""); + expected = Files.readString(path).replace("\r", ""); actual = new String(bytes.get("session.jos"), StandardCharsets.UTF_8).replace("\r", ""); assertEquals(expected, actual); path = Paths.get(TestUtils.getTestDataRoot() + "/sessions/data_export.gpx"); - expected = new String(Files.readAllBytes(path), StandardCharsets.UTF_8).replace("\r", ""); + expected = Files.readString(path).replace("\r", ""); actual = new String(bytes.get("layers/01/data.gpx"), StandardCharsets.UTF_8).replace("\r", ""); assertEquals(expected, actual); path = Paths.get(TestUtils.getTestDataRoot() + "/sessions/markers.gpx"); - expected = new String(Files.readAllBytes(path), StandardCharsets.UTF_8).replace("\r", ""); + expected = Files.readString(path).replace("\r", ""); actual = new String(bytes.get("layers/02/data.gpx"), StandardCharsets.UTF_8).replace("\r", ""); assertEquals(expected, actual); diff --git a/test/unit/org/openstreetmap/josm/plugins/PluginHandlerJOSMTooOldTest.java b/test/unit/org/openstreetmap/josm/plugins/PluginHandlerJOSMTooOldTest.java index 917894d0f7d..53fe63a3aac 100644 --- a/test/unit/org/openstreetmap/josm/plugins/PluginHandlerJOSMTooOldTest.java +++ b/test/unit/org/openstreetmap/josm/plugins/PluginHandlerJOSMTooOldTest.java @@ -33,7 +33,6 @@ import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.junit5.WireMockExtension; -import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; /** * Test parts of {@link PluginHandler} class when the reported JOSM version is too old for the plugin. @@ -109,13 +108,13 @@ private static String u202f(String s) { * @throws IOException never */ @Test - void testUpdatePluginsDownloadBoth(WireMockRuntimeInfo wireMockRuntimeInfo) throws IOException { + void testUpdatePluginsDownloadBoth() throws IOException { TestUtils.assumeWorkingJMockit(); final PluginServer pluginServer = new PluginServer( new PluginServer.RemotePlugin(this.referenceDummyJarNew), new PluginServer.RemotePlugin(this.referenceBazJarNew) ); - pluginServer.applyToWireMockServer(wireMockRuntimeInfo); + pluginServer.applyToWireMockServer(pluginServerRule.getRuntimeInfo()); Config.getPref().putList("plugins", Arrays.asList("dummy_plugin", "baz_plugin")); final ExtendedDialogMocker edMocker = new ExtendedDialogMocker(); @@ -171,13 +170,13 @@ void testUpdatePluginsDownloadBoth(WireMockRuntimeInfo wireMockRuntimeInfo) thro * @throws IOException never */ @Test - void testUpdatePluginsSkipOne(WireMockRuntimeInfo wireMockRuntimeInfo) throws IOException { + void testUpdatePluginsSkipOne() throws IOException { TestUtils.assumeWorkingJMockit(); final PluginServer pluginServer = new PluginServer( new PluginServer.RemotePlugin(this.referenceDummyJarNew), new PluginServer.RemotePlugin(this.referenceBazJarNew) ); - pluginServer.applyToWireMockServer(wireMockRuntimeInfo); + pluginServer.applyToWireMockServer(pluginServerRule.getRuntimeInfo()); Config.getPref().putList("plugins", Arrays.asList("dummy_plugin", "baz_plugin")); final ExtendedDialogMocker edMocker = new ExtendedDialogMocker(); @@ -243,13 +242,13 @@ void testUpdatePluginsSkipOne(WireMockRuntimeInfo wireMockRuntimeInfo) throws IO * @throws IOException never */ @Test - void testUpdatePluginsUnexpectedlyJOSMTooOld(WireMockRuntimeInfo wireMockRuntimeInfo) throws IOException { + void testUpdatePluginsUnexpectedlyJOSMTooOld() throws IOException { TestUtils.assumeWorkingJMockit(); final PluginServer pluginServer = new PluginServer( new PluginServer.RemotePlugin(this.referenceDummyJarNew), new PluginServer.RemotePlugin(this.referenceBazJarNew, Collections.singletonMap("Plugin-Mainversion", "5500")) ); - pluginServer.applyToWireMockServer(wireMockRuntimeInfo); + pluginServer.applyToWireMockServer(pluginServerRule.getRuntimeInfo()); Config.getPref().putList("plugins", Collections.singletonList("baz_plugin")); // setting up blank ExtendedDialogMocker which would raise an exception if any attempt to show @@ -298,7 +297,7 @@ void testUpdatePluginsUnexpectedlyJOSMTooOld(WireMockRuntimeInfo wireMockRuntime */ @Test @AssumeRevision("Revision: 7200\n") - void testUpdatePluginsMultiVersionInsufficient(WireMockRuntimeInfo wireMockRuntimeInfo) throws IOException { + void testUpdatePluginsMultiVersionInsufficient() throws IOException { TestUtils.assumeWorkingJMockit(); final PluginServer pluginServer = new PluginServer( @@ -307,7 +306,7 @@ void testUpdatePluginsMultiVersionInsufficient(WireMockRuntimeInfo wireMockRunti "7499_Plugin-Url", "346;" + pluginServerRule.url("/dont/bother.jar") )) ); - pluginServer.applyToWireMockServer(wireMockRuntimeInfo); + pluginServer.applyToWireMockServer(pluginServerRule.getRuntimeInfo()); Config.getPref().putList("plugins", Arrays.asList("qux_plugin", "baz_plugin")); new ExtendedDialogMocker(Collections.singletonMap(u202f("JOSM version 7\u202F500 required for plugin qux_plugin."), "Download Plugin")); diff --git a/test/unit/org/openstreetmap/josm/plugins/PluginHandlerMultiVersionTest.java b/test/unit/org/openstreetmap/josm/plugins/PluginHandlerMultiVersionTest.java index dd075a7b3bd..770ecbf02ea 100644 --- a/test/unit/org/openstreetmap/josm/plugins/PluginHandlerMultiVersionTest.java +++ b/test/unit/org/openstreetmap/josm/plugins/PluginHandlerMultiVersionTest.java @@ -90,11 +90,11 @@ void setUp() { */ @AssumeRevision("Revision: 7501\n") @Test - void testUpdatePluginsOneMultiVersion(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception { + void testUpdatePluginsOneMultiVersion() throws Exception { TestUtils.assumeWorkingJMockit(); final String quxNewerServePath = "/qux/newer.jar"; - final Map attrOverrides = new HashMap() {{ + final Map attrOverrides = new HashMap<>() {{ put("7500_Plugin-Url", "432;" + pluginServerRule.url(quxNewerServePath)); put("7499_Plugin-Url", "346;" + pluginServerRule.url("/not/served.jar")); put("6999_Plugin-Url", "345;" + pluginServerRule.url("/not/served/eithejar")); @@ -103,6 +103,7 @@ void testUpdatePluginsOneMultiVersion(WireMockRuntimeInfo wireMockRuntimeInfo) t new PluginServer.RemotePlugin(this.referenceBazJarOld), new PluginServer.RemotePlugin(this.referenceQuxJarNewest, attrOverrides) ); + final WireMockRuntimeInfo wireMockRuntimeInfo = pluginServerRule.getRuntimeInfo(); pluginServer.applyToWireMockServer(wireMockRuntimeInfo); // need to actually serve this older jar from somewhere wireMockRuntimeInfo.getWireMock().register( @@ -157,10 +158,10 @@ void testUpdatePluginsOneMultiVersion(WireMockRuntimeInfo wireMockRuntimeInfo) t */ @AssumeRevision("Revision: 7000\n") @Test - void testUpdatePluginsExistingVersionLatestPossible(WireMockRuntimeInfo wireMockRuntimeInfo) throws Exception { + void testUpdatePluginsExistingVersionLatestPossible() throws Exception { TestUtils.assumeWorkingJMockit(); - final Map attrOverrides = new HashMap() {{ + final Map attrOverrides = new HashMap<>() {{ put("7500_Plugin-Url", "432;" + pluginServerRule.url("/dont.jar")); put("7499_Plugin-Url", "346;" + pluginServerRule.url("/even.jar")); put("6999_Plugin-Url", "345;" + pluginServerRule.url("/bother.jar")); @@ -169,7 +170,7 @@ void testUpdatePluginsExistingVersionLatestPossible(WireMockRuntimeInfo wireMock new PluginServer.RemotePlugin(this.referenceBazJarOld), new PluginServer.RemotePlugin(this.referenceQuxJarNewest, attrOverrides) ); - pluginServer.applyToWireMockServer(wireMockRuntimeInfo); + pluginServer.applyToWireMockServer(pluginServerRule.getRuntimeInfo()); Config.getPref().putList("plugins", Arrays.asList("qux_plugin", "baz_plugin")); // catch any (unexpected) attempts to show us an ExtendedDialog diff --git a/test/unit/org/openstreetmap/josm/plugins/PluginHandlerTestIT.java b/test/unit/org/openstreetmap/josm/plugins/PluginHandlerTestIT.java index b76b7dfb2f3..ee3f52da1c8 100644 --- a/test/unit/org/openstreetmap/josm/plugins/PluginHandlerTestIT.java +++ b/test/unit/org/openstreetmap/josm/plugins/PluginHandlerTestIT.java @@ -246,18 +246,6 @@ public static void loadAllPlugins() { } } - // On Java < 11 and headless mode, filter plugins requiring JavaFX as Monocle is not available - int javaVersion = Utils.getJavaVersion(); - if (GraphicsEnvironment.isHeadless() && javaVersion < 11) { - for (Iterator it = plugins.iterator(); it.hasNext();) { - PluginInformation pi = it.next(); - if (pi.getRequiredPlugins().contains("javafx")) { - System.out.println("Ignoring " + pi.name + " (requiring JavaFX and we're using Java < 11 in headless mode)"); - it.remove(); - } - } - } - // Skip unofficial plugins in headless mode, too much work for us for little added-value if (GraphicsEnvironment.isHeadless()) { for (Iterator it = plugins.iterator(); it.hasNext();) { diff --git a/test/unit/org/openstreetmap/josm/spi/lifecycle/LifecycleTest.java b/test/unit/org/openstreetmap/josm/spi/lifecycle/LifecycleTest.java index 41225b98382..e5023b63d12 100644 --- a/test/unit/org/openstreetmap/josm/spi/lifecycle/LifecycleTest.java +++ b/test/unit/org/openstreetmap/josm/spi/lifecycle/LifecycleTest.java @@ -18,7 +18,7 @@ @OsmApi(OsmApi.APIType.DEV) @Projection class LifecycleTest { - private static class InitStatusListenerStub implements InitStatusListener { + private static final class InitStatusListenerStub implements InitStatusListener { boolean updated; boolean finished; diff --git a/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java b/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java index 33727e383a6..8ed3fdc78aa 100644 --- a/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java +++ b/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java @@ -2,8 +2,6 @@ package org.openstreetmap.josm.testutils; import java.awt.Color; -import java.awt.GraphicsEnvironment; -import java.awt.Toolkit; import java.awt.Window; import java.awt.event.WindowEvent; import java.io.ByteArrayInputStream; @@ -16,7 +14,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.text.MessageFormat; @@ -25,7 +22,6 @@ import java.util.TimeZone; import java.util.concurrent.TimeUnit; import java.util.logging.Handler; -import java.util.logging.Level; import org.awaitility.Awaitility; import org.awaitility.Durations; @@ -77,8 +73,6 @@ import org.openstreetmap.josm.tools.JosmRuntimeException; import org.openstreetmap.josm.tools.Logging; import org.openstreetmap.josm.tools.MemoryManagerTest; -import org.openstreetmap.josm.tools.PlatformManager; -import org.openstreetmap.josm.tools.Utils; import org.openstreetmap.josm.tools.bugreport.ReportedException; import org.openstreetmap.josm.tools.date.DateUtils; @@ -645,7 +639,6 @@ protected void before() throws InitializationError, ReflectiveOperationException this.navigableComponentMockingRunnable.run(); } - workaroundJdkBug8159956(); new MainApplication(); JOSMFixture.initContentPane(); JOSMFixture.initMainPanel(true); @@ -655,20 +648,6 @@ protected void before() throws InitializationError, ReflectiveOperationException } } - private void workaroundJdkBug8159956() { - // Note: This has been backported to Java 8u381 (2023-07-18) - try { - if (PlatformManager.isPlatformWindows() && Utils.getJavaVersion() == 8 && GraphicsEnvironment.isHeadless()) { - // https://bugs.openjdk.java.net/browse/JDK-8159956 - Method initIDs = Toolkit.class.getDeclaredMethod("initIDs"); - initIDs.setAccessible(true); - initIDs.invoke(Toolkit.getDefaultToolkit()); - } - } catch (Exception e) { - Logging.log(Level.WARNING, "Failed to Toolkit.initIDs", e); - } - } - /** * Clean up what test not using these test rules may have broken. */ diff --git a/test/unit/org/openstreetmap/josm/testutils/PluginServer.java b/test/unit/org/openstreetmap/josm/testutils/PluginServer.java index 0eb8fb67e1f..da27e7dac1f 100644 --- a/test/unit/org/openstreetmap/josm/testutils/PluginServer.java +++ b/test/unit/org/openstreetmap/josm/testutils/PluginServer.java @@ -24,7 +24,13 @@ import org.openstreetmap.josm.TestUtils; import org.openstreetmap.josm.tools.Logging; +/** + * Serve "remote" test plugins for tests + */ public class PluginServer { + /** + * A holder class for a "remote" plugin for tests + */ public static class RemotePlugin { private final File srcJar; private final Map attrOverrides; @@ -225,6 +231,9 @@ public PluginServerRule asWireMockRule(Options ruleOptions, boolean failOnUnmatc return new PluginServerRule(ruleOptions, failOnUnmatchedRequests); } + /** + * A wiremock server rule for serving plugins + */ public class PluginServerRule extends WireMockExtension { public PluginServerRule(Options ruleOptions, boolean failOnUnmatchedRequests) { super(extensionOptions().options(ruleOptions).failOnUnmatchedRequests(failOnUnmatchedRequests)); diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/AnnotationUtils.java b/test/unit/org/openstreetmap/josm/testutils/annotations/AnnotationUtils.java index c297efd4616..df09280a07e 100644 --- a/test/unit/org/openstreetmap/josm/testutils/annotations/AnnotationUtils.java +++ b/test/unit/org/openstreetmap/josm/testutils/annotations/AnnotationUtils.java @@ -47,13 +47,13 @@ public static Optional findFirstParentAnnotation(Exten */ public static void resetStaticClass(Class clazz) throws ReflectiveOperationException { for (Field field : clazz.getDeclaredFields()) { - if (!field.isAccessible()) { - field.setAccessible(true); - } // Don't reset fields that are not static if ((field.getModifiers() & Modifier.STATIC) == 0) { continue; } + if (!field.canAccess(null)) { + field.setAccessible(true); + } final boolean isFinal = (field.getModifiers() & Modifier.FINAL) != 0; if (field.get(null) instanceof Collection && isFinal) { // Clear all collections (assume they start empty) diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/AssertionsInEDT.java b/test/unit/org/openstreetmap/josm/testutils/annotations/AssertionsInEDT.java index cf2d853143a..e666a5836b6 100644 --- a/test/unit/org/openstreetmap/josm/testutils/annotations/AssertionsInEDT.java +++ b/test/unit/org/openstreetmap/josm/testutils/annotations/AssertionsInEDT.java @@ -21,6 +21,9 @@ @Retention(RetentionPolicy.RUNTIME) @ExtendWith(AssertionsInEDT.AssertionsExtension.class) public @interface AssertionsInEDT { + /** + * Check for assertions in the EDT + */ class AssertionsExtension implements BeforeEachCallback { private Runnable edtAssertionMockingRunnable = EDTAssertionMocker::new; @Override diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/BasicPreferences.java b/test/unit/org/openstreetmap/josm/testutils/annotations/BasicPreferences.java index 7ae10cd91b3..b08c971e992 100644 --- a/test/unit/org/openstreetmap/josm/testutils/annotations/BasicPreferences.java +++ b/test/unit/org/openstreetmap/josm/testutils/annotations/BasicPreferences.java @@ -64,6 +64,9 @@ public void afterAll(ExtensionContext context) throws Exception { public void afterEach(ExtensionContext context) throws Exception { if (AnnotationSupport.isAnnotated(context.getElement(), BasicPreferences.class)) { this.afterAll(context); + if (AnnotationSupport.isAnnotated(context.getTestClass(), BasicPreferences.class)) { + this.beforeAll(context); + } } } diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/BasicWiremock.java b/test/unit/org/openstreetmap/josm/testutils/annotations/BasicWiremock.java index 3e677613098..266b0d8f8d5 100644 --- a/test/unit/org/openstreetmap/josm/testutils/annotations/BasicWiremock.java +++ b/test/unit/org/openstreetmap/josm/testutils/annotations/BasicWiremock.java @@ -40,7 +40,8 @@ import org.openstreetmap.josm.tools.Utils; import com.github.tomakehurst.wiremock.WireMockServer; -import com.github.tomakehurst.wiremock.extension.ResponseTransformer; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.extension.ResponseTransformerV2; import com.github.tomakehurst.wiremock.verification.LoggedRequest; /** @@ -64,7 +65,7 @@ String value() default ""; /** - * {@link ResponseTransformer} for use with the WireMock server. + * {@link ResponseTransformerV2} for use with the WireMock server. * Current constructors supported: *

            *
          • {@code new ResponseTransformer()}
          • @@ -72,7 +73,7 @@ *
          * @return The transformers to instantiate */ - Class[] responseTransformers() default {}; + Class[] responseTransformers() default {}; /** * Start/stop WireMock automatically, and check for missed calls. @@ -91,13 +92,13 @@ static WireMockServer getWiremock(ExtensionContext context) { BasicWiremock annotation = AnnotationUtils.findFirstParentAnnotation(context, BasicWiremock.class) .orElseThrow(() -> new IllegalArgumentException("There must be a @BasicWiremock annotation")); return context.getStore(namespace).getOrComputeIfAbsent(WireMockServer.class, clazz -> { - final List transformers = new ArrayList<>(annotation.responseTransformers().length); - for (Class responseTransformer : annotation.responseTransformers()) { + final List transformers = new ArrayList<>(annotation.responseTransformers().length); + for (Class responseTransformer : annotation.responseTransformers()) { for (Pair[], Object[]> parameterMapping : Arrays.asList( new Pair<>(new Class[] {ExtensionContext.class }, new Object[] {context }), new Pair<>(new Class[0], new Object[0]))) { try { - Constructor constructor = responseTransformer + Constructor constructor = responseTransformer .getConstructor(parameterMapping.a); transformers.add(constructor.newInstance(parameterMapping.b)); break; @@ -108,7 +109,7 @@ static WireMockServer getWiremock(ExtensionContext context) { } return new WireMockServer( options().usingFilesUnderDirectory(Utils.isStripEmpty(annotation.value()) ? TestUtils.getTestDataRoot() : - annotation.value()).extensions(transformers.toArray(new ResponseTransformer[0])).dynamicPort()); + annotation.value()).extensions(transformers.toArray(new ResponseTransformerV2[0])).dynamicPort()); }, WireMockServer.class); } @@ -166,7 +167,7 @@ public void beforeEach(ExtensionContext context) throws Exception { List wireMockFields = AnnotationSupport.findAnnotatedFields(context.getRequiredTestClass(), BasicWiremock.class); for (Field field : wireMockFields) { if (WireMockServer.class.isAssignableFrom(field.getType())) { - final boolean isAccessible = field.isAccessible(); + final boolean isAccessible = field.canAccess(context.getRequiredTestInstance()); field.setAccessible(true); try { field.set(context.getTestInstance().orElse(null), getWiremock(context)); @@ -213,7 +214,11 @@ public void beforeAll(ExtensionContext context) throws Exception { fail("OsmApiExtension requires @BasicPreferences"); } super.beforeAll(context); - Config.getPref().put("osm-server.url", getWiremock(context).baseUrl()); + Config.getPref().put("osm-server.url", getWiremock(context).baseUrl() + "/api"); + getWiremock(context).stubFor(WireMock.get("/api/0.6/capabilities") + .willReturn(WireMock.aResponse().withBodyFile("api/0.6/capabilities"))); + getWiremock(context).stubFor(WireMock.get("/api/capabilities") + .willReturn(WireMock.aResponse().withBodyFile("api/capabilities"))); OsmApi.getOsmApi().initialize(NullProgressMonitor.INSTANCE); } } diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/FakeImagery.java b/test/unit/org/openstreetmap/josm/testutils/annotations/FakeImagery.java index 1af565661ee..bc154102749 100644 --- a/test/unit/org/openstreetmap/josm/testutils/annotations/FakeImagery.java +++ b/test/unit/org/openstreetmap/josm/testutils/annotations/FakeImagery.java @@ -14,15 +14,10 @@ import java.util.Collections; import java.util.List; -import org.junit.jupiter.api.extension.AfterAllCallback; -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; -import org.junit.jupiter.api.extension.ParameterResolver; import org.openstreetmap.josm.data.imagery.ImageryInfo; import org.openstreetmap.josm.data.imagery.ImageryLayerInfo; import org.openstreetmap.josm.gui.bbox.JosmMapViewer; @@ -45,74 +40,11 @@ @Retention(RUNTIME) @Target(TYPE) @BasicPreferences -@ExtendWith(FakeImagery.FakeImageryExtension.class) +@ExtendWith(FakeImagery.FakeImageryWireMockExtension.class) public @interface FakeImagery { /** - * This is a stop-gap for WireMock #1981. - * We just wrap everything. + * A wiremock extension for fake imagery */ - class FakeImageryExtension implements ParameterResolver, - BeforeEachCallback, - BeforeAllCallback, - AfterEachCallback, - AfterAllCallback { - - @Override - public void afterAll(ExtensionContext extensionContext) throws Exception { - getActualExtension(extensionContext).afterAll(extensionContext); - } - - @Override - public void afterEach(ExtensionContext extensionContext) throws Exception { - final FakeImageryWireMockExtension extension = getActualExtension(extensionContext); - extension.afterEach(extensionContext); - extension.onAfterEach(extensionContext, getWireMockRuntimeInfo(extensionContext)); - } - - @Override - public void beforeAll(ExtensionContext extensionContext) throws Exception { - getActualExtension(extensionContext).beforeAll(extensionContext); - } - - @Override - public void beforeEach(ExtensionContext extensionContext) throws Exception { - final FakeImageryWireMockExtension extension = getActualExtension(extensionContext); - extension.beforeEach(extensionContext); - extension.onBeforeEach(extensionContext, getWireMockRuntimeInfo(extensionContext)); - } - - @Override - public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) - throws ParameterResolutionException { - if (parameterContext.getParameter().getType().equals(FakeImageryWireMockExtension.class)) { - return true; - } - return getActualExtension(extensionContext).supportsParameter(parameterContext, extensionContext); - } - - @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) - throws ParameterResolutionException { - if (parameterContext.getParameter().getType().equals(FakeImageryWireMockExtension.class)) { - return getActualExtension(extensionContext); - } - return getActualExtension(extensionContext).resolveParameter(parameterContext, extensionContext); - } - - private static FakeImageryWireMockExtension getActualExtension(ExtensionContext extensionContext) { - return FakeImageryWireMockExtension.getStore(extensionContext) - .getOrComputeIfAbsent(FakeImageryWireMockExtension.class, ignored -> new FakeImageryWireMockExtension(), - FakeImageryWireMockExtension.class); - } - - private static WireMockRuntimeInfo getWireMockRuntimeInfo(ExtensionContext extensionContext) { - return FakeImageryWireMockExtension.getStore(extensionContext) - .getOrComputeIfAbsent(WireMockRuntimeInfo.class, ignored -> getActualExtension(extensionContext).getRuntimeInfo(), - WireMockRuntimeInfo.class); - - } - } - class FakeImageryWireMockExtension extends WireMockExtension { private final boolean clearLayerList; @@ -175,6 +107,7 @@ public List getSourcesList() { return this.sources; } + @Override protected void onBeforeEach(ExtensionContext extensionContext, WireMockRuntimeInfo wireMockRuntimeInfo) { super.onBeforeEach(wireMockRuntimeInfo); final ExtensionContext.Store store = getStore(extensionContext); @@ -184,32 +117,55 @@ protected void onBeforeEach(ExtensionContext extensionContext, WireMockRuntimeIn } } + @Override protected void onAfterEach(ExtensionContext extensionContext, WireMockRuntimeInfo wireMockRuntimeInfo) { - super.onAfterEach(wireMockRuntimeInfo); - final ExtensionContext.Store store = getStore(extensionContext); - unregisterLayers(store); + try { + super.onAfterEach(wireMockRuntimeInfo); + } finally { + final ExtensionContext.Store store = getStore(extensionContext); + unregisterLayers(store); + } + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + if (parameterContext.getParameter().getType().equals(FakeImageryWireMockExtension.class)) { + return true; + } + return super.supportsParameter(parameterContext, extensionContext); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + if (parameterContext.getParameter().getType().equals(FakeImageryWireMockExtension.class)) { + return this; + } + return super.resolveParameter(parameterContext, extensionContext); } private void registerLayers(ExtensionContext.Store store, WireMockRuntimeInfo wireMockRuntimeInfo) { if (this.clearSlippyMapSources) { try { @SuppressWarnings("unchecked") - List slippyMapProviders = - (List) getPrivateStaticField( + List slippyMapProviders = + (List) getPrivateStaticField( SlippyMapBBoxChooser.class, "providers" ); // pop this off the beginning of the list, keep for later - SlippyMapBBoxChooser.TileSourceProvider slippyMapDefaultProvider = slippyMapProviders.remove(0); + JosmMapViewer.TileSourceProvider slippyMapDefaultProvider = slippyMapProviders.remove(0); store.put("slippyMapProviders", slippyMapProviders); store.put("slippyMapDefaultProvider", slippyMapDefaultProvider); } catch (ReflectiveOperationException e) { Logging.warn("Failed to remove default SlippyMapBBoxChooser TileSourceProvider"); + Logging.trace(e); } } if (this.clearLayerList) { - store.put("originalImageryInfoList", ImageryLayerInfo.instance.getLayers()); + store.put("originalImageryInfoList", List.copyOf(ImageryLayerInfo.instance.getLayers())); ImageryLayerInfo.instance.clear(); } if (this.registerInLayerList) { @@ -219,14 +175,13 @@ private void registerLayers(ExtensionContext.Store store, WireMockRuntimeInfo wi } } - private void unregisterLayers(ExtensionContext.Store store) { + private static void unregisterLayers(ExtensionContext.Store store) { @SuppressWarnings("unchecked") - final List slippyMapProviders = - (List) store.get("slippyMapProviders", List.class); - SlippyMapBBoxChooser.TileSourceProvider slippyMapDefaultProvider = + final List slippyMapProviders = store.get("slippyMapProviders", List.class); + JosmMapViewer.TileSourceProvider slippyMapDefaultProvider = store.get("slippyMapDefaultProvider", JosmMapViewer.TileSourceProvider.class); @SuppressWarnings("unchecked") - List originalImageryInfoList = (List) store.get("originalImageryInfoList", List.class); + List originalImageryInfoList = store.get("originalImageryInfoList", List.class); // clean up to original state if (slippyMapDefaultProvider != null && slippyMapProviders != null) { slippyMapProviders.add(0, slippyMapDefaultProvider); diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/HTTPS.java b/test/unit/org/openstreetmap/josm/testutils/annotations/HTTPS.java index 0b646081320..579e1fa861e 100644 --- a/test/unit/org/openstreetmap/josm/testutils/annotations/HTTPS.java +++ b/test/unit/org/openstreetmap/josm/testutils/annotations/HTTPS.java @@ -30,6 +30,9 @@ @BasicPreferences @ExtendWith(HTTPS.HTTPSExtension.class) public @interface HTTPS { + /** + * Initialize HTTPS support + */ class HTTPSExtension implements BeforeEachCallback { private static boolean initialized; @Override diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/I18n.java b/test/unit/org/openstreetmap/josm/testutils/annotations/I18n.java index 4baedecb66e..ae515470e51 100644 --- a/test/unit/org/openstreetmap/josm/testutils/annotations/I18n.java +++ b/test/unit/org/openstreetmap/josm/testutils/annotations/I18n.java @@ -45,15 +45,22 @@ public void beforeEach(ExtensionContext context) { String language = AnnotationUtils.findFirstParentAnnotation(context, I18n.class).map(I18n::value).orElse("en"); if (!Locale.getDefault().equals(LanguageInfo.getLocale(language, false))) { org.openstreetmap.josm.tools.I18n.set(language); + // We want to have a consistent "country", so we don't use a locale with a country code from the original locale. + // Unless someone specified it via the _ syntax. + if (!language.contains("_")) { + Locale.setDefault(LanguageInfo.getLocale(language, false)); + } } } @Override public void afterEach(ExtensionContext context) { - if (!Locale.ENGLISH.equals(Locale.getDefault())) { + Locale original = org.openstreetmap.josm.tools.I18n.getOriginalLocale(); + if (original == null) { org.openstreetmap.josm.tools.I18n.set("en"); - org.openstreetmap.josm.tools.I18n.set(org.openstreetmap.josm.tools.I18n.getOriginalLocale().getLanguage()); - Locale.setDefault(Locale.ENGLISH); + } else if (!original.equals(Locale.getDefault())) { + org.openstreetmap.josm.tools.I18n.set(original.getLanguage()); + Locale.setDefault(original); } } } diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/LayerManager.java b/test/unit/org/openstreetmap/josm/testutils/annotations/LayerManager.java index 5ca67dfd373..ea208fc0223 100644 --- a/test/unit/org/openstreetmap/josm/testutils/annotations/LayerManager.java +++ b/test/unit/org/openstreetmap/josm/testutils/annotations/LayerManager.java @@ -24,6 +24,9 @@ @Retention(RetentionPolicy.RUNTIME) @ExtendWith(LayerManager.LayerManagerExtension.class) public @interface LayerManager { + /** + * Clean the layer environment + */ class LayerManagerExtension implements BeforeEachCallback, AfterEachCallback { @Override public void afterEach(ExtensionContext context) { diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/Logging.java b/test/unit/org/openstreetmap/josm/testutils/annotations/Logging.java index 82d939a4e80..b2c7078c372 100644 --- a/test/unit/org/openstreetmap/josm/testutils/annotations/Logging.java +++ b/test/unit/org/openstreetmap/josm/testutils/annotations/Logging.java @@ -13,6 +13,9 @@ */ @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Logging { + /** + * Set up loggers for testing + */ class LoggingExtension implements BeforeEachCallback { @Override diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/MapPaintStyles.java b/test/unit/org/openstreetmap/josm/testutils/annotations/MapPaintStyles.java index 5ea25e060ee..7d696d29c5f 100644 --- a/test/unit/org/openstreetmap/josm/testutils/annotations/MapPaintStyles.java +++ b/test/unit/org/openstreetmap/josm/testutils/annotations/MapPaintStyles.java @@ -25,6 +25,9 @@ @BasicPreferences @ExtendWith(MapPaintStyles.MapPaintStylesExtension.class) public @interface MapPaintStyles { + /** + * Set up the default paintstyles + */ class MapPaintStylesExtension implements BeforeEachCallback { private static int lastHashcode; diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/MeasurementSystem.java b/test/unit/org/openstreetmap/josm/testutils/annotations/MeasurementSystem.java index 8f01fd3c3ba..563545f7c74 100644 --- a/test/unit/org/openstreetmap/josm/testutils/annotations/MeasurementSystem.java +++ b/test/unit/org/openstreetmap/josm/testutils/annotations/MeasurementSystem.java @@ -41,6 +41,9 @@ */ String value() default "Metric"; + /** + * Set up the system of measurement + */ class SystemOfMeasurementExtension implements BeforeEachCallback { @Override public void beforeEach(ExtensionContext extensionContext) throws Exception { diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/OsmApi.java b/test/unit/org/openstreetmap/josm/testutils/annotations/OsmApi.java index 435b0213fc0..b2df8a0e166 100644 --- a/test/unit/org/openstreetmap/josm/testutils/annotations/OsmApi.java +++ b/test/unit/org/openstreetmap/josm/testutils/annotations/OsmApi.java @@ -30,6 +30,10 @@ @Target({ElementType.TYPE, ElementType.METHOD}) public @interface OsmApi { APIType value() default APIType.NONE; + + /** + * The API type to set up + */ enum APIType { /** Don't use any API */ NONE, @@ -39,6 +43,9 @@ enum APIType { DEV } + /** + * Set up {@link org.openstreetmap.josm.io.OsmApi} for testing + */ class OsmApiExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback { @Override public void afterEach(ExtensionContext context) throws Exception { diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/ProjectionNadGrids.java b/test/unit/org/openstreetmap/josm/testutils/annotations/ProjectionNadGrids.java index 22076cf7044..2722e0fa822 100644 --- a/test/unit/org/openstreetmap/josm/testutils/annotations/ProjectionNadGrids.java +++ b/test/unit/org/openstreetmap/josm/testutils/annotations/ProjectionNadGrids.java @@ -27,10 +27,13 @@ @Target({ ElementType.METHOD, ElementType.TYPE }) @ExtendWith(ProjectionNadGrids.NadGridsExtension.class) public @interface ProjectionNadGrids { + /** + * Set up the NAD grids for testing + */ class NadGridsExtension implements BeforeEachCallback { @Override public void beforeEach(ExtensionContext extensionContext) throws Exception { - if (Utils.isBlank(Utils.getSystemProperty("PROJ_LIB"))) { + if (Utils.isStripEmpty(Utils.getSystemProperty("PROJ_LIB"))) { Utils.updateSystemProperty("PROJ_LIB", Paths.get("nodist", "data", "projection").toString()); } MainApplication.setupNadGridSources(); diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/ResetUniquePrimitiveIdCounters.java b/test/unit/org/openstreetmap/josm/testutils/annotations/ResetUniquePrimitiveIdCounters.java index 6d026be4b7b..fe23d2e293b 100644 --- a/test/unit/org/openstreetmap/josm/testutils/annotations/ResetUniquePrimitiveIdCounters.java +++ b/test/unit/org/openstreetmap/josm/testutils/annotations/ResetUniquePrimitiveIdCounters.java @@ -36,6 +36,10 @@ @BasicPreferences @ExtendWith(ResetUniquePrimitiveIdCounters.Reset.class) public @interface ResetUniquePrimitiveIdCounters { + /** + * Reset the id counters for {@link Node}, {@link Way}, and {@link Relation} + * {@link org.openstreetmap.josm.data.osm.AbstractPrimitive#getIdGenerator} calls. + */ class Reset implements BeforeEachCallback { private static AtomicLong[] ID_COUNTERS; diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/TaggingPresets.java b/test/unit/org/openstreetmap/josm/testutils/annotations/TaggingPresets.java index 5c16c55ad69..4407cd69dd3 100644 --- a/test/unit/org/openstreetmap/josm/testutils/annotations/TaggingPresets.java +++ b/test/unit/org/openstreetmap/josm/testutils/annotations/TaggingPresets.java @@ -7,7 +7,10 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Collection; +import java.util.Locale; +import java.util.Objects; +import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; @@ -24,11 +27,21 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.TYPE }) @BasicPreferences +@Territories @ExtendWith(TaggingPresets.TaggingPresetsExtension.class) public @interface TaggingPresets { - class TaggingPresetsExtension implements BeforeEachCallback { + /** + * Reset the tagging presets between each test -- presets will be reset if they are changed. + */ + class TaggingPresetsExtension implements BeforeEachCallback, BeforeAllCallback { private static int expectedHashcode = 0; + private static Locale lastLocale; + + @Override + public void beforeAll(ExtensionContext extensionContext) throws Exception { + setup(); + } @Override public void beforeEach(ExtensionContext extensionContext) { @@ -36,13 +49,14 @@ public void beforeEach(ExtensionContext extensionContext) { } /** - * Setup the tagging presets + * Set up the tagging presets */ public static synchronized void setup() { final Collection oldPresets = org.openstreetmap.josm.gui.tagging.presets.TaggingPresets.getTaggingPresets(); - if (oldPresets.isEmpty() || expectedHashcode != oldPresets.hashCode()) { + if (oldPresets.isEmpty() || expectedHashcode != oldPresets.hashCode() || !Objects.equals(lastLocale, Locale.getDefault())) { org.openstreetmap.josm.gui.tagging.presets.TaggingPresets.readFromPreferences(); expectedHashcode = org.openstreetmap.josm.gui.tagging.presets.TaggingPresets.getTaggingPresets().hashCode(); + lastLocale = Locale.getDefault(); } } } diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/TestUser.java b/test/unit/org/openstreetmap/josm/testutils/annotations/TestUser.java index 40095f0857c..e7e8b73d098 100644 --- a/test/unit/org/openstreetmap/josm/testutils/annotations/TestUser.java +++ b/test/unit/org/openstreetmap/josm/testutils/annotations/TestUser.java @@ -9,7 +9,6 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.net.Authenticator; -import java.net.PasswordAuthentication; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; @@ -18,6 +17,9 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.openstreetmap.josm.TestUtils; import org.openstreetmap.josm.data.UserIdentityManager; +import org.openstreetmap.josm.data.oauth.OAuth20Token; +import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder; +import org.openstreetmap.josm.data.oauth.OAuthParameters; import org.openstreetmap.josm.io.auth.CredentialsManager; import org.openstreetmap.josm.spi.preferences.Config; import org.openstreetmap.josm.tools.Utils; @@ -32,6 +34,9 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface TestUser { + /** + * Initialize a user for tests + */ class TestUserExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback { @Override public void afterEach(ExtensionContext context) throws Exception { @@ -47,17 +52,20 @@ public void beforeAll(ExtensionContext context) throws Exception { @Override public void beforeEach(ExtensionContext context) throws Exception { assumeTrue(TestUtils.areCredentialsProvided(), - "OSM DEV API credentials not provided. Please define them with -Dosm.username and -Dosm.password"); - final String username = Utils.getSystemProperty("osm.username"); - final String password = Utils.getSystemProperty("osm.password"); - assumeTrue(username != null && !username.isEmpty(), "Please add -Dosm.username for the OSM DEV API"); - assumeTrue(password != null && !password.isEmpty(), "Please add -Dosm.password for the OSM DEV API"); - Config.getPref().put("osm-server.auth-method", "basic"); + "OSM DEV API credentials not provided. Please define them with -Dosm.oauth2"); + final String oauth2 = Utils.getSystemProperty("osm.oauth2"); + Config.getPref().put("osm-server.auth-method", "oauth20"); // don't use atomic upload, the test API server can't cope with large diff uploads Config.getPref().putBoolean("osm-server.atomic-upload", false); - CredentialsManager.getInstance().store(Authenticator.RequestorType.SERVER, org.openstreetmap.josm.io.OsmApi.getOsmApi().getHost(), - new PasswordAuthentication(username, password.toCharArray())); + final String serverUrl = org.openstreetmap.josm.io.OsmApi.getOsmApi().getServerUrl(); + final OAuth20Token token = new OAuth20Token(OAuthParameters.createDefault(), + "{\"token_type\":\"bearer\", \"access_token\": \"" + oauth2 + "\"}"); + OAuthAccessTokenHolder.getInstance().setAccessToken(serverUrl, token); + CredentialsManager.getInstance().storeOAuthAccessToken(serverUrl, token); + if (!UserIdentityManager.getInstance().isFullyIdentified()) { + UserIdentityManager.getInstance().initFromOAuth(); + } } } } diff --git a/test/unit/org/openstreetmap/josm/testutils/annotations/Timezone.java b/test/unit/org/openstreetmap/josm/testutils/annotations/Timezone.java index c73838f70ef..272b2b6cf1c 100644 --- a/test/unit/org/openstreetmap/josm/testutils/annotations/Timezone.java +++ b/test/unit/org/openstreetmap/josm/testutils/annotations/Timezone.java @@ -20,6 +20,9 @@ @Retention(RetentionPolicy.RUNTIME) @ExtendWith(Timezone.TimezoneExtension.class) public @interface Timezone { + /** + * Set the default timezone for tests (UTC) + */ class TimezoneExtension implements BeforeEachCallback { @Override public void beforeEach(ExtensionContext context) { diff --git a/test/unit/org/openstreetmap/josm/tools/AlphanumComparatorTest.java b/test/unit/org/openstreetmap/josm/tools/AlphanumComparatorTest.java index 4f42cdd9673..9e0a7fe7cbc 100644 --- a/test/unit/org/openstreetmap/josm/tools/AlphanumComparatorTest.java +++ b/test/unit/org/openstreetmap/josm/tools/AlphanumComparatorTest.java @@ -1,17 +1,27 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.tools; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; +import java.text.Collator; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.function.BiFunction; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; /** * Unit tests of {@link AlphanumComparator}. */ class AlphanumComparatorTest { + @AfterEach + void teardown() { + AlphanumComparator.useFastASCIISort = true; + } /** * Test numeric strings. @@ -32,4 +42,76 @@ void testMixed() { lst.sort(AlphanumComparator.getInstance()); assertEquals(Arrays.asList("a5", "a100", "a00999", "b1", "b20"), lst); } + + private static Stream testNonRegression23471Arguments() { + List testStrings = Arrays.asList( + "AMEN", + "Ameriabank", + "America First Credit Union", + "BAC Credomatic", + "BADR Banque", + "BAI", + "Banca Popolare di Cividale", + "Banca Popolare di Sondrio", + "Banca Sella", + "Banca Transilvania", + "Bancaribe", + "BancaStato", + "Banco Agrario", + "Banco AV Villas", + "Banco Azteca", + "Banco Bicentenario", + "Banco BISA", + "Banco BMG", + "Banco BPI (Portugal)", + "Banco BPM", + "Banco Caja Social", + "Banco Ciudad", + "Banco Continental (Paraguay)", + "Banco di Sardegna" + ); + List testChars = new ArrayList<>(AlphanumComparator.ASCII_SORT_ORDER.length()); + for (char c : AlphanumComparator.ASCII_SORT_ORDER.toCharArray()) { + testChars.add(Character.toString(c)); + } + BiFunction, String, List> subList = (list, string) -> list.subList(list.indexOf(string), list.size()); + return Stream.concat( + testStrings.stream().flatMap(first -> subList.apply(testStrings, first).stream().map(second -> new String[]{first, second})), + testChars.stream().flatMap(first -> subList.apply(testChars, first).stream().map(second -> new String[]{first, second})) + ); + } + + /** + * Non-regression test for #23471 + * This ensures that the comparison contract holds. + * There are ~5300 combinations run in <1s (as of 2024-02-14). + */ + @Test + void testNonRegression23471() { + assertAll(testNonRegression23471Arguments().map(strings -> () -> testNonRegression23471(strings[0], strings[1]))); + } + + private static void testNonRegression23471(String first, String second) { + AlphanumComparator.useFastASCIISort = true; + final AlphanumComparator instance = AlphanumComparator.getInstance(); + assertEquals(-instance.compare(first, second), instance.compare(second, first)); + // Ensure that the fast sort is equivalent to the slow sort + AlphanumComparator.useFastASCIISort = false; + final int slowFirstSecond = instance.compare(first, second); + final int slowSecondFirst = instance.compare(second, first); + AlphanumComparator.useFastASCIISort = true; + final int fastFirstSecond = instance.compare(first, second); + final int fastSecondFirst = instance.compare(second, first); + assertEquals(slowFirstSecond, fastFirstSecond); + assertEquals(slowSecondFirst, fastSecondFirst); + + final Collator collator = Collator.getInstance(); + collator.setStrength(Collator.SECONDARY); + // Check against the collator instance + assertEquals(Utils.clamp(collator.compare(first, second), -1, 1), + Utils.clamp(instance.compare(first, second), -1, 1)); + assertEquals(Utils.clamp(collator.compare(second, first), -1, 1), + Utils.clamp(instance.compare(second, first), -1, 1)); + } + } diff --git a/test/unit/org/openstreetmap/josm/tools/JosmDecimalFormatSymbolsProviderTest.java b/test/unit/org/openstreetmap/josm/tools/JosmDecimalFormatSymbolsProviderTest.java index 469c6009df4..093a0f68e01 100644 --- a/test/unit/org/openstreetmap/josm/tools/JosmDecimalFormatSymbolsProviderTest.java +++ b/test/unit/org/openstreetmap/josm/tools/JosmDecimalFormatSymbolsProviderTest.java @@ -2,8 +2,9 @@ package org.openstreetmap.josm.tools; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import java.io.InputStream; @@ -14,6 +15,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; /** * Unit tests of {@link JosmDecimalFormatSymbolsProvider}. @@ -22,29 +25,46 @@ class JosmDecimalFormatSymbolsProviderTest { @BeforeAll static void beforeAll() throws IOException { - if (Utils.getJavaVersion() >= 9) { - assertEquals("SPI,JRE,CLDR", System.getProperty("java.locale.providers"), - "This test must be launched with -Djava.locale.providers=SPI,JRE,CLDR"); - try (InputStream in = I18n.class.getResourceAsStream("/META-INF/services/java.text.spi.DecimalFormatSymbolsProvider")) { - assertEquals("org.openstreetmap.josm.tools.JosmDecimalFormatSymbolsProvider", - new String(Utils.readBytesFromStream(in), StandardCharsets.UTF_8).trim()); - } + assertEquals("SPI,CLDR", System.getProperty("java.locale.providers"), + "This test must be launched with -Djava.locale.providers=SPI,CLDR"); + try (InputStream in = I18n.class.getResourceAsStream("/META-INF/services/java.text.spi.DecimalFormatSymbolsProvider")) { + assertNotNull(in); + assertEquals("org.openstreetmap.josm.tools.JosmDecimalFormatSymbolsProvider", + new String(in.readAllBytes(), StandardCharsets.UTF_8).trim()); } } - @Test - void testGroupingSeparator() { + static Stream testGroupingSeparator() { System.out.println(Locale.getDefault()); - assumeTrue(Utils.getJavaVersion() >= 9); assertTrue(I18n.getAvailableTranslations().count() > 10); - I18n.getAvailableTranslations().forEach(this::checkGroupingSymbol); - Stream.of("", "AU", "IE", "US", "UK").map(country -> new Locale("en", country, "")).forEach(this::checkGroupingSymbol); - Stream.of("", "AT", "CH", "DE").map(country -> new Locale("de", country, "")).forEach(this::checkGroupingSymbol); + return Stream.concat( + I18n.getAvailableTranslations(), + Stream.concat(Stream.of("", "AU", "IE", "US", "UK").map(country -> new Locale("en", country, "")), + Stream.of("", "AT", "CH", "DE").map(country -> new Locale("de", country, "")) + )); } - private void checkGroupingSymbol(Locale locale) { - assertEquals("123\u202F456", DecimalFormat.getInstance(locale).format(123_456), locale.toString()); + @ParameterizedTest + @MethodSource + void testGroupingSeparator(Locale locale) { + final String formattedNumber = DecimalFormat.getInstance(locale).format(123_456); + // Note: If you have to add another numeral system, please indicate the name and the locale(s) it is for. + if (formattedNumber.startsWith("1")) { + // Western Arabic (for most locales) + assertEquals("123\u202F456", formattedNumber, locale.toString() + ": " + locale.getDisplayName()); + } else if (formattedNumber.startsWith("١")) { + // Eastern Arabic (for Arabic locale) + assertEquals("١٢٣\u202F٤٥٦", formattedNumber, locale.toString() + ": " + locale.getDisplayName()); + } else if (formattedNumber.startsWith("۱")) { + // Urdu (for Persian locale) + assertEquals("۱۲۳\u202F۴۵۶", formattedNumber, locale.toString() + ": " + locale.getDisplayName()); + } else if (formattedNumber.startsWith("१")) { + // Devanagari (for Marathi locale) + assertEquals("१२३\u202F४५६", formattedNumber, locale.toString() + ": " + locale.getDisplayName()); + } else { + fail(locale.toString() + " (" + locale.getDisplayName() + "): " + formattedNumber); + } } /** diff --git a/test/unit/org/openstreetmap/josm/tools/LanguageInfoTest.java b/test/unit/org/openstreetmap/josm/tools/LanguageInfoTest.java index 2c911620201..fda1e877b2a 100644 --- a/test/unit/org/openstreetmap/josm/tools/LanguageInfoTest.java +++ b/test/unit/org/openstreetmap/josm/tools/LanguageInfoTest.java @@ -45,7 +45,7 @@ void testWikiLanguagePrefix() { "", "DE:", "Pt:", "Ca:", "Zh-hans:", "Zh-hant:", "Ast:", "", "RU:", "No:"); } - private static void testGetWikiLanguagePrefixes(LanguageInfo.LocaleType type, String...expected) { + private static void testGetWikiLanguagePrefixes(LanguageInfo.LocaleType type, String... expected) { final List actual = Stream.of(EN_NZ, DE_DE, PT_BR, CA_ES_VALENCIA, ZN_CN, ZN_TW, AST, EN_GB, RU, NB) .map(locale -> LanguageInfo.getWikiLanguagePrefix(locale, type)) .collect(Collectors.toList()); diff --git a/test/unit/org/openstreetmap/josm/tools/PlatformHookOsxTest.java b/test/unit/org/openstreetmap/josm/tools/PlatformHookOsxTest.java index fc17ad679cb..c3e41a345f6 100644 --- a/test/unit/org/openstreetmap/josm/tools/PlatformHookOsxTest.java +++ b/test/unit/org/openstreetmap/josm/tools/PlatformHookOsxTest.java @@ -5,13 +5,14 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.File; import java.io.IOException; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; import org.openstreetmap.josm.spi.preferences.Config; /** @@ -34,7 +35,8 @@ public static void setUp() { */ @Test void testStartupHook() { - hook.startupHook((a, b, c, d) -> System.out.println("java callback"), u -> System.out.println("webstart callback")); + hook.startupHook((a, b, c, d) -> System.out.println("java callback"), + (a, b, c) -> System.out.println("sanity check callback")); } /** @@ -49,9 +51,9 @@ void testAfterPrefStartupHook() { * Test method for {@code PlatformHookOsx#openUrl} * @throws IOException if an error occurs */ + @EnabledOnOs(OS.MAC) @Test void testOpenUrl() throws IOException { - assumeTrue(PlatformManager.isPlatformOsx()); hook.openUrl(Config.getUrls().getJOSMWebsite()); } diff --git a/test/unit/org/openstreetmap/josm/tools/PlatformHookWindowsTest.java b/test/unit/org/openstreetmap/josm/tools/PlatformHookWindowsTest.java index 16d19e4f050..92382bb646f 100644 --- a/test/unit/org/openstreetmap/josm/tools/PlatformHookWindowsTest.java +++ b/test/unit/org/openstreetmap/josm/tools/PlatformHookWindowsTest.java @@ -45,8 +45,8 @@ public static void setUp() { @Test void testStartupHook() { final PlatformHook.JavaExpirationCallback javaCallback = (a, b, c, d) -> System.out.println("java callback"); - final PlatformHook.WebStartMigrationCallback webstartCallback = u -> System.out.println("webstart callback"); - assertDoesNotThrow(() -> hook.startupHook(javaCallback, webstartCallback)); + final PlatformHook.SanityCheckCallback sanityCheckCallback = (a, b, c) -> System.out.println("sanity check callback"); + assertDoesNotThrow(() -> hook.startupHook(javaCallback, sanityCheckCallback)); } /** diff --git a/test/unit/org/openstreetmap/josm/tools/UtilsTest.java b/test/unit/org/openstreetmap/josm/tools/UtilsTest.java index 756c70c79d0..c66c37487a2 100644 --- a/test/unit/org/openstreetmap/josm/tools/UtilsTest.java +++ b/test/unit/org/openstreetmap/josm/tools/UtilsTest.java @@ -9,7 +9,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -21,9 +20,10 @@ import java.util.TreeMap; import java.util.regex.Pattern; -import net.trajano.commons.testing.UtilityClassTestUtil; import org.junit.jupiter.api.Test; +import net.trajano.commons.testing.UtilityClassTestUtil; + /** * Unit tests of {@link Utils} class. */ @@ -350,15 +350,6 @@ void testGetJavaBuild() { } } - /** - * Tests if readBytesFromStream handles null streams (might happen when there is no data on error stream) - * @throws IOException in case of I/O error - */ - @Test - void testNullStreamForReadBytesFromStream() throws IOException { - assertEquals(0, Utils.readBytesFromStream(null).length, "Empty on null stream"); - } - /** * Test of {@link Utils#getLevenshteinDistance} method. */ @@ -547,4 +538,28 @@ void testGetStandardDeviation() { assertEquals(-1.0, Utils.getStandardDeviation(new double[]{})); assertEquals(-1.0, Utils.getStandardDeviation(new double[]{0})); } + + /** + * Test of {@link Utils#unitToMeter(String)} + */ + @Test + void testUnitToMeter() { + assertEquals(1.2, Utils.unitToMeter("1.2")); + assertEquals(1.3, Utils.unitToMeter(" 1,3 m ")); + assertEquals(1.4, Utils.unitToMeter("1.4m")); + assertEquals(1.5, Utils.unitToMeter("150cm")); + assertEquals(1.6, Utils.unitToMeter("1600.0mm")); + assertEquals(1700, Utils.unitToMeter("1.7km")); + assertEquals(-1800, Utils.unitToMeter("-1.8km")); + assertEquals(3.048, Utils.unitToMeter("10ft")); + assertEquals(6.096, Utils.unitToMeter("20'")); + assertEquals(2.54, Utils.unitToMeter("100in")); + assertEquals(5.08, Utils.unitToMeter("200\"")); + assertEquals(1852, Utils.unitToMeter("1nmi")); + assertEquals(1609.344, Utils.unitToMeter("1mi")); + assertEquals(3.0734, Utils.unitToMeter("10ft1in")); + assertEquals(6.1468, Utils.unitToMeter("20'2\"")); + assertEquals(-6.1468, Utils.unitToMeter("-20'2\"")); + assertThrows(IllegalArgumentException.class, () -> Utils.unitToMeter("Hallo")); + } } diff --git a/test/unit/org/openstreetmap/josm/tools/date/DateUtilsTest.java b/test/unit/org/openstreetmap/josm/tools/date/DateUtilsTest.java index 937b59b3bac..456952fe063 100644 --- a/test/unit/org/openstreetmap/josm/tools/date/DateUtilsTest.java +++ b/test/unit/org/openstreetmap/josm/tools/date/DateUtilsTest.java @@ -1,6 +1,7 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.tools.date; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -16,6 +17,7 @@ import java.util.TimeZone; import java.util.concurrent.ForkJoinPool; +import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -23,6 +25,7 @@ import org.openstreetmap.josm.testutils.annotations.BasicPreferences; import org.openstreetmap.josm.testutils.annotations.I18n; import org.openstreetmap.josm.tools.UncheckedParseException; +import org.openstreetmap.josm.tools.Utils; import net.trajano.commons.testing.UtilityClassTestUtil; @@ -152,14 +155,20 @@ void testFromDate() { */ @Test void testFormatTime() { - assertEquals("12:00 AM", DateUtils.formatTime(new Date(0), DateFormat.SHORT)); - assertEquals("1:00 AM", DateUtils.formatTime(new Date(60 * 60 * 1000), DateFormat.SHORT)); - assertEquals("12:00 AM", DateUtils.formatTime(new Date(999), DateFormat.SHORT)); + // Somewhere between Java 17 and Java 21, a non-breaking space replaced the original space between the time and AM/PM. + final var separator = Utils.getJavaVersion() >= 21 ? '\u202f' : ' '; + final var twelveAM = "12:00" + separator + "AM"; + assertEquals(twelveAM, DateUtils.formatTime(new Date(0), DateFormat.SHORT)); + assertEquals("1:00" + separator + "AM", DateUtils.formatTime(new Date(60 * 60 * 1000), DateFormat.SHORT)); + assertEquals(twelveAM, DateUtils.formatTime(new Date(999), DateFormat.SHORT)); // ignore seconds - assertEquals("12:00 AM", DateUtils.formatTime(new Date(5999), DateFormat.SHORT)); + assertEquals(twelveAM, DateUtils.formatTime(new Date(5999), DateFormat.SHORT)); setTimeZone(TimeZone.getTimeZone("Europe/Berlin")); - assertEquals("1:00:00 AM CET", DateUtils.formatTime(new Date(0), DateFormat.LONG)); + String p1 = "1:00:00" + separator + "AM GMT+01:00"; + String p2 = "1:00:00" + separator + "AM CET"; + assertThat("This is mostly dependent upon java.locale.providers.", DateUtils.formatTime(new Date(0), DateFormat.LONG), + CoreMatchers.anyOf(CoreMatchers.is(p1), CoreMatchers.is(p2))); } /** @@ -307,4 +316,13 @@ void testDateTimeFormatter() { DateUtils.PROP_ISO_DATES.put(iso); } } + + /** + * Some Java version use narrow no-break space ("NNBSP") instead of a space. + * @param time The time string with NNBSP instead of a space + * @return The time with spaces instead of NNBSP + */ + private static String replaceWhitespace(String time) { + return time.replace((char) 8239 /* "NNBSP" */, ' '); + } } diff --git a/tools/checkstyle/.classpath b/tools/checkstyle/.classpath deleted file mode 100644 index 7498423dfbd..00000000000 --- a/tools/checkstyle/.classpath +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/tools/checkstyle/.project b/tools/checkstyle/.project deleted file mode 100644 index 7b6d2c0ea08..00000000000 --- a/tools/checkstyle/.project +++ /dev/null @@ -1,46 +0,0 @@ - - - JOSM-Checkstyle-Eclipse-Plugin - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - org.maven.ide.eclipse.maven2Builder - - - - - net.sf.eclipsecs.core.CheckstyleBuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.m2e.core.maven2Nature - org.maven.ide.eclipse.maven2Nature - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - net.sf.eclipsecs.core.CheckstyleNature - - diff --git a/tools/checkstyle/.settings/org.eclipse.core.resources.prefs b/tools/checkstyle/.settings/org.eclipse.core.resources.prefs deleted file mode 100644 index 4824b802631..00000000000 --- a/tools/checkstyle/.settings/org.eclipse.core.resources.prefs +++ /dev/null @@ -1,2 +0,0 @@ -eclipse.preferences.version=1 -encoding/=UTF-8 diff --git a/tools/checkstyle/.settings/org.eclipse.jdt.core.prefs b/tools/checkstyle/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index bb35fa0a87b..00000000000 --- a/tools/checkstyle/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,11 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 -org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.8 -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.enumIdentifier=error -org.eclipse.jdt.core.compiler.source=1.8 diff --git a/tools/checkstyle/josm_checks.xml b/tools/checkstyle/josm_checks.xml index c7453aa997a..ab25f9b1214 100644 --- a/tools/checkstyle/josm_checks.xml +++ b/tools/checkstyle/josm_checks.xml @@ -31,6 +31,7 @@ JOSM Checkstyle rules + @@ -95,7 +96,6 @@ JOSM Checkstyle rules - diff --git a/tools/checkstyle/josm_filters.xml b/tools/checkstyle/josm_filters.xml index df27fdcf969..825d5d740dd 100644 --- a/tools/checkstyle/josm_filters.xml +++ b/tools/checkstyle/josm_filters.xml @@ -44,6 +44,4 @@ - - diff --git a/tools/checkstyle/src/checkstyle_packages.xml b/tools/checkstyle/src/checkstyle_packages.xml deleted file mode 100644 index 28b4213c196..00000000000 --- a/tools/checkstyle/src/checkstyle_packages.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - diff --git a/tools/checkstyle/src/org/openstreetmap/josm/TopLevelJavadocCheck.java b/tools/checkstyle/src/org/openstreetmap/josm/TopLevelJavadocCheck.java deleted file mode 100644 index 2c406dfabf2..00000000000 --- a/tools/checkstyle/src/org/openstreetmap/josm/TopLevelJavadocCheck.java +++ /dev/null @@ -1,96 +0,0 @@ -// License: GPL. For details, see LICENSE file. -package org.openstreetmap.josm; - -import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser; -import com.puppycrawl.tools.checkstyle.api.AbstractCheck; -import com.puppycrawl.tools.checkstyle.api.DetailAST; -import com.puppycrawl.tools.checkstyle.api.DetailNode; -import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; -import com.puppycrawl.tools.checkstyle.api.TokenTypes; -import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; - -/** - * Checks that there is Javadoc for every top level class, interface or enum. - */ -public class TopLevelJavadocCheck extends AbstractCheck { - - private boolean foundTopLevelClass; - - @Override - public int[] getAcceptableTokens() { - return getDefaultTokens(); - } - - @Override - public int[] getDefaultTokens() { - return new int[]{TokenTypes.CLASS_DEF, TokenTypes.INTERFACE_DEF, TokenTypes.ENUM_DEF}; - } - - @Override - public int[] getRequiredTokens() { - return new int[0]; - } - - @Override - public boolean isCommentNodesRequired() { - return true; - } - - @Override - public void beginTree(DetailAST rootAST) { - foundTopLevelClass = false; - } - - @Override - public void finishTree(DetailAST rootAST) { - if (!foundTopLevelClass) { - this.log(rootAST.getLineNo(), "assertion failure: unable to find toplevel class or interface"); - } - } - - private boolean hasJavadoc(DetailAST ast) { - DetailAST blockCommentBegin = ast.findFirstToken(TokenTypes.BLOCK_COMMENT_BEGIN); - if (blockCommentBegin == null) { - DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); - if (modifiers == null) - return false; - blockCommentBegin = modifiers.findFirstToken(TokenTypes.BLOCK_COMMENT_BEGIN); - if (blockCommentBegin == null) { - DetailAST annotation = modifiers.findFirstToken(TokenTypes.ANNOTATION); - if (annotation == null) - return false; - blockCommentBegin = annotation.findFirstToken(TokenTypes.BLOCK_COMMENT_BEGIN); - if (blockCommentBegin == null) - return false; - } - } - if (!JavadocUtil.isJavadocComment(blockCommentBegin)) - return false; - DetailNode javadocTree = new JavadocDetailNodeParser().parseJavadocAsDetailNode(blockCommentBegin).getTree(); - return hasProperText(javadocTree); - } - - private boolean hasProperText(DetailNode javadoc) { - if (javadoc == null) return false; - for (DetailNode child : javadoc.getChildren()) { - if (child.getType() == JavadocTokenTypes.TEXT) { - if (!child.getText().trim().isEmpty()) - return true; - } else if (child.getType() == JavadocTokenTypes.HTML_ELEMENT) { - return true; - } - } - return false; - } - - @Override - public void visitToken(DetailAST ast) { - DetailAST parent = ast.getParent(); - if (parent == null || parent.getType() == TokenTypes.COMPILATION_UNIT) { - foundTopLevelClass = true; - if (!hasJavadoc(ast)) { - this.log(ast.getLineNo(), "incomplete or missing Javadoc for top level class or interface"); - } - } - } -} diff --git a/tools/checkstyle/src/org/openstreetmap/josm/checkstyle-metadata.properties b/tools/checkstyle/src/org/openstreetmap/josm/checkstyle-metadata.properties deleted file mode 100644 index b54f97b0b08..00000000000 --- a/tools/checkstyle/src/org/openstreetmap/josm/checkstyle-metadata.properties +++ /dev/null @@ -1,4 +0,0 @@ -JosmCustomChecks.desc = JOSM custom Checkstyle modules. - -TopLevelJavadoc.name = Top-Level Javadoc -TopLevelJavadoc.desc = Checks that there is Javadoc for every top level class, interface or enum diff --git a/tools/checkstyle/src/org/openstreetmap/josm/checkstyle-metadata.xml b/tools/checkstyle/src/org/openstreetmap/josm/checkstyle-metadata.xml deleted file mode 100644 index 2feab0a6b74..00000000000 --- a/tools/checkstyle/src/org/openstreetmap/josm/checkstyle-metadata.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - %JosmCustomChecks.desc - - - - %TopLevelJavadoc.desc - - - diff --git a/tools/checkstyle/src/org/openstreetmap/josm/messages.properties b/tools/checkstyle/src/org/openstreetmap/josm/messages.properties deleted file mode 100644 index d3f5a12faa9..00000000000 --- a/tools/checkstyle/src/org/openstreetmap/josm/messages.properties +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tools/eclipse/JOSM (Java 11).launch b/tools/eclipse/JOSM (Java 11).launch index 15d079b70a9..caae05d29c2 100644 --- a/tools/eclipse/JOSM (Java 11).launch +++ b/tools/eclipse/JOSM (Java 11).launch @@ -9,10 +9,10 @@ - + - + diff --git a/tools/eclipse/JOSM (Java 8).launch b/tools/eclipse/JOSM (Java 8).launch deleted file mode 100644 index aaa75dfae9b..00000000000 --- a/tools/eclipse/JOSM (Java 8).launch +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/tools/eclipse/JOSM (Java latest).launch b/tools/eclipse/JOSM (Java latest).launch index 245e0c37377..df80a2db0be 100644 --- a/tools/eclipse/JOSM (Java latest).launch +++ b/tools/eclipse/JOSM (Java latest).launch @@ -11,9 +11,9 @@ - + - + diff --git a/tools/ivy.xml b/tools/ivy.xml index 22c911aef1b..2b18a33ea90 100644 --- a/tools/ivy.xml +++ b/tools/ivy.xml @@ -14,25 +14,21 @@ - + - + - + - - - - - - + + + + - - + + - - - + diff --git a/tools/netbeans/manifest.mf b/tools/netbeans/manifest.mf deleted file mode 100644 index 328e8e5bc3b..00000000000 --- a/tools/netbeans/manifest.mf +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -X-COMMENT: Main-Class will be added automatically by build - diff --git a/tools/netbeans/nbbuild.xml b/tools/netbeans/nbbuild.xml deleted file mode 100644 index 73bb4ee2758..00000000000 --- a/tools/netbeans/nbbuild.xml +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - Builds, tests, and runs the project josm. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tools/netbeans/nbproject/configs/local_preferences.properties b/tools/netbeans/nbproject/configs/local_preferences.properties deleted file mode 100644 index f8be4bd31e5..00000000000 --- a/tools/netbeans/nbproject/configs/local_preferences.properties +++ /dev/null @@ -1,2 +0,0 @@ -$label=local preferences -run.jvmargs=-Djosm.home=.josm-local diff --git a/tools/netbeans/nbproject/configs/unit-test.properties b/tools/netbeans/nbproject/configs/unit-test.properties deleted file mode 100644 index 3b6aa87498d..00000000000 --- a/tools/netbeans/nbproject/configs/unit-test.properties +++ /dev/null @@ -1 +0,0 @@ -run.jvmargs=-Djava.awt.headless=true diff --git a/tools/netbeans/nbproject/genfiles.properties b/tools/netbeans/nbproject/genfiles.properties deleted file mode 100644 index d802f7d5c7a..00000000000 --- a/tools/netbeans/nbproject/genfiles.properties +++ /dev/null @@ -1,8 +0,0 @@ -nbbuild.xml.data.CRC32=34ed5462 -nbbuild.xml.script.CRC32=148a2ea3 -nbbuild.xml.stylesheet.CRC32=8064a381@1.75.2.48 -# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. -# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. -nbproject/build-impl.xml.data.CRC32=f36994fb -nbproject/build-impl.xml.script.CRC32=250d22b4 -nbproject/build-impl.xml.stylesheet.CRC32=f89f7d21@1.94.0.48 diff --git a/tools/netbeans/nbproject/private/config.properties b/tools/netbeans/nbproject/private/config.properties deleted file mode 100644 index 5a30365f913..00000000000 --- a/tools/netbeans/nbproject/private/config.properties +++ /dev/null @@ -1 +0,0 @@ -config=local_preferences diff --git a/tools/netbeans/nbproject/private/configs/local_preferences.properties b/tools/netbeans/nbproject/private/configs/local_preferences.properties deleted file mode 100644 index 19eb82fcab3..00000000000 --- a/tools/netbeans/nbproject/private/configs/local_preferences.properties +++ /dev/null @@ -1 +0,0 @@ -work.dir=../.. diff --git a/tools/netbeans/nbproject/private/configs/unit-test.properties b/tools/netbeans/nbproject/private/configs/unit-test.properties deleted file mode 100644 index 19eb82fcab3..00000000000 --- a/tools/netbeans/nbproject/private/configs/unit-test.properties +++ /dev/null @@ -1 +0,0 @@ -work.dir=../.. diff --git a/tools/netbeans/nbproject/project.properties b/tools/netbeans/nbproject/project.properties deleted file mode 100644 index 6d2a369dd79..00000000000 --- a/tools/netbeans/nbproject/project.properties +++ /dev/null @@ -1,109 +0,0 @@ -annotation.processing.enabled=true -annotation.processing.enabled.in.editor=false -annotation.processing.processors.list= -annotation.processing.run.all.processors=true -annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output -application.homepage=https://josm.openstreetmap.de/ -application.title=josm -application.vendor= -build.classes.dir=${build.dir}/classes -build.classes.excludes=**/*.java,**/*.form -# This directory is removed when the project is cleaned: -build.dir=build -build.generated.dir=${build.dir}/generated -build.generated.sources.dir=${build.dir}/generated-sources -# Only compile against the classpath explicitly listed here: -build.sysclasspath=ignore -build.test.classes.dir=${build.dir}/test/classes -build.test.results.dir=${build.dir}/test/results -buildfile=nbbuild.xml -# Uncomment to specify the preferred debugger connection transport: -#debug.transport=dt_socket -debug.classpath=\ - ${run.classpath} -debug.test.classpath=\ - ${run.test.classpath} -# Files in build.classes.dir which should be excluded from distribution jar -dist.archive.excludes= -# This directory is removed when the project is cleaned: -dist.dir=dist -dist.jar=${dist.dir}/josm.jar -dist.javadoc.dir=${dist.dir}/javadoc -endorsed.classpath= -excludes=org/apache/commons/jcs3/JCS.java,org/apache/commons/jcs3/access/GroupCacheAccess.java,org/apache/commons/jcs3/access/PartitionedCacheAccess.java,org/apache/commons/jcs3/access/behavior/IGroupCacheAccess.java,org/apache/commons/jcs3/access/exception/InvalidGroupException.java,org/apache/commons/jcs3/admin/servlet/**,org/apache/commons/jcs3/auxiliary/AbstractAuxiliaryCacheMonitor.java,org/apache/commons/jcs3/auxiliary/disk/jdbc/**,org/apache/commons/jcs3/auxiliary/lateral/**,org/apache/commons/jcs3/auxiliary/remote/AbstractRemoteAuxiliaryCache.java,org/apache/commons/jcs3/auxiliary/remote/AbstractRemoteCacheListener.java,org/apache/commons/jcs3/auxiliary/remote/AbstractRemoteCacheNoWaitFacade.java,org/apache/commons/jcs3/auxiliary/remote/RemoteCache.java,org/apache/commons/jcs3/auxiliary/remote/RemoteCacheFactory.java,org/apache/commons/jcs3/auxiliary/remote/RemoteCacheFailoverRunner.java,org/apache/commons/jcs3/auxiliary/remote/RemoteCacheListener.java,org/apache/commons/jcs3/auxiliary/remote/RemoteCacheManager.java,org/apache/commons/jcs3/auxiliary/remote/RemoteCacheMonitor.java,org/apache/commons/jcs3/auxiliary/remote/RemoteCacheNoWait.java,org/apache/commons/jcs3/auxiliary/remote/RemoteCacheNoWaitFacade.java,org/apache/commons/jcs3/auxiliary/remote/RemoteCacheRestore.java,org/apache/commons/jcs3/auxiliary/remote/http/**,org/apache/commons/jcs3/auxiliary/remote/server/RemoteCacheStartupServlet.java,org/apache/commons/jcs3/auxiliary/remote/server/TimeoutConfigurableRMISocketFactory.java,org/apache/commons/jcs3/engine/CacheAdaptor.java,org/apache/commons/jcs3/engine/CacheGroup.java,org/apache/commons/jcs3/engine/CacheWatchRepairable.java,org/apache/commons/jcs3/engine/ZombieCacheService.java,org/apache/commons/jcs3/engine/ZombieCacheServiceNonLocal.java,org/apache/commons/jcs3/engine/ZombieCacheWatch.java,org/apache/commons/jcs3/engine/logging/CacheEventLoggerDebugLogger.java,org/apache/commons/jcs3/utils/access/**,org/apache/commons/jcs3/utils/discovery/**,org/apache/commons/jcs3/utils/net/**,org/apache/commons/jcs3/utils/props/**,org/apache/commons/jcs3/utils/servlet/**,org/apache/commons/logging/impl/AvalonLogger.java,org/apache/commons/logging/impl/Jdk13LumberjackLogger.java,org/apache/commons/logging/impl/Log4JLogger.java,org/apache/commons/logging/impl/LogKitLogger.java,org/apache/commons/logging/impl/ServletContextCleaner.java,org/openstreetmap/gui/jmapviewer/Demo.java,org/openstreetmap/gui/jmapviewer/JMapViewerTree.java,org/openstreetmap/gui/jmapviewer/checkBoxTree/**,org/apache/commons/compress/archivers/**,org/apache/commons/compress/changes/**,org/apache/commons/compress/parallel/**,org/apache/commons/compress/PasswordRequiredException.java -file.reference.commons-lang3-3.8.1.jar=../../tools/commons-lang3-3.8.1.jar -file.reference.commons-testing-2.1.0.jar=../../test/lib/commons-testing/commons-testing-2.1.0.jar -file.reference.core-src=../../src -file.reference.equalsverifier-3.1.2.jar=../../test/lib/equalsverifier-3.1.2.jar -file.reference.spotbugs-annotations.jar=../../tools/spotbugs/spotbugs-annotations.jar -file.reference.hamcrest-core-1.3.jar=../../test/lib/junit/hamcrest-core-1.3.jar -file.reference.jfcunit.jar=../../test/lib/jfcunit.jar -file.reference.jmockit.jar=../../test/lib/jmockit.jar -file.reference.junit-4.12.jar=../../test/lib/junit/junit-4.12.jar -file.reference.classgraph.jar=../../test/lib/classgraph-4.6.32.jar -file.reference.test-functional=../../test/functional -file.reference.test-performance=../../test/performance -file.reference.test-unit=../../test/unit -file.reference.system-rules-1.19.0.jar=../../test/lib/system-rules-1.19.0.jar -file.reference.wiremock-standalone-2.20.0.jar=../../test/lib/wiremock-standalone-2.20.0.jar -file.reference.awaitility-3.1.5.jar=../../test/lib/awaitility-3.1.5.jar -includes=**/*.java -jar.compress=false -javac.classpath=${ivy.classpath} -# Space-separated list of extra javac options -javac.compilerargs=-Xlint:unchecked -Xlint:cast -Xlint:dep-ann -Xlint:divzero -Xlint:empty -Xlint:finally -Xlint:overrides -Xlint:static -Xlint:try -Xlint:deprecation -javac.deprecation=true -javac.external.vm=false -javac.modulepath= -javac.processormodulepath= -javac.processorpath=\ - ${javac.classpath} -javac.source=1.8 -javac.target=1.8 -javac.test.classpath=\ - ${javac.classpath}:\ - ${build.classes.dir}:\ - ${file.reference.junit-4.12.jar}:\ - ${file.reference.jmockit.jar}:\ - ${file.reference.jfcunit.jar}:\ - ${file.reference.equalsverifier-3.1.2.jar}:\ - ${file.reference.hamcrest-core-1.3.jar}:\ - ${file.reference.classgraph.jar}:\ - ${file.reference.commons-lang3-3.8.1.jar}:\ - ${file.reference.system-rules-1.19.0.jar}:\ - ${file.reference.wiremock-standalone-2.20.0.jar}:\ - ${file.reference.awaitility-3.1.5.jar}:\ - ${file.reference.spotbugs-annotations.jar}:\ - ${file.reference.commons-testing-2.1.0.jar} -javac.test.modulepath=${javac.modulepath} -javac.test.processorpath=\ - ${javac.test.classpath} -javadoc.additionalparam= -javadoc.author=false -javadoc.encoding=${source.encoding} -javadoc.noindex=false -javadoc.nonavbar=false -javadoc.notree=false -javadoc.private=false -javadoc.splitindex=true -javadoc.use=true -javadoc.version=false -javadoc.windowtitle= -main.class=org.openstreetmap.josm.gui.MainApplication -manifest.file=manifest.mf -meta.inf.dir=${src.dir}/META-INF -mkdist.disabled=false -platform.active=default_platform -run.classpath=\ - ${javac.classpath}:\ - ${build.classes.dir} -run.modulepath=${javac.modulepath} -run.test.classpath=\ - ${javac.test.classpath}:\ - ${build.test.classes.dir} -run.test.modulepath=${javac.test.modulepath} -source.encoding=UTF-8 -src.dir=${file.reference.core-src} -test.functional.dir=${file.reference.test-functional} -test.performance.dir=${file.reference.test-performance} -test.unit.dir=${file.reference.test-unit} diff --git a/tools/netbeans/nbproject/project.xml b/tools/netbeans/nbproject/project.xml deleted file mode 100644 index 8ffca0d066b..00000000000 --- a/tools/netbeans/nbproject/project.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - org.netbeans.modules.java.j2seproject - - - josm - - - - - - - - - - - diff --git a/tools/pmd/josm-ruleset.xml b/tools/pmd/josm-ruleset.xml index 74723a3a2aa..1c42ca38f5e 100644 --- a/tools/pmd/josm-ruleset.xml +++ b/tools/pmd/josm-ruleset.xml @@ -5,65 +5,54 @@ xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> JOSM PMD ruleset + + .*/org/openstreetmap/josm/gui/mappaint/mapcss/parsergen/.* - - - - - - - + - - + - + - + + - + - + - - - - - - - @@ -80,38 +69,35 @@ - - + - + - + - + @@ -123,6 +109,7 @@ + @@ -141,6 +128,7 @@ + @@ -152,7 +140,7 @@ - + @@ -166,11 +154,9 @@ - - + - @@ -179,7 +165,6 @@ - @@ -189,15 +174,13 @@ - + - -