diff --git a/.github/build.sh b/.github/build.sh new file mode 100755 index 000000000..523abeb87 --- /dev/null +++ b/.github/build.sh @@ -0,0 +1,3 @@ +#!/bin/sh +curl -fsLO https://raw.githubusercontent.com/scijava/scijava-scripts/main/ci-build.sh +sh ci-build.sh diff --git a/.github/setup.sh b/.github/setup.sh new file mode 100755 index 000000000..0ebca586f --- /dev/null +++ b/.github/setup.sh @@ -0,0 +1,10 @@ +#!/bin/sh +curl -fsLO https://raw.githubusercontent.com/scijava/scijava-scripts/main/ci-setup-github-actions.sh +sh ci-setup-github-actions.sh + +# Let the Linux build handle artifact deployment. +if [ "$(uname)" != Linux ] +then + echo "No deploy -- non-Linux build" + echo "NO_DEPLOY=1" >> $GITHUB_ENV +fi diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..a57c0df0b --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,42 @@ +name: build + +on: + push: + branches: + - master + tags: + - "*-[0-9]+.*" + pull_request: + branches: + - master + +jobs: + build: + name: build-${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - uses: actions/checkout@v4 + - name: Set up Java + uses: actions/setup-java@v4 + with: + java-version: '8' + distribution: 'zulu' + cache: 'maven' + - name: Set up CI environment + run: .github/setup.sh + shell: bash + - name: Execute the build + run: .github/build.sh + shell: bash + env: + GPG_KEY_NAME: ${{ secrets.GPG_KEY_NAME }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + MAVEN_USER: ${{ secrets.MAVEN_USER }} + MAVEN_PASS: ${{ secrets.MAVEN_PASS }} + CENTRAL_USER: ${{ secrets.CENTRAL_USER }} + CENTRAL_PASS: ${{ secrets.CENTRAL_PASS }} + SIGNING_ASC: ${{ secrets.SIGNING_ASC }} diff --git a/.mailmap b/.mailmap index e5a0e733c..5157cbbe1 100644 --- a/.mailmap +++ b/.mailmap @@ -1,7 +1,16 @@ Barry DeZonia -Christian Dietz +Christian Dietz +Christian Dietz +Gabriel Einsdorf +Gabriel Einsdorf +Gabriel Selzer +Gabriel Selzer ImageJ Jenkins +Jan Eglinger Johannes Schindelin Johannes Schindelin Jonathan Hale +Leon Yang +Leon Yang Mark Hiner +Richard Domander diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1570c3987..000000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -language: java -jdk: oraclejdk8 -branches: - only: master -install: true -script: ".travis/build.sh" -after_success: ".travis/notify.sh Travis-Success" -after_failure: ".travis/notify.sh Travis-Failure" -env: - global: - - secure: l5k7gyNsC7cS1QcqPcSzxQeueZmakAvYRDBevIGA61yDjL/cK4Pyy/7yNz4CHo+mgHaHul9uLIQuZvQdeAs4FqHi37brY1//emVI4BiUow+XDvge7DttDJ+JjBlGsZrxX0YKtnn/V83WO23RtGGQUZ8PBwpNWYB52stDpizhqqg= - - secure: Hs3V7J4BmasiXNWOBzFuxYcPeImFYo8Ze3UWqg85gRa1YxKNbnJLGPjQoIcUxXQU34Fa0qvvWcPUyEjne+nAbWtAVrq/xRSVj5Fnen+UmH3fjV1aR6Rsr56gkdqlaeIfcxaaeGTeguHQB1LSNCIvcF5s8f6J+37l65UUxGsL3+4= diff --git a/.travis/build.sh b/.travis/build.sh deleted file mode 100755 index 8cddb5f18..000000000 --- a/.travis/build.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh -dir="$(dirname "$0")" -if [ "$TRAVIS_SECURE_ENV_VARS" = true \ - -a "$TRAVIS_PULL_REQUEST" = false \ - -a "$TRAVIS_BRANCH" = master ] -then - mvn -Pdeploy-to-imagej deploy --settings "$dir/settings.xml" -else - mvn install -fi diff --git a/.travis/notify.sh b/.travis/notify.sh deleted file mode 100755 index b3b239e46..000000000 --- a/.travis/notify.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -curl -fs "https://jenkins.imagej.net/job/$1/buildWithParameters?token=$TOKEN_NAME&repo=$TRAVIS_REPO_SLUG&commit=$TRAVIS_COMMIT&pr=$TRAVIS_PULL_REQUEST" diff --git a/.travis/settings.xml b/.travis/settings.xml deleted file mode 100644 index 71a56300a..000000000 --- a/.travis/settings.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - imagej.releases - travis - ${env.MAVEN_PASS} - - - imagej.snapshots - travis - ${env.MAVEN_PASS} - - - diff --git a/LICENSE.txt b/LICENSE.txt index aaa28e8b4..55024a7c5 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,4 @@ -Copyright (c) 2009 - 2017, Board of Regents of the University of -Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck -Institute of Molecular Cell Biology and Genetics. +Copyright (c) 2009 - 2026, SciJava developers. All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 000000000..5b605b01d --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,207 @@ +This project contains code adapted from Apache Commons Lang +(https://commons.apache.org/proper/commons-lang/) version 3.4, +as well as GenTyRef (https://github.com/coekie/gentyref) version 1.1.0, +and EventBus (https://github.com/michaelbushe/EventBus) version 1.4, +each of which is licensed under the Apache 2.0 license, as follows: + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2012] [MOJO Codehaus] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 0838f7b7b..ba1f15173 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -[![](https://img.shields.io/maven-central/v/org.scijava/scijava-common.svg)](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.scijava%22%20AND%20a%3A%22scijava-common%22) -[![](https://travis-ci.org/scijava/scijava-common.svg?branch=master)](https://travis-ci.org/scijava/scijava-common) -[![Join the chat at https://gitter.im/scijava/scijava-common](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/scijava/scijava-common?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![](https://img.shields.io/maven-central/v/org.scijava/scijava-common.svg)](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.scijava%22%20AND%20a%3A%22scijava-common%22) +[![](https://github.com/scijava/scijava-common/actions/workflows/build.yml/badge.svg)](https://github.com/scijava/scijava-common/actions/workflows/build.yml) +[![developer chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://imagesc.zulipchat.com/#narrow/stream/327237-SciJava) SciJava Common is a common library for SciJava software. It provides a plugin framework, with an extensible mechanism for service discovery, backed by its own annotation processor, so that plugins can be loaded dynamically. -It is used by both [ImageJ](https://github.com/imagej/imagej) and +It is used by both [ImageJ2](https://github.com/imagej/imagej2) and [SCIFIO](https://github.com/scifio/scifio). diff --git a/pom.xml b/pom.xml index fd1a7ddea..c3715d0b5 100644 --- a/pom.xml +++ b/pom.xml @@ -1,16 +1,16 @@ - + 4.0.0 org.scijava pom-scijava - 13.1.0 + 43.0.0 scijava-common - 2.62.2-SNAPSHOT + 2.100.1-SNAPSHOT SciJava Common SciJava Common is a shared library for SciJava software. It provides a plugin framework, with an extensible mechanism for service discovery, backed by its own annotation processor, so that plugins can be loaded dynamically. It is used by downstream projects in the SciJava ecosystem, such as ImageJ and SCIFIO. @@ -18,7 +18,7 @@ 2009 SciJava - http://www.scijava.org/ + https://scijava.org/ @@ -31,7 +31,7 @@ ctrueden Curtis Rueden - http://imagej.net/User:Rueden + https://imagej.net/people/ctrueden founder lead @@ -46,53 +46,99 @@ Mark Hiner - http://imagej.net/User:Hinerm + https://imagej.net/people/hinerm founder hinerm Johannes Schindelin - http://imagej.net/User:Schindelin + https://imagej.net/people/dscho dscho - Barry DeZonia - http://imagej.net/User:Bdezonia - bdezonia + Chris Allan + chris-allan - Lee Kamentsky - http://imagej.net/User:Leek - LeeKamentsky + Nicolas Chiaruttini + https://imagej.net/people/NicoKiaru + NicoKiaru + + + Barry DeZonia + https://imagej.net/people/bdezonia + bdezonia Christian Dietz - http://imagej.net/User:Dietzc + https://imagej.net/people/dietzc dietzc Richard Domander - http://imagej.net/User:Rdom + https://imagej.net/people/rimadoma rimadoma Gabriel Einsdorf - http://imagej.net/User:Gab1one + https://imagej.net/people/gab1one gab1one + + Aivar Grislis + https://imagej.net/people/grislis + grislis + Jonathan Hale + https://imagej.net/people/Squareys Squareys + + Grant Harris + https://imagej.net/people/tnargsirrah + tnargsirrah + + + Lee Kamentsky + https://imagej.net/people/LeeKamentsky + LeeKamentsky + + + Rick Lentz + https://imagej.net/people/ricklentz + ricklentz + + + Melissa Linkert + https://imagej.net/people/melissalinkert + melissalinkert + Kevin Mader - http://imagej.net/User:Ksmader + https://imagej.net/people/kmader kmader + + Hadrien Mary + https://imagej.net/people/hadim + hadim + + + Alison Walter + https://imagej.net/people/awalter17 + awalter17 + Jay Warrick + https://imagej.net/people/jaywarrick jaywarrick + + Christian Tischer + https://imagej.net/people/tischi + tischi + @@ -106,7 +152,7 @@ - scm:git:git://github.com/scijava/scijava-common + scm:git:https://github.com/scijava/scijava-common scm:git:git@github.com:scijava/scijava-common HEAD https://github.com/scijava/scijava-common @@ -116,8 +162,8 @@ https://github.com/scijava/scijava-common/issues - Travis CI - https://travis-ci.org/scijava/scijava-common + GitHub Actions + https://github.com/scijava/scijava-common/actions @@ -125,9 +171,11 @@ bsd_2 SciJava Common shared library for SciJava software. - Board of Regents of the University of -Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck -Institute of Molecular Cell Biology and Genetics. + SciJava developers. + **/bushe/** + + + 2.22.2 @@ -137,39 +185,32 @@ Institute of Molecular Cell Biology and Genetics. parsington - - - com.googlecode.gentyref - gentyref - 1.1.0 - - - org.bushe - eventbus - 1.4 - - junit junit test + + org.mockito + mockito-core + test + - + org.apache.maven.plugins maven-compiler-plugin @@ -182,11 +223,22 @@ Institute of Molecular Cell Biology and Genetics. exec-maven-plugin + index-annotations process-classes java + + index-test-annotations + process-test-classes + + java + + + test + + org.scijava.annotations.EclipseHelper diff --git a/src/it/apt-test/pom.xml b/src/it/apt-test/pom.xml index fba517e29..c416a3860 100644 --- a/src/it/apt-test/pom.xml +++ b/src/it/apt-test/pom.xml @@ -3,9 +3,7 @@ #%L SciJava Common shared library for SciJava software. %% - Copyright (C) 2009 - 2017 Board of Regents of the University of - Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - Institute of Molecular Cell Biology and Genetics. + Copyright (C) 2009 - 2026 SciJava developers. %% Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -33,7 +31,7 @@ + https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 @project.groupId@ diff --git a/src/it/apt-test/setup.bsh b/src/it/apt-test/setup.bsh index 86549ce6d..30380371c 100644 --- a/src/it/apt-test/setup.bsh +++ b/src/it/apt-test/setup.bsh @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/it/apt-test/src/main/java/org/scijava/annotation/its/Annotated.java b/src/it/apt-test/src/main/java/org/scijava/annotation/its/Annotated.java index 466bd78c3..2d9e5d420 100644 --- a/src/it/apt-test/src/main/java/org/scijava/annotation/its/Annotated.java +++ b/src/it/apt-test/src/main/java/org/scijava/annotation/its/Annotated.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/it/apt-test/src/main/java/org/scijava/annotation/its/CustomAnnotation.java b/src/it/apt-test/src/main/java/org/scijava/annotation/its/CustomAnnotation.java index a21cd5344..1b456f1c3 100644 --- a/src/it/apt-test/src/main/java/org/scijava/annotation/its/CustomAnnotation.java +++ b/src/it/apt-test/src/main/java/org/scijava/annotation/its/CustomAnnotation.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/it/apt-test/verify.bsh b/src/it/apt-test/verify.bsh index edc3dfab2..679bbf6ed 100644 --- a/src/it/apt-test/verify.bsh +++ b/src/it/apt-test/verify.bsh @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/it/settings.xml b/src/it/settings.xml index d4ee30ac3..b3042e9e1 100644 --- a/src/it/settings.xml +++ b/src/it/settings.xml @@ -3,9 +3,7 @@ #%L SciJava Common shared library for SciJava software. %% - Copyright (C) 2009 - 2017 Board of Regents of the University of - Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - Institute of Molecular Cell Biology and Genetics. + Copyright (C) 2009 - 2026 SciJava developers. %% Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/AbstractBasicDetails.java b/src/main/java/org/scijava/AbstractBasicDetails.java index df0bcba15..75e28c611 100644 --- a/src/main/java/org/scijava/AbstractBasicDetails.java +++ b/src/main/java/org/scijava/AbstractBasicDetails.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/AbstractContextual.java b/src/main/java/org/scijava/AbstractContextual.java index 768ce4986..6d47180bf 100644 --- a/src/main/java/org/scijava/AbstractContextual.java +++ b/src/main/java/org/scijava/AbstractContextual.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/AbstractGateway.java b/src/main/java/org/scijava/AbstractGateway.java index a4980d63d..14f066a7f 100644 --- a/src/main/java/org/scijava/AbstractGateway.java +++ b/src/main/java/org/scijava/AbstractGateway.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -44,6 +42,7 @@ import org.scijava.input.InputService; import org.scijava.io.IOService; import org.scijava.io.RecentFileService; +import org.scijava.io.location.LocationService; import org.scijava.log.LogService; import org.scijava.main.MainService; import org.scijava.menu.MenuService; @@ -53,10 +52,12 @@ import org.scijava.platform.AppEventService; import org.scijava.platform.PlatformService; import org.scijava.plugin.AbstractRichPlugin; +import org.scijava.plugin.PluginInfo; import org.scijava.plugin.PluginService; import org.scijava.prefs.PrefService; import org.scijava.script.ScriptService; import org.scijava.service.Service; +import org.scijava.startup.StartupService; import org.scijava.text.TextService; import org.scijava.thread.ThreadService; import org.scijava.tool.IconService; @@ -84,7 +85,15 @@ public AbstractGateway() { public AbstractGateway(final String appName, final Context context) { this.appName = appName; - if (context != null) setContext(context); + if (context != null) { + setContext(context); + + // NB: Make a best effort to inject plugin metadata. + final PluginInfo info = PluginInfo.getOrCreate(getClass(), + Gateway.class, context.getPluginIndex()); + info.inject(this); + Priority.inject(this, info.getPriority()); + } } // -- Gateway methods -- @@ -101,6 +110,9 @@ public void launch(final String... args) { // NB: When running headless, the HeadlessUI will be used. if (mainCount == 0) ui().showUI(); + // perform all pending startup operations + startup().executeOperations(); + if (ui().isHeadless()) { // now that CLI processing/execution is done, we can shut down getContext().dispose(); @@ -109,6 +121,8 @@ public void launch(final String... args) { @Override public String getShortName() { + final String pluginName = getInfo() == null ? null : getInfo().getName(); + if (pluginName != null && !pluginName.isEmpty()) return pluginName; return getClass().getSimpleName().toLowerCase(); } @@ -177,6 +191,11 @@ public InputService input() { public IOService io() { return get(IOService.class); } + + @Override + public LocationService location() { + return get(LocationService.class); + } @Override public LogService log() { @@ -232,6 +251,11 @@ public ScriptService script() { return get(ScriptService.class); } + @Override + public StartupService startup() { + return get(StartupService.class); + } + @Override public StatusService status() { return get(StatusService.class); diff --git a/src/main/java/org/scijava/AbstractUIDetails.java b/src/main/java/org/scijava/AbstractUIDetails.java index 2c81d5f31..11476a03a 100644 --- a/src/main/java/org/scijava/AbstractUIDetails.java +++ b/src/main/java/org/scijava/AbstractUIDetails.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -50,7 +48,7 @@ public abstract class AbstractUIDetails extends AbstractBasicDetails implements private String iconPath; /** Sort priority of the object. */ - private double priority = Priority.NORMAL_PRIORITY; + private double priority = Priority.NORMAL; /** Whether the object can be selected in the user interface. */ private boolean selectable; diff --git a/src/main/java/org/scijava/BasicDetails.java b/src/main/java/org/scijava/BasicDetails.java index 4dd50a1a9..6c37807e6 100644 --- a/src/main/java/org/scijava/BasicDetails.java +++ b/src/main/java/org/scijava/BasicDetails.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/Cancelable.java b/src/main/java/org/scijava/Cancelable.java index 030ba023a..5bc0fe61c 100644 --- a/src/main/java/org/scijava/Cancelable.java +++ b/src/main/java/org/scijava/Cancelable.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/Context.java b/src/main/java/org/scijava/Context.java index e74971f46..bfb118099 100644 --- a/src/main/java/org/scijava/Context.java +++ b/src/main/java/org/scijava/Context.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -35,11 +33,15 @@ import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; +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.concurrent.ConcurrentHashMap; +import org.scijava.event.ContextCreatedEvent; import org.scijava.event.ContextDisposingEvent; import org.scijava.event.EventHandler; import org.scijava.event.EventService; @@ -51,6 +53,7 @@ import org.scijava.service.ServiceIndex; import org.scijava.util.ClassUtils; import org.scijava.util.Query; +import org.scijava.util.Types; /** * Top-level SciJava application context, which initializes and maintains a list @@ -59,7 +62,7 @@ * @author Curtis Rueden * @see Service */ -public class Context implements Disposable { +public class Context implements Disposable, AutoCloseable { // -- Constants -- @@ -73,6 +76,14 @@ public class Context implements Disposable { */ public static final String STRICT_PROPERTY = "scijava.context.strict"; + /** Set of currently active (not disposed) application contexts. */ + private static final Map CONTEXTS = + new ConcurrentHashMap<>(); // NB: ConcurrentHashMap disallows nulls. + + // -- Static fields -- + + private static Thread shutdownThread = null; + // -- Fields -- /** Index of the application context's services. */ @@ -98,6 +109,12 @@ public class Context implements Disposable { */ private boolean strict; + /** + * False if the context is currently active; true if the context + * has already been disposed, or is in the process of being disposed. + */ + private boolean disposed; + /** * Creates a new SciJava application context with all available services. * @@ -249,6 +266,13 @@ public Context(final Collection> serviceClasses, * those of lower priority). See {@link ServiceHelper#loadServices()} for more * information. *

+ *

+ * NB: Instiantiation of a Context has an implied requirement of a + * corresponding call to {@link Context#dispose()} at the end of the SciJava + * applicaton's lifecycle. This cleans up any remaining resources and allows + * the JVM to exit gracefully. This is called automatically when constructed as + * an {@link AutoCloseable}. + *

* * @param serviceClasses A collection of types that implement the * {@link Service} interface (e.g., {@code DisplayService.class}). @@ -278,6 +302,26 @@ public Context(final Collection> serviceClasses, new ServiceHelper(this, serviceClasses, strict); serviceHelper.loadServices(); } + + // If JVM shuts down with context still active, clean up after ourselves. + if (shutdownThread == null) { + synchronized (Context.class) { + if (shutdownThread == null) { + shutdownThread = new Thread(() -> { + final List contexts = new ArrayList<>(CONTEXTS.keySet()); + for (final Context context : contexts) { + context.doDispose(false); + } + }); + Runtime.getRuntime().addShutdownHook(shutdownThread); + } + } + } + CONTEXTS.put(this, true); + + // Publish an event to indicate that context initialization is complete. + final EventService eventService = getService(EventService.class); + if (eventService != null) eventService.publish(new ContextCreatedEvent()); } // -- Context methods -- @@ -322,7 +366,7 @@ public S service(final Class c) { * service. */ public Service service(final String className) { - final Class c = ClassUtils.loadClass(className, false); + final Class c = Types.load(className, false); if (!Service.class.isAssignableFrom(c)) { throw new IllegalArgumentException("Not a service class: " + c.getName()); } @@ -341,7 +385,7 @@ public S getService(final Class c) { /** Gets the service of the given class name (useful for scripts). */ public Service getService(final String className) { - final Class c = ClassUtils.loadClass(className); + final Class c = Types.load(className); if (c == null) return null; if (!Service.class.isAssignableFrom(c)) return null; // not a service class @SuppressWarnings("unchecked") @@ -412,16 +456,14 @@ public boolean isInjectable(final Class type) { @Override public void dispose() { - final EventService eventService = getService(EventService.class); - if (eventService != null) eventService.publish(new ContextDisposingEvent()); + doDispose(true); + } - // NB: Dispose services in reverse order. - // This may or may not actually be necessary, but seems safer, since - // dependent services will be disposed *before* their dependencies. - final List services = serviceIndex.getAll(); - for (int s = services.size() - 1; s >= 0; s--) { - services.get(s).dispose(); - } + // -- AutoCloseable methods -- + + @Override + public void close() { + dispose(); } // -- Utility methods -- @@ -438,6 +480,19 @@ public static List> serviceClassList( Arrays.asList(serviceClasses) : Arrays.asList(Service.class); } + /** + * Gets the class loader to use. This will be the current thread's context + * class loader if non-null; otherwise it will be the system class loader. + * + * @see Thread#getContextClassLoader() + * @see ClassLoader#getSystemClassLoader() + */ + public static ClassLoader getClassLoader() { + final ClassLoader contextCL = Thread.currentThread() + .getContextClassLoader(); + return contextCL != null ? contextCL : ClassLoader.getSystemClassLoader(); + } + // -- Helper methods -- private List getParameterFields(final Object o) { @@ -528,8 +583,7 @@ private String createMissingServiceMessage( final Class serviceType) { final String nl = System.getProperty("line.separator"); - final ClassLoader classLoader = // - Thread.currentThread().getContextClassLoader(); + final ClassLoader classLoader = getClassLoader(); final StringBuilder msg = new StringBuilder( "Required service is missing: " + serviceType.getName() + nl); msg.append("Context: " + this + nl); @@ -555,17 +609,34 @@ private String createMissingServiceMessage( return msg.toString(); } + private synchronized void doDispose(final boolean announce) { + if (disposed) return; + disposed = true; + CONTEXTS.remove(this); + if (announce) { + final EventService eventService = getService(EventService.class); + if (eventService != null) eventService.publish(new ContextDisposingEvent()); + } + + // NB: Dispose services in reverse order. + // This may or may not actually be necessary, but seems safer, since + // dependent services will be disposed *before* their dependencies. + final List services = serviceIndex.getAll(); + for (int s = services.size() - 1; s >= 0; s--) { + services.get(s).dispose(); + } + } + private static PluginIndex plugins(final boolean empty) { return empty ? new PluginIndex(null) : null; } private static List> services(final boolean empty) { - if (empty) return Collections.>emptyList(); - return Arrays.>asList(Service.class); + if (empty) return Collections.> emptyList(); + return Arrays.> asList(Service.class); } private static boolean strict() { return !"false".equals(System.getProperty(STRICT_PROPERTY)); } - } diff --git a/src/main/java/org/scijava/Contextual.java b/src/main/java/org/scijava/Contextual.java index 9821c0dd7..f76c7fc0d 100644 --- a/src/main/java/org/scijava/Contextual.java +++ b/src/main/java/org/scijava/Contextual.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/Disposable.java b/src/main/java/org/scijava/Disposable.java index 0a86dfbce..ac72c90b4 100644 --- a/src/main/java/org/scijava/Disposable.java +++ b/src/main/java/org/scijava/Disposable.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/Gateway.java b/src/main/java/org/scijava/Gateway.java index e74b7188e..7cd98413e 100644 --- a/src/main/java/org/scijava/Gateway.java +++ b/src/main/java/org/scijava/Gateway.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -42,6 +40,7 @@ import org.scijava.input.InputService; import org.scijava.io.IOService; import org.scijava.io.RecentFileService; +import org.scijava.io.location.LocationService; import org.scijava.log.LogService; import org.scijava.main.MainService; import org.scijava.menu.MenuService; @@ -55,6 +54,7 @@ import org.scijava.plugin.RichPlugin; import org.scijava.script.ScriptService; import org.scijava.service.Service; +import org.scijava.startup.StartupService; import org.scijava.text.TextService; import org.scijava.thread.ThreadService; import org.scijava.tool.IconService; @@ -118,7 +118,7 @@ * @author Mark Hiner * @author Curtis Rueden */ -public interface Gateway extends RichPlugin { +public interface Gateway extends RichPlugin, Disposable { /** * Perform launch operations associated with this gateway. @@ -239,6 +239,13 @@ public interface Gateway extends RichPlugin { */ IOService io(); + /** + * Gets this application context's {@link LocationService}. + * + * @return The {@link LocationService} of this application context. + */ + LocationService location(); + /** * Gets this application context's {@link LogService}. * @@ -310,6 +317,13 @@ public interface Gateway extends RichPlugin { */ ScriptService script(); + /** + * Gets this application context's {@link StartupService}. + * + * @return The {@link StartupService} of this application context. + */ + StartupService startup(); + /** * Gets this application context's {@link StatusService}. * @@ -363,4 +377,8 @@ public interface Gateway extends RichPlugin { /** @see org.scijava.app.App#getInfo(boolean) */ String getInfo(boolean mem); + @Override + default void dispose() { + context().dispose(); + } } diff --git a/src/main/java/org/scijava/Identifiable.java b/src/main/java/org/scijava/Identifiable.java index 116ee2775..53db16039 100644 --- a/src/main/java/org/scijava/Identifiable.java +++ b/src/main/java/org/scijava/Identifiable.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/Initializable.java b/src/main/java/org/scijava/Initializable.java index ce331d0df..56aa725ae 100644 --- a/src/main/java/org/scijava/Initializable.java +++ b/src/main/java/org/scijava/Initializable.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/Instantiable.java b/src/main/java/org/scijava/Instantiable.java index 041ffa413..3edca09a4 100644 --- a/src/main/java/org/scijava/Instantiable.java +++ b/src/main/java/org/scijava/Instantiable.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/InstantiableException.java b/src/main/java/org/scijava/InstantiableException.java index 1af1cef0c..0ad031f7b 100644 --- a/src/main/java/org/scijava/InstantiableException.java +++ b/src/main/java/org/scijava/InstantiableException.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ItemIO.java b/src/main/java/org/scijava/ItemIO.java index 96b7b49da..fa8b68684 100644 --- a/src/main/java/org/scijava/ItemIO.java +++ b/src/main/java/org/scijava/ItemIO.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ItemVisibility.java b/src/main/java/org/scijava/ItemVisibility.java index db5e7db88..47897a359 100644 --- a/src/main/java/org/scijava/ItemVisibility.java +++ b/src/main/java/org/scijava/ItemVisibility.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/Locatable.java b/src/main/java/org/scijava/Locatable.java index aff02fe28..a7da30625 100644 --- a/src/main/java/org/scijava/Locatable.java +++ b/src/main/java/org/scijava/Locatable.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -33,7 +31,7 @@ import java.net.URL; -import org.scijava.util.ClassUtils; +import org.scijava.util.Types; /** * An object whose location is defined by a URL string. @@ -44,7 +42,7 @@ public interface Locatable { /** Gets the URL string defining the object's location. */ default String getLocation() { - final URL location = ClassUtils.getLocation(getClass()); + final URL location = Types.location(getClass()); return location == null ? null : location.toExternalForm(); } diff --git a/src/main/java/org/scijava/MenuEntry.java b/src/main/java/org/scijava/MenuEntry.java index f2fc2b8f1..4a2b33bb9 100644 --- a/src/main/java/org/scijava/MenuEntry.java +++ b/src/main/java/org/scijava/MenuEntry.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/MenuPath.java b/src/main/java/org/scijava/MenuPath.java index 1771a7e8b..533fd2461 100644 --- a/src/main/java/org/scijava/MenuPath.java +++ b/src/main/java/org/scijava/MenuPath.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -61,7 +59,7 @@ public MenuPath(final Collection menuEntries) { /** * Creates a menu path with entries parsed from the given string. Assumes - * ">" as the separator (e.g., "File>New>Image"). + * {@code >} as the separator (e.g., {@code File>New>Image}). * * @see #PATH_SEPARATOR */ diff --git a/src/main/java/org/scijava/Named.java b/src/main/java/org/scijava/Named.java index 654fce4ab..9a381ee3a 100644 --- a/src/main/java/org/scijava/Named.java +++ b/src/main/java/org/scijava/Named.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/NoSuchServiceException.java b/src/main/java/org/scijava/NoSuchServiceException.java index b291f021d..d97d95647 100644 --- a/src/main/java/org/scijava/NoSuchServiceException.java +++ b/src/main/java/org/scijava/NoSuchServiceException.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/NullContextException.java b/src/main/java/org/scijava/NullContextException.java index ce05a776b..794926da9 100644 --- a/src/main/java/org/scijava/NullContextException.java +++ b/src/main/java/org/scijava/NullContextException.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/Optional.java b/src/main/java/org/scijava/Optional.java index 1dddc3441..2ab55b3c9 100644 --- a/src/main/java/org/scijava/Optional.java +++ b/src/main/java/org/scijava/Optional.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/Prioritized.java b/src/main/java/org/scijava/Prioritized.java index 416ae93f6..86bd0691a 100644 --- a/src/main/java/org/scijava/Prioritized.java +++ b/src/main/java/org/scijava/Prioritized.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/Priority.java b/src/main/java/org/scijava/Priority.java index 019ae17bf..69da7767e 100644 --- a/src/main/java/org/scijava/Priority.java +++ b/src/main/java/org/scijava/Priority.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -45,26 +43,45 @@ private Priority() { // prevent instantiation of utility class } - /** Priority for items that must be sorted first. */ - public static final double FIRST_PRIORITY = Double.POSITIVE_INFINITY; + /** + * Priority for items that must be sorted first. + *

+ * Note that it is still possible to prioritize something earlier + * than this value (e.g., for testing purposes), although doing so strongly + * discouraged in production. + *

+ */ + public static final double FIRST = +1e300; + + /** Priority for items that very strongly prefer to be sorted early. */ + public static final double EXTREMELY_HIGH = +1000000; /** Priority for items that strongly prefer to be sorted early. */ - public static final double VERY_HIGH_PRIORITY = +10000; + public static final double VERY_HIGH = +10000; /** Priority for items that prefer to be sorted earlier. */ - public static final double HIGH_PRIORITY = +100; + public static final double HIGH = +100; /** Default priority for items. */ - public static final double NORMAL_PRIORITY = 0; + public static final double NORMAL = 0; /** Priority for items that prefer to be sorted later. */ - public static final double LOW_PRIORITY = -100; + public static final double LOW = -100; /** Priority for items that strongly prefer to be sorted late. */ - public static final double VERY_LOW_PRIORITY = -10000; + public static final double VERY_LOW = -10000; - /** Priority for items that must be sorted last. */ - public static final double LAST_PRIORITY = Double.NEGATIVE_INFINITY; + /** Priority for items that very strongly prefer to be sorted late. */ + public static final double EXTREMELY_LOW = -1000000; + + /** Priority for items that must be sorted last. + *

+ * Note that it is still possible to prioritize something later + * than this value (e.g., for testing purposes), although doing so strongly + * discouraged in production. + *

+ */ + public static final double LAST = -1e300; /** * Compares two {@link Prioritized} objects. @@ -108,4 +125,33 @@ public static boolean inject(final Object o, final double priority) { return true; } + // -- Deprecated -- + + /** @deprecated Use {@link #FIRST} instead. */ + @Deprecated + public static final double FIRST_PRIORITY = Double.POSITIVE_INFINITY; + + /** @deprecated Use {@link #VERY_HIGH} instead. */ + @Deprecated + public static final double VERY_HIGH_PRIORITY = +10000; + + /** @deprecated Use {@link #HIGH} instead. */ + @Deprecated + public static final double HIGH_PRIORITY = +100; + + /** @deprecated Use {@link #NORMAL} instead. */ + @Deprecated + public static final double NORMAL_PRIORITY = 0; + + /** @deprecated Use {@link #LOW} instead. */ + @Deprecated + public static final double LOW_PRIORITY = -100; + + /** @deprecated Use {@link #VERY_LOW} instead. */ + @Deprecated + public static final double VERY_LOW_PRIORITY = -10000; + + /** @deprecated Use {@link #LAST} instead. */ + @Deprecated + public static final double LAST_PRIORITY = Double.NEGATIVE_INFINITY; } diff --git a/src/main/java/org/scijava/SciJava.java b/src/main/java/org/scijava/SciJava.java index 927cbb224..81375dcd6 100644 --- a/src/main/java/org/scijava/SciJava.java +++ b/src/main/java/org/scijava/SciJava.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -44,7 +42,7 @@ * * @author Curtis Rueden */ -@Plugin(type = Gateway.class) +@Plugin(type = Gateway.class, name = "sj") public class SciJava extends AbstractGateway { // -- Constructors -- @@ -112,12 +110,4 @@ public SciJava(final Collection> serviceClasses) { public SciJava(final Context context) { super(SciJavaApp.NAME, context); } - - // -- Gateway methods -- - - @Override - public String getShortName() { - return "sj"; - } - } diff --git a/src/main/java/org/scijava/Typed.java b/src/main/java/org/scijava/Typed.java index e76d0fc93..cfb55d690 100644 --- a/src/main/java/org/scijava/Typed.java +++ b/src/main/java/org/scijava/Typed.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -42,13 +40,17 @@ public interface Typed { /** * Gets whether this object is compatible with the given data object. *

- * By default, this method will return {@code true} always, since the type is - * known to be compatible. But individual implementations may have other - * requirements beyond class assignability. + * By default, this method will return {@code true} iff the data is assignable + * to the associated type given by {@link #getType()}. But individual + * implementations may have other requirements beyond class assignability. *

*/ - default boolean supports(@SuppressWarnings("unused") T data) { - return true; + default boolean supports(final T data) { + // NB: Even though the compiler will often guarantee that only data + // of type T is provided here, we still need the runtime check + // for cases where the exact type is not known to compiler -- + // e.g., if the object was manufactured by reflection. + return getType().isInstance(data); } /** Gets the type associated with the object. */ diff --git a/src/main/java/org/scijava/UIDetails.java b/src/main/java/org/scijava/UIDetails.java index 3545d6ce8..ccf12c261 100644 --- a/src/main/java/org/scijava/UIDetails.java +++ b/src/main/java/org/scijava/UIDetails.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/Validated.java b/src/main/java/org/scijava/Validated.java index 7b006ffed..11835ba5c 100644 --- a/src/main/java/org/scijava/Validated.java +++ b/src/main/java/org/scijava/Validated.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ValidityProblem.java b/src/main/java/org/scijava/ValidityProblem.java index ea8b3b121..782bcc642 100644 --- a/src/main/java/org/scijava/ValidityProblem.java +++ b/src/main/java/org/scijava/ValidityProblem.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/Versioned.java b/src/main/java/org/scijava/Versioned.java index 233510e35..6e746dad4 100644 --- a/src/main/java/org/scijava/Versioned.java +++ b/src/main/java/org/scijava/Versioned.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/annotations/AbstractIndexWriter.java b/src/main/java/org/scijava/annotations/AbstractIndexWriter.java index 0f8a06221..28fe310a5 100644 --- a/src/main/java/org/scijava/annotations/AbstractIndexWriter.java +++ b/src/main/java/org/scijava/annotations/AbstractIndexWriter.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/annotations/AnnotationCombiner.java b/src/main/java/org/scijava/annotations/AnnotationCombiner.java index a4f02935f..9b152b924 100644 --- a/src/main/java/org/scijava/annotations/AnnotationCombiner.java +++ b/src/main/java/org/scijava/annotations/AnnotationCombiner.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -42,6 +40,7 @@ import java.util.HashSet; import java.util.Set; +import org.scijava.Context; import org.scijava.util.Combiner; import org.scijava.util.FileUtils; @@ -64,7 +63,7 @@ public void combine(File outputDirectory) throws Exception { } final Set annotationFiles = getAnnotationFiles(); - final ClassLoader loader = Thread.currentThread().getContextClassLoader(); + final ClassLoader loader = Context.getClassLoader(); log(""); log("Writing annotations to " + outputDirectory.getAbsolutePath()); @@ -91,7 +90,7 @@ public Set getAnnotationFiles() throws IOException { for (final String prefix : new String[] { PREFIX, LEGACY_PREFIX }) { final Enumeration directories = - Thread.currentThread().getContextClassLoader().getResources(prefix); + Context.getClassLoader().getResources(prefix); while (directories.hasMoreElements()) { final URL url = directories.nextElement(); for (final URL annotationIndexURL : FileUtils.listContents(url)) { diff --git a/src/main/java/org/scijava/annotations/AnnotationProcessor.java b/src/main/java/org/scijava/annotations/AnnotationProcessor.java index 0f96bab4f..8355dcda9 100644 --- a/src/main/java/org/scijava/annotations/AnnotationProcessor.java +++ b/src/main/java/org/scijava/annotations/AnnotationProcessor.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -39,6 +37,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; +import java.nio.file.NoSuchFileException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -70,11 +69,11 @@ import org.scijava.annotations.AbstractIndexWriter.StreamFactory; /** - * The annotation processor for use with Java 6 and above. + * The annotation processor for use with Java 8 and earlier. * * @author Johannes Schindelin */ -@SupportedSourceVersion(SourceVersion.RELEASE_6) +@SupportedSourceVersion(SourceVersion.RELEASE_8) @SupportedAnnotationTypes("*") public class AnnotationProcessor extends AbstractProcessor { @@ -233,7 +232,7 @@ public InputStream openInput(final String annotationName) return filer.getResource(StandardLocation.CLASS_OUTPUT, "", Index.INDEX_PREFIX + annotationName).openInputStream(); } - catch (final FileNotFoundException e) { + catch (final FileNotFoundException | NoSuchFileException e) { return null; } } diff --git a/src/main/java/org/scijava/annotations/ByteCodeAnalyzer.java b/src/main/java/org/scijava/annotations/ByteCodeAnalyzer.java index 933ce8197..915d9148f 100644 --- a/src/main/java/org/scijava/annotations/ByteCodeAnalyzer.java +++ b/src/main/java/org/scijava/annotations/ByteCodeAnalyzer.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/annotations/DirectoryIndexer.java b/src/main/java/org/scijava/annotations/DirectoryIndexer.java index b292d9831..ac6be8fcd 100644 --- a/src/main/java/org/scijava/annotations/DirectoryIndexer.java +++ b/src/main/java/org/scijava/annotations/DirectoryIndexer.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/annotations/EclipseHelper.java b/src/main/java/org/scijava/annotations/EclipseHelper.java index 80cad4334..67f17ff7d 100644 --- a/src/main/java/org/scijava/annotations/EclipseHelper.java +++ b/src/main/java/org/scijava/annotations/EclipseHelper.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -42,6 +40,7 @@ import java.util.jar.JarFile; import java.util.jar.Manifest; +import org.scijava.Context; import org.scijava.util.FileUtils; /** @@ -296,7 +295,7 @@ else if (file.isDirectory()) { */ public static void main(final String... args) { System.setProperty(FORCE_ANNOTATION_INDEX_PROPERTY, "true"); - updateAnnotationIndex(Thread.currentThread().getContextClassLoader()); + updateAnnotationIndex(Context.getClassLoader()); } } diff --git a/src/main/java/org/scijava/annotations/Index.java b/src/main/java/org/scijava/annotations/Index.java index 84ae391d1..244b00d37 100644 --- a/src/main/java/org/scijava/annotations/Index.java +++ b/src/main/java/org/scijava/annotations/Index.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/annotations/IndexItem.java b/src/main/java/org/scijava/annotations/IndexItem.java index 31d849867..e5265fc21 100644 --- a/src/main/java/org/scijava/annotations/IndexItem.java +++ b/src/main/java/org/scijava/annotations/IndexItem.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/annotations/IndexReader.java b/src/main/java/org/scijava/annotations/IndexReader.java index ef8a2a576..4eab67552 100644 --- a/src/main/java/org/scijava/annotations/IndexReader.java +++ b/src/main/java/org/scijava/annotations/IndexReader.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/annotations/Indexable.java b/src/main/java/org/scijava/annotations/Indexable.java index 69e26bcc1..325739efd 100644 --- a/src/main/java/org/scijava/annotations/Indexable.java +++ b/src/main/java/org/scijava/annotations/Indexable.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/annotations/legacy/LegacyReader.java b/src/main/java/org/scijava/annotations/legacy/LegacyReader.java index acf3c3d29..9df62abcc 100644 --- a/src/main/java/org/scijava/annotations/legacy/LegacyReader.java +++ b/src/main/java/org/scijava/annotations/legacy/LegacyReader.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/app/AbstractApp.java b/src/main/java/org/scijava/app/AbstractApp.java index 6a79a67ff..33ff29ff4 100644 --- a/src/main/java/org/scijava/app/AbstractApp.java +++ b/src/main/java/org/scijava/app/AbstractApp.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/app/App.java b/src/main/java/org/scijava/app/App.java index 5f5c4979d..95cf249e7 100644 --- a/src/main/java/org/scijava/app/App.java +++ b/src/main/java/org/scijava/app/App.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -146,7 +144,7 @@ default void quit() { /** * Gets the version of the application. *

- * SciJava conforms to the Semantic + * SciJava conforms to the Semantic * Versioning specification. *

* diff --git a/src/main/java/org/scijava/app/AppService.java b/src/main/java/org/scijava/app/AppService.java index 884b0aea5..2d9c7e3de 100644 --- a/src/main/java/org/scijava/app/AppService.java +++ b/src/main/java/org/scijava/app/AppService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/app/DefaultAppService.java b/src/main/java/org/scijava/app/DefaultAppService.java index 52bb33ca4..9c8afc0c3 100644 --- a/src/main/java/org/scijava/app/DefaultAppService.java +++ b/src/main/java/org/scijava/app/DefaultAppService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/app/DefaultStatusService.java b/src/main/java/org/scijava/app/DefaultStatusService.java index 43a5e58b0..1b68a27ba 100644 --- a/src/main/java/org/scijava/app/DefaultStatusService.java +++ b/src/main/java/org/scijava/app/DefaultStatusService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/app/SciJavaApp.java b/src/main/java/org/scijava/app/SciJavaApp.java index ef758592f..b7604ca14 100644 --- a/src/main/java/org/scijava/app/SciJavaApp.java +++ b/src/main/java/org/scijava/app/SciJavaApp.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -40,8 +38,7 @@ * @author Curtis Rueden * @see AppService */ -@Plugin(type = App.class, name = SciJavaApp.NAME, - priority = Priority.LOW_PRIORITY) +@Plugin(type = App.class, name = SciJavaApp.NAME, priority = Priority.LOW) public class SciJavaApp extends AbstractApp { public static final String NAME = "SciJava"; diff --git a/src/main/java/org/scijava/app/StatusService.java b/src/main/java/org/scijava/app/StatusService.java index 67cccf86d..70d0edf75 100644 --- a/src/main/java/org/scijava/app/StatusService.java +++ b/src/main/java/org/scijava/app/StatusService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/app/event/StatusEvent.java b/src/main/java/org/scijava/app/event/StatusEvent.java index d8a3005e2..653aefb8e 100644 --- a/src/main/java/org/scijava/app/event/StatusEvent.java +++ b/src/main/java/org/scijava/app/event/StatusEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/cache/CacheService.java b/src/main/java/org/scijava/cache/CacheService.java index c0ab8850b..ca5808918 100644 --- a/src/main/java/org/scijava/cache/CacheService.java +++ b/src/main/java/org/scijava/cache/CacheService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/cache/DefaultCacheService.java b/src/main/java/org/scijava/cache/DefaultCacheService.java index 87bcc909a..667ffdb30 100644 --- a/src/main/java/org/scijava/cache/DefaultCacheService.java +++ b/src/main/java/org/scijava/cache/DefaultCacheService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -42,7 +40,7 @@ /** * Trivial {@link CacheService} implementation. Wraps a {@link WeakHashMap} */ -@Plugin(type = Service.class, priority = Priority.VERY_LOW_PRIORITY) +@Plugin(type = Service.class, priority = Priority.VERY_LOW) public class DefaultCacheService extends AbstractService implements CacheService { diff --git a/src/main/java/org/scijava/command/Command.java b/src/main/java/org/scijava/command/Command.java index 19f5f1de4..17a37ebb1 100644 --- a/src/main/java/org/scijava/command/Command.java +++ b/src/main/java/org/scijava/command/Command.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/command/CommandInfo.java b/src/main/java/org/scijava/command/CommandInfo.java index 222237422..f25c213db 100644 --- a/src/main/java/org/scijava/command/CommandInfo.java +++ b/src/main/java/org/scijava/command/CommandInfo.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -54,8 +52,10 @@ import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; import org.scijava.plugin.PluginInfo; +import org.scijava.service.Service; import org.scijava.util.ClassUtils; import org.scijava.util.StringMaker; +import org.scijava.util.Types; /** * A collection of metadata about a particular {@link Command}. @@ -299,9 +299,7 @@ public Class loadDelegateClass() throws ClassNotFoundException { return loadClass(); } catch (final InstantiableException exc) { - final ClassNotFoundException cnfe = new ClassNotFoundException(); - cnfe.initCause(exc); - throw cnfe; + throw new ClassNotFoundException(null, exc); } } @@ -463,7 +461,8 @@ private void checkFields(final Class type) { } final String name = f.getName(); - if (inputMap.containsKey(name) || outputMap.containsKey(name)) { + if ((inputMap.containsKey(name) || outputMap.containsKey(name)) + && !Service.class.isAssignableFrom(f.getType())) { // NB: Shadowed parameters are bad because they are ambiguous. final String error = "Invalid duplicate parameter: " + f; problems.add(new ValidityProblem(error)); @@ -503,8 +502,7 @@ private void checkFields(final Class type) { private boolean isImmutable(final Class type) { // NB: All eight primitive types, as well as the boxed primitive // wrapper classes, as well as strings, are immutable objects. - return ClassUtils.isNumber(type) || ClassUtils.isText(type) || - ClassUtils.isBoolean(type); + return Types.isNumber(type) || Types.isText(type) || Types.isBoolean(type); } private Class loadCommandClass() { diff --git a/src/main/java/org/scijava/command/CommandModule.java b/src/main/java/org/scijava/command/CommandModule.java index 6542022b6..226edc678 100644 --- a/src/main/java/org/scijava/command/CommandModule.java +++ b/src/main/java/org/scijava/command/CommandModule.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/command/CommandModuleItem.java b/src/main/java/org/scijava/command/CommandModuleItem.java index 10c0c7b78..079193311 100644 --- a/src/main/java/org/scijava/command/CommandModuleItem.java +++ b/src/main/java/org/scijava/command/CommandModuleItem.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -45,7 +43,7 @@ import org.scijava.plugin.Attr; import org.scijava.plugin.Parameter; import org.scijava.util.ConversionUtils; -import org.scijava.util.GenericUtils; +import org.scijava.util.Types; /** * {@link ModuleItem} implementation describing an input or output of a command. @@ -75,8 +73,7 @@ public Parameter getParameter() { @Override public Class getType() { - final Class type = - GenericUtils.getFieldClasses(field, getDelegateClass()).get(0); + final Class type = Types.raw(Types.fieldType(field, getDelegateClass())); @SuppressWarnings("unchecked") final Class typedType = (Class) type; return typedType; @@ -84,7 +81,7 @@ public Class getType() { @Override public Type getGenericType() { - return GenericUtils.getFieldType(field, getDelegateClass()); + return Types.fieldType(field, getDelegateClass()); } @Override @@ -253,7 +250,7 @@ private T tValue(final String value) { private D tValue(final String value, final Class type) { if (value == null || value.isEmpty()) return null; - final Class saneType = ConversionUtils.getNonprimitiveType(type); + final Class saneType = Types.box(type); return ConversionUtils.convert(value, saneType); } diff --git a/src/main/java/org/scijava/command/CommandService.java b/src/main/java/org/scijava/command/CommandService.java index 7a4feb704..9cd4f5841 100644 --- a/src/main/java/org/scijava/command/CommandService.java +++ b/src/main/java/org/scijava/command/CommandService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/command/ContextCommand.java b/src/main/java/org/scijava/command/ContextCommand.java index 3ce3ec96d..c40649d53 100644 --- a/src/main/java/org/scijava/command/ContextCommand.java +++ b/src/main/java/org/scijava/command/ContextCommand.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/command/DefaultCommandService.java b/src/main/java/org/scijava/command/DefaultCommandService.java index 2e344365a..29756debb 100644 --- a/src/main/java/org/scijava/command/DefaultCommandService.java +++ b/src/main/java/org/scijava/command/DefaultCommandService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -40,6 +38,7 @@ import org.scijava.event.EventHandler; import org.scijava.event.EventService; import org.scijava.log.LogService; +import org.scijava.module.Module; import org.scijava.module.ModuleService; import org.scijava.plugin.AbstractPTService; import org.scijava.plugin.Parameter; @@ -181,20 +180,16 @@ public Future run( public Future run(final CommandInfo info, final boolean process, final Object... inputs) { - @SuppressWarnings({ "rawtypes", "unchecked" }) - final Future future = - (Future) moduleService.run(info, process, inputs); - return future; + final Future future = moduleService.run(info, process, inputs); + return validateFuture(future, info); } @Override public Future run(final CommandInfo info, final boolean process, final Map inputMap) { - @SuppressWarnings({ "rawtypes", "unchecked" }) - final Future future = - (Future) moduleService.run(info, process, inputMap); - return future; + final Future future = moduleService.run(info, process, inputMap); + return validateFuture(future, info); } // -- PTService methods -- @@ -347,4 +342,33 @@ private List> downcast( return typedPlugins; } + /** + * A HACK to avoid {@link ClassCastException} when calling run when the + * resultant module will not be a {@link CommandModule}. This is an API design + * flaw in CommandService currently, but for now we work around it rather than + * breaking backwards API compatibility. + */ + private Future validateFuture(final Future future, + final CommandInfo info) + { + try { + final Class commandClass = info.loadDelegateClass(); + if (Module.class.isAssignableFrom(commandClass)) { + log.debug("The command '" + info.getIdentifier() + + "' extends Module directly. Due to a design flaw in the " + + "CommandService API, the result cannot be coerced to a " + + "Future, so null will be returned instead. " + + "If you need the resulting module, please instead call " + + "moduleService.run(commandService.getCommand(commandClass), ...)."); + return null; + } + } + catch (final ClassNotFoundException exc) { + throw new IllegalStateException("Command class unavailable: " + // + info.getDelegateClassName(), exc); + } + @SuppressWarnings({ "rawtypes", "unchecked" }) + final Future result = (Future) future; + return result; + } } diff --git a/src/main/java/org/scijava/command/DynamicCommand.java b/src/main/java/org/scijava/command/DynamicCommand.java index aa81671c0..5cc3ab4b1 100644 --- a/src/main/java/org/scijava/command/DynamicCommand.java +++ b/src/main/java/org/scijava/command/DynamicCommand.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -38,7 +36,9 @@ import org.scijava.Contextual; import org.scijava.NullContextException; import org.scijava.module.DefaultMutableModule; +import org.scijava.module.ModuleService; import org.scijava.plugin.Parameter; +import org.scijava.plugin.PluginService; import org.scijava.util.ClassUtils; /** @@ -59,6 +59,12 @@ public abstract class DynamicCommand extends DefaultMutableModule implements @Parameter private CommandService commandService; + @Parameter + protected PluginService pluginService; + + @Parameter + protected ModuleService moduleService; + private DynamicCommandInfo info; /** Reason for cancelation, or null if not canceled. */ @@ -70,7 +76,8 @@ public abstract class DynamicCommand extends DefaultMutableModule implements public DynamicCommandInfo getInfo() { if (info == null) { // NB: Create dynamic metadata lazily. - final CommandInfo commandInfo = commandService.getCommand(getClass()); + CommandInfo commandInfo = commandService.getCommand(getClass()); + if (commandInfo == null) commandInfo = new CommandInfo(getClass()); info = new DynamicCommandInfo(commandInfo, getClass()); } return info; @@ -86,7 +93,7 @@ public Object getInput(final String name) { @Override public Object getOutput(final String name) { final Field field = getInfo().getOutputField(name); - if (field == null) return super.getInput(name); + if (field == null) return super.getOutput(name); return ClassUtils.getValue(field, this); } @@ -139,4 +146,19 @@ public String getCancelReason() { return cancelReason; } + // HACK: For OptionsPlugin. + public void uncancel() { + cancelReason = null; + } + + // -- Internal methods -- + + /** + * Persists current input values. Use e.g. for {@link InteractiveCommand}s + * that want to persist values as they change, since interactive commands do + * not complete the module execution lifecycle normally. + */ + protected void saveInputs() { + moduleService.saveInputs(this); + } } diff --git a/src/main/java/org/scijava/command/DynamicCommandInfo.java b/src/main/java/org/scijava/command/DynamicCommandInfo.java index b47e64d91..06c4a16e7 100644 --- a/src/main/java/org/scijava/command/DynamicCommandInfo.java +++ b/src/main/java/org/scijava/command/DynamicCommandInfo.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -50,9 +48,9 @@ * Helper class for maintaining a {@link DynamicCommand}'s associated * {@link ModuleInfo}. *

- * The {@link CommandService} has a plain {@link CommandInfo} object in its - * index, populated from the {@link DynamicCommand}'s @{@link Plugin} - * annotation. So this class adapts that object, delegating to it for the + * This class wraps a plain {@link CommandInfo} object (e.g. from the + * {@link CommandService}'s index, present due to an @{@link Plugin} annotation + * on the {@link DynamicCommand} class), delegating to it for the * {@link UIDetails} methods. The plain {@link CommandInfo} cannot be used * as-is, however, because we need to override the {@link ModuleInfo} methods as * well as provide metadata manipulation functionality such as diff --git a/src/main/java/org/scijava/command/Inputs.java b/src/main/java/org/scijava/command/Inputs.java new file mode 100644 index 000000000..b2f5e4217 --- /dev/null +++ b/src/main/java/org/scijava/command/Inputs.java @@ -0,0 +1,102 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.command; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import org.scijava.Context; +import org.scijava.module.process.PreprocessorPlugin; + +/** + * A way to build a dynamic set of inputs, whose values are then harvested by + * the preprocessing framework. + *

+ * The {@link #run()} method of this command does nothing. If you want something + * custom to happen during execution, use a normal {@link Command} instead: + * either implement {@link Command directly}, or extend {@link ContextCommand} + * or {@link DynamicCommand}. + *

+ *

+ * Here is are some examples of usage: + *

+ * + *
+ * {@code
+ * // Single input, no configuration.
+ * Inputs inputs = new Inputs(context);
+ * inputs.addInput("sigma", Double.class);
+ * Double sigma = (Double) inputs.harvest().get("sigma");
+ *
+ * // Two inputs, no configuration.
+ * Inputs inputs = new Inputs(context);
+ * inputs.addInput("name", String.class);
+ * inputs.addInput("age", Integer.class);
+ * Map values = inputs.harvest();
+ * String name = (String) values.get("name");
+ * Integer age = (Integer) values.get("age");
+ *
+ * // Inputs with configuration.
+ * Inputs inputs = new Inputs(context);
+ * MutableModuleItem wordInput = inputs.addInput("word", String.class);
+ * wordInput.setLabel("Favorite word");
+ * wordInput.setChoices(Arrays.asList("quick", "brown", "fox"));
+ * wordInput.setDefaultValue("fox");
+ * MutableModuleItem opacityInput = inputs.addInput("opacity", Double.class);
+ * opacityInput.setMinimumValue(0.0);
+ * opacityInput.setMaximumValue(1.0);
+ * opacityInput.setDefaultValue(0.5);
+ * opacityInput.setWidgetStyle(NumberWidget.SCROLL_BAR_STYLE);
+ * inputs.harvest();
+ * String word = wordInput.getValue(inputs);
+ * Double opacity = opacityInput.getValue(inputs);
+ * }
+ * 
+ * + * @author Curtis Rueden + */ +public final class Inputs extends DynamicCommand { + + public Inputs(final Context context) { + context.inject(this); + } + + public Map harvest() { + try { + final List pre = // + pluginService.createInstancesOfType(PreprocessorPlugin.class); + return moduleService.run(this, pre, null).get().getInputs(); + } + catch (final InterruptedException | ExecutionException exc) { + throw new RuntimeException(exc); + } + } +} diff --git a/src/main/java/org/scijava/command/Interactive.java b/src/main/java/org/scijava/command/Interactive.java index bb41ca906..9714f4a6e 100644 --- a/src/main/java/org/scijava/command/Interactive.java +++ b/src/main/java/org/scijava/command/Interactive.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/command/InteractiveCommand.java b/src/main/java/org/scijava/command/InteractiveCommand.java index 920ad4047..b45e3f21f 100644 --- a/src/main/java/org/scijava/command/InteractiveCommand.java +++ b/src/main/java/org/scijava/command/InteractiveCommand.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -95,6 +93,7 @@ public InteractiveCommand(final String... listenerNames) { public void preview() { // NB: Interactive commands call run upon any parameter change. run(); + saveInputs(); } @Override diff --git a/src/main/java/org/scijava/command/ModuleCommand.java b/src/main/java/org/scijava/command/ModuleCommand.java index 14bdb641a..c09ad922c 100644 --- a/src/main/java/org/scijava/command/ModuleCommand.java +++ b/src/main/java/org/scijava/command/ModuleCommand.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/command/Previewable.java b/src/main/java/org/scijava/command/Previewable.java index 6d062edf4..4fbeaebb9 100644 --- a/src/main/java/org/scijava/command/Previewable.java +++ b/src/main/java/org/scijava/command/Previewable.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/command/UnimplementedCommand.java b/src/main/java/org/scijava/command/UnimplementedCommand.java index 8bc9448f5..f1103751a 100644 --- a/src/main/java/org/scijava/command/UnimplementedCommand.java +++ b/src/main/java/org/scijava/command/UnimplementedCommand.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/command/console/RunArgument.java b/src/main/java/org/scijava/command/console/RunArgument.java index c2a8ce8bb..b2529d279 100644 --- a/src/main/java/org/scijava/command/console/RunArgument.java +++ b/src/main/java/org/scijava/command/console/RunArgument.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/command/run/CommandCodeRunner.java b/src/main/java/org/scijava/command/run/CommandCodeRunner.java index f0ecfcb9b..bc8550275 100644 --- a/src/main/java/org/scijava/command/run/CommandCodeRunner.java +++ b/src/main/java/org/scijava/command/run/CommandCodeRunner.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/console/AbstractConsoleArgument.java b/src/main/java/org/scijava/console/AbstractConsoleArgument.java index 4711e5d18..76040544d 100644 --- a/src/main/java/org/scijava/console/AbstractConsoleArgument.java +++ b/src/main/java/org/scijava/console/AbstractConsoleArgument.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/console/ConsoleArgument.java b/src/main/java/org/scijava/console/ConsoleArgument.java index 49bd88590..44d058211 100644 --- a/src/main/java/org/scijava/console/ConsoleArgument.java +++ b/src/main/java/org/scijava/console/ConsoleArgument.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/console/ConsoleService.java b/src/main/java/org/scijava/console/ConsoleService.java index 7e9a9be58..3003892a4 100644 --- a/src/main/java/org/scijava/console/ConsoleService.java +++ b/src/main/java/org/scijava/console/ConsoleService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -48,6 +46,7 @@ */ public interface ConsoleService extends HandlerService, ConsoleArgument>, SciJavaService + // TODO: SJC3: Extend Listenable { /** Handles arguments from an external source such as the command line. */ diff --git a/src/main/java/org/scijava/console/ConsoleUtils.java b/src/main/java/org/scijava/console/ConsoleUtils.java index 47c712b62..2f62f0c14 100644 --- a/src/main/java/org/scijava/console/ConsoleUtils.java +++ b/src/main/java/org/scijava/console/ConsoleUtils.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/console/DefaultConsoleService.java b/src/main/java/org/scijava/console/DefaultConsoleService.java index 99b9867b0..b3a1ef64b 100644 --- a/src/main/java/org/scijava/console/DefaultConsoleService.java +++ b/src/main/java/org/scijava/console/DefaultConsoleService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -36,6 +34,7 @@ import java.util.ArrayList; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import org.scijava.Context; import org.scijava.console.OutputEvent.Source; @@ -68,9 +67,7 @@ public class DefaultConsoleService extends private OutputStreamReporter out, err; /** List of listeners for {@code stdout} and {@code stderr} output. */ - private ArrayList listeners; - - private OutputListener[] cachedListeners; + private List listeners; // -- ConsoleService methods -- @@ -115,26 +112,19 @@ public void processArgs(final String... args) { @Override public void addOutputListener(final OutputListener l) { if (listeners == null) initListeners(); - synchronized (listeners) { - listeners.add(l); - cacheListeners(); - } + listeners.add(l); } @Override public void removeOutputListener(final OutputListener l) { if (listeners == null) initListeners(); - synchronized (listeners) { - listeners.remove(l); - cacheListeners(); - } + listeners.remove(l); } @Override public void notifyListeners(final OutputEvent event) { if (listeners == null) initListeners(); - final OutputListener[] toNotify = cachedListeners; - for (final OutputListener l : toNotify) + for (final OutputListener l : listeners) l.outputOccurred(event); } @@ -162,16 +152,11 @@ private synchronized void initListeners() { err = new OutputStreamReporter(Source.STDERR); syserr.getParent().addOutputStream(err); - listeners = new ArrayList<>(); - cachedListeners = listeners.toArray(new OutputListener[0]); + listeners = new CopyOnWriteArrayList<>(); } // -- Helper methods -- - private void cacheListeners() { - cachedListeners = listeners.toArray(new OutputListener[listeners.size()]); - } - private MultiPrintStream multiPrintStream(final PrintStream ps) { if (ps instanceof MultiPrintStream) return (MultiPrintStream) ps; return new MultiPrintStream(ps); diff --git a/src/main/java/org/scijava/console/MultiOutputStream.java b/src/main/java/org/scijava/console/MultiOutputStream.java index 855947bdf..a8d8238fd 100644 --- a/src/main/java/org/scijava/console/MultiOutputStream.java +++ b/src/main/java/org/scijava/console/MultiOutputStream.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -34,6 +32,8 @@ import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; /** * A {@code MultiOutputStream} is a collection of constituent @@ -48,9 +48,7 @@ */ public class MultiOutputStream extends OutputStream { - private final ArrayList streams; - - private OutputStream[] cachedStreams; + private final List streams; /** * Forwards output to a list of output streams. @@ -58,37 +56,26 @@ public class MultiOutputStream extends OutputStream { * @param os Output streams which will receive this stream's output. */ public MultiOutputStream(final OutputStream... os) { - streams = new ArrayList<>(os.length); - for (int i = 0; i < os.length; i++) { - streams.add(os[i]); - } - cacheStreams(); + streams = new CopyOnWriteArrayList<>(os); } // -- MultiOutputStream methods -- /** Adds an output stream to those receiving this stream's output. */ public void addOutputStream(final OutputStream os) { - synchronized (streams) { - streams.add(os); - cacheStreams(); - } + streams.add(os); } /** Removes an output stream from those receiving this stream's output. */ public void removeOutputStream(final OutputStream os) { - synchronized (streams) { - streams.remove(os); - cacheStreams(); - } + streams.remove(os); } // -- OutputStream methods -- @Override public void write(final int b) throws IOException { - final OutputStream[] toWrite = cachedStreams; - for (final OutputStream stream : toWrite) + for (final OutputStream stream : streams) stream.write(b); } @@ -96,8 +83,7 @@ public void write(final int b) throws IOException { public void write(final byte[] buf, final int off, final int len) throws IOException { - final OutputStream[] toWrite = cachedStreams; - for (final OutputStream stream : toWrite) + for (final OutputStream stream : streams) stream.write(buf, off, len); } @@ -105,8 +91,7 @@ public void write(final byte[] buf, final int off, final int len) @Override public void close() throws IOException { - final OutputStream[] toClose = cachedStreams; - for (final OutputStream stream : toClose) + for (final OutputStream stream : streams) stream.close(); } @@ -114,15 +99,8 @@ public void close() throws IOException { @Override public void flush() throws IOException { - final OutputStream[] toFlush = cachedStreams; - for (final OutputStream stream : toFlush) + for (final OutputStream stream : streams) stream.flush(); } - // -- Helper methods -- - - private void cacheStreams() { - cachedStreams = streams.toArray(new OutputStream[streams.size()]); - } - } diff --git a/src/main/java/org/scijava/console/MultiPrintStream.java b/src/main/java/org/scijava/console/MultiPrintStream.java index 72fae4aea..c27df7e9b 100644 --- a/src/main/java/org/scijava/console/MultiPrintStream.java +++ b/src/main/java/org/scijava/console/MultiPrintStream.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/console/OutputEvent.java b/src/main/java/org/scijava/console/OutputEvent.java index d3bc2c5e7..c408bcaa3 100644 --- a/src/main/java/org/scijava/console/OutputEvent.java +++ b/src/main/java/org/scijava/console/OutputEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/console/OutputListener.java b/src/main/java/org/scijava/console/OutputListener.java index 1641542f0..b1543d339 100644 --- a/src/main/java/org/scijava/console/OutputListener.java +++ b/src/main/java/org/scijava/console/OutputListener.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/console/SystemPropertyArgument.java b/src/main/java/org/scijava/console/SystemPropertyArgument.java index ef812d1ef..33ba2771f 100644 --- a/src/main/java/org/scijava/console/SystemPropertyArgument.java +++ b/src/main/java/org/scijava/console/SystemPropertyArgument.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -46,7 +44,7 @@ @Plugin(type = ConsoleArgument.class) public class SystemPropertyArgument extends AbstractConsoleArgument { - private static final String SYS_PROP_REGEX = "-D([\\w\\._-]+)(=(.*))?"; + private static final String SYS_PROP_REGEX = "-D([^=]+)(=(.*))?"; private static final Pattern SYS_PROP_PAT = Pattern.compile(SYS_PROP_REGEX); // -- Constructor -- diff --git a/src/main/java/org/scijava/convert/AbstractConvertService.java b/src/main/java/org/scijava/convert/AbstractConvertService.java index 3938415f3..c3a56ae47 100644 --- a/src/main/java/org/scijava/convert/AbstractConvertService.java +++ b/src/main/java/org/scijava/convert/AbstractConvertService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -31,145 +29,16 @@ package org.scijava.convert; -import java.lang.reflect.Type; -import java.util.Collection; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Set; - import org.scijava.plugin.AbstractHandlerService; -import org.scijava.util.ConversionUtils; /** - * Abstract superclass for {@link ConvertService} implementations. Sets this - * service as the active delegate service in {@link ConversionUtils}. + * Abstract superclass for {@link ConvertService} implementations. * * @author Mark Hiner */ -public abstract class AbstractConvertService extends AbstractHandlerService> - implements ConvertService { - - // -- ConversionService methods -- - @SuppressWarnings({ "unchecked", "rawtypes" }) - @Override - public Class> getPluginType() { - return (Class) Converter.class; - } - - @Override - public Class getType() { - return ConversionRequest.class; - } - - @Override - public Converter getHandler(final Object src, final Class dest) { - return getHandler(new ConversionRequest(src, dest)); - } - - @Override - public Converter getHandler(final Class src, final Class dest) { - return getHandler(new ConversionRequest(src, dest)); - } - - @Override - public Converter getHandler(final Object src, final Type dest) { - return getHandler(new ConversionRequest(src, dest)); - } - - @Override - public Converter getHandler(final Class src, final Type dest) { - return getHandler(new ConversionRequest(src, dest)); - } - - @Override - public boolean supports(final Object src, final Class dest) { - return supports(new ConversionRequest(src, dest)); - } - - @Override - public boolean supports(final Class src, final Class dest) { - return supports(new ConversionRequest(src, dest)); - } - - @Override - public boolean supports(final Object src, final Type dest) { - return supports(new ConversionRequest(src, dest)); - } - - @Override - public boolean supports(final Class src, final Type dest) { - return supports(new ConversionRequest(src, dest)); - } - - @Override - public Collection getCompatibleInputs(final Class dest) { - final Set objects = new LinkedHashSet<>(); - - for (final Converter c : getInstances()) { - if (dest.isAssignableFrom(c.getOutputType())) { - c.populateInputCandidates(objects); - } - } - - return objects; - } - - @Override - public Object convert(final Object src, final Type dest) { - return convert(new ConversionRequest(src, dest)); - } - - @Override - public T convert(final Object src, final Class dest) { - // NB: repeated code with convert(ConversionRequest), because the - // handler's convert method respects the T provided - final Converter handler = getHandler(src, dest); - return handler == null ? null : handler.convert(src, dest); - } - - @Override - public Object convert(final ConversionRequest request) { - final Converter handler = getHandler(request); - return handler == null ? null : handler.convert(request); - } - - @Override - public Collection> getCompatibleInputClasses(final Class dest) { - final Set> compatibleClasses = new HashSet<>(); - - for (final Converter converter : getInstances()) { - addIfMatches(dest, converter.getOutputType(), converter.getInputType(), compatibleClasses); - } - - return compatibleClasses; - } - - @Override - public Collection> getCompatibleOutputClasses(final Class source) { - final Set> compatibleClasses = new HashSet<>(); - - for (final Converter converter : getInstances()) { - addIfMatches(source, converter.getInputType(), converter.getOutputType(), compatibleClasses); - } - - return compatibleClasses; - } - - // -- Service methods -- - - @Override - public void initialize() { - ConversionUtils.setDelegateService(this, getPriority()); - } - - // -- Helper methods -- - - /** - * Test two classes; if they match, a third class is added to the provided - * set of classes. - */ - private void addIfMatches(final Class c1, final Class c2, final Class toAdd, final Set> classes) { - if (c1 == c2) - classes.add(toAdd); - } +public abstract class AbstractConvertService // + extends AbstractHandlerService> // + implements ConvertService +{ + // NB: This layer remains merely for backwards compatibility. } diff --git a/src/main/java/org/scijava/convert/AbstractConverter.java b/src/main/java/org/scijava/convert/AbstractConverter.java index 080788ae8..c6132c396 100644 --- a/src/main/java/org/scijava/convert/AbstractConverter.java +++ b/src/main/java/org/scijava/convert/AbstractConverter.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -31,38 +29,17 @@ package org.scijava.convert; -import java.lang.reflect.Type; import java.util.Collection; import org.scijava.object.ObjectService; import org.scijava.plugin.AbstractHandlerPlugin; import org.scijava.plugin.Parameter; -import org.scijava.util.ConversionUtils; -import org.scijava.util.GenericUtils; /** * Abstract superclass for {@link Converter} plugins. Performs appropriate * dispatching of {@link #canConvert(ConversionRequest)} and * {@link #convert(ConversionRequest)} calls based on the actual state of the * given {@link ConversionRequest}. - *

- * Note that the {@link #supports(ConversionRequest)} method is overridden as - * well, to delegate to the appropriate {@link #canConvert}. - *

- *

- * NB: by default, the {@link #populateInputCandidates(Collection)} method has a - * dummy implementation. Effectively, this is opt-in behavior. If a converter - * implementation would like to suggest candidates for conversion, this method - * can be overridden. - *

- *

- * NB: by default, the provied {@link #canConvert} methods will return - * {@code false} if the input is {@code null}. This allows {@link Converter} - * implementors to assume any input is non-{@code null} - but this behavior is - * overridden. Casting {@code null Object} inputs is handled by the - * {@link NullConverter}, while {@code null class} inputs are handled by the - * {@link DefaultConverter}. - *

* * @author Mark Hiner */ @@ -75,59 +52,7 @@ public abstract class AbstractConverter extends @Parameter(required = false) private ObjectService objectService; - // -- ConversionHandler methods -- - - @Override - public boolean canConvert(final ConversionRequest request) { - Object src = request.sourceObject(); - if (src == null) { - Class srcClass = request.sourceClass(); - if (request.destType() != null) return canConvert(srcClass, request.destType()); - return canConvert(srcClass, request.destClass()); - } - - if (request.destType() != null) return canConvert(src, request.destType()); - return canConvert(src, request.destClass()); - } - - @Override - public boolean canConvert(final Object src, final Type dest) { - if (src == null) return false; - final Class srcClass = src.getClass(); - return canConvert(srcClass, dest); - } - - @Override - public boolean canConvert(final Object src, final Class dest) { - if (src == null) return false; - final Class srcClass = src.getClass(); - - return canConvert(srcClass, dest); - } - - @Override - public boolean canConvert(final Class src, final Class dest) { - if (src == null) return false; - final Class saneSrc = ConversionUtils.getNonprimitiveType(src); - final Class saneDest = ConversionUtils.getNonprimitiveType(dest); - return ConversionUtils.canCast(saneSrc, getInputType()) && - ConversionUtils.canCast(getOutputType(), saneDest); - } - - @Override - public Object convert(final Object src, final Type dest) { - final Class destClass = GenericUtils.getClass(dest); - return convert(src, destClass); - } - - @Override - public Object convert(final ConversionRequest request) { - if (request.destType() != null) { - return convert(request.sourceObject(), request.destType()); - } - - return convert(request.sourceObject(), request.destClass()); - } + // -- Converter methods -- @Override public void populateInputCandidates(final Collection objects) { @@ -141,20 +66,8 @@ public void populateInputCandidates(final Collection objects) { @Override public boolean supports(final ConversionRequest request) { - return canConvert(request); - } - - @Override - public Class getType() { - return ConversionRequest.class; - } - - // -- Deprecated API -- - - @Override - @Deprecated - public boolean canConvert(final Class src, final Type dest) { - final Class destClass = GenericUtils.getClass(dest); - return canConvert(src, destClass); + // NB: Overridden just for backwards compatibility, so that + // downstream classes which call super.supports do the right thing. + return Converter.super.supports(request); } } diff --git a/src/main/java/org/scijava/convert/AbstractDelegateConverter.java b/src/main/java/org/scijava/convert/AbstractDelegateConverter.java new file mode 100644 index 000000000..97b8b23ea --- /dev/null +++ b/src/main/java/org/scijava/convert/AbstractDelegateConverter.java @@ -0,0 +1,57 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.convert; + +import org.scijava.plugin.Parameter; + +/** + * Abstract superclass for {@link Converter} plugins that delegate to other + * converters to chain two conversion steps together. + * + * @author Jan Eglinger + * @param the input type + * @param the delegate type + * @param the output type + */ +public abstract class AbstractDelegateConverter extends + AbstractConverter +{ + + @Parameter + private ConvertService convertService; + + @Override + public T convert(Object src, Class dest) { + D delegate = convertService.convert(src, getDelegateType()); + return convertService.convert(delegate, dest); + } + + protected abstract Class getDelegateType(); +} diff --git a/src/main/java/org/scijava/convert/ArrayConverters.java b/src/main/java/org/scijava/convert/ArrayConverters.java index 9837b58a0..0c16551a0 100644 --- a/src/main/java/org/scijava/convert/ArrayConverters.java +++ b/src/main/java/org/scijava/convert/ArrayConverters.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -53,7 +51,7 @@ public class ArrayConverters { // -- Integer array converters -- - @Plugin(type = Converter.class, priority = Priority.HIGH_PRIORITY) + @Plugin(type = Converter.class, priority = Priority.HIGH) public static class IntArrayWrapper extends PrimitiveArrayWrapper { @@ -69,7 +67,7 @@ public Class getInputType() { } } - @Plugin(type = Converter.class, priority = Priority.HIGH_PRIORITY) + @Plugin(type = Converter.class, priority = Priority.HIGH) public static class IntArrayUnwrapper extends PrimitiveArrayUnwrapper { @@ -87,7 +85,7 @@ public Class getInputType() { // -- Byte array converters -- - @Plugin(type = Converter.class, priority = Priority.HIGH_PRIORITY) + @Plugin(type = Converter.class, priority = Priority.HIGH) public static class ByteArrayWrapper extends PrimitiveArrayWrapper { @@ -103,7 +101,7 @@ public Class getInputType() { } } - @Plugin(type = Converter.class, priority = Priority.HIGH_PRIORITY) + @Plugin(type = Converter.class, priority = Priority.HIGH) public static class ByteArrayUnwrapper extends PrimitiveArrayUnwrapper { @@ -121,7 +119,7 @@ public Class getInputType() { // -- Bool array converters -- - @Plugin(type = Converter.class, priority = Priority.HIGH_PRIORITY) + @Plugin(type = Converter.class, priority = Priority.HIGH) public static class BoolArrayWrapper extends PrimitiveArrayWrapper { @@ -137,7 +135,7 @@ public Class getInputType() { } } - @Plugin(type = Converter.class, priority = Priority.HIGH_PRIORITY) + @Plugin(type = Converter.class, priority = Priority.HIGH) public static class BoolArrayUnwrapper extends PrimitiveArrayUnwrapper { @@ -155,7 +153,7 @@ public Class getInputType() { // -- Char array converters -- - @Plugin(type = Converter.class, priority = Priority.HIGH_PRIORITY) + @Plugin(type = Converter.class, priority = Priority.HIGH) public static class CharArrayWrapper extends PrimitiveArrayWrapper { @@ -171,7 +169,7 @@ public Class getInputType() { } } - @Plugin(type = Converter.class, priority = Priority.HIGH_PRIORITY) + @Plugin(type = Converter.class, priority = Priority.HIGH) public static class CharArrayUnwrapper extends PrimitiveArrayUnwrapper { @@ -189,7 +187,7 @@ public Class getInputType() { // -- Short array converters -- - @Plugin(type = Converter.class, priority = Priority.HIGH_PRIORITY) + @Plugin(type = Converter.class, priority = Priority.HIGH) public static class ShortArrayWrapper extends PrimitiveArrayWrapper { @@ -205,7 +203,7 @@ public Class getInputType() { } } - @Plugin(type = Converter.class, priority = Priority.HIGH_PRIORITY) + @Plugin(type = Converter.class, priority = Priority.HIGH) public static class ShortArrayUnwrapper extends PrimitiveArrayUnwrapper { @@ -223,7 +221,7 @@ public Class getInputType() { // -- Float array converters -- - @Plugin(type = Converter.class, priority = Priority.HIGH_PRIORITY) + @Plugin(type = Converter.class, priority = Priority.HIGH) public static class FloatArrayWrapper extends PrimitiveArrayWrapper { @@ -239,7 +237,7 @@ public Class getInputType() { } } - @Plugin(type = Converter.class, priority = Priority.HIGH_PRIORITY) + @Plugin(type = Converter.class, priority = Priority.HIGH) public static class FloatArrayUnwrapper extends PrimitiveArrayUnwrapper { @@ -257,7 +255,7 @@ public Class getInputType() { // -- Double array converters -- - @Plugin(type = Converter.class, priority = Priority.HIGH_PRIORITY) + @Plugin(type = Converter.class, priority = Priority.HIGH) public static class DoubleArrayWrapper extends PrimitiveArrayWrapper { @@ -273,7 +271,7 @@ public Class getInputType() { } } - @Plugin(type = Converter.class, priority = Priority.HIGH_PRIORITY) + @Plugin(type = Converter.class, priority = Priority.HIGH) public static class DoubleArrayUnwrapper extends PrimitiveArrayUnwrapper { @@ -291,7 +289,7 @@ public Class getInputType() { // -- Long array converters -- - @Plugin(type = Converter.class, priority = Priority.HIGH_PRIORITY) + @Plugin(type = Converter.class, priority = Priority.HIGH) public static class LongArrayWrapper extends PrimitiveArrayWrapper { @@ -307,7 +305,7 @@ public Class getInputType() { } } - @Plugin(type = Converter.class, priority = Priority.HIGH_PRIORITY) + @Plugin(type = Converter.class, priority = Priority.HIGH) public static class LongArrayUnwrapper extends PrimitiveArrayUnwrapper { diff --git a/src/main/java/org/scijava/convert/ArrayToStringConverter.java b/src/main/java/org/scijava/convert/ArrayToStringConverter.java new file mode 100644 index 000000000..e1e3ce96b --- /dev/null +++ b/src/main/java/org/scijava/convert/ArrayToStringConverter.java @@ -0,0 +1,128 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.convert; + +import java.lang.reflect.Array; +import java.lang.reflect.Type; +import java.util.stream.Collectors; + +import org.scijava.Priority; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.util.ArrayUtils; + +/** + * A {@link Converter} that specializes in converting n-dimensional arrays into + * {@link String}s. This {@link Converter} can convert any array whose component + * types can be converted into {@link String}s. By default, this + * {@link Converter} delimits the array elements with commas. + * + * @author Gabriel Selzer + */ +@Plugin(type = Converter.class, priority = Priority.VERY_LOW) +public class ArrayToStringConverter extends AbstractConverter { + + @Parameter(required = false) + private ConvertService convertService; + + @Override + public boolean canConvert(final Class src, final Class dest) { + return src != null && src.isArray() && dest == String.class; + } + + @Override + public boolean canConvert(final Object src, final Class dest) { + if (convertService == null || src == null) return false; + if (!canConvert(src.getClass(), dest)) return false; + if (Array.getLength(src) == 0) return true; + return convertService.supports(Array.get(src, 0), dest); + } + + @Override + public Object convert(Object src, final Type dest) { + // Preprocess the "string-likes" + final Class srcClass = src.getClass(); + if (srcClass == String[].class || // + srcClass == Character[].class || // + srcClass == char[].class) // + { + src = preprocessCharacters(src); + } + // Convert each element to Strings + final String elementString = ArrayUtils.toCollection(src).stream() // + .map(object -> convertService.convert(object, String.class)) // + .collect(Collectors.joining(", ")); + return "{" + elementString + "}"; + } + + private String[] preprocessStrings(final Object src) { + final int numElements = Array.getLength(src); + final String[] processed = new String[numElements]; + for (int i = 0; i < numElements; i++) { + processed[i] = preprocessString(Array.get(src, i)); + } + return processed; + } + + private String preprocessString(final Object o) { + if (o == null) return null; + String s = o.toString(); + s = s.replace("\\", "\\\\"); + s = s.replace("\"", "\\\""); + return "\"" + s + "\""; + } + + private String[] preprocessCharacters(Object src) { + final String[] processed = new String[Array.getLength(src)]; + for (int i = 0; i < processed.length; i++) { + final Object value = Array.get(src, i); + processed[i] = value == null ? null : value.toString(); + } + return preprocessStrings(processed); + } + + @Override + public T convert(final Object src, final Class dest) { + final Type destType = dest; + @SuppressWarnings("unchecked") + final T converted = (T) convert(src, destType); + return converted; + } + + @Override + public Class getOutputType() { + return String.class; + } + + @Override + public Class getInputType() { + return Object.class; + } +} diff --git a/src/main/java/org/scijava/convert/CastingConverter.java b/src/main/java/org/scijava/convert/CastingConverter.java index a611d67f2..3b5a4e91f 100644 --- a/src/main/java/org/scijava/convert/CastingConverter.java +++ b/src/main/java/org/scijava/convert/CastingConverter.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -30,49 +28,56 @@ */ package org.scijava.convert; +import java.lang.reflect.Type; + import org.scijava.Priority; import org.scijava.plugin.Plugin; -import org.scijava.util.ClassUtils; -import org.scijava.util.ConversionUtils; -import org.scijava.util.GenericUtils; +import org.scijava.util.Types; /** * Minimal {@link Converter} implementation to do direct casting. * * @author Mark Hiner */ -@Plugin(type = Converter.class, priority = Priority.FIRST_PRIORITY) +@Plugin(type = Converter.class, priority = Priority.EXTREMELY_HIGH - 1) public class CastingConverter extends AbstractConverter { - @SuppressWarnings("deprecation") @Override public boolean canConvert(final Object src, final Class dest) { - return ClassUtils.canCast(src, dest); + return Types.isInstance(src, dest); } @Override - public boolean canConvert(final Class src, final Class dest) { - // OK if the existing object can be casted - if (ConversionUtils.canCast(src, dest)) - return true; + public boolean canConvert(final Class src, final Type dest) { + // NB: You might think we want to use Types.isAssignable(src, dest) + // directly here. And you might be right. However, assignment involving + // generic types gets very tricky. If dest is e.g. a wildcard type such as + // "? extends Object", or a type variable such as "C extends Object", then + // no specific class will be assignable to it, because for that ? or C we + // do not know anything about the bound type other than that it's something + // that extends Object, so it could be anything, including things that + // aren't assignable from whatever src is. + // + // Unfortunately, when this casting conversion code was originally written, + // it did not have generics in mind, and calling code will pass in capture + // types (e.g. Type objects gleaned via ModuleItem#getGenericType()) + // expecting them to be convertible as long as dest's raw type(s) are + // compatible targets for src. + // + // And so for backwards compatibility, we continue to behave that way here. + + return dest != null && // + Types.raws(dest).stream().allMatch(c -> c.isAssignableFrom(src)); + } - return false; + @Override + public boolean canConvert(final Class src, final Class dest) { + return dest != null && dest.isAssignableFrom(src); } - @SuppressWarnings("unchecked") @Override public T convert(final Object src, final Class dest) { - // NB: Regardless of whether the destination type is an array or - // collection, we still want to cast directly if doing so is possible. - // But note that in general, this check does not detect cases of - // incompatible generic parameter types. If this limitation becomes a - // problem in the future we can extend the logic here to provide - // additional signatures of canCast which operate on Types in general - // rather than only Classes. However, the logic could become complex - // very quickly in various subclassing cases, generic parameters - // resolved vs. propagated, etc. - final Class c = GenericUtils.getClass(dest); - return (T) ConversionUtils.cast(src, c); + return Types.cast(src, dest); } @Override diff --git a/src/main/java/org/scijava/convert/ConversionRequest.java b/src/main/java/org/scijava/convert/ConversionRequest.java index 14c1ae26f..c4ee0fd2b 100644 --- a/src/main/java/org/scijava/convert/ConversionRequest.java +++ b/src/main/java/org/scijava/convert/ConversionRequest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -34,7 +32,7 @@ import java.lang.reflect.Type; import org.scijava.plugin.HandlerService; -import org.scijava.util.GenericUtils; +import org.scijava.util.Types; /** * Currency for use in {@link Converter} and {@link ConvertService} methods. @@ -99,7 +97,7 @@ public Type sourceType() { * @return Source class for conversion or lookup. */ public Class sourceClass() { - return GenericUtils.getClass(srcType); + return Types.raw(srcType); } /** @@ -120,7 +118,7 @@ public Type destType() { * @return Destination class for conversion. */ public Class destClass() { - return GenericUtils.getClass(destType); + return Types.raw(destType); } // -- Setters -- diff --git a/src/main/java/org/scijava/convert/ConvertService.java b/src/main/java/org/scijava/convert/ConvertService.java index 39d950e1e..ff3309baf 100644 --- a/src/main/java/org/scijava/convert/ConvertService.java +++ b/src/main/java/org/scijava/convert/ConvertService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -33,14 +31,17 @@ import java.lang.reflect.Type; import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; import org.scijava.plugin.HandlerService; import org.scijava.service.SciJavaService; /** - * Service for converting between types using an extensible plugin: - * {@link Converter}. Contains convenience signatures for the - * {@link #getHandler} and {@link #supports} methods to avoid the need to create + * Service for converting between types using an {@link Converter} plugins. + * Contains convenience signatures for the {@link #getHandler} and + * {@link #supports} methods to avoid the need to create * {@link ConversionRequest} objects. * * @see ConversionRequest @@ -53,83 +54,148 @@ public interface ConvertService extends /** * @see Converter#convert(Object, Type) */ - Object convert(Object src, Type dest); + default Object convert(final Object src, final Type dest) { + return convert(new ConversionRequest(src, dest)); + } /** * @see Converter#convert(Object, Class) */ - T convert(Object src, Class dest); + default T convert(final Object src, final Class dest) { + // NB: repeated code with convert(ConversionRequest), because the + // handler's convert method respects the T provided + final Converter handler = getHandler(src, dest); + return handler == null ? null : handler.convert(src, dest); + } /** * @see Converter#convert(ConversionRequest) */ - Object convert(ConversionRequest request); + default Object convert(final ConversionRequest request) { + final Converter handler = getHandler(request); + return handler == null ? null : handler.convert(request); + } /** * @see HandlerService#supports(Object) */ - Converter getHandler(Object src, Class dest); + default Converter getHandler(final Object src, final Type dest) { + return getHandler(new ConversionRequest(src, dest)); + } /** * @see HandlerService#supports(Object) */ - Converter getHandler(Object src, Type dest); + default Converter getHandler(final Object src, final Class dest) { + return getHandler(new ConversionRequest(src, dest)); + } /** - * @see HandlerService#supports(Object) + * @see HandlerService#getHandler(Object) */ - boolean supports(Object src, Class dest); + default Converter getHandler(final Class src, final Type dest) { + return getHandler(new ConversionRequest(src, dest)); + } /** - * @see HandlerService#supports(Object) + * @see HandlerService#getHandler(Object) */ - boolean supports(Object src, Type dest); + default Converter getHandler(final Class src, final Class dest) { + return getHandler(new ConversionRequest(src, dest)); + } /** - * @return A collection of instances that could be converted to the - * specified class. + * @see HandlerService#supports(Object) */ - Collection getCompatibleInputs(Class dest); + default boolean supports(final Object src, final Type dest) { + return supports(new ConversionRequest(src, dest)); + } /** - * @return A collection of all classes that could potentially be converted - * to the specified class. + * @see HandlerService#supports(Object) */ - Collection> getCompatibleInputClasses(Class dest); + default boolean supports(final Object src, final Class dest) { + return supports(new ConversionRequest(src, dest)); + } /** - * @return A collection of all classes that could potentially be converted - * from the specified class. + * @see HandlerService#supports(Object) */ - Collection> getCompatibleOutputClasses(Class dest); - - // -- Deprecated API -- + default boolean supports(final Class src, final Type dest) { + return supports(new ConversionRequest(src, dest)); + } /** - * @see HandlerService#getHandler(Object) - * @deprecated Use {@link #getHandler(Object, Class)} + * @see HandlerService#supports(Object) */ - @Deprecated - Converter getHandler(Class src, Class dest); + default boolean supports(final Class src, final Class dest) { + return supports(new ConversionRequest(src, dest)); + } /** - * @see HandlerService#getHandler(Object) - * @deprecated Use {@link #getHandler(Object, Type)} + * @return A collection of instances that could be converted to the + * specified class. */ - @Deprecated - Converter getHandler(Class src, Type dest); + default Collection getCompatibleInputs(final Class dest) { + final Set objects = new LinkedHashSet<>(); + + for (final Converter c : getInstances()) { + if (dest.isAssignableFrom(c.getOutputType())) { + c.populateInputCandidates(objects); + } + } + + return objects; + } /** - * @see HandlerService#supports(Object) - * @deprecated Use {@link #supports(Object, Class)} + * @return A collection of all classes that could potentially be converted + * to the specified class. */ - @Deprecated - boolean supports(Class src, Class dest); + default Collection> getCompatibleInputClasses(final Class dest) { + final Set> compatibleClasses = new HashSet<>(); + + for (final Converter converter : getInstances()) { + if (dest == converter.getOutputType()) // + compatibleClasses.add(converter.getInputType()); + } + + return compatibleClasses; + } /** - * @see HandlerService#supports(Object) - * @deprecated Use {@link #supports(Object, Type)} + * @return A collection of all classes that could potentially be converted + * from the specified class. */ - @Deprecated - boolean supports(Class src, Type dest); + default Collection> getCompatibleOutputClasses(final Class source) { + final Set> compatibleClasses = new HashSet<>(); + + for (final Converter converter : getInstances()) { + try { + if (source == converter.getInputType()) // + compatibleClasses.add(converter.getOutputType()); + } + catch (final Throwable t) { + log().error("Malfunctioning converter plugin: " + // + converter.getClass().getName(), t); + } + } + + return compatibleClasses; + } + + // -- PTService methods -- + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + default Class> getPluginType() { + return (Class) Converter.class; + } + + // -- Typed methods -- + + @Override + default Class getType() { + return ConversionRequest.class; + } } diff --git a/src/main/java/org/scijava/convert/Converter.java b/src/main/java/org/scijava/convert/Converter.java index f918d4ee0..1578a96dd 100644 --- a/src/main/java/org/scijava/convert/Converter.java +++ b/src/main/java/org/scijava/convert/Converter.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -34,15 +32,24 @@ import java.lang.reflect.Type; import java.util.Collection; import java.util.List; +import java.util.Queue; import java.util.Set; import org.scijava.object.ObjectService; import org.scijava.plugin.HandlerPlugin; import org.scijava.plugin.Plugin; +import org.scijava.util.Types; /** * Extensible conversion {@link Plugin} for converting between classes and * types. + *

+ * NB: by default, the provided {@link #canConvert} methods will return + * {@code false} if the input is {@code null}. This allows {@link Converter} + * implementors to assume any input is non-{@code null}. Casting + * {@code null Object} inputs is handled by the {@link NullConverter}, while + * {@code null} class inputs are handled by the {@link DefaultConverter}. + *

* * @see ConversionRequest * @author Mark Hiner @@ -57,41 +64,107 @@ public interface Converter extends HandlerPlugin { * * @see #convert(ConversionRequest) */ - boolean canConvert(ConversionRequest request); + default boolean canConvert(final ConversionRequest request) { + if (request == null) return false; + final Object src = request.sourceObject(); + final Type destType = request.destType(); + if (src != null && destType != null) { + return canConvert(src, destType); + } + if (src != null) { + return canConvert(src, request.destClass()); + } + if (destType != null) { + return canConvert(request.sourceClass(), destType); + } + return canConvert(request.sourceClass(), request.destClass()); + } /** * Checks whether the given object's type can be converted to the specified * type. + * + * @see #convert(Object, Type) + */ + default boolean canConvert(final Object src, final Type dest) { + if (src == null || dest == null) return false; + return canConvert(src.getClass(), dest); + } + + /** + * Checks whether the given object's type can be converted to the specified + * type. + * + * @see #convert(Object, Class) + */ + default boolean canConvert(final Object src, final Class dest) { + if (src == null) return false; + Class srcClass = src.getClass(); + return canConvert(srcClass, dest); + } + + /** + * Checks whether objects of the given class can be converted to the specified + * type. *

* Note that this does not necessarily entail that - * {@link #convert(Object, Type)} on that specific object will succeed. For - * example: {@code canConvert("5.1", int.class)} will return {@code true} - * because a {@link String} can in general be converted to an {@code int}, but - * calling {@code convert("5.1", int.class)} will throw a + * {@link #convert(Object, Type)} on a specific object of the given source + * class will succeed. For example: + * {@code canConvert(String.class, List)} will return {@code true} + * because a {@link String} can in general be converted to an {@code Integer} + * and then wrapped into a {@code List}, but calling + * {@code convert("5.1", List)} will throw a * {@link NumberFormatException} when the conversion is actually attempted via * the {@link Integer#Integer(String)} constructor. *

- * + * * @see #convert(Object, Type) */ - boolean canConvert(Object src, Type dest); + default boolean canConvert(final Class src, final Type dest) { + final Class destClass = Types.raw(dest); + return canConvert(src, destClass); + } /** - * Checks whether the given object's type can be converted to the specified + * Checks whether objects of the given class can be converted to the specified * type. *

* Note that this does not necessarily entail that - * {@link #convert(Object, Class)} on that specific object will succeed. For - * example: {@code canConvert("5.1", int.class)} will return {@code true} + * {@link #convert(Object, Class)} on a specific object of the given source + * class will succeed. For example: + * {@code canConvert(String.class, int.class)} will return {@code true} * because a {@link String} can in general be converted to an {@code int}, but * calling {@code convert("5.1", int.class)} will throw a * {@link NumberFormatException} when the conversion is actually attempted via * the {@link Integer#Integer(String)} constructor. *

+ * + * @see #convert(Object, Class) + */ + default boolean canConvert(final Class src, final Class dest) { + if (src == null) return false; + final Class saneSrc = Types.box(src); + final Class saneDest = Types.box(dest); + return Types.isAssignable(saneSrc, getInputType()) && // + Types.isAssignable(getOutputType(), saneDest); + } + + /** + * Converts the given {@link ConversionRequest#sourceObject()} to the + * specified {@link ConversionRequest#destClass()} or + * {@link ConversionRequest#destType()}. * * @see #convert(Object, Class) + * @see #convert(Object, Type) + * @param request {@link ConversionRequest} to process. + * @return The conversion output */ - boolean canConvert(Object src, Class dest); + default Object convert(final ConversionRequest request) { + if (request.destType() != null) { + return convert(request.sourceObject(), request.destType()); + } + return convert(request.sourceObject(), request.destClass()); + } /** * As {@link #convert(Object, Class)} but capable of creating and populating @@ -104,14 +177,17 @@ public interface Converter extends HandlerPlugin { *

* NB: This method should be capable of creating any array type, but if a * {@link Collection} interface or abstract class is provided we can only make - * a best guess as to what container type to instantiate. Defaults are - * provided for {@link Set} and {@link List} subclasses. + * a best guess as to what container type to instantiate; defaults are + * provided for {@link Set}, {@link Queue}, and {@link List}. *

* * @param src The object to convert. * @param dest Type to which the object should be converted. */ - Object convert(Object src, Type dest); + default Object convert(final Object src, final Type dest) { + final Class destClass = Types.raw(dest); + return convert(src, destClass); + } /** * Converts the given object to an object of the specified type. The object is @@ -128,18 +204,6 @@ public interface Converter extends HandlerPlugin { */ T convert(Object src, Class dest); - /** - * Converts the given {@link ConversionRequest#sourceObject()} to the - * specified {@link ConversionRequest#destClass()} or - * {@link ConversionRequest#destType()}. - * - * @see #convert(Object, Class) - * @see #convert(Object, Type) - * @param request {@link ConversionRequest} to process. - * @return The conversion output - */ - Object convert(ConversionRequest request); - /** * Populates the given collection with objects which are known to exist, and * which are usable as inputs for this converter. @@ -172,25 +236,15 @@ public interface Converter extends HandlerPlugin { */ Class getInputType(); - // -- Deprecated API -- + // -- Typed methods -- - /** - * Checks whether objects of the given class can be converted to the specified - * type. - * - * @see #convert(Object, Type) - * @deprecated Use {@link #canConvert(Object, Type)} - */ - @Deprecated - boolean canConvert(Class src, Type dest); + @Override + default boolean supports(final ConversionRequest request) { + return canConvert(request); + } - /** - * Checks whether objects of the given class can be converted to the specified - * type. - * - * @see #convert(Object, Class) - * @deprecated Use {@link #canConvert(Object, Class)} - */ - @Deprecated - boolean canConvert(Class src, Class dest); + @Override + default Class getType() { + return ConversionRequest.class; + } } diff --git a/src/main/java/org/scijava/convert/DefaultConvertService.java b/src/main/java/org/scijava/convert/DefaultConvertService.java index 71c2d47ec..2ddd292ad 100644 --- a/src/main/java/org/scijava/convert/DefaultConvertService.java +++ b/src/main/java/org/scijava/convert/DefaultConvertService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -42,5 +40,5 @@ @Plugin(type = Service.class) public class DefaultConvertService extends AbstractConvertService { - // Trivial implementation + // NB: No implementation needed. } diff --git a/src/main/java/org/scijava/convert/DefaultConverter.java b/src/main/java/org/scijava/convert/DefaultConverter.java index b3f091258..c99c5a889 100644 --- a/src/main/java/org/scijava/convert/DefaultConverter.java +++ b/src/main/java/org/scijava/convert/DefaultConverter.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -33,107 +31,91 @@ import java.lang.reflect.Array; import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; +import java.util.Deque; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Queue; import java.util.Set; +import org.scijava.Priority; import org.scijava.plugin.Plugin; import org.scijava.util.ArrayUtils; -import org.scijava.util.ClassUtils; -import org.scijava.util.ConversionUtils; -import org.scijava.util.GenericUtils; +import org.scijava.util.Types; /** * Default {@link Converter} implementation. Provides useful conversion * functionality for many common conversion cases. + *

+ * Supported conversions include: + *

+ *
    + *
  • Object to Array
  • + *
  • Object to Collection
  • + *
  • Number to Number
  • + *
  • Object to String
  • + *
  • String to Character
  • + *
  • String to Enum
  • + *
  • Objects where the destination Class has a constructor which takes that + * Object + *
  • + *
* * @author Mark Hiner */ -@Plugin(type = Converter.class) +@Plugin(type = Converter.class, priority = Priority.EXTREMELY_LOW) public class DefaultConverter extends AbstractConverter { // -- ConversionHandler methods -- @Override public Object convert(final Object src, final Type dest) { + // special case: CharSequence -> char[] + // otherwise, String -> char[] ends up length 1 with first char only + if (src instanceof CharSequence && dest == char[].class) { + return ((CharSequence) src).toString().toCharArray(); + } // Handle array types, including generic array types. - if (isArray(dest)) { - return convertToArray(src, GenericUtils.getComponentClass(dest)); + final Type componentType = Types.component(dest); + if (componentType != null) { + // NB: Destination is an array type. + return convertToArray(src, Types.raw(componentType)); } - // Handle parameterized collection types. - if (dest instanceof ParameterizedType && isCollection(dest)) { - return convertToCollection(src, (ParameterizedType) dest); + // Handle collection types, either raw or parameterized. + Class cClass = collectionClass(dest); + if (cClass != null) { + Type elementType = Types.param(dest, Collection.class, 0); + if (elementType == null) elementType = Object.class; // raw collection + final Object collection = convertToCollection(src, cClass, elementType); + if (collection != null) return collection; + // NB: If this conversion failed, it might still succeed later + // when looking for a wrapping constructor. So let's keep going. + // In particular, see ConvertServiceTest#testConvertSubclass(). } - // This wasn't a collection or array, so convert it as a single element. - return convert(src, GenericUtils.getClass(dest)); - } - - @Override - public T convert(final Object src, final Class dest) { - if (dest == null) return null; - if (src == null) return ConversionUtils.getNullValue(dest); - - // ensure type is well-behaved, rather than a primitive type - final Class saneDest = ConversionUtils.getNonprimitiveType(dest); - - // cast the existing object, if possible - if (ConversionUtils.canCast(src, saneDest)) return ConversionUtils.cast( - src, saneDest); + // Ensure type is a well-behaved class, rather than a primitive type. + final Class destClass = Types.raw(dest); + final Class saneDest = Types.box(destClass); - // Handle array types - if (isArray(dest)) { - @SuppressWarnings("unchecked") - T array = (T) convertToArray(src, GenericUtils.getComponentClass(dest)); - return array; - } + // Object is already the requested type. + if (Types.isInstance(src, saneDest)) return src; // special case for conversion from number to number if (src instanceof Number) { final Number number = (Number) src; - if (saneDest == Byte.class) { - final Byte result = number.byteValue(); - @SuppressWarnings("unchecked") - final T typedResult = (T) result; - return typedResult; - } - if (saneDest == Double.class) { - final Double result = number.doubleValue(); - @SuppressWarnings("unchecked") - final T typedResult = (T) result; - return typedResult; - } - if (saneDest == Float.class) { - final Float result = number.floatValue(); - @SuppressWarnings("unchecked") - final T typedResult = (T) result; - return typedResult; - } - if (saneDest == Integer.class) { - final Integer result = number.intValue(); - @SuppressWarnings("unchecked") - final T typedResult = (T) result; - return typedResult; - } - if (saneDest == Long.class) { - final Long result = number.longValue(); - @SuppressWarnings("unchecked") - final T typedResult = (T) result; - return typedResult; - } - if (saneDest == Short.class) { - final Short result = number.shortValue(); - @SuppressWarnings("unchecked") - final T typedResult = (T) result; - return typedResult; - } + if (saneDest == Byte.class) return number.byteValue(); + if (saneDest == Double.class) return number.doubleValue(); + if (saneDest == Float.class) return number.floatValue(); + if (saneDest == Integer.class) return number.intValue(); + if (saneDest == Long.class) return number.longValue(); + if (saneDest == Short.class) return number.shortValue(); } // special cases for strings @@ -142,46 +124,55 @@ public T convert(final Object src, final Class dest) { final String s = (String) src; if (s.isEmpty()) { // return null for empty strings - return ConversionUtils.getNullValue(dest); + return Types.nullValue(saneDest); } // use first character when converting to Character if (saneDest == Character.class) { - final Character c = new Character(s.charAt(0)); - @SuppressWarnings("unchecked") - final T result = (T) c; - return result; + return new Character(s.charAt(0)); } // special case for conversion to enum - if (dest.isEnum()) { - final T result = ConversionUtils.convertToEnum(s, dest); - if (result != null) return result; + if (saneDest.isEnum()) { + try { + return Types.enumFromString(s, saneDest); + } + catch (final IllegalArgumentException exc) { + // NB: No action needed. + } } } + if (saneDest == String.class) { // destination type is String; use Object.toString() method - final String sValue = src.toString(); - @SuppressWarnings("unchecked") - final T result = (T) sValue; - return result; + return src.toString(); } // wrap the original object with one of the new type, using a constructor try { final Constructor ctor = getConstructor(saneDest, src.getClass()); if (ctor == null) return null; - @SuppressWarnings("unchecked") - final T instance = (T) ctor.newInstance(src); - return instance; + return ctor.newInstance(src); } - catch (final Exception exc) { - // TODO: Best not to catch blanket Exceptions here. + catch (final InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException exc) + { // no known way to convert - return null; + return Types.nullValue(destClass); } } + @Override + public T convert(final Object src, final Class dest) { + // NB: Invert functional flow from Converter interface: + // Converter: convert(Class, Type) calling convert(Class, Class) + // becomes: convert(Class, Class) calling convert(Class, Type) + final Type destType = dest; + @SuppressWarnings("unchecked") + final T result = (T) convert(src, destType); + return result; + } + @Override public Class getOutputType() { return Object.class; @@ -199,7 +190,9 @@ private Constructor getConstructor(final Class type, { for (final Constructor ctor : type.getConstructors()) { final Class[] params = ctor.getParameterTypes(); - if (params.length == 1 && ConversionUtils.canCast(argType, params[0])) { + if (params.length == 1 && // + Types.isAssignable(Types.box(argType), Types.box(params[0]))) + { return ctor; } } @@ -207,12 +200,13 @@ private Constructor getConstructor(final Class type, } private boolean isArray(final Type type) { - return GenericUtils.getComponentClass(type) != null; + return Types.component(type) != null; } - private boolean isCollection(final Type type) { - return ConversionUtils.canCast(GenericUtils.getClass(type), - Collection.class); + private Class collectionClass(final Type type) { + return Types.raws(type).stream() // + .filter(t -> Types.isAssignable(t, Collection.class)) // + .findFirst().orElse(null); } private Object @@ -236,35 +230,32 @@ private boolean isCollection(final Type type) { } private Object convertToCollection(final Object value, - final ParameterizedType pType) + final Class collectionType, final Type elementType) { - final Collection collection = - createCollection(GenericUtils.getClass(pType)); + final Collection collection = createCollection(collectionType); if (collection == null) return null; // Populate the collection. final Collection items = ArrayUtils.toCollection(value); - // TODO: The following can fail; e.g. "Foo extends ArrayList" - final Type collectionType = pType.getActualTypeArguments()[0]; for (final Object item : items) { - collection.add(convert(item, collectionType)); + collection.add(convert(item, elementType)); } return collection; } - private Collection createCollection(final Class type) { - // If we were given an interface or abstract class, and not a concrete - // class, we attempt to make default implementations. - if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) { - // We don't have a concrete class. If it's a set or a list, we use - // the typical default implementation. Otherwise we won't convert. - if (ConversionUtils.canCast(type, List.class)) return new ArrayList<>(); - if (ConversionUtils.canCast(type, Set.class)) return new HashSet<>(); + private Collection createCollection(Class type) { + // Support conversion to common collection interface types. + if (type == Queue.class || type == Deque.class) type = ArrayDeque.class; + else if (type == Set.class) type = LinkedHashSet.class; + else if (type == List.class || type == Collection.class) type = ArrayList.class; + else if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) { + // We were given an interface or abstract class, and not a concrete + // class, and we don't know what default implementation to use. return null; } - // Got a concrete type. Instantiate it. + // We now have a concrete type. Instantiate it. try { @SuppressWarnings("unchecked") final Collection c = (Collection) type.newInstance(); @@ -281,64 +272,37 @@ private Collection createCollection(final Class type) { // -- Deprecated API -- @Override - @Deprecated - public boolean canConvert(final Class src, final Type dest) { - - // Handle array types, including generic array types. - if (isArray(dest)) return true; - - // Handle parameterized collection types. - if (dest instanceof ParameterizedType && isCollection(dest) && - createCollection(GenericUtils.getClass(dest)) != null) - { - return true; - } - - return super.canConvert(src, dest); - } - - @Override - @Deprecated public boolean canConvert(final Class src, final Class dest) { - - if (src == null || dest == null) return true; + // OK for array and collection types. + if (isArray(dest)) return true; + Class cClass = collectionClass(dest); + if (cClass != null && createCollection(cClass) != null) return true; // ensure type is well-behaved, rather than a primitive type - final Class saneDest = ConversionUtils.getNonprimitiveType(dest); - - // OK if the existing object can be casted - if (ConversionUtils.canCast(src, saneDest)) return true; - + final Class saneDest = Types.box(dest); + // OK for numerical conversions - if (ConversionUtils.canCast(ConversionUtils.getNonprimitiveType(src), - Number.class) && - (ClassUtils.isByte(dest) || ClassUtils.isDouble(dest) || - ClassUtils.isFloat(dest) || ClassUtils.isInteger(dest) || - ClassUtils.isLong(dest) || ClassUtils.isShort(dest))) + if (Types.isAssignable(Types.box(src), Number.class) && // + (Types.isByte(saneDest) || Types.isDouble(saneDest) || // + Types.isFloat(saneDest) || Types.isInteger(saneDest) || // + Types.isLong(saneDest) || Types.isShort(saneDest))) { return true; } - + // OK if string if (saneDest == String.class) return true; - - if (ConversionUtils.canCast(src, String.class)) { + + if (Types.isAssignable(src, String.class)) { // OK if source type is string and destination type is character // (in this case, the first character of the string would be used) if (saneDest == Character.class) return true; - + // OK if source type is string and destination type is an enum if (dest.isEnum()) return true; } - + // OK if appropriate wrapper constructor exists - try { - return getConstructor(saneDest, src) != null; - } - catch (final Exception exc) { - // TODO: Best not to catch blanket Exceptions here. - // no known way to convert - return false; - } + return getConstructor(saneDest, src) != null; } } diff --git a/src/main/java/org/scijava/convert/FileListConverters.java b/src/main/java/org/scijava/convert/FileListConverters.java new file mode 100644 index 000000000..8b5d86b73 --- /dev/null +++ b/src/main/java/org/scijava/convert/FileListConverters.java @@ -0,0 +1,160 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.convert; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.scijava.plugin.Plugin; +import org.scijava.util.StringUtils; + +/** + * A collection of {@link Converter} plugins for going between {@link String}, + * {@link File} and {@code File[]}. + * + * @author Jan Eglinger + * @author Curtis Rueden + */ +public class FileListConverters { + // -- String to File (list) converters -- + + @Plugin(type = Converter.class) + public static class StringToFileConverter extends + AbstractConverter + { + + @SuppressWarnings("unchecked") + @Override + public T convert(final Object src, final Class dest) { + return (T) new File((String) src); + } + + @Override + public Class getOutputType() { + return File.class; + } + + @Override + public Class getInputType() { + return String.class; + } + + } + + @Plugin(type = Converter.class) + public static class StringToFileArrayConverter extends + AbstractConverter + { + + @SuppressWarnings("unchecked") + @Override + public T convert(final Object src, final Class dest) { + final String[] tokens = StringUtils.splitUnquoted((String) src, ","); + final List fileList = new ArrayList<>(); + for (final String filePath : tokens) { + if ( filePath.isEmpty() ) + continue; + fileList.add(new File(filePath.replaceAll("^\"|\"$", ""))); + } + return (T) fileList.toArray(new File[fileList.size()]); + } + + @Override + public Class getOutputType() { + return File[].class; + } + + @Override + public Class getInputType() { + return String.class; + } + + } + + // TODO add StringToFileListConverter + + // -- File (list) to String converters -- + + @Plugin(type = Converter.class) + public static class FileToStringConverter extends + AbstractConverter + { + + @SuppressWarnings("unchecked") + @Override + public T convert(final Object src, final Class dest) { + return (T) ((File) src).getAbsolutePath(); + } + + @Override + public Class getOutputType() { + return String.class; + } + + @Override + public Class getInputType() { + return File.class; + } + + } + + @Plugin(type = Converter.class) + public static class FileArrayToStringConverter extends + AbstractConverter + { + + @SuppressWarnings("unchecked") + @Override + public T convert(final Object src, final Class dest) { + final List result = Arrays.asList((File[]) src).stream().map( + f -> { + final String path = f.getAbsolutePath(); + return path.contains(",") ? "\"" + path + "\"" : path; + }).collect(Collectors.toList()); + return (T) String.join(",", result); + } + + @Override + public Class getOutputType() { + return String.class; + } + + @Override + public Class getInputType() { + return File[].class; + } + + } + + // TODO add FileListToStringConverter +} diff --git a/src/main/java/org/scijava/convert/FileToPathConverter.java b/src/main/java/org/scijava/convert/FileToPathConverter.java new file mode 100644 index 000000000..9c8717286 --- /dev/null +++ b/src/main/java/org/scijava/convert/FileToPathConverter.java @@ -0,0 +1,61 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.convert; + +import java.io.File; +import java.nio.file.Path; + +import org.scijava.plugin.Plugin; + +/** + * A {@link Converter} used to convert {@link File}s into {@link Path}s. + * + * @author Gabriel Selzer + */ +@Plugin(type = Converter.class) +public class FileToPathConverter extends AbstractConverter { + + @SuppressWarnings("unchecked") + @Override + public T convert(final Object src, final Class dest) { + File f = (File) src; + return (T) f.toPath(); + } + + @Override + public Class getOutputType() { + return Path.class; + } + + @Override + public Class getInputType() { + return File.class; + } +} diff --git a/src/main/java/org/scijava/convert/NullConverter.java b/src/main/java/org/scijava/convert/NullConverter.java index 5eb02c50a..5c91943db 100644 --- a/src/main/java/org/scijava/convert/NullConverter.java +++ b/src/main/java/org/scijava/convert/NullConverter.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -35,56 +33,47 @@ import org.scijava.Priority; import org.scijava.plugin.Plugin; -import org.scijava.util.ConversionUtils; +import org.scijava.util.Types; /** - * {@link Converter} implementation for handling {@code null} values. Performs - * basic casting when given a {@code null} source and returns {@code} null - * directly when given a {@code} null destination. + * {@link Converter} implementation for handling {@code null} values. Returns + * {@code null} when given a {@code null} source or {@code null} destination. *

- * By running at {@link Priority#FIRST_PRIORITY}, other converters should - * not need to worry about {@code null} source or destination parameters. - *

- *

- * NB: if a {@link Class} source is queried for the {@link #canConvert}, - * this converter will always return false (as there is no way of knowing - * if the source object will be null or not). + * By running at {@link Priority#EXTREMELY_HIGH}, other converters should not + * need to worry about {@code null} source or destination parameters. *

* * @author Mark Hiner */ -@Plugin(type = Converter.class, priority = Priority.FIRST_PRIORITY) +@Plugin(type = Converter.class, priority = Priority.EXTREMELY_HIGH) public class NullConverter extends AbstractConverter { @Override - public boolean canConvert(final ConversionRequest request) { - if (request == null) return false; - if (request.destType() == null && request.destClass() == null) return false; - return request.sourceObject() == null && request.sourceClass() == null; + public boolean canConvert(final Object src, final Type dest) { + return src == null || dest == null; } @Override - public boolean canConvert(final Object src, final Type dest) { - return src == null && dest != null; + public boolean canConvert(final Object src, final Class dest) { + return src == null || dest == null; } @Override - public boolean canConvert(final Object src, final Class dest) { - return src == null && dest != null; + public boolean canConvert(final Class src, final Type dest) { + return src == null || dest == null; } @Override public boolean canConvert(final Class src, final Class dest) { - return src == null && dest != null; + return src == null || dest == null; } @Override public T convert(final Object src, final Class dest) { if (dest == null) return null; - if (src == null) return ConversionUtils.getNullValue(dest); - + if (src == null) return Types.nullValue(dest); throw new IllegalArgumentException("Attempting non-null conversion: " + - src + " > " + dest + " using NullConverter."); + src + " -> " + dest + " using NullConverter."); } @Override @@ -96,5 +85,4 @@ public Class getOutputType() { public Class getInputType() { return Object.class; } - } diff --git a/src/main/java/org/scijava/convert/NumberConverters.java b/src/main/java/org/scijava/convert/NumberConverters.java index 98f636b60..4046d6503 100644 --- a/src/main/java/org/scijava/convert/NumberConverters.java +++ b/src/main/java/org/scijava/convert/NumberConverters.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/convert/NumberToBigDecimalConverter.java b/src/main/java/org/scijava/convert/NumberToBigDecimalConverter.java index e55b2db61..910d5a02a 100644 --- a/src/main/java/org/scijava/convert/NumberToBigDecimalConverter.java +++ b/src/main/java/org/scijava/convert/NumberToBigDecimalConverter.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/convert/NumberToBigIntegerConverter.java b/src/main/java/org/scijava/convert/NumberToBigIntegerConverter.java index 5080cb153..ba3279439 100644 --- a/src/main/java/org/scijava/convert/NumberToBigIntegerConverter.java +++ b/src/main/java/org/scijava/convert/NumberToBigIntegerConverter.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/convert/NumberToDoubleConverter.java b/src/main/java/org/scijava/convert/NumberToDoubleConverter.java index cbc2e9f24..302ea4d22 100644 --- a/src/main/java/org/scijava/convert/NumberToDoubleConverter.java +++ b/src/main/java/org/scijava/convert/NumberToDoubleConverter.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/convert/NumberToFloatConverter.java b/src/main/java/org/scijava/convert/NumberToFloatConverter.java index 8da2cf802..afd969dbe 100644 --- a/src/main/java/org/scijava/convert/NumberToFloatConverter.java +++ b/src/main/java/org/scijava/convert/NumberToFloatConverter.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/convert/NumberToIntegerConverter.java b/src/main/java/org/scijava/convert/NumberToIntegerConverter.java index ab230327c..e68c4cf29 100644 --- a/src/main/java/org/scijava/convert/NumberToIntegerConverter.java +++ b/src/main/java/org/scijava/convert/NumberToIntegerConverter.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/convert/NumberToLongConverter.java b/src/main/java/org/scijava/convert/NumberToLongConverter.java index ed8846725..40c2c0bb8 100644 --- a/src/main/java/org/scijava/convert/NumberToLongConverter.java +++ b/src/main/java/org/scijava/convert/NumberToLongConverter.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/convert/NumberToNumberConverter.java b/src/main/java/org/scijava/convert/NumberToNumberConverter.java index a47ff573a..40035dc1f 100644 --- a/src/main/java/org/scijava/convert/NumberToNumberConverter.java +++ b/src/main/java/org/scijava/convert/NumberToNumberConverter.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -31,7 +29,7 @@ package org.scijava.convert; -import org.scijava.util.ConversionUtils; +import org.scijava.util.Types; /** * Converts numbers to numbers, and throws IllegalArgumentException for null or @@ -45,19 +43,21 @@ public abstract class NumberToNumberConverter T convert(final Object src, final Class dest) { - if (src == null || dest == null) throw new IllegalArgumentException( - "Null input"); + if (src == null || dest == null) // + throw new IllegalArgumentException("Null input"); if (!getInputType().isInstance(src)) { throw new IllegalArgumentException("Expected input of type " + - getInputType().getSimpleName() + ", but got " + + getInputType().getSimpleName() + ", but got " + // src.getClass().getSimpleName()); } - if (ConversionUtils.getNonprimitiveType(dest) != getOutputType()) { + if (Types.box(dest) != getOutputType()) { throw new IllegalArgumentException( "Expected output class of " + getOutputType().getSimpleName() + ", but got " + dest.getSimpleName()); } - return (T) convert((Number) src); + @SuppressWarnings("unchecked") + final T result = (T) convert((Number) src); + return result; } public abstract O convert(Number n); diff --git a/src/main/java/org/scijava/convert/NumberToShortConverter.java b/src/main/java/org/scijava/convert/NumberToShortConverter.java index 9c6a83448..eda48e07f 100644 --- a/src/main/java/org/scijava/convert/NumberToShortConverter.java +++ b/src/main/java/org/scijava/convert/NumberToShortConverter.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/convert/PathToFileConverter.java b/src/main/java/org/scijava/convert/PathToFileConverter.java new file mode 100644 index 000000000..cc8aa967d --- /dev/null +++ b/src/main/java/org/scijava/convert/PathToFileConverter.java @@ -0,0 +1,61 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.convert; + +import java.io.File; +import java.nio.file.Path; + +import org.scijava.plugin.Plugin; + +/** + * A {@link Converter} used to convert {@link Path}s into {@link File}s. + * + * @author Gabriel Selzer + */ +@Plugin(type = Converter.class) +public class PathToFileConverter extends AbstractConverter { + + @SuppressWarnings("unchecked") + @Override + public T convert(final Object src, final Class dest) { + final Path p = (Path) src; + return (T) p.toFile(); + } + + @Override + public Class getOutputType() { + return File.class; + } + + @Override + public Class getInputType() { + return Path.class; + } +} diff --git a/src/main/java/org/scijava/convert/PrimitiveArrayUnwrapper.java b/src/main/java/org/scijava/convert/PrimitiveArrayUnwrapper.java index c0a564c05..6292aee6d 100644 --- a/src/main/java/org/scijava/convert/PrimitiveArrayUnwrapper.java +++ b/src/main/java/org/scijava/convert/PrimitiveArrayUnwrapper.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -43,7 +41,7 @@ public abstract class PrimitiveArrayUnwrapper T convert(Object src, Class dest) { + public T convert(final Object src, final Class dest) { final W primitiveArray = (W) src; return (T) primitiveArray.getArray(); diff --git a/src/main/java/org/scijava/convert/PrimitiveArrayWrapper.java b/src/main/java/org/scijava/convert/PrimitiveArrayWrapper.java index ab31f5359..f022900a8 100644 --- a/src/main/java/org/scijava/convert/PrimitiveArrayWrapper.java +++ b/src/main/java/org/scijava/convert/PrimitiveArrayWrapper.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/convert/StringToArrayConverter.java b/src/main/java/org/scijava/convert/StringToArrayConverter.java new file mode 100644 index 000000000..deeb58010 --- /dev/null +++ b/src/main/java/org/scijava/convert/StringToArrayConverter.java @@ -0,0 +1,170 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.convert; + +import java.lang.reflect.Array; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import org.scijava.Priority; +import org.scijava.parse.Item; +import org.scijava.parse.Items; +import org.scijava.parse.ParseService; +import org.scijava.parsington.Token; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.util.Types; + +/** + * A {@link Converter} that specializes in converting {@link String}s to + * n-dimensional arrays. This {@link Converter} can convert any array whose + * component types can be created from a {@link String}. By default, this + * {@link Converter} delimits the {@link String} based on commas. + * + * @author Gabriel Selzer + */ +@Plugin(type = Converter.class, priority = Priority.VERY_LOW) +public class StringToArrayConverter extends AbstractConverter { + + @Parameter(required = false) + private ConvertService convertService; + + @Parameter(required = false) + private ParseService parseService; + + @Override + public boolean canConvert(final Object src, final Type dest) { + return canConvert(src, Types.raw(dest)); + } + + @Override + public boolean canConvert(final Object src, final Class dest) { + if (convertService == null || parseService == null) return false; + + // First, ensure the base types conform + if (!canConvert(src.getClass(), dest)) return false; + // Then, ensure we can parse the string + try { + parseService.parse((String) src, false); + } + catch (final IllegalArgumentException e) { + return false; + } + return true; + } + + @Override + public boolean canConvert(final Class src, final Class dest) { + return src == String.class && dest.isArray(); + } + + @Override + public Object convert(final Object src, final Type dest) { + final Type componentType = Types.component(dest); + if (componentType == null) { + throw new IllegalArgumentException(dest + " is not an array type!"); + } + final List items = parse((String) src); + return convertToArray(items, Types.raw(componentType)); + } + + @Override + public T convert(final Object src, final Class dest) { + // NB: Invert functional flow from Converter interface: + // Converter: convert(Object, Type) calling convert(Object, Class) + // becomes: convert(Object, Class) calling convert(Object, Type) + final Type destType = dest; + @SuppressWarnings("unchecked") + T result = (T) convert(src, destType); + return result; + } + + @Override + public Class getOutputType() { + return Object.class; + } + + @Override + public Class getInputType() { + return String.class; + } + + // -- Helper methods -- + + /** + * Converts {@code src} into an array of component type {@code componentType}. + * + * @param tree the {@link String} to convert + * @param componentType the component type of the output array + * @return an array of {@code componentType} whose elements were created from + * {@code src} + */ + private Object convertToArray(final List tree, + final Class componentType) + { + // Create the array + final Object array = Array.newInstance(componentType, tree.size()); + // Set each element of the array + for (int i = 0; i < tree.size(); i++) { + Object element = tree.get(i); + final Object converted = convertService.convert(element, componentType); + Array.set(array, i, converted); + } + return array; + } + + /** Parses a string to a list, using the {@link ParseService}. */ + private List parse(final String s) { + try { + Items items = parseService.parse(s, false); + return (List) unwrap(items); + } + catch (final IllegalArgumentException e) { + return null; + } + } + + private Object unwrap(final Object o) { + if (o instanceof Collection) { + return ((Collection) o).stream() // + .map(item -> unwrap(item)) // + .collect(Collectors.toList()); + } + if (o instanceof Item) { + return unwrap(((Item) o).value()); + } + if (o instanceof Token) { + return ((Token) o).getToken(); + } + return o; + } +} diff --git a/src/main/java/org/scijava/convert/StringToNumberConverter.java b/src/main/java/org/scijava/convert/StringToNumberConverter.java new file mode 100644 index 000000000..00a3f2640 --- /dev/null +++ b/src/main/java/org/scijava/convert/StringToNumberConverter.java @@ -0,0 +1,99 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.convert; + +import org.scijava.plugin.Plugin; +import org.scijava.util.Types; + +/** + * Converts a {@link String} to a {@link Number}. Currently handles all boxed + * and unboxed primitives, along with the Numbers. In particular, + * {@link String}s converted {@link Number}s are just {@link Double}s, as this + * features the broadest range of integers. + * + * @author Gabriel Selzer + */ +@Plugin(type = Converter.class) +public class StringToNumberConverter extends AbstractConverter { + + @Override + @SuppressWarnings("unchecked") + public T convert(Object src, Class dest) { + // ensure type is well-behaved, rather than a primitive type + Class saneDest = sane(dest); + if (!(src instanceof String)) throw new IllegalArgumentException( + "Expected src to be a String but got a " + src.getClass()); + if (!(Number.class.isAssignableFrom(saneDest))) + throw new IllegalArgumentException( + "Expected dest to be Number.class (or a subclass of Number, or a numerical primitive), but got " + + saneDest); + String srcString = (String) src; + if (saneDest == Byte.class) return (T) new Byte(srcString); + if (saneDest == Short.class) return (T) new Short(srcString); + if (saneDest == Integer.class) return (T) new Integer(srcString); + if (saneDest == Long.class) return (T) new Long(srcString); + if (saneDest == Float.class) return (T) new Float(srcString); + if (saneDest == Double.class) return (T) new Double(srcString); + else throw new IllegalArgumentException("Unknown destination type: " + + saneDest); + } + + @Override + public Class getOutputType() { + return Number.class; + } + + @Override + public Class getInputType() { + return String.class; + } + + @Override + public boolean canConvert(Object src, Class dest) { + if (!Types.isAssignable(src.getClass(), String.class)) return false; + // The only way to know if the conversion is valid is to actually do it. + try { + String srcString = (String) src; + sane(dest).getConstructor(String.class).newInstance(srcString); + return true; + } + catch (Exception e) { + return false; + } + } + + // -- Helper functionality -- // + + @SuppressWarnings("unchecked") + private Class sane(Class c) { + if (c == Number.class) return (Class) Double.class; + return Types.box(c); + } +} diff --git a/src/main/java/org/scijava/display/AbstractDisplay.java b/src/main/java/org/scijava/display/AbstractDisplay.java index e9156d608..e190f844c 100644 --- a/src/main/java/org/scijava/display/AbstractDisplay.java +++ b/src/main/java/org/scijava/display/AbstractDisplay.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/ActiveDisplayPreprocessor.java b/src/main/java/org/scijava/display/ActiveDisplayPreprocessor.java index 5406ba4ec..5997d66e8 100644 --- a/src/main/java/org/scijava/display/ActiveDisplayPreprocessor.java +++ b/src/main/java/org/scijava/display/ActiveDisplayPreprocessor.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -52,8 +50,7 @@ * * @author Curtis Rueden */ -@Plugin(type = PreprocessorPlugin.class, - priority = Priority.VERY_HIGH_PRIORITY) +@Plugin(type = PreprocessorPlugin.class, priority = Priority.VERY_HIGH) public class ActiveDisplayPreprocessor extends AbstractPreprocessorPlugin { @Parameter(required = false) diff --git a/src/main/java/org/scijava/display/DefaultDisplay.java b/src/main/java/org/scijava/display/DefaultDisplay.java index a891da42c..8b7755d44 100644 --- a/src/main/java/org/scijava/display/DefaultDisplay.java +++ b/src/main/java/org/scijava/display/DefaultDisplay.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -38,7 +36,7 @@ // using Object#toString()), but until it exists, discovery of this display // merely causes the UIService to eventually issue some warnings anyway // ("No suitable viewer found for display" and "No viewer found for display"). -//@Plugin(type = Display.class, priority = Priority.VERY_LOW_PRIORITY) +//@Plugin(type = Display.class, priority = Priority.VERY_LOW) /** * Default display for objects, when no other displays are available. * diff --git a/src/main/java/org/scijava/display/DefaultDisplayService.java b/src/main/java/org/scijava/display/DefaultDisplayService.java index e383301fe..23a0c9c7f 100644 --- a/src/main/java/org/scijava/display/DefaultDisplayService.java +++ b/src/main/java/org/scijava/display/DefaultDisplayService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/DefaultTextDisplay.java b/src/main/java/org/scijava/display/DefaultTextDisplay.java index acf37864e..380507bdf 100644 --- a/src/main/java/org/scijava/display/DefaultTextDisplay.java +++ b/src/main/java/org/scijava/display/DefaultTextDisplay.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -39,7 +37,7 @@ * * @author Curtis Rueden */ -@Plugin(type = Display.class, priority = Priority.LOW_PRIORITY) +@Plugin(type = Display.class, priority = Priority.LOW) public class DefaultTextDisplay extends AbstractDisplay implements TextDisplay { diff --git a/src/main/java/org/scijava/display/Display.java b/src/main/java/org/scijava/display/Display.java index 810350a13..2221db211 100644 --- a/src/main/java/org/scijava/display/Display.java +++ b/src/main/java/org/scijava/display/Display.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/DisplayPostprocessor.java b/src/main/java/org/scijava/display/DisplayPostprocessor.java index bee7ccb7a..7a6c54264 100644 --- a/src/main/java/org/scijava/display/DisplayPostprocessor.java +++ b/src/main/java/org/scijava/display/DisplayPostprocessor.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -53,8 +51,7 @@ * @author Lee Kamentsky * @author Barry DeZonia */ -@Plugin(type = PostprocessorPlugin.class, - priority = Priority.VERY_LOW_PRIORITY) +@Plugin(type = PostprocessorPlugin.class, priority = Priority.VERY_LOW) public class DisplayPostprocessor extends AbstractPostprocessorPlugin { @Parameter(required = false) diff --git a/src/main/java/org/scijava/display/DisplayService.java b/src/main/java/org/scijava/display/DisplayService.java index 2f50512c4..fcc13e0a0 100644 --- a/src/main/java/org/scijava/display/DisplayService.java +++ b/src/main/java/org/scijava/display/DisplayService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/Displayable.java b/src/main/java/org/scijava/display/Displayable.java index 6236703b8..67c0e26ee 100644 --- a/src/main/java/org/scijava/display/Displayable.java +++ b/src/main/java/org/scijava/display/Displayable.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/TextDisplay.java b/src/main/java/org/scijava/display/TextDisplay.java index 600e4a729..0ada43aad 100644 --- a/src/main/java/org/scijava/display/TextDisplay.java +++ b/src/main/java/org/scijava/display/TextDisplay.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/DisplayActivatedEvent.java b/src/main/java/org/scijava/display/event/DisplayActivatedEvent.java index 89ab73888..1fe30c96c 100644 --- a/src/main/java/org/scijava/display/event/DisplayActivatedEvent.java +++ b/src/main/java/org/scijava/display/event/DisplayActivatedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/DisplayCreatedEvent.java b/src/main/java/org/scijava/display/event/DisplayCreatedEvent.java index f7a632a17..347557f2f 100644 --- a/src/main/java/org/scijava/display/event/DisplayCreatedEvent.java +++ b/src/main/java/org/scijava/display/event/DisplayCreatedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/DisplayDeletedEvent.java b/src/main/java/org/scijava/display/event/DisplayDeletedEvent.java index 978ff2296..125ce25e3 100644 --- a/src/main/java/org/scijava/display/event/DisplayDeletedEvent.java +++ b/src/main/java/org/scijava/display/event/DisplayDeletedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/DisplayEvent.java b/src/main/java/org/scijava/display/event/DisplayEvent.java index a533eaf74..99deab3ea 100644 --- a/src/main/java/org/scijava/display/event/DisplayEvent.java +++ b/src/main/java/org/scijava/display/event/DisplayEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/DisplayUpdatedEvent.java b/src/main/java/org/scijava/display/event/DisplayUpdatedEvent.java index 2df034dc3..cbb1fc3ab 100644 --- a/src/main/java/org/scijava/display/event/DisplayUpdatedEvent.java +++ b/src/main/java/org/scijava/display/event/DisplayUpdatedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/input/InputEvent.java b/src/main/java/org/scijava/display/event/input/InputEvent.java index fab6bcc83..4e278a185 100644 --- a/src/main/java/org/scijava/display/event/input/InputEvent.java +++ b/src/main/java/org/scijava/display/event/input/InputEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/input/KyEvent.java b/src/main/java/org/scijava/display/event/input/KyEvent.java index a019dc780..058c986dc 100644 --- a/src/main/java/org/scijava/display/event/input/KyEvent.java +++ b/src/main/java/org/scijava/display/event/input/KyEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/input/KyPressedEvent.java b/src/main/java/org/scijava/display/event/input/KyPressedEvent.java index 089dd17e2..e74dae63d 100644 --- a/src/main/java/org/scijava/display/event/input/KyPressedEvent.java +++ b/src/main/java/org/scijava/display/event/input/KyPressedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/input/KyReleasedEvent.java b/src/main/java/org/scijava/display/event/input/KyReleasedEvent.java index 128e10b50..15659de2d 100644 --- a/src/main/java/org/scijava/display/event/input/KyReleasedEvent.java +++ b/src/main/java/org/scijava/display/event/input/KyReleasedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/input/KyTypedEvent.java b/src/main/java/org/scijava/display/event/input/KyTypedEvent.java index e3071db3b..9f5d96c6e 100644 --- a/src/main/java/org/scijava/display/event/input/KyTypedEvent.java +++ b/src/main/java/org/scijava/display/event/input/KyTypedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/input/MsButtonEvent.java b/src/main/java/org/scijava/display/event/input/MsButtonEvent.java index d6ca4f712..09a91dabd 100644 --- a/src/main/java/org/scijava/display/event/input/MsButtonEvent.java +++ b/src/main/java/org/scijava/display/event/input/MsButtonEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/input/MsClickedEvent.java b/src/main/java/org/scijava/display/event/input/MsClickedEvent.java index 6110f6330..f13524996 100644 --- a/src/main/java/org/scijava/display/event/input/MsClickedEvent.java +++ b/src/main/java/org/scijava/display/event/input/MsClickedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/input/MsDraggedEvent.java b/src/main/java/org/scijava/display/event/input/MsDraggedEvent.java index 765c73e73..f683dc4b9 100644 --- a/src/main/java/org/scijava/display/event/input/MsDraggedEvent.java +++ b/src/main/java/org/scijava/display/event/input/MsDraggedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/input/MsEnteredEvent.java b/src/main/java/org/scijava/display/event/input/MsEnteredEvent.java index 2bbe67f53..310bcb444 100644 --- a/src/main/java/org/scijava/display/event/input/MsEnteredEvent.java +++ b/src/main/java/org/scijava/display/event/input/MsEnteredEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/input/MsEvent.java b/src/main/java/org/scijava/display/event/input/MsEvent.java index 7b4238bdb..b370e1e79 100644 --- a/src/main/java/org/scijava/display/event/input/MsEvent.java +++ b/src/main/java/org/scijava/display/event/input/MsEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/input/MsExitedEvent.java b/src/main/java/org/scijava/display/event/input/MsExitedEvent.java index 8f1a9864f..921e674f1 100644 --- a/src/main/java/org/scijava/display/event/input/MsExitedEvent.java +++ b/src/main/java/org/scijava/display/event/input/MsExitedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/input/MsMovedEvent.java b/src/main/java/org/scijava/display/event/input/MsMovedEvent.java index f702b1927..658b1b904 100644 --- a/src/main/java/org/scijava/display/event/input/MsMovedEvent.java +++ b/src/main/java/org/scijava/display/event/input/MsMovedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/input/MsPressedEvent.java b/src/main/java/org/scijava/display/event/input/MsPressedEvent.java index ba6f9d7ab..ea2593493 100644 --- a/src/main/java/org/scijava/display/event/input/MsPressedEvent.java +++ b/src/main/java/org/scijava/display/event/input/MsPressedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/input/MsReleasedEvent.java b/src/main/java/org/scijava/display/event/input/MsReleasedEvent.java index 0efacbe71..61786d727 100644 --- a/src/main/java/org/scijava/display/event/input/MsReleasedEvent.java +++ b/src/main/java/org/scijava/display/event/input/MsReleasedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/input/MsWheelEvent.java b/src/main/java/org/scijava/display/event/input/MsWheelEvent.java index 73f93a14f..32d92bd2f 100644 --- a/src/main/java/org/scijava/display/event/input/MsWheelEvent.java +++ b/src/main/java/org/scijava/display/event/input/MsWheelEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/window/WinActivatedEvent.java b/src/main/java/org/scijava/display/event/window/WinActivatedEvent.java index a63bae0d0..f8c81ec30 100644 --- a/src/main/java/org/scijava/display/event/window/WinActivatedEvent.java +++ b/src/main/java/org/scijava/display/event/window/WinActivatedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/window/WinClosedEvent.java b/src/main/java/org/scijava/display/event/window/WinClosedEvent.java index 8638eb469..24476f5aa 100644 --- a/src/main/java/org/scijava/display/event/window/WinClosedEvent.java +++ b/src/main/java/org/scijava/display/event/window/WinClosedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/window/WinClosingEvent.java b/src/main/java/org/scijava/display/event/window/WinClosingEvent.java index 331f82b72..ea6192681 100644 --- a/src/main/java/org/scijava/display/event/window/WinClosingEvent.java +++ b/src/main/java/org/scijava/display/event/window/WinClosingEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/window/WinDeactivatedEvent.java b/src/main/java/org/scijava/display/event/window/WinDeactivatedEvent.java index fbb144fe4..8a27cfdce 100644 --- a/src/main/java/org/scijava/display/event/window/WinDeactivatedEvent.java +++ b/src/main/java/org/scijava/display/event/window/WinDeactivatedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/window/WinDeiconifiedEvent.java b/src/main/java/org/scijava/display/event/window/WinDeiconifiedEvent.java index 12a830f82..b6f620918 100644 --- a/src/main/java/org/scijava/display/event/window/WinDeiconifiedEvent.java +++ b/src/main/java/org/scijava/display/event/window/WinDeiconifiedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/window/WinEvent.java b/src/main/java/org/scijava/display/event/window/WinEvent.java index ad9ad56ab..8190bfcde 100644 --- a/src/main/java/org/scijava/display/event/window/WinEvent.java +++ b/src/main/java/org/scijava/display/event/window/WinEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/window/WinIconifiedEvent.java b/src/main/java/org/scijava/display/event/window/WinIconifiedEvent.java index 8cede75c9..044182b2f 100644 --- a/src/main/java/org/scijava/display/event/window/WinIconifiedEvent.java +++ b/src/main/java/org/scijava/display/event/window/WinIconifiedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/display/event/window/WinOpenedEvent.java b/src/main/java/org/scijava/display/event/window/WinOpenedEvent.java index c2ffa1794..4f10240f0 100644 --- a/src/main/java/org/scijava/display/event/window/WinOpenedEvent.java +++ b/src/main/java/org/scijava/display/event/window/WinOpenedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/download/DefaultDownloadService.java b/src/main/java/org/scijava/download/DefaultDownloadService.java new file mode 100644 index 000000000..44c1b9aac --- /dev/null +++ b/src/main/java/org/scijava/download/DefaultDownloadService.java @@ -0,0 +1,193 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.download; + +import java.io.IOException; +import java.util.Date; + +import org.scijava.io.handle.DataHandle; +import org.scijava.io.handle.DataHandleService; +import org.scijava.io.handle.DataHandles; +import org.scijava.io.location.Location; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.service.AbstractService; +import org.scijava.service.Service; +import org.scijava.task.Task; +import org.scijava.task.TaskService; + +/** + * Default implementation of {@link DownloadService}. + * + * @author Curtis Rueden + */ +@Plugin(type = Service.class) +public class DefaultDownloadService extends AbstractService implements + DownloadService +{ + + @Parameter + private DataHandleService dataHandleService; + + @Parameter + private TaskService taskService; + + @Override + public Download download(final Location source, final Location destination) { + final Task task = taskService.createTask("Download"); + return new DefaultDownload(source, destination, task, () -> { + try (final DataHandle in = dataHandleService.create(source); + final DataHandle out = dataHandleService.create( + destination)) + { + task.setStatusMessage("Downloading " + source.getURI()); + DataHandles.copy(in, out, task); + } + catch (final IOException exc) { + // TODO: Improve error handling: + // 1. Consider a better exception handling design here. + // 2. Retry at least a few times if something goes wrong. + throw new RuntimeException(exc); + } + }); + } + + @Override + public Download download(final Location source, final Location destination, + final LocationCache cache) + { + if (cache == null || !cache.canCache(source)) { + // Caching this location is not supported. + return download(source, destination); + } + + final Task task = taskService.createTask("Download"); + return new DefaultDownload(source, destination, task, () -> { + final Location cached = cache.cachedLocation(source); + try ( + final DataHandle sourceHandle = dataHandleService.create(source); + final DataHandle cachedHandle = dataHandleService.create(cached); + final DataHandle destHandle = dataHandleService.create(destination) + ) + { + if (isCachedHandleValid(source, cache, sourceHandle, cachedHandle)) { + // The data is cached; download from the cached source instead. + task.setStatusMessage("Retrieving " + source.getURI()); + DataHandles.copy(cachedHandle, destHandle, task); + } + else { + // Data is not yet cached; write to the destination _and_ the cache. + task.setStatusMessage("Downloading + caching " + source.getURI()); + DataHandles.copy(sourceHandle, // + new MultiWriteHandle(cachedHandle, destHandle), task); + } + } + catch (final IOException exc) { + // TODO: Improve error handling: + // 1. Consider a better exception handling design here. + // 2. Retry at least a few times if something goes wrong. + throw new RuntimeException(exc); + } + }); + } + + // -- Helper methods -- + + private boolean isCachedHandleValid(final Location source, + final LocationCache cache, final DataHandle sourceHandle, + final DataHandle cachedHandle) throws IOException + { + if (!cachedHandle.exists()) return false; // No cached data is present. + + // Compare data lengths. + final long sourceLen = sourceHandle.length(); + final long cachedLen = cachedHandle.length(); + if (sourceLen >= 0 && cachedLen >= 0 && sourceLen != cachedLen) { + // Original and cached sources report different lengths; cache is invalid. + return false; + } + + // Compare last modified timestamps. + final Date sourceDate = sourceHandle.lastModified(); + final Date cachedDate = cachedHandle.lastModified(); + if (sourceDate != null && cachedDate != null && // + sourceDate.after(cachedDate)) + { + // Source was changed after cache was written; cache is invalid. + return false; + } + + // Compare checksums. + final String sourceChecksum = sourceHandle.checksum(); + final String cachedChecksum = cache.loadChecksum(source); + if (sourceChecksum != null && cachedChecksum != null && // + !sourceChecksum.equals(cachedChecksum)) + { + // Checksums do not match; cache is invalid. + return false; + } + + // Everything matched; we're all good. + return true; + } + + // -- Helper classes -- + + private class DefaultDownload implements Download { + + private Location source; + private Location destination; + private Task task; + + private DefaultDownload(final Location source, final Location destination, + final Task task, final Runnable r) + { + this.source = source; + this.destination = destination; + this.task = task; + task.run(r); + } + + @Override + public Location source() { + return source; + } + + @Override + public Location destination() { + return destination; + } + + @Override + public Task task() { + return task; + } + } +} diff --git a/src/main/java/org/scijava/download/DiskLocationCache.java b/src/main/java/org/scijava/download/DiskLocationCache.java new file mode 100644 index 000000000..ed576b4c4 --- /dev/null +++ b/src/main/java/org/scijava/download/DiskLocationCache.java @@ -0,0 +1,123 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.download; + +import java.io.File; +import java.io.IOException; + +import org.scijava.io.location.FileLocation; +import org.scijava.io.location.Location; +import org.scijava.util.DigestUtils; +import org.scijava.util.FileUtils; + +/** + * A file-based implementation of {@link LocationCache}. + * + * @author Curtis Rueden + */ +public class DiskLocationCache implements LocationCache { + + private File baseDir = new File(System.getProperty("user.home") + + File.separator + ".scijava" + File.separator + "cache" + File.separator); + + private boolean cacheFileLocations; + + // -- DiskLocationCache methods -- + + public File getBaseDirectory() { + return baseDir; + } + + public void setBaseDirectory(final File baseDir) { + if (!baseDir.isDirectory()) { + throw new IllegalArgumentException("Not a directory: " + baseDir); + } + this.baseDir = baseDir; + } + + public boolean isFileLocationCachingEnabled() { + return cacheFileLocations; + } + + public void setFileLocationCachingEnabled(final boolean enabled) { + // NB: It is possible the input file is stored on a volume which is much + // slower than the local disk cache, so we make this setting configurable. + cacheFileLocations = enabled; + } + + // -- LocationCache methods -- + + @Override + public boolean canCache(final Location source) { + if (source instanceof FileLocation && !isFileLocationCachingEnabled()) { + // The cache is not configured to cache files to other files. + return false; + } + return source.getURI() != null; + } + + @Override + public Location cachedLocation(final Location source) { + if (!canCache(source)) { + throw new IllegalArgumentException("Uncacheable source: " + source); + } + return new FileLocation(cachedData(source)); + } + + @Override + public String loadChecksum(final Location source) throws IOException { + final File cachedChecksum = cachedChecksum(source); + if (!cachedChecksum.exists()) return null; + return DigestUtils.string(FileUtils.readFile(cachedChecksum)); + } + + @Override + public void saveChecksum(final Location source, final String checksum) + throws IOException + { + final File cachedChecksum = cachedChecksum(source); + FileUtils.writeFile(cachedChecksum, DigestUtils.bytes(checksum)); + } + + // -- Helper methods -- + + private File cachedData(final Location source) { + return cachedFile(source, ".data"); + } + + private File cachedChecksum(final Location source) { + return cachedFile(source, ".checksum"); + } + + private File cachedFile(final Location source, final String suffix) { + final String hexCode = Integer.toHexString(source.hashCode()); + return new File(getBaseDirectory(), hexCode + suffix); + } +} diff --git a/src/main/java/org/scijava/download/Download.java b/src/main/java/org/scijava/download/Download.java new file mode 100644 index 000000000..f311000b4 --- /dev/null +++ b/src/main/java/org/scijava/download/Download.java @@ -0,0 +1,45 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.scijava.download; + +import org.scijava.io.location.Location; +import org.scijava.task.Task; + +/** + * Object representing an asynchronous download task. + * + * @author Curtis Rueden + * @see Task + */ +public interface Download { + + Location source(); + Location destination(); + Task task(); +} diff --git a/src/main/java/org/scijava/download/DownloadService.java b/src/main/java/org/scijava/download/DownloadService.java new file mode 100644 index 000000000..ecf576475 --- /dev/null +++ b/src/main/java/org/scijava/download/DownloadService.java @@ -0,0 +1,62 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.download; + +import org.scijava.io.location.Location; +import org.scijava.service.SciJavaService; + +/** + * Service for managing retrieval of remote resources. + * + * @author Curtis Rueden + */ +public interface DownloadService extends SciJavaService { + + /** + * Downloads data from the given source, storing it into the given + * destination. + * + * @param source The location of the needed data. + * @param destination The location where the needed data should be stored. + */ + Download download(Location source, Location destination); + + /** + * Downloads data from the given source, storing it into the given + * destination. + * + * @param source The location of the needed data. + * @param destination The location where the needed data should be stored. + * @param cache The cache from which already-downloaded data should be pulled + * preferentially, and to which newly-downloaded data should be + * stored for next time. + */ + Download download(Location source, Location destination, LocationCache cache); +} diff --git a/src/main/java/org/scijava/download/LocationCache.java b/src/main/java/org/scijava/download/LocationCache.java new file mode 100644 index 000000000..3a5f7c031 --- /dev/null +++ b/src/main/java/org/scijava/download/LocationCache.java @@ -0,0 +1,76 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.download; + +import java.io.IOException; + +import org.scijava.io.handle.DataHandle; +import org.scijava.io.location.Location; + +/** + * An object which knows how to convert a slow (typically remote) + * {@link Location} to a faster (typically local) one. + * + * @author Curtis Rueden + */ +public interface LocationCache { + + /** Gets whether the given location can be cached by this cache. */ + boolean canCache(Location source); + + /** + * Gets the cache location of a given data source. + * + * @return A {@link Location} where the source data is, or would be, cached. + * @throws IllegalArgumentException if the given source cannot be cached (see + * {@link #canCache}). + */ + Location cachedLocation(Location source); + + /** + * Loads the checksum value which corresponds to the cached location. + * + * @param source The source location for which the cached checksum is desired. + * @return The loaded checksum, or null if one is not available. + * @see DataHandle#checksum() + * @throws IOException If something goes wrong accessing the checksum. + */ + String loadChecksum(Location source) throws IOException; + + /** + * Associates the given checksum value with the specified source location. + * + * @param source The source location for which the checksum should be cached. + * @param checksum The checksum value to cache. + * @see DataHandle#checksum() + * @throws IOException If something goes wrong caching the checksum. + */ + void saveChecksum(Location source, String checksum) throws IOException; +} diff --git a/src/main/java/org/scijava/download/MultiWriteHandle.java b/src/main/java/org/scijava/download/MultiWriteHandle.java new file mode 100644 index 000000000..4dd40b874 --- /dev/null +++ b/src/main/java/org/scijava/download/MultiWriteHandle.java @@ -0,0 +1,155 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.scijava.download; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import org.scijava.io.handle.AbstractDataHandle; +import org.scijava.io.handle.DataHandle; +import org.scijava.io.location.Location; + +/** + * {@link DataHandle} plugin for writing to multiple {@link DataHandle}s. + * + * @author Curtis Rueden + */ +public class MultiWriteHandle extends AbstractDataHandle { + + private final List> handles; + + public MultiWriteHandle(final DataHandle... handles) { + this.handles = new ArrayList<>(Arrays.asList(handles)); + } + + // -- DataHandle methods -- + + @Override + public boolean isReadable() { + return false; + } + + @Override + public boolean isWritable() { + boolean writable = true; + // NB: Somewhat arbitrarily, we are writable iff all our constituents are. + for (final DataHandle h : handles) + writable &= h.isWritable(); + return writable; + } + + @Override + public boolean exists() throws IOException { + boolean exists = true; + // NB: Somewhat arbitrarily, we exist iff any of our constituents exist. + for (final DataHandle h : handles) + exists |= h.isWritable(); + return exists; + } + + @Override + public Date lastModified() throws IOException { + for (final DataHandle h : handles) { + final Date lastModified = h.lastModified(); + if (lastModified != null) return lastModified; + } + return null; + } + + @Override + public String checksum() throws IOException { + for (final DataHandle h : handles) { + final String checksum = h.checksum(); + if (checksum != null) return checksum; + } + return null; + } + + @Override + public long offset() throws IOException { + return handles.get(0).offset(); + } + + @Override + public void seek(long pos) throws IOException { + // TODO: parallelStream().forEach() for performance. + for (final DataHandle h : handles) + h.seek(pos); + } + + @Override + public long length() throws IOException { + return handles.get(0).length(); + } + + @Override + public void setLength(long length) throws IOException { + for (final DataHandle h : handles) + h.setLength(length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public Class getType() { + return null; + } + + @Override + public byte readByte() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void write(final int b) throws IOException { + // TODO: parallelStream().forEach() for performance. + for (final DataHandle h : handles) + h.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + // TODO: parallelStream().forEach() for performance. + for (final DataHandle h : handles) + h.write(b, off, len); + } + + @Override + public void close() throws IOException { + // TODO: parallelStream().forEach() for performance. + for (final DataHandle h : handles) + h.close(); + } +} diff --git a/src/main/java/org/scijava/event/ContextCreatedEvent.java b/src/main/java/org/scijava/event/ContextCreatedEvent.java new file mode 100644 index 000000000..c279e9a7e --- /dev/null +++ b/src/main/java/org/scijava/event/ContextCreatedEvent.java @@ -0,0 +1,38 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.event; + +/** + * Event to be published immediately after a context has been fully created + * with all services initialized. + * + * @author Curtis Rueden + */ +public class ContextCreatedEvent extends SciJavaEvent { } diff --git a/src/main/java/org/scijava/event/ContextDisposingEvent.java b/src/main/java/org/scijava/event/ContextDisposingEvent.java index 0dc1aaf34..570c0974c 100644 --- a/src/main/java/org/scijava/event/ContextDisposingEvent.java +++ b/src/main/java/org/scijava/event/ContextDisposingEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -34,6 +32,6 @@ /** * Event to be published just before disposing a context. * - * @author Johannes Schindein + * @author Johannes Schindelin */ public class ContextDisposingEvent extends SciJavaEvent { } diff --git a/src/main/java/org/scijava/event/DefaultEventBus.java b/src/main/java/org/scijava/event/DefaultEventBus.java index 4ebb70883..5587f6d59 100644 --- a/src/main/java/org/scijava/event/DefaultEventBus.java +++ b/src/main/java/org/scijava/event/DefaultEventBus.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -36,18 +34,17 @@ import java.util.Arrays; import java.util.List; -import org.bushe.swing.event.CleanupEvent; -import org.bushe.swing.event.ThreadSafeEventService; +import org.scijava.event.bushe.ThreadSafeEventService; import org.scijava.log.LogService; import org.scijava.service.Service; import org.scijava.thread.ThreadService; /** - * An {@link org.bushe.swing.event.EventService} implementation for SciJava. + * An {@code org.scijava.event.bushe.EventService} implementation for SciJava. *

* It is called "DefaultEventBus" rather than "DefaultEventService" to avoid a * name clash with {@link DefaultEventService}, which is not an - * {@link org.bushe.swing.event.EventService} but rather a SciJava + * {@code org.scijava.event.bushe.EventService} but rather a SciJava * {@link Service} implementation. *

* @@ -61,7 +58,7 @@ public class DefaultEventBus extends ThreadSafeEventService { public DefaultEventBus(final ThreadService threadService, final LogService log) { - super(200L, false, null, null, null); + super(200L, null, null, null); this.threadService = threadService; this.log = log; } @@ -114,37 +111,10 @@ public void publishLater(final String topicName, final Object eventObj) { getVetoEventListeners(topicName), null); } - // -- org.bushe.swing.event.EventService methods -- + // -- org.scijava.event.bushe.EventService methods -- @Override public void publish(final Object event) { - // HACK: Work around a deadlock problem caused by ThreadSafeEventService: - - // 1) The ThreadSafeEventService superclass has a special cleanup thread - // that takes care of cleaning up stale references. Every time it runs, it - // publishes some CleanupEvents using publish(Object) to announce that this - // is occurring. Normally, such publication delegates to - // publishNow, which calls ThreadService#invoke, which calls - // EventQueue.invokeAndWait, which queues the publication for execution on - // the EDT and then blocks until publication is complete. - - // 2) When the ThreadSafeEventService publishes the CleanupEvents, it does - // so inside a synchronized block that locks on a "listenerLock" object. - - // 3) Unfortunately, since the CleanupEvent publication is merely *queued*, - // any other pending operations on the EDT happen first. If one such - // operation meanwhile calls e.g. - // ThreadSafeEventService#getSubscribers(Class), it will deadlock because - // those getter methods are also synchronized on the listenerLock object. - - // Hence, our hack workaround is to instead use publishLater for the - // CleanupEvents, since no one really cares about them anyway. ;-) - - if (event instanceof CleanupEvent) { - publishLater(event); - return; - } - publishNow(event); } diff --git a/src/main/java/org/scijava/event/DefaultEventHistory.java b/src/main/java/org/scijava/event/DefaultEventHistory.java index 4b468cd09..4b94b8bc3 100644 --- a/src/main/java/org/scijava/event/DefaultEventHistory.java +++ b/src/main/java/org/scijava/event/DefaultEventHistory.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/event/DefaultEventService.java b/src/main/java/org/scijava/event/DefaultEventService.java index f7e924b46..3eca42db0 100644 --- a/src/main/java/org/scijava/event/DefaultEventService.java +++ b/src/main/java/org/scijava/event/DefaultEventService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -42,10 +40,9 @@ import java.util.Map; import java.util.WeakHashMap; -import org.bushe.swing.event.annotation.AbstractProxySubscriber; -import org.bushe.swing.event.annotation.BaseProxySubscriber; -import org.bushe.swing.event.annotation.ReferenceStrength; import org.scijava.Priority; +import org.scijava.event.bushe.AbstractProxySubscriber; +import org.scijava.event.bushe.ReferenceStrength; import org.scijava.log.LogService; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; @@ -73,7 +70,7 @@ public class DefaultEventService extends AbstractService implements * {@code priority = DefaultEventService.PRIORITY + 1} or similar. *

*/ - public static final double PRIORITY = 10 * Priority.VERY_HIGH_PRIORITY; + public static final double PRIORITY = 10 * Priority.VERY_HIGH; @Parameter private LogService log; @@ -141,6 +138,11 @@ public List> subscribe(final Object o) { return subscribers; } + @Override + public void subscribe(final EventSubscriber subscriber) { + eventBus.subscribe(subscriber.getEventClass(), subscriber); + } + @Override public void unsubscribe(final Collection> subscribers) { for (final EventSubscriber subscriber : subscribers) { @@ -264,9 +266,10 @@ private synchronized void keepIt(final Object o, final ProxySubscriber subscr /** * Helper class used by {@link #subscribe(Object)}. *

- * Recapitulates some logic from {@link BaseProxySubscriber}, because that - * class implements {@link org.bushe.swing.event.EventSubscriber} as a raw - * type, which is incompatible with this class implementing SciJava's + * Recapitulates some logic from + * {@code org.scijava.event.bushe.BaseProxySubscriber}, because that class + * implements {@link org.scijava.event.bushe.EventSubscriber} as a raw type, + * which is incompatible with this class implementing SciJava's * {@link EventSubscriber} as a typed interface; it becomes impossible to * implement both {@code onEvent(Object)} and {@code onEvent(E)}. *

diff --git a/src/main/java/org/scijava/event/EventDetails.java b/src/main/java/org/scijava/event/EventDetails.java index 3336f4da9..5e9344b24 100644 --- a/src/main/java/org/scijava/event/EventDetails.java +++ b/src/main/java/org/scijava/event/EventDetails.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/event/EventHandler.java b/src/main/java/org/scijava/event/EventHandler.java index fc549034e..fa59a2a50 100644 --- a/src/main/java/org/scijava/event/EventHandler.java +++ b/src/main/java/org/scijava/event/EventHandler.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -42,15 +40,15 @@ * handling methods and annotating each with @{@link EventHandler}. *

* Note to developers: This annotation serves exactly the same purpose as - * EventBus's {@link org.bushe.swing.event.annotation.EventSubscriber} - * annotation, recapitulating a subset of the same functionality. We do this to - * avoid third party code depending directly on EventBus. That is, we do not - * wish to require SciJava developers to {@code import org.bushe.swing.event.*} - * or similar. In this way, EventBus is isolated as only a transitive dependency - * of downstream code, rather than a direct dependency. Unfortunately, because - * Java annotation interfaces cannot utilize inheritance, we have to - * recapitulate the functionality rather than extend it (as we are able to do - * with {@link EventSubscriber}). + * EventBus's {@code org.scijava.event.bushe.EventSubscriber} annotation, + * recapitulating a subset of the same functionality. We do this to avoid third + * party code depending directly on EventBus. That is, we do not wish to require + * SciJava developers to {@code import org.scijava.event.bushe.*} or similar. In + * this way, EventBus is isolated as only a transitive dependency of downstream + * code, rather than a direct dependency. Unfortunately, because Java annotation + * interfaces cannot utilize inheritance, we have to recapitulate the + * functionality rather than extend it (as we are able to do with + * {@link EventSubscriber}). *

* * @author Curtis Rueden diff --git a/src/main/java/org/scijava/event/EventHistory.java b/src/main/java/org/scijava/event/EventHistory.java index 706ac59f3..7caaaf022 100644 --- a/src/main/java/org/scijava/event/EventHistory.java +++ b/src/main/java/org/scijava/event/EventHistory.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/event/EventHistoryListener.java b/src/main/java/org/scijava/event/EventHistoryListener.java index a5426bb76..30a305c83 100644 --- a/src/main/java/org/scijava/event/EventHistoryListener.java +++ b/src/main/java/org/scijava/event/EventHistoryListener.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/event/EventService.java b/src/main/java/org/scijava/event/EventService.java index efe3b1179..1b0907f7a 100644 --- a/src/main/java/org/scijava/event/EventService.java +++ b/src/main/java/org/scijava/event/EventService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -127,6 +125,24 @@ public interface EventService extends SciJavaService { */ List> subscribe(Object o); + /** + * Subscribes the given {@link EventSubscriber} to its associated event class. + * Its {@link EventSubscriber#onEvent} method will be called whenever an event + * of the matching type is published. + *

+ * Important note: The event service does not keep a + * strong reference to the subscriber! If you use this method, you are also + * responsible for keeping a reference to the subscriber, or else it is likely + * to be garbage collected, and thus no longer respond to events as intended. + * One simple way to force a strong reference to exist is to add it to + * SciJava's {@link org.scijava.object.ObjectService} via + * {@link org.scijava.object.ObjectService#addObject}. + *

+ * + * @param subscriber the event subscriber to register + */ + void subscribe(EventSubscriber subscriber); + /** * Removes all the given subscribers; they will no longer be notified when * events are published. diff --git a/src/main/java/org/scijava/event/EventSubscriber.java b/src/main/java/org/scijava/event/EventSubscriber.java index 79c328cf6..9dcd25d94 100644 --- a/src/main/java/org/scijava/event/EventSubscriber.java +++ b/src/main/java/org/scijava/event/EventSubscriber.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -46,7 +44,7 @@ * @param Type of event for which to listen */ public interface EventSubscriber extends - org.bushe.swing.event.EventSubscriber + org.scijava.event.bushe.EventSubscriber { @Override diff --git a/src/main/java/org/scijava/event/SciJavaEvent.java b/src/main/java/org/scijava/event/SciJavaEvent.java index e644763e8..4e51d775f 100644 --- a/src/main/java/org/scijava/event/SciJavaEvent.java +++ b/src/main/java/org/scijava/event/SciJavaEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -32,6 +30,7 @@ package org.scijava.event; import org.scijava.AbstractContextual; +import org.scijava.util.DebugUtils; /** * Base class for all SciJava events. @@ -82,6 +81,14 @@ public StackTraceElement[] getStackTrace() { return stackTrace; } + /** + * Gets a stack trace for the calling thread when the event was published. + * This method is useful for debugging what triggered an event. + */ + public String dumpStack() { + return DebugUtils.getStackDump(getCallingThread(), getStackTrace()); + } + // Object methods -- @Override diff --git a/src/main/java/org/scijava/event/bushe/AbstractProxySubscriber.java b/src/main/java/org/scijava/event/bushe/AbstractProxySubscriber.java new file mode 100644 index 000000000..bd745a92c --- /dev/null +++ b/src/main/java/org/scijava/event/bushe/AbstractProxySubscriber.java @@ -0,0 +1,173 @@ +package org.scijava.event.bushe; + +import java.lang.ref.WeakReference; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Common base class for EventService Proxies. + *

+ * Implementing Prioritized even when Priority is not used is always OK. The default + * value of 0 retains the FIFO order. + */ +public abstract class AbstractProxySubscriber implements ProxySubscriber, Prioritized { + private Object proxiedSubscriber; + private Method subscriptionMethod; + private ReferenceStrength referenceStrength; + private EventService eventService; + private int priority; + protected boolean veto; + + protected AbstractProxySubscriber(Object proxiedSubscriber, Method subscriptionMethod, + ReferenceStrength referenceStrength, EventService es, boolean veto) { + this(proxiedSubscriber, subscriptionMethod, referenceStrength, 0, es, veto); + } + + protected AbstractProxySubscriber(Object proxiedSubscriber, Method subscriptionMethod, + ReferenceStrength referenceStrength, int priority, EventService es, boolean veto) { + this.referenceStrength = referenceStrength; + this.priority = priority; + eventService = es; + this.veto = veto; + if (proxiedSubscriber == null) { + throw new IllegalArgumentException("The realSubscriber cannot be null when constructing a proxy subscriber."); + } + if (subscriptionMethod == null) { + throw new IllegalArgumentException("The subscriptionMethod cannot be null when constructing a proxy subscriber."); + } + Class returnType = subscriptionMethod.getReturnType(); + if (veto && returnType != Boolean.TYPE) { + throw new IllegalArgumentException("The subscriptionMethod must have the two parameters, the first one must be a String and the second a non-primitive (Object or derivative)."); + } + if (ReferenceStrength.WEAK.equals(referenceStrength)) { + this.proxiedSubscriber = new WeakReference(proxiedSubscriber); + } else { + this.proxiedSubscriber = proxiedSubscriber; + } + this.subscriptionMethod = subscriptionMethod; + } + + /** @return the object this proxy is subscribed on behalf of */ + public Object getProxiedSubscriber() { + if (proxiedSubscriber instanceof WeakReference) { + return ((WeakReference)proxiedSubscriber).get(); + } + return proxiedSubscriber; + } + + /** @return the subscriptionMethod passed in the constructor */ + public Method getSubscriptionMethod() { + return subscriptionMethod; + } + + /** @return the EventService passed in the constructor */ + public EventService getEventService() { + return eventService; + } + + /** @return the ReferenceStrength passed in the constructor */ + public ReferenceStrength getReferenceStrength() { + return referenceStrength; + } + + /** + * @return the priority, no effect if priority is 0 (the default value) + */ + public int getPriority() { + return priority; + } + + /** + * Called by EventServices to inform the proxy that it is unsubscribed. + * The ProxySubscriber should perform any necessary cleanup. + *

+ * Overriding classes must call super.proxyUnsubscribed() or risk + * things not being cleanup up properly. + */ + public void proxyUnsubscribed() { + proxiedSubscriber = null; + } + + @Override + public final int hashCode() { + throw new RuntimeException("Proxy subscribers are not allowed in Hash " + + "Maps, since the underlying values use Weak References that" + + "may disappear, the calculations may not be the same in" + + "successive calls as required by hashCode."); + } + + protected boolean retryReflectiveCallUsingAccessibleObject(Object[] args, Method subscriptionMethod, Object obj, + IllegalAccessException e, String message) { + boolean accessibleTriedAndFailed = false; + if (subscriptionMethod != null) { + AccessibleObject[] accessibleMethod = {subscriptionMethod}; + try { + AccessibleObject.setAccessible(accessibleMethod, true); + } catch (SecurityException ex) { + // SecurityManager (Java 8 and earlier) denied setAccessible + accessibleTriedAndFailed = true; + } catch (RuntimeException ex) { + // InaccessibleObjectException (Java 9+) or similar denied setAccessible + accessibleTriedAndFailed = true; + } + if (!accessibleTriedAndFailed) { + try { + Object returnValue = subscriptionMethod.invoke(obj, args); + return Boolean.valueOf(returnValue+""); + } catch (InvocationTargetException e1) { + throw new RuntimeException(message, e); + } catch (IllegalAccessException e1) { + throw new RuntimeException(message, e); + } + } + } + if (accessibleTriedAndFailed) { + message = message + ". An attempt was made to make the method accessible, but access was denied."; + } + throw new RuntimeException(message, e); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof AbstractProxySubscriber) { + AbstractProxySubscriber bps = (AbstractProxySubscriber) obj; + if (referenceStrength != bps.referenceStrength) { + return false; + } + if (subscriptionMethod != bps.subscriptionMethod) { + return false; + } + if (ReferenceStrength.WEAK == referenceStrength) { + if (((WeakReference)proxiedSubscriber).get() != ((WeakReference)bps.proxiedSubscriber).get()) { + return false; + } + } else { + if (proxiedSubscriber != bps.proxiedSubscriber) { + return false; + } + } + if (veto != bps.veto) { + return false; + } + if (eventService != bps.eventService) { + return false; + } + return true; + } else { + return false; + } + } + + @Override + public String toString() { + return "AbstractProxySubscriber{" + + "realSubscriber=" + (proxiedSubscriber instanceof WeakReference? + ((WeakReference)proxiedSubscriber).get():proxiedSubscriber) + + ", subscriptionMethod=" + subscriptionMethod + + ", veto=" + veto + + ", referenceStrength=" + referenceStrength + + ", eventService=" + eventService + + '}'; + } +} diff --git a/src/main/java/org/scijava/event/bushe/EventService.java b/src/main/java/org/scijava/event/bushe/EventService.java new file mode 100644 index 000000000..c2d41e9bb --- /dev/null +++ b/src/main/java/org/scijava/event/bushe/EventService.java @@ -0,0 +1,988 @@ +/** + * Copyright 2005 Bushe Enterprises, Inc., Hopkinton, MA, USA, www.bushe.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.scijava.event.bushe; + +import java.util.List; +import java.util.regex.Pattern; +import java.lang.reflect.Type; + +/** + * The core interface. An EventService provides publish/subscribe services to a single JVM using Class-based and + * String-based (i.e. "topic") publications and subscriptions. + *

+ * In class-based pub/sub, {@link EventSubscriber}s subscribe to a type on an {@link EventService}, such + * as the {@link org.scijava.event.bushe.EventBus}, by providing a class, interface or generic type. The EventService + * notifies subscribers when objects are published on the EventService with a matching type. Full class semantics are + * respected. That is, if a subscriber subscribes to a class, the subscriber is notified if an object of + * that class is publish or if an object of a subclass of that class is published. Likewise if a subscriber subscribes + * to an interface, it will be notified if any object that implements that interface is published. Subscribers can + * subscribe "exactly" using {@link #subscribeExactly(Class, EventSubscriber)} so that they are notified only if an + * object of the exact class is published (and will not be notified if subclasses are published, since this would not + * be "exact") + *

+ *

+ * In topic-based pub/sub, an object "payload" is published on a topic name (String). {@link EventTopicSubscriber}s subscribe + * to either the exact name of the topic or they may subscribe using a Regular Expression that is used to match topic + * names. + *

+ *

+ * See the overview for an general introduction + * and package documentation for usage details and examples. + *

+ *

+ * A single subscriber cannot subscribe more than once to an event or topic name. EventService implementations should + * handle double-subscription requests by returning false on subscribe(). A single EventSubscriber can subscribe to more + * than one event class, and a single EventTopicSubscriber can subscribe to more than one topic name or pattern. A + * single object may implement both EventSubscriber and EventTopicSubscriber interfaces. Subscribers are guaranteed to + * only be called for the classes and/or topic names they subscribe to. If a subscriber subscribes to a topic and to a + * regular expression that matches the topic name, this is considered two different subscriptions and the subscriber + * will be called twice for the publication on the topic. Similarly, if a subscriber subscribes to a class and its + * subclasses using subscribe() and again to a class of the same type using subscribeExactly(), this is considered two + * different subscriptions and the subscriber will be called twice for the publication for a single event of the exact + * type. + *

+ *

+ * By default the EventService only holds WeakReferences to subscribers. If a subscriber has no references to it, then + * it can be garbage collected. This avoids memory leaks in exchange for the risk of accidentally adding a listener and + * have it disappear unexpectedly. If you want to subscribe a subscriber that will have no other reference to it, then + * use one of the subscribeStrongly() methods, which will prevent garbage collection. + *

+ *

+ * Unless garbage collected, EventSubscribers will remain subscribed until they are passed to one of the unsubscribe() + * methods with the event class or topic name to which there are subscribed. + *

+ *

+ * Subscribers are called in the order in which they are subscribed by default (FIFO), unless subscribers implement + * {@link Prioritized}. Those subscribers that implement Prioritized and return a negative priority are moved to the + * front of the list (the more negative, the more to the front). Those subscribers that implement Prioritized and return + * a positive priority are moved to the end of the list (the more positive, the more to the back). The FIFO guarantee + * is only valid for the same subscribe() call. That is, the order of two subscribers, one to List.class and the other + * to ArrayList.class is not guaranteed to be in the order of subscription when an ArrayList is published. The same is + * true for topic subscribers when using RegEx expressions - when "Foo" is published, the order of subscribers that are + * subscribed to "Foo", "Fo*" and "F*" are not guaranteed, though the second "Fo*" subscriber will never be called + * before the first "Fo*" subscriber (ditto List and ArrayList). Prioritized subscribers are always guaranteed to be in + * the order of priority, no matter the call or the resulting mix of subscribers. All ordering rules apply to all + * types subscribers: class, topic, pattern, veto, etc. For Swing users, note that FIFO is + * the opposite of Swing, where event listeners are called in the reverse order of when they were subscribed (FILO). + *

+ *

+ * Publication on a class or topic name can be vetoed by a {@link VetoEventListener}. All VetoEventListeners are checked + * before any EventSubscribers or EventTopicSubscribers are called. This is unlike the JavaBean's + * VetoPropertyEventListener which can leave side effects and half-propogated events. VetoEventListeners are subscribed + * in the same manner as EventSubscribers and EventTopicSubscribers. + *

+ *

+ * The state of a published event can be tracked if an event or a topic's payload object implements the + * {@link org.scijava.event.bushe.PublicationStatus} interface. EventServices are required to set such objects' + * {@link org.scijava.event.bushe.PublicationStatus} at the appropriate times during publication. + *

+*

+ * This simple example prints "Hello World" + *

+ * EventService eventService = new ThreadSafeEventService();
+ * //Create a subscriber
+ * EventTopicSubscriber subscriber = new EventTopicSubscriber() {
+ *    public void onEvent(String topic, Object event) {
+ *        System.out.println(topic+" "+event);
+ *    }
+ * });
+ * eventService.subscribe("Hello", subscriber);
+ * eventService.publish("Hello", "World");
+ * System.out.println(subscriber + " Since the reference is used after it is subscribed, it doesn't get garbage collected, this is not necessary if you use subscribeStrongly()");
+ * 
+ *

+ *

+ * Events and/or topic data can be cached, but are not by default. To cache events or topic data, call + * {@link #setDefaultCacheSizePerClassOrTopic(int)}, {@link #setCacheSizeForEventClass(Class, int)}, or + * {@link #setCacheSizeForTopic(String, int)}, {@link #setCacheSizeForTopic(Pattern, int)}. Retrieve cached values + * with {@link #getLastEvent(Class)}, {@link #getLastTopicData(String)}, {@link #getCachedEvents(Class)}, or + * {@link #getCachedTopicData(String)}. Using caching while subscribing is most likely to make sense only if you + * subscribe and publish on the same thread (so caching is very useful for Swing applications since both happen on + * the EDT in a single-threaded manner). In multithreaded applications, you never know if your subscriber has handled + * an event while it was being subscribed (before the subscribe() method returned) that is newer or older than the + * retrieved cached value (taken before or after subscribe() respectively). + *

+ *

+ * There is nothing special about the term "Event," this could just as easily be called a "Message" Service, this term + * is already taken by the JMS, which is similar, but is used across processes and networks. + *

+ * + * @author Michael Bushe michael@bushe.com + * @see {@link ThreadSafeEventService} for the default implementation + */ +interface EventService { + + /** + * Publishes an object so that subscribers will be notified if they subscribed to the object's class, one of its + * subclasses, or to one of the interfaces it implements. + * + * @param event the object to publish + */ + public void publish(Object event); + + /** + * Use this method to publish generified objects to subscribers of Types, i.e. subscribers that use + * {@link #subscribe(Type, EventSubscriber)}, and to publish to subscribers of the non-generic type. + *

+ * Due to generic type erasure, the type must be supplied by the caller. You can get a declared object's + * type by using the {@link org.scijava.event.bushe.TypeReference} class. For Example: + *

+ *
+    * TypeReference<List<Trade>> subscribingTypeReference = new TypeReference<List<Trade>>(){};
+    * EventBus.subscribe(subscribingTypeReference.getType(), mySubscriber);
+    * EventBus.subscribe(List.class, thisSubscriberWillGetCalledToo);
+    * ...
+    * //Likely in some other class
+    * TypeReference<List<Trade>> publishingTypeReference = new TypeReference<List<Trade>>(){};
+    * List<Trade> trades = new ArrayList<Trade>();
+    * EventBus.publish(publishingTypeReference.getType(), trades);
+    * trades.add(trade);
+    * EventBus.publish(publishingTypeReference.getType(), trades);
+    * 
+ * @param genericType the generified type of the published object. + * @param event The event that occurred + */ + public void publish(Type genericType, Object event); + + /** + * Publishes an object on a topic name so that all subscribers to that name or a Regular Expression that matches + * the topic name will be notified. + * + * @param topic The name of the topic subscribed to + * @param o the object to publish + */ + public void publish(String topic, Object o); + + /** + * Subscribes an EventSubscriber to the publication of objects matching a type. Only a WeakReference to + * the subscriber is held by the EventService. + *

+ * Subscribing to a class means the subscriber will be called when objects of that class are published, when + * objects of subclasses of the class are published, when objects implementing any of the interfaces of the + * class are published, or when generic types are published with the class' raw type. + *

+ *

+ * Subscription is weak by default to avoid having to call unsubscribe(), and to avoid the memory leaks that would + * occur if unsubscribe was not called. The service will respect the WeakReference semantics. In other words, if + * the subscriber has not been garbage collected, then onEvent(Object) will be called normally. If the hard + * reference has been garbage collected, the service will unsubscribe it's WeakReference. + *

+ *

+ * It's allowable to call unsubscribe() with the same EventSubscriber hard reference to stop a subscription + * immediately. + *

+ *

+ * The service will create the WeakReference on behalf of the caller. + *

+ * + * @param eventClass the class of published objects to subscriber listen to + * @param subscriber The subscriber that will accept the events of the event class when published. + * + * @return true if the subscriber was subscribed successfully, false otherwise + */ + public boolean subscribe(Class eventClass, EventSubscriber subscriber); + + /** + * Subscribe an EventSubscriber to publication of generic Types. + * Subscribers will only be notified for publications using {@link #publish(java.lang.reflect.Type, Object)}. + *

+ * Due to generic type erasure, the type must be supplied by the publisher. You can get a declared object's + * type by using the {@link org.scijava.event.bushe.TypeReference} class. For Example: + *

+ *
+   * TypeReference<List<Trade>> subscribingTypeReference = new TypeReference<List<Trade>>(){};
+   * EventBus.subscribe(subscribingTypeReference.getType(), mySubscriber);
+   * EventBus.subscribe(List.class, thisSubscriberWillGetCalledToo);
+   * ...
+   * //Likely in some other class
+   * TypeReference<List<Trade>> publishingTypeReference = new TypeReference<List<Trade>>(){};
+   * List<Trade> trades = new ArrayList<Trade>();
+   * EventBus.publish(publishingTypeReference.getType(), trades);
+   * trades.add(trade);
+   * EventBus.publish(publishingTypeReference.getType(), trades);
+   * 
+ * @param type the generic type to subscribe to + * @param subscriber the subscriber to the type + * @return true if a new subscription is made, false if it already existed + */ + public boolean subscribe(Type type, EventSubscriber subscriber); + + /** + * Subscribes an EventSubscriber to the publication of objects exactly matching a type. Only a WeakReference + * to the subscriber is held by the EventService. + *

+ * Subscription is weak by default to avoid having to call unsubscribe(), and to avoid the memory leaks that would + * occur if unsubscribe was not called. The service will respect the WeakReference semantics. In other words, if + * the subscriber has not been garbage collected, then the onEvent will be called normally. If the hard reference + * has been garbage collected, the service will unsubscribe it's WeakReference. + *

+ *

+ * It's allowable to call unsubscribe() with the same EventSubscriber hard reference to stop a subscription + * immediately. + *

+ *

+ * The service will create the WeakReference on behalf of the caller. + *

+ * + * @param eventClass the class of published objects to listen to + * @param subscriber The subscriber that will accept the events when published. + * + * @return true if the subscriber was subscribed successfully, false otherwise + */ + public boolean subscribeExactly(Class eventClass, EventSubscriber subscriber); + + /** + * Subscribes an EventTopicSubscriber to the publication of a topic name. Only a WeakReference + * to the subscriber is held by the EventService. + *

+ * Subscription is weak by default to avoid having to call unsubscribe(), and to avoid the memory leaks that would + * occur if unsubscribe was not called. The service will respect the WeakReference semantics. In other words, if + * the subscriber has not been garbage collected, then the onEvent will be called normally. If the hard reference + * has been garbage collected, the service will unsubscribe it's WeakReference. + *

+ *

+ * It's allowable to call unsubscribe() with the same EventSubscriber hard reference to stop a subscription + * immediately. + *

+ * + * @param topic the name of the topic listened to + * @param subscriber The topic subscriber that will accept the events when published. + * + * @return true if the subscriber was subscribed successfully, false otherwise + */ + public boolean subscribe(String topic, EventTopicSubscriber subscriber); + + /** + * Subscribes an EventSubscriber to the publication of all the topic names that match a RegEx Pattern. Only a + * WeakReference to the subscriber is held by the EventService. + *

+ * Subscription is weak by default to avoid having to call unsubscribe(), and to avoid the memory leaks that would + * occur if unsubscribe was not called. The service will respect the WeakReference semantics. In other words, if + * the subscriber has not been garbage collected, then the onEvent will be called normally. If the hard reference + * has been garbage collected, the service will unsubscribe it's WeakReference. + *

+ *

+ * It's allowable to call unsubscribe() with the same EventSubscriber hard reference to stop a subscription + * immediately. + *

+ * + * @param topicPattern pattern that matches to the name of the topic published to + * @param subscriber The topic subscriber that will accept the events when published. + * + * @return true if the subscriber was subscribed successfully, false otherwise + */ + public boolean subscribe(Pattern topicPattern, EventTopicSubscriber subscriber); + + /** + * Subscribes an EventSubscriber to the publication of objects matching a type. + *

+ * The semantics are the same as {@link #subscribe(Class, EventSubscriber)}, except that the EventService holds + * a regularly reference, not a WeakReference. + *

+ *

+ * The subscriber will remain subscribed until {@link #unsubscribe(Class,EventSubscriber)} is called. + *

+ * + * @param eventClass the class of published objects to listen to + * @param subscriber The subscriber that will accept the events when published. + * + * @return true if the subscriber was subscribed successfully, false otherwise + */ + public boolean subscribeStrongly(Class eventClass, EventSubscriber subscriber); + + /** + * Subscribes an EventSubscriber to the publication of objects matching a type exactly. + *

+ * The semantics are the same as {@link #subscribeExactly(Class, EventSubscriber)}, except that the EventService + * holds a regularly reference, not a WeakReference. + *

+ *

+ * The subscriber will remain subscribed until {@link #unsubscribe(Class,EventSubscriber)} is called. + *

+ * + * @param eventClass the class of published objects to listen to + * @param subscriber The subscriber that will accept the events when published. + * + * @return true if the subscriber was subscribed successfully, false otherwise + */ + public boolean subscribeExactlyStrongly(Class eventClass, EventSubscriber subscriber); + + /** + * Subscribes a subscriber to an event topic name. + *

+ * The semantics are the same as {@link #subscribe(String, EventTopicSubscriber)}, except that the EventService + * holds a regularly reference, not a WeakReference. + *

+ *

+ * The subscriber will remain subscribed until {@link #unsubscribe(String,EventTopicSubscriber)} is called. + *

+ * + * @param topic the name of the topic listened to + * @param subscriber The topic subscriber that will accept the events when published. + * + * @return true if the subscriber was subscribed successfully, false otherwise + */ + public boolean subscribeStrongly(String topic, EventTopicSubscriber subscriber); + + /** + * Subscribes a subscriber to all the event topic names that match a RegEx expression. + *

+ * The semantics are the same as {@link #subscribe(java.util.regex.Pattern, EventTopicSubscriber)}, except that the + * EventService holds a regularly reference, not a WeakReference. + *

+ *

+ * The subscriber will remain subscribed until {@link #unsubscribe(String,EventTopicSubscriber)} is called. + *

+ * + * @param topicPattern the name of the topic listened to + * @param subscriber The topic subscriber that will accept the events when published. + * + * @return true if the subscriber was subscribed successfully, false otherwise + */ + public boolean subscribeStrongly(Pattern topicPattern, EventTopicSubscriber subscriber); + + /** + * Stop the subscription for a subscriber that is subscribed to a class. + * + * @param eventClass the class of published objects to listen to + * @param subscriber The subscriber that is subscribed to the event. The same reference as the one subscribed. + * + * @return true if the subscriber was subscribed to the event, false if it wasn't + */ + public boolean unsubscribe(Class eventClass, EventSubscriber subscriber); + + /** + * Stop the subscription for a subscriber that is subscribed to an exact class. + * + * @param eventClass the class of published objects to listen to + * @param subscriber The subscriber that is subscribed to the event. The same reference as the one subscribed. + * + * @return true if the subscriber was subscribed to the event, false if it wasn't + */ + public boolean unsubscribeExactly(Class eventClass, EventSubscriber subscriber); + + /** + * Stop the subscription for a subscriber that is subscribed to an event topic. + * + * @param topic the topic listened to + * @param subscriber The subscriber that is subscribed to the topic. The same reference as the one subscribed. + * + * @return true if the subscriber was subscribed to the event, false if it wasn't + */ + public boolean unsubscribe(String topic, EventTopicSubscriber subscriber); + + /** + * Stop the subscription for a subscriber that is subscribed to event topics via a Pattern. + * + * @param topicPattern the regex expression matching topics listened to + * @param subscriber The subscriber that is subscribed to the topic. The same reference as the one subscribed. + * + * @return true if the subscriber was subscribed to the event, false if it wasn't + */ + public boolean unsubscribe(Pattern topicPattern, EventTopicSubscriber subscriber); + + /** + * Subscribes a VetoEventListener to publication of event matching a class. Only a WeakReference to the + * VetoEventListener is held by the EventService. + *

+ * Use this method to avoid having to call unsubscribe(), though with care since garbage collection semantics is + * indeterminate. The service will respect the WeakReference semantics. In other words, if the vetoListener has not + * been garbage collected, then the onEvent will be called normally. If the hard reference has been garbage + * collected, the service will unsubscribe it's WeakReference. + *

+ *

+ * It's allowable to call unsubscribe() with the same VetoEventListener hard reference to stop a subscription + * immediately. + *

+ *

+ * The service will create the WeakReference on behalf of the caller. + *

+ * + * @param eventClass the class of published objects that can be vetoed + * @param vetoListener The VetoEventListener that can determine whether an event is published. + * + * @return true if the VetoEventListener was subscribed successfully, false otherwise + */ + public boolean subscribeVetoListener(Class eventClass, VetoEventListener vetoListener); + + /** + * Subscribes a VetoEventListener to publication of an exact event class. Only a WeakReference to the + * VetoEventListener is held by the EventService. + *

+ * Use this method to avoid having to call unsubscribe(), though with care since garbage collection semantics is + * indeterminate. The service will respect the WeakReference semantics. In other words, if the vetoListener has not + * been garbage collected, then the onEvent will be called normally. If the hard reference has been garbage + * collected, the service will unsubscribe it's WeakReference. + *

+ *

+ * It's allowable to call unsubscribe() with the same VetoEventListener hard reference to stop a subscription + * immediately. + *

+ *

+ * The service will create the WeakReference on behalf of the caller. + *

+ * + * @param eventClass the class of published objects that can be vetoed + * @param vetoListener The vetoListener that can determine whether an event is published. + * + * @return true if the vetoListener was subscribed successfully, false otherwise + */ + public boolean subscribeVetoListenerExactly(Class eventClass, VetoEventListener vetoListener); + + /** + * Subscribes a VetoTopicEventListener to a topic name. Only a WeakReference to the + * VetoEventListener is held by the EventService. + * + * @param topic the name of the topic listened to + * @param vetoListener The vetoListener that can determine whether an event is published. + * + * @return true if the vetoListener was subscribed successfully, false otherwise + */ + public boolean subscribeVetoListener(String topic, VetoTopicEventListener vetoListener); + + /** + * Subscribes an VetoTopicEventListener to all the topic names that match the RegEx Pattern. Only a + * WeakReference to the VetoEventListener is held by the EventService. + * + * @param topicPattern the RegEx pattern to match topics with + * @param vetoListener The vetoListener that can determine whether an event is published. + * + * @return true if the vetoListener was subscribed successfully, false otherwise + */ + public boolean subscribeVetoListener(Pattern topicPattern, VetoTopicEventListener vetoListener); + + /** + * Subscribes a VetoEventListener for an event class and its subclasses. Only a WeakReference to the + * VetoEventListener is held by the EventService. + *

+ * The VetoEventListener will remain subscribed until {@link #unsubscribeVetoListener(Class,VetoEventListener)} is + * called. + *

+ * + * @param eventClass the class of published objects to listen to + * @param vetoListener The vetoListener that will accept the events when published. + * + * @return true if the vetoListener was subscribed successfully, false otherwise + */ + public boolean subscribeVetoListenerStrongly(Class eventClass, VetoEventListener vetoListener); + + /** + * Subscribes a VetoEventListener for an event class (but not its subclasses). + *

+ * The VetoEventListener will remain subscribed until {@link #unsubscribeVetoListener(Class,VetoEventListener)} is + * called. + *

+ * + * @param eventClass the class of published objects to listen to + * @param vetoListener The vetoListener that will accept the events when published. + * + * @return true if the vetoListener was subscribed successfully, false otherwise + */ + public boolean subscribeVetoListenerExactlyStrongly(Class eventClass, VetoEventListener vetoListener); + + /** + * Subscribes a VetoEventListener to a topic name. + *

+ * The VetoEventListener will remain subscribed until {@link #unsubscribeVetoListener(String,VetoTopicEventListener)} is + * called. + *

+ * + * @param topic the name of the topic listened to + * @param vetoListener The topic vetoListener that will accept or reject publication. + * + * @return true if the vetoListener was subscribed successfully, false otherwise + * + * @see #subscribeVetoListenerStrongly(Class,VetoEventListener) + */ + public boolean subscribeVetoListenerStrongly(String topic, VetoTopicEventListener vetoListener); + + /** + * Subscribes a VetoTopicEventListener to a set of topics that match a RegEx expression. + *

+ * The VetoEventListener will remain subscribed until {@link #unsubscribeVetoListener(Pattern,VetoTopicEventListener)} is + * called. + *

+ * + * @param topicPattern the RegEx pattern that matches the name of the topics listened to + * @param vetoListener The topic vetoListener that will accept or reject publication. + * + * @return true if the vetoListener was subscribed successfully, false otherwise + * + * @see #subscribeVetoListenerStrongly(Pattern,VetoTopicEventListener) + */ + public boolean subscribeVetoListenerStrongly(Pattern topicPattern, VetoTopicEventListener vetoListener); + + /** + * Stop the subscription for a vetoListener that is subscribed to an event class and its subclasses. + * + * @param eventClass the class of published objects that can be vetoed + * @param vetoListener The vetoListener that will accept or reject publication of an event. + * + * @return true if the vetoListener was subscribed to the event, false if it wasn't + */ + public boolean unsubscribeVetoListener(Class eventClass, VetoEventListener vetoListener); + + /** + * Stop the subscription for a vetoListener that is subscribed to an event class (but not its subclasses). + * + * @param eventClass the class of published objects that can be vetoed + * @param vetoListener The vetoListener that will accept or reject publication of an event. + * + * @return true if the vetoListener was subscribed to the event, false if it wasn't + */ + public boolean unsubscribeVetoListenerExactly(Class eventClass, VetoEventListener vetoListener); + + /** + * Stop the subscription for a VetoTopicEventListener that is subscribed to an event topic name. + * + * @param topic the name of the topic that is listened to + * @param vetoListener The vetoListener that can determine whether an event is published on that topic + * + * @return true if the vetoListener was subscribed to the topic, false if it wasn't + */ + public boolean unsubscribeVetoListener(String topic, VetoTopicEventListener vetoListener); + + /** + * Stop the subscription for a VetoTopicEventListener that is subscribed to an event topic RegEx pattern. + * + * @param topicPattern the RegEx pattern matching the name of the topics listened to + * @param vetoListener The vetoListener that can determine whether an event is published on that topic + * + * @return true if the vetoListener was subscribed to the topicPattern, false if it wasn't + */ + public boolean unsubscribeVetoListener(Pattern topicPattern, VetoTopicEventListener vetoListener); + + /** + * Union of getSubscribersToClass(Class) and getSubscribersToExactClass(Class) + * + * @param eventClass the eventClass of interest + * + * @return the subscribers that will be called when an event of eventClass is published, this includes those + * subscribed that match by exact class and those that match to a class and its supertypes + */ + public List getSubscribers(Class eventClass); + + /** + * Gets subscribers that subscribed with the given a class, but not those subscribed exactly to the class. + * @param eventClass the eventClass of interest + * + * @return the subscribers that are subscribed to match to a class and its supertypes, but not those subscribed by + * exact class + */ + public List getSubscribersToClass(Class eventClass); + + /** + * Gets subscribers that are subscribed exactly to a class, but not those subscribed non-exactly to a class. + * @param eventClass the eventClass of interest + * + * @return the subscribers that are subscribed by exact class but not those subscribed to match to a class and its + * supertypes + */ + public List getSubscribersToExactClass(Class eventClass); + + /** + * Gets the subscribers that subscribed to a generic type. + * + * @param type the type of interest + * + * @return the subscribers that will be called when an event of eventClass is published, this includes those + * subscribed that match by exact class and those that match to a class and its supertypes + */ + public List getSubscribers(Type type); + + /** + * Union of getSubscribersByPattern(String) and geSubscribersToTopic(String) + * + * @param topic the topic of interest + * + * @return the subscribers that will be called when an event is published on the topic. This includes subscribers + * subscribed to match the exact topic name and those subscribed by a RegEx Pattern that matches the topic + * name. + */ + public List getSubscribers(String topic); + + /** + * Get the subscribers that subscribed to a topic. + * @param topic the topic of interest + * + * @return the subscribers that subscribed to the exact topic name. + */ + public List getSubscribersToTopic(String topic); + + /** + * Gets the subscribers that subscribed to a regular expression. + * @param pattern the RegEx pattern that was subscribed to + * + * @return the subscribers that were subscribed to this pattern. + */ + public List getSubscribers(Pattern pattern); + + /** + * Gets the subscribers that subscribed with a Pattern that matches the given topic. + * @param topic a topic to match Patterns against + * + * @return the subscribers that subscribed by a RegEx Pattern that matches the topic name. + */ + public List getSubscribersByPattern(String topic); + + /** + * Gets veto subscribers that subscribed to a given class. + * @param eventClass the eventClass of interest + * + * @return the veto subscribers that will be called when an event of eventClass or its subclasses is published. + */ + public List getVetoSubscribers(Class eventClass); + + /** + * Get veto subscribers that subscribed to a given class exactly. + * @param eventClass the eventClass of interest + * + * @return the veto subscribers that will be called when an event of eventClass (but not its subclasses) is + * published. + */ + public List getVetoSubscribersToExactClass(Class eventClass); + + /** + * Gets the veto subscribers that subscribed to a class. + * @param eventClass the eventClass of interest + * + * @return the veto subscribers that are subscribed to the eventClass and its subclasses + */ + public List getVetoSubscribersToClass(Class eventClass); + + /** + * Union of {@link #getVetoSubscribersToTopic(String)} and {@link #getVetoSubscribersByPattern(String)} + * Misnamed method, should be called {@link #getVetoSubscribers(String)}. Will be deprecated in 1.5. + * + * @param topicOrPattern the topic or pattern of interest + * + * @return the veto subscribers that will be called when an event is published on the topic. + */ + public List getVetoEventListeners(String topicOrPattern); + + /** + * Gets the veto subscribers that subscribed to a topic. + * @param topic the topic of interest + * + * @return the veto subscribers that will be called when an event is published on the topic. + */ + public List getVetoSubscribersToTopic(String topic); + + /** + * Gets the veto subscribers that subscribed to a regular expression. + * @param pattern the RegEx pattern for the topic of interest + * + * @return the veto subscribers that were subscribed to this pattern. + */ + public List getVetoSubscribers(Pattern pattern); + + /** + * Gets the veto subscribers that are subscribed by pattern that match the topic. + * @param topic the topic to match the pattern string subscribed to + * + * @return the veto subscribers that subscribed by pattern that will be called when an event is published on the topic. + */ + public List getVetoSubscribersByPattern(String topic); + + /** + * Misnamed method for backwards compatibility. + * Duplicate of {@link #getVetoSubscribersToTopic(String)}. + * Out of sync with {@link #getSubscribers(String)}. + * @param topic the topic exactly subscribed to + * + * @return the veto subscribers that are subscribed to the topic. + * @deprecated use getVetoSubscribersToTopic instead for direct replacement, + * or use getVetoEventListeners to get topic and pattern matchers. + * In EventBus 2.0 this name will replace getVetoEventListeners() + * and have it's union functionality + */ + public List getVetoSubscribers(String topic); + + /** Clears all current subscribers and veto subscribers */ + public void clearAllSubscribers(); + + /** + * Sets the default cache size for each kind of event, default is 0 (no caching). + *

+ * If this value is set to a positive number, then when an event is published, the EventService caches the event or + * topic payload data for later retrieval. This allows subscribers to find out what has most recently happened + * before they subscribed. The cached event(s) are returned from #getLastEvent(Class), #getLastTopicData(String), + * #getCachedEvents(Class), or #getCachedTopicData(String) + *

+ *

+ * The default can be overridden on a by-event-class or by-topic basis. + *

+ * + * @param defaultCacheSizePerClassOrTopic the cache size per event + */ + public void setDefaultCacheSizePerClassOrTopic(int defaultCacheSizePerClassOrTopic); + + /** + * The default number of events or payloads kept per event class or topic + * @return the default number of event payloads kept per event class or topic + */ + public int getDefaultCacheSizePerClassOrTopic(); + + /** + * Set the number of events cached for a particular class of event. By default, no events are cached. + *

+ * This overrides any setting for the DefaultCacheSizePerClassOrTopic. + *

+ *

+ * Class hierarchy semantics are respected. That is, if there are three events, A, X and Y, and X and Y are both + * derived from A, then setting the cache size for A applies the cache size for all three. Setting the cache size + * for X applies to X and leaves the settings for A and Y in tact. Interfaces can be passed to this method, but they + * only take effect if the cache size of a class or it's superclasses has been set. Just like Class.getInterfaces(), + * if multiple cache sizes are set, the interface names declared earliest in the implements clause of the eventClass + * takes effect. + *

+ *

+ * The cache for an event is not adjusted until the next event of that class is published. + *

+ * + * @param eventClass the class of event + * @param cacheSize the number of published events to cache for this event + */ + public void setCacheSizeForEventClass(Class eventClass, int cacheSize); + + /** + * Returns the number of events cached for a particular class of event. By default, no events are cached. + *

+ * This result is computed for a particular class from the values passed to #setCacheSizeForEventClass(Class, int), + * and respects the class hierarchy. + *

+ * + * @param eventClass the class of event + * + * @return the maximum size of the event cache for the given event class + * + * @see #setCacheSizeForEventClass(Class,int) + */ + public int getCacheSizeForEventClass(Class eventClass); + + /** + * Set the number of published data objects cached for a particular event topic. By default, no data are cached. + *

+ * This overrides any setting for the DefaultCacheSizePerClassOrTopic. + *

+ *

+ * Exact topic names take precedence over pattern matching. + *

+ *

+ * The cache for a topic is not adjusted until the next publication on that topic. + *

+ * + * @param topicName the topic name + * @param cacheSize the number of published data Objects to cache for this topic + */ + public void setCacheSizeForTopic(String topicName, int cacheSize); + + /** + * Set the number of published data objects cached for a topics matching a pattern. By default, no data are cached. + *

+ * This overrides any setting for the DefaultCacheSizePerClassOrTopic. + *

+ *

+ * Exact topic names take precedence over pattern matching. + *

+ *

+ * The cache for a topic is not adjusted until the next publication on that topic. + *

+ * + * @param pattern the pattern matching topic names + * @param cacheSize the number of data Objects to cache for this topic + */ + public void setCacheSizeForTopic(Pattern pattern, int cacheSize); + + /** + * Returns the number of cached data objects published on a particular topic. + *

+ * This result is computed for a particular class from the values passed to #setCacheSizeForEventClass(Class, int), + * and respects the class hierarchy. + *

+ * + * @param topic the topic name + * + * @return the maximum size of the data Object cache for the given topic + * + * @see #setCacheSizeForTopic(String,int) + * @see #setCacheSizeForTopic(java.util.regex.Pattern,int) + */ + public int getCacheSizeForTopic(String topic); + + /** + * When caching, returns the last event publish for the type supplied. + * @param eventClass an index into the cache + * + * @return the last event published for this event class, or null if caching is turned off (the default) + */ + public T getLastEvent(Class eventClass); + + /** + * When caching, returns the last set of event published for the type supplied. + * @param eventClass an index into the cache + * + * @return the last events published for this event class, or null if caching is turned off (the default) + */ + public List getCachedEvents(Class eventClass); + + /** + * When caching, returns the last payload published on the topic name supplied. + * @param topic an index into the cache + * + * @return the last data Object published on this topic, or null if caching is turned off (the default) + */ + public Object getLastTopicData(String topic); + + /** + * When caching, returns the last set of payload objects published on the topic name supplied. + * @param topic an index into the cache + * + * @return the last data Objects published on this topic, or null if caching is turned off (the default) + */ + public List getCachedTopicData(String topic); + + /** + * Clears the event cache for a specific event class or interface and it's any of it's subclasses or implementing + * classes. + * + * @param eventClass the event class to clear the cache for + */ + public void clearCache(Class eventClass); + + /** + * Clears the topic data cache for a specific topic name. + * + * @param topic the topic name to clear the cache for + */ + public void clearCache(String topic); + + /** + * Clears the topic data cache for all topics that match a particular pattern. + * + * @param pattern the pattern to match topic caches to + */ + public void clearCache(Pattern pattern); + + /** Clear all event caches for all topics and event. */ + public void clearCache(); + + /** + * Stop a subscription for an object that is subscribed with a ProxySubscriber. + *

+ * If an object is subscribed by proxy and it implements EventSubscriber, then the normal unsubscribe methods will + * still unsubscribe the object. + *

+ * + * @param eventClass class this object is subscribed to by proxy + * @param subscribedByProxy object subscribed by proxy + * @return true if the subscription was cancelled, false if it never existed + */ + boolean unsubscribe(Class eventClass, Object subscribedByProxy); + + /** + * Stop a subscription for an object that is subscribed exactly with a ProxySubscriber. + *

+ * If an object is subscribed by proxy and it implements EventSubscriber, then the normal unsubscribe methods will + * still unsubscribe the object. + *

+ * + * @param eventClass class this object is subscribed to by proxy + * @param subscribedByProxy object subscribed by proxy + * @return true if the subscription was cancelled, false if it never existed + */ + boolean unsubscribeExactly(Class eventClass, Object subscribedByProxy); + + /** + * Stop a subscription for an object that is subscribed to a topic with a ProxySubscriber. + *

+ * If an object is subscribed by proxy and it implements EventSubscriber, then the normal unsubscribe methods will + * still unsubscribe the object. + *

+ * + * @param topic the topic this object is subscribed to by proxy + * @param subscribedByProxy object subscribed by proxy + * @return true if the subscription was cancelled, false if it never existed + */ + boolean unsubscribe(String topic, Object subscribedByProxy); + + /** + * When using annotations, an object may be subscribed by proxy. This unsubscribe method will unsubscribe an object + * that is subscribed with a ProxySubscriber. + *

+ * If an object is subscribed by proxy and it implements EventSubscriber, then the normal unsubscribe methods will + * still unsubscribe the object. + *

+ * + * @param pattern the RegEx expression this object is subscribed to by proxy + * @param subscribedByProxy object subscribed by proxy + * @return true if the subscription was cancelled, false if it never existed + */ + boolean unsubscribe(Pattern pattern, Object subscribedByProxy); + + /** + * Stop a veto subscription for an object that is subscribed with a ProxySubscriber. + *

+ * If an object is subscribed by proxy and it implements VetoSubscriber, then the normal unsubscribe methods will + * still unsubscribe the object. + *

+ * + * @param eventClass class this object is subscribed to by proxy + * @param subscribedByProxy object subscribed by proxy + * @return true if the subscription was cancelled, false if it never existed + */ + boolean unsubscribeVeto(Class eventClass, Object subscribedByProxy); + + /** + * Stop a veto subscription for an object that is subscribed exactly with a ProxySubscriber. + *

+ * If an object is subscribed by proxy and it implements VetoSubscriber, then the normal unsubscribe methods will + * still unsubscribe the object. + *

+ * + * @param eventClass class this object is subscribed to by proxy + * @param subscribedByProxy object subscribed by proxy + * @return true if the subscription was cancelled, false if it never existed + */ + boolean unsubscribeVetoExactly(Class eventClass, Object subscribedByProxy); + + /** + * Stop a veto subscription for an object that is subscribed to a topic with a ProxySubscriber. + *

+ * If an object is subscribed by proxy and it implements EventSubscriber, then the normal unsubscribe methods will + * still unsubscribe the object. + *

+ * + * @param topic the topic this object is subscribed to by proxy + * @param subscribedByProxy object subscribed by proxy + * @return true if the subscription was cancelled, false if it never existed + */ + boolean unsubscribeVeto(String topic, Object subscribedByProxy); + + /** + * When using annotations, an object may be subscribed by proxy. This unsubscribe method will unsubscribe an object + * that is subscribed with a ProxySubscriber. + *

+ * If an object is subscribed by proxy and it implements EventSubscriber, then the normal unsubscribe methods will + * still unsubscribe the object. + *

+ * + * @param pattern the RegEx expression this object is subscribed to by proxy + * @param subscribedByProxy object subscribed by proxy + * @return true if the subscription was cancelled, false if it never existed + */ + boolean unsubscribeVeto(Pattern pattern, Object subscribedByProxy); +} diff --git a/src/main/java/org/scijava/event/bushe/EventSubscriber.java b/src/main/java/org/scijava/event/bushe/EventSubscriber.java new file mode 100644 index 000000000..03e8f9227 --- /dev/null +++ b/src/main/java/org/scijava/event/bushe/EventSubscriber.java @@ -0,0 +1,35 @@ +/** + * Copyright 2005 Bushe Enterprises, Inc., Hopkinton, MA, USA, www.bushe.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.scijava.event.bushe; + +/** + * Callback interface for class-based subscribers of an {@link EventService}. + * + * @author Michael Bushe michael@bushe.com + */ +public interface EventSubscriber { + + /** + * Handle a published event.

The EventService calls this method on each publication of an object that matches the + * class or interface passed to one of the EventService's class-based subscribe methods, specifically, {@link + * EventService#subscribe(Class,EventSubscriber)} {@link EventService#subscribeExactly(Class,EventSubscriber)} + * {@link EventService#subscribeStrongly(Class,EventSubscriber)} and {@link EventService#subscribeExactlyStrongly(Class, + *EventSubscriber)}. + * + * @param event The Object that is being published. + */ + public void onEvent(T event); +} diff --git a/src/main/java/org/scijava/event/bushe/EventTopicSubscriber.java b/src/main/java/org/scijava/event/bushe/EventTopicSubscriber.java new file mode 100644 index 000000000..37bf575e4 --- /dev/null +++ b/src/main/java/org/scijava/event/bushe/EventTopicSubscriber.java @@ -0,0 +1,39 @@ +/** + * Copyright 2005 Bushe Enterprises, Inc., Hopkinton, MA, USA, www.bushe.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.scijava.event.bushe; + +/** + * Callback interface for topic-based subscribers of an {@link EventService}. + * + * @author Michael Bushe michael@bushe.com + */ +interface EventTopicSubscriber { + + /** + * Handle an event published on a topic. + *

+ * The EventService calls this method on each publication on a matching topic name passed to one of the + * EventService's topic-based subscribe methods, specifically, {@link EventService#subscribe(String, + *EventTopicSubscriber)} {@link EventService#subscribe(java.util.regex.Pattern,EventTopicSubscriber)} {@link + * EventService#subscribeStrongly(String,EventTopicSubscriber)} and {@link EventService#subscribeStrongly(java.util.regex.Pattern, + *EventTopicSubscriber)}. + *

+ * + * @param topic the name of the topic published on + * @param data the data object published on the topic + */ + public void onEvent(String topic, T data); +} diff --git a/src/main/java/org/scijava/event/bushe/Logger.java b/src/main/java/org/scijava/event/bushe/Logger.java new file mode 100644 index 000000000..f2de3981b --- /dev/null +++ b/src/main/java/org/scijava/event/bushe/Logger.java @@ -0,0 +1,221 @@ +package org.scijava.event.bushe; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.HashMap; + +/** + * Central Logging class. Shields code from Logging implementation. + *

+ * The EventBus allows operation in two modes - using java.util.logging so that + * the EventBus can be deployed in its own jar or using any logging system supported + * by apache commons logging, which of course requires other jars. + *

+ *

+ * The EventBus logging uses the names of its classes as the log, primarily + * "org.scijava.event.bushe.EventService". This aids in debugging which subscription and publication issues. + *

+ *

+ * Implementation note: There are no imports in this class to make things + * explicit. There is also no explicit use of classes outside java.util, + * anything else is used by reflection to avoid NoClassDefFound errors on class load. + *

+ */ +class Logger { + private java.util.logging.Logger utilLogger; + private /*Untyped to avoid java.lang.NoClassDefFoundError + org.apache.commons.logging.Log*/ Object commonsLogger; + private Map METHOD_CACHE_NO_PARAMS; + private Map METHOD_CACHE_ONE_PARAM; + private Map METHOD_CACHE_TWO_PARAMS; + private static Class logFactoryClass; + private static Class logClass; + private static Method getLogMethod; + private static final Object[] EMPTY_ARGS = new Object[0]; + private static final Class[] CLASS_ARGS_EMPTY = new Class[0]; + private static final Class[] CLASS_ARGS_ONE = new Class[]{Object.class}; + private static final Class[] CLASS_ARGS_TWO = new Class[]{Object.class, Throwable.class}; + + /** Allows switching between Java and Commons logging.*/ + public static enum LoggerType { + /*java.util.logging*/ + JAVA, + /*org.apache.commons.logging*/ + COMMONS + } + + /** Standardized logging levels. */ + public static enum Level { + FATAL, + ERROR, + WARN, + INFO, + DEBUG, + TRACE + } + + public static LoggerType LOGGER_TYPE= null; + + public static Logger getLogger(String name) { + if (LOGGER_TYPE == null) { + LOGGER_TYPE = getLoggerType(); + } + if (LOGGER_TYPE == LoggerType.COMMONS) { + try { + Object logger = getLogMethod.invoke(null, name); + return new Logger(logger); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } + return new Logger(java.util.logging.Logger.getLogger(name)); + } + + /** + * This method should only be called once in a JVM run. + * @return + */ + private static LoggerType getLoggerType() { + LoggerType result = null; + //See if apache commons is available + try { + logFactoryClass = Class.forName("org.apache.commons.logging.LogFactory"); + getLogMethod = logFactoryClass.getMethod("getLog", new Class[]{String.class}); + logClass = Class.forName("org.apache.commons.logging.Log"); + return LoggerType.COMMONS; + } catch (Throwable e) { + } + return LoggerType.JAVA; + } + + public Logger(java.util.logging.Logger utilLogger) { + this.utilLogger = utilLogger; + } + + public Logger(Object commonsLogger) { + this.commonsLogger = commonsLogger; + } + + /** + * Returns whether this level is loggable. If there is + * a misconfiguration, this will always return false. + * @param level the EventBus Logger level + * @return whether this level is loggable. + */ + public boolean isLoggable(Level level) { + if (utilLogger != null) { + java.util.logging.Level javaLevel = getJavaLevelFor(level); + return javaLevel != null && utilLogger.isLoggable(javaLevel); + } else if (commonsLogger != null) { + switch (level) { + case ERROR: return (Boolean)callCommonsLogger("isErrorEnabled"); + case FATAL: return (Boolean)callCommonsLogger("isFatalEnabled"); + case WARN: return (Boolean)callCommonsLogger("isWarnEnabled"); + case INFO: return (Boolean)callCommonsLogger("isInfoEnabled"); + case DEBUG: return (Boolean)callCommonsLogger("isDebugEnabled"); + case TRACE: return (Boolean)callCommonsLogger("isTraceEnabled"); + } + } + return false; + } + + private java.util.logging.Level getJavaLevelFor(Level level) { + switch (level) { + case FATAL: return java.util.logging.Level.SEVERE; + case ERROR: return java.util.logging.Level.SEVERE; + case WARN: return java.util.logging.Level.WARNING; + case INFO: return java.util.logging.Level.INFO; + case DEBUG: return java.util.logging.Level.FINE; + case TRACE: return java.util.logging.Level.FINEST; + } + return null; + } + + public void debug(String message) { + log(Level.DEBUG, message); + } + + public void log(Level level, String message) { + log(level, message, null); + } + + public void log(Level level, String message, Throwable throwable) { + if (!isLoggable(level)) { + return; + } + if (utilLogger != null) { + java.util.logging.Level javaLevel = getJavaLevelFor(level); + if (throwable == null) { + utilLogger.log(javaLevel, message); + } else { + utilLogger.log(javaLevel, message, throwable); + } + } else if (commonsLogger != null) { + if (throwable == null) { + switch (level) { + case ERROR: callCommonsLogger("error", message); break; + case FATAL: callCommonsLogger("fatal", message); break; + case WARN: callCommonsLogger("warn", message); break; + case INFO: callCommonsLogger("info", message); break; + case DEBUG: callCommonsLogger("debug", message); break; + case TRACE: callCommonsLogger("trace", message); break; + } + } else { + switch (level) { + case ERROR: callCommonsLogger("error", message, throwable); break; + case FATAL: callCommonsLogger("fatal", message, throwable); break; + case WARN: callCommonsLogger("warn", message, throwable); break; + case INFO: callCommonsLogger("info", message, throwable); break; + case DEBUG: callCommonsLogger("debug", message, throwable); break; + case TRACE: callCommonsLogger("trace", message, throwable); break; + } + } + } + } + + private Object callCommonsLogger(String methodName) { + if (METHOD_CACHE_NO_PARAMS == null) { + METHOD_CACHE_NO_PARAMS = new HashMap(); + } + return callCommonsLogger(METHOD_CACHE_NO_PARAMS, methodName, CLASS_ARGS_EMPTY, EMPTY_ARGS); + } + + private Object callCommonsLogger(String methodName, String message) { + if (METHOD_CACHE_ONE_PARAM == null) { + METHOD_CACHE_ONE_PARAM = new HashMap(); + } + return callCommonsLogger(METHOD_CACHE_ONE_PARAM, methodName, CLASS_ARGS_ONE, new Object[]{message}); + } + + private Object callCommonsLogger(String methodName, String message, Throwable throwable) { + if (METHOD_CACHE_TWO_PARAMS == null) { + METHOD_CACHE_TWO_PARAMS = new HashMap(); + } + return callCommonsLogger(METHOD_CACHE_TWO_PARAMS, methodName, CLASS_ARGS_TWO, new Object[]{message, throwable}); + } + + private Object callCommonsLogger(Map cache, String methodName, Class[] classOfArgs, Object[] args) { + Method method = cache.get(methodName); + if (method == null) { + try { + method = logClass.getMethod(methodName, classOfArgs); + cache.put(methodName, method); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + } + if (method == null) { + return null; + } + try { + return method.invoke(commonsLogger, args); + } catch (IllegalAccessException e) { + return null; + } catch (InvocationTargetException e) { + return null; + } + } +} diff --git a/src/main/java/org/scijava/event/bushe/Prioritized.java b/src/main/java/org/scijava/event/bushe/Prioritized.java new file mode 100644 index 000000000..7a3187055 --- /dev/null +++ b/src/main/java/org/scijava/event/bushe/Prioritized.java @@ -0,0 +1,14 @@ +package org.scijava.event.bushe; + +/** + * Subscribers can implement this interface in order to affect the order in which they are called. + *

+ * Subscribers that do not implement this interface are called on a FIFO basis, as are subscribers that implement this + * interface and return 0. If the priority returned from this interface is negative, then this subscriber will be + * called before non-Prioritized subscribers, the more negative, the earlier it is called. If the priority returned + * from this interface is positive, then this subscriber will be called after non-Prioritized subscribers, the more + * positive, the later it is called. + */ +interface Prioritized { + int getPriority(); +} diff --git a/src/main/java/org/scijava/event/bushe/ProxySubscriber.java b/src/main/java/org/scijava/event/bushe/ProxySubscriber.java new file mode 100644 index 000000000..04ac3ce0a --- /dev/null +++ b/src/main/java/org/scijava/event/bushe/ProxySubscriber.java @@ -0,0 +1,43 @@ +/** + * Copyright 2007 Bushe Enterprises, Inc., Hopkinton, MA, USA, www.bushe.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.scijava.event.bushe; + +/** + * An interface that can be implemented when proxies are used for subscription, not needed in normal usage. When an + * unsubscribe method is called on an EventService, the EventService is required to check if any of subscribed objects + * are ProxySubscribers and if the object to be unsubscribed is the ProxySubscriber's proxiedSubscriber. If so, the + * EventService proxy is unsubscribed and the ProxySubscriber's proxyUnsubscribed() method is called to allow the proxy + * to perform any cleanup if necessary. ProxySubscribers should set their references to their proxied objects to null + * for strong subscriptions to allow garbage collection. + * + * @author Michael Bushe + */ +interface ProxySubscriber { + + /** @return the object this proxy is subscribed on behalf of */ + public Object getProxiedSubscriber(); + + /** + * Called by EventServices to inform the proxy that it is unsubscribed. The ProxySubscriber should null the + * reference to it's proxied subscriber + */ + public void proxyUnsubscribed(); + + /** + * @return the reference strength from this proxy to the proxied subscriber + */ + public ReferenceStrength getReferenceStrength(); +} diff --git a/src/main/java/org/scijava/event/bushe/PublicationStatus.java b/src/main/java/org/scijava/event/bushe/PublicationStatus.java new file mode 100644 index 000000000..84c0090d0 --- /dev/null +++ b/src/main/java/org/scijava/event/bushe/PublicationStatus.java @@ -0,0 +1,30 @@ +package org.scijava.event.bushe; + +/** + * The status of an event as it makes its way from publication through processing by subscribers. + *

+ * EventServices are required to stamp any event object or payload that implements the PublicationStatusTracker + * with the corresponding PublicationStatus as the event object is processed. The EventService is not + * required to set the Unpublished state. + */ +enum PublicationStatus { + /** Recommended default.*/ + Unpublished, + /** Set directly after publication on an EventService.*/ + Initiated, + /** End status for events that are vetoed and never sent to subscribers.*/ + Vetoed, + /** State set after veto test is passed before the event is send to any subscribers.*/ + Queued, + /** Set while the event is sent to it's subscribers. EventService implementations + * such as the ThreadSafeEventService and the SwingEventService will transition from Queued to + * Publishing immediately. Others implementations that call subscribers on threads different + * from veto subscribers are free to leave an event in the Queued state and wait until + * the event is passed to the thread(s) that subscribers are called on to set the + * Publishing state */ + Publishing, + /** + * Called when all subscribers have finished handling the event publication. + */ + Completed +} diff --git a/src/main/java/org/scijava/event/bushe/PublicationStatusTracker.java b/src/main/java/org/scijava/event/bushe/PublicationStatusTracker.java new file mode 100644 index 000000000..cc6a98595 --- /dev/null +++ b/src/main/java/org/scijava/event/bushe/PublicationStatusTracker.java @@ -0,0 +1,24 @@ +package org.scijava.event.bushe; + +/** + * An optional interface that can be implemented by Events objects or topic Payloads + * to enable the events' status to be stamped on the event by an event service. + *

+ * EventService implementations must call setEventStatus(status) on event objects and + * payloads that implement this interface. + */ +interface PublicationStatusTracker { + + /** + * Implementations of this method must be made thread safe. + * @return last value set by setPublicationStatus(), or + * {@link PublicationStatus#Unpublished} if setPublicationStatus was never called. + */ + public PublicationStatus getPublicationStatus(); + + /** + * Implementations of this method must be made thread safe. + * @param status the status of the event during it's current publication + */ + public void setPublicationStatus(PublicationStatus status); +} diff --git a/src/main/java/org/scijava/event/bushe/ReferenceStrength.java b/src/main/java/org/scijava/event/bushe/ReferenceStrength.java new file mode 100644 index 000000000..15e7a7eb5 --- /dev/null +++ b/src/main/java/org/scijava/event/bushe/ReferenceStrength.java @@ -0,0 +1,11 @@ +package org.scijava.event.bushe; + +/** + * The two kinds of references that are used in the EventBus. + * + * @author Michael Bushe + */ +public enum ReferenceStrength { + WEAK, + STRONG +} diff --git a/src/main/java/org/scijava/event/bushe/SwingException.java b/src/main/java/org/scijava/event/bushe/SwingException.java new file mode 100644 index 000000000..f36fc36d6 --- /dev/null +++ b/src/main/java/org/scijava/event/bushe/SwingException.java @@ -0,0 +1,128 @@ +/** + * Copyright 2005 Bushe Enterprises, Inc., Hopkinton, MA, USA, www.bushe.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.scijava.event.bushe; + +import java.io.PrintStream; +import java.io.PrintWriter; + +/** + * Aids in troubleshooting Swing application exceptions or any exception where the caller's stack may not be the + * exception stack (such as producer-consumer patterns that cross threads). + *

+ * Swing exceptions usually occur on the Swing Event Dispatch Thread, and often occur when code puts events on the EDT. + * This code is often in a non-EDT thread such as a thread that is receiving data from a server. If the non-EDT threads + * puts a call on the EDT and that EDT call causes and exception, the stack trace of the exception is lost, and it often + * difficult or impossible to determine where the non-EDT call came from. + *

+ *

+ * This Exception class is used to handle exceptions that occur when events are posted on the Swing EDT or occur on + * another thread from the Swing EDT. It includes a "swing" call stack to record from where the event occurred, and + * overrides so that the exception and the swing calling stack print nicely to logs. + *

+ *

+ * The swing calling stack is different from the cause of the exception since it is gathered before the exception occurs + * in a different stack from the cause and used after the exception in a new thread occurs. + *

+ * + * @author Michael Bushe michael@bushe.com + */ +class SwingException extends Exception { + protected StackTraceElement[] callingStackTrace; + + /** Default constructor */ + public SwingException() { + super(); + } + + /** + * Constructor for compatibility with Exception. Use ClientException(String, Throwable, StackTraceElement[]) + * instead + */ + public SwingException(String message) { + super(message); + } + + /** Constructor for compatibility with Exception Use ClientException(String, Throwable, StackTraceElement[]) instead */ + public SwingException(Throwable cause) { + super(cause); + } + + /** Constructor for compatibility with Exception Use ClientException(String, Throwable, StackTraceElement[]) instead */ + public SwingException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Preferred constructor. + * + * @param message The message of exception + * @param cause The cause of the exception in the same call stack + * @param callingStack the stack trace that the client used to call the exception to occur. + */ + public SwingException(String message, Throwable cause, StackTraceElement[] callingStack) { + super(message, cause); + setCallingStack(callingStack); + } + + /** + * Swing exceptions often have two stacks - one thread causes the posting of an action on another thread - usually + * the Swing EDT thread. The other is the stack of the actual thread the exception occurred on, the exception occurs + * after the post. + * + * @param swingCallingStack the stack trace that the client used to cause the exception to occur. + */ + public void setCallingStack(StackTraceElement[] swingCallingStack) { + this.callingStackTrace = swingCallingStack; + } + + /** + * Client exceptions often have two stacks - one thread causes the posting of an action on another thread - usually + * the Swing EDT thread. The other is the stack of the actual thread the exception occurred on. + * + * @return the stack trace that the client used to cause the exception to occur. + */ + public StackTraceElement[] getCallingStack() { + return callingStackTrace; + } + + /** + * Calls printWriter(ps, true) + * + * @param ps the print stream + */ + public void printStackTrace(PrintStream ps) { + PrintWriter pw = new PrintWriter(ps, true); + printStackTrace(pw); + } + + /** + * Prints the calling stack and the exception stack trace. + * + * @param pw + */ + public void printStackTrace(PrintWriter pw) { + pw.println(this); + if (callingStackTrace != null) { + pw.println("Calling stack:"); + for (int i = 0; i < callingStackTrace.length; i++) { + pw.println("\tat " + callingStackTrace[i]); + } + pw.println("Stack after call:"); + } + super.printStackTrace(pw); + } +} + diff --git a/src/main/java/org/scijava/event/bushe/ThreadSafeEventService.java b/src/main/java/org/scijava/event/bushe/ThreadSafeEventService.java new file mode 100644 index 000000000..920d0add4 --- /dev/null +++ b/src/main/java/org/scijava/event/bushe/ThreadSafeEventService.java @@ -0,0 +1,2180 @@ +/** + * Copyright 2005-2007 Bushe Enterprises, Inc., Hopkinton, MA, USA, www.bushe.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.scijava.event.bushe; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Type; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.WildcardType; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.Collections; +import java.util.Comparator; +import java.util.regex.Pattern; + +import org.scijava.event.bushe.Logger.Level; + +/** + * A thread-safe EventService implementation. + *

Multithreading

+ *

+ * This implementation is not Swing thread-safe. If publication occurs on a thread other than the Swing + * EventDispatchThread, subscribers will receive the event on the calling thread, and not the EDT. Swing components + * should use the SwingEventService instead, which is the implementation used by the EventBus. + *

+ *

+ * Two threads may be accessing the ThreadSafeEventService at the same time, one unsubscribing a + * listener for topic "A" and the other publishing on topic "A". If the unsubscribing thread gets the lock first, + * then it is unsubscribed, end of story. If the publisher gets the lock first, then a snapshot copy of the current + * subscribers is made during the publication, the lock is released and the subscribers are called. Between the time + * the lock is released and the time that the listener is called, the unsubscribing thread can unsubscribe, resulting + * in an unsubscribed object receiving notification of the event after it was unsubscribed (but just once). + *

+ *

+ * On event publication, subscribers are called in the order in which they subscribed. + *

+ *

+ * Events and/or topic data can be cached, but are not by default. To cache events or topic data, call + * {@link #setDefaultCacheSizePerClassOrTopic(int)}, {@link #setCacheSizeForEventClass(Class, int)}, or + * {@link #setCacheSizeForTopic(String, int)}, {@link #setCacheSizeForTopic(Pattern, int)}. Retrieve cached values + * with {@link #getLastEvent(Class)}, {@link #getLastTopicData(String)}, {@link #getCachedEvents(Class)}, or + * {@link #getCachedTopicData(String)}. Using caching while subscribing + * is most likely to make sense only if you subscribe and publish on the same thread (so caching is very useful for + * Swing applications since both happen on the EDT in a single-threaded manner). In multithreaded applications, you + * never know if your subscriber has handled an event while it was being subscribed (before the subscribe() method + * returned) that is newer or older than the retrieved cached value (taken before or after subscribe() respectively). + *

+ *

Logging

+ *

+ * All logging goes through the {@link Logger}. The Logger is configurable and supports multiple logging systems. + *

+ *

+ * Exceptions are logged by default, override {@link #handleException(String,Object,String,Object,Throwable, + * StackTraceElement[],String)} to handleException exceptions in another way. Each call to a subscriber is wrapped in + * a try block to ensure one listener does not interfere with another. + *

+ *

Cleanup of Stale WeakReferences and Stale Annotation Proxies

+ *

+ * The EventService may need to clean up stale WeakReferences and ProxySubscribers created for EventBus annotations. (Aside: EventBus + * Annotations are handled by the creation of proxies to the annotated objects. Since the annotations create weak references + * by default, annotation proxies must held strongly by the EventService, otherwise the proxy is garbage collected.) When + * a WeakReference's referent or an ProxySubscriber's proxiedObject (the annotated object) is claimed by the garbage collector, + * the EventService still holds onto the actual WeakReference or ProxySubscriber subscribed to the EventService (which are pretty tiny). + *

+ *

+ * There are two ways that these stale WeakReferences and ProxySubscribers are cleaned up. + *

+ *
    + *
  1. On every publish, subscribe and unsubscribe, every subscriber and veto subscriber to a class or topic is checked to see + * if it is a stale WeakReference or a stale ProxySubscriber (one whose getProxySubscriber() returns null). If the subscriber + * is stale, it is unsubscribed from the EventService immediately. If it is a ProxySubscriber, it's proxyUnsubscribed() + * method is called after it is unsubscribed. (This isn't as expensive as it sounds, since checks to avoid double subscription is + * necessary anyway). + *
  2. Another cleanup thread may get started to clean up remaining stale subscribers. This cleanup thread only comes into + * play for subscribers to topic or classes that haven't been used (published/subscribed/unsibscribed to). A detailed description + * of the cleanup thread follows. + *
+ *

The Cleanup Thread

+ *

+ * If a topic or class is never published to again, WeakReferences and ProxySubscribers can be left behind if they + * are not cleaned up. To prevent loitering stale subscribers, the ThreadSafeEventService may periodically run through + * all the EventSubscribers and VetoSubscribers for all topics and classes and clean up stale proxies. Proxies for + * Annotations that have a ReferenceStrength.STRONG are never cleaned up in normal usage. (By specifying + * ReferenceStrength.STRONG, the programmer is buying into unsubscribing annotated objects themselves. There is + * one caveat: If getProxiedSubscriber() returns null, even for a ProxySubscriber with a STRONG reference strength, that proxy + * is cleaned up as it is assumed it is stale or just wrong. This would not occur normally in EventBus usage, but only + * if someone is implementing their own custom ProxySubscriber and/or AnnotationProcessor.) + *

+ *

+ * Cleanup is pretty rare in general. Not only are stale subscribers cleaned up with regular usage, stale + * subscribers on abandoned topics and classes do not take up a lot of memory, hence, they are allowed to build up to a certain degree. + * Cleanup does not occur until the number of WeakReferences and SubscriptionsProxy's with WeakReference strength + * subscribed to an EventService for all the EventService's subscriptions in total exceed the cleanupStartThreshhold, + * which is set to CLEANUP_START_THRESHOLD_DEFAULT (500) by default. The default is overridable in the constructor + * or via #setCleanupStartThreshhold(Integer). If set to null, cleanup will never start. + *

+ *

+ * Once the cleanup start threshold is exceeded, a java.util.Timer is started to clean up stale subscribers periodically + * in another thread. The timer will fire every cleanupPeriodMS milliseconds, which is set to the + * CLEANUP_PERIOD_MS_DEFAULT (20 minutes) by default. The default is overridable in the constructor or + * via #setCleanupPeriodMS(Integer). If set to null, cleanup will not start. This is implemented with a java.util.Timer, + * so Timer's warnings apply - setting this too low will cause cleanups to bunch up and hog the cleanup thread. + *

+ *

+ * After a cleanup cycle completes, if the number of stale subscribers falls at or below the cleanupStopThreshhold + * cleanup stops until the cleanupStartThreshhold is exceeded again. The cleanupStopThreshhold is set + * to CLEANUP_STOP_THRESHOLD_DEFAULT (100) by default. The default is overridable in the constructor or via + * #setCleanupStopThreshhold(Integer). If set to null or 0, cleanup will not stop if it is ever started. + *

+ *

+ * All cleanup parameters are tunable "live" and checked after each subscription and after each cleanup cycle. + * To make cleanup never run, set cleanupStartThreshhold to Integer.MAX_VALUE and cleanupPeriodMS to null. + * To get cleanup to run continuously, set set cleanupStartThreshhold to 0 and cleanupPeriodMS to some reasonable value, + * perhaps 1000 (1 second) or so (not recommended, cleanup is conducted with regular usage and the cleanup thread is + * rarely created or invoked). + *

+ *

+ * Cleanup is not run in a daemon thread, and thus will not stop the JVM from exiting. + *

+ * + * @author Michael Bushe michael@bushe.com + * @see EventService for a complete description of the API + */ +@SuppressWarnings({"unchecked"}) +public class ThreadSafeEventService implements EventService { + public static final Integer CLEANUP_START_THRESHOLD_DEFAULT = 250; + public static final Integer CLEANUP_STOP_THRESHOLD_DEFAULT = 100; + public static final Long CLEANUP_PERIOD_MS_DEFAULT = 20L*60L*1000L; + + protected static final Logger LOG = Logger.getLogger(EventService.class.getName()); + + //Making these generic collections is a bad idea, it doesn't compile since it's better to have all the maps + //go through the same set of code to do all the real publish and subscribe work + private Map subscribersByEventType = new HashMap(); + private Map subscribersByEventClass = new HashMap(); + private Map subscribersByExactEventClass = new HashMap(); + private Map subscribersByTopic = new HashMap(); + private Map subscribersByTopicPattern = new HashMap(); + private Map vetoListenersByClass = new HashMap(); + private Map vetoListenersByExactClass = new HashMap(); + private Map vetoListenersByTopic = new HashMap(); + private Map vetoListenersByTopicPattern = new HashMap(); + private final Object listenerLock = new Object(); + private final Object cacheLock = new Object(); + private Long timeThresholdForEventTimingEventPublication; + private Map cacheByEvent = new HashMap(); + private int defaultCacheSizePerClassOrTopic = 0; + private Map cacheSizesForEventClass; + private Map rawCacheSizesForEventClass; + private boolean rawCacheSizesForEventClassChanged; + private Map cacheByTopic = new HashMap(); + private Map cacheSizesForTopic; + private Map rawCacheSizesForTopic; + private boolean rawCacheSizesForTopicChanged; + private Map rawCacheSizesForPattern; + private boolean rawCacheSizesForPatternChanged; + private Integer cleanupStartThreshhold; + private Integer cleanupStopThreshold; + private Long cleanupPeriodMS; + private int weakRefPlusProxySubscriberCount; + private Timer cleanupTimer; + private TimerTask cleanupTimerTask; + private static final Comparator PRIORITIZED_SUBSCRIBER_COMPARATOR = new PrioritizedSubscriberComparator(); + private boolean hasEverUsedPrioritized; + + /** Creates a ThreadSafeEventService that does not monitor timing of handlers. */ + public ThreadSafeEventService() { + this(null, null, null, null); + } + + /** + * Creates a ThreadSafeEventService while providing time monitoring options. + * + * @param timeThresholdForEventTimingEventPublication the longest time a subscriber should spend handling an event, + * The service will publish an SubscriberTimingEvent after listener processing if the time was exceeded. If null, no + * EventSubscriberTimingEvent will be issued. + */ + public ThreadSafeEventService(Long timeThresholdForEventTimingEventPublication) { + this(timeThresholdForEventTimingEventPublication, null, null, null); + } + + /** + * Creates a ThreadSafeEventService while providing proxy cleanup customization. + * Proxies are used with Annotations. + * + * @param cleanupStartThreshold see class javadoc. + * @param cleanupStopThreshold see class javadoc. + * @param cleanupPeriodMS see class javadoc. + */ + public ThreadSafeEventService(Integer cleanupStartThreshold, + Integer cleanupStopThreshold, Long cleanupPeriodMS) { + this(null, cleanupStartThreshold, cleanupStopThreshold, cleanupPeriodMS); + } + + /** + * Creates a ThreadSafeEventService while providing time monitoring options. + * + * @param timeThresholdForEventTimingEventPublication the longest time a subscriber should spend handling an event. + * The service will publish an SubscriberTimingEvent after listener processing if the time was exceeded. If null, no + * SubscriberTimingEvent will be issued. + * @param cleanupStartThreshold see class javadoc. + * @param cleanupStopThreshold see class javadoc. + * @param cleanupPeriodMS see class javadoc. + */ + public ThreadSafeEventService(Long timeThresholdForEventTimingEventPublication, + Integer cleanupStartThreshold, Integer cleanupStopThreshold, Long cleanupPeriodMS) { + this.timeThresholdForEventTimingEventPublication = timeThresholdForEventTimingEventPublication; + if (cleanupStartThreshold == null) { + this.cleanupStartThreshhold = CLEANUP_START_THRESHOLD_DEFAULT; + } else { + this.cleanupStartThreshhold = cleanupStartThreshold; + } + if (cleanupStopThreshold == null) { + this.cleanupStopThreshold = CLEANUP_STOP_THRESHOLD_DEFAULT; + } else { + this.cleanupStopThreshold = cleanupStopThreshold; + } + if (cleanupPeriodMS == null) { + this.cleanupPeriodMS = CLEANUP_PERIOD_MS_DEFAULT; + } else { + this.cleanupPeriodMS = cleanupPeriodMS; + } + } + + /** + * Gets the threshold above which cleanup starts. See the class javadoc on cleanup. + * @return the threshold at which cleanup starts + */ + public Integer getCleanupStartThreshhold() { + synchronized (listenerLock) { + return cleanupStartThreshhold; + } + } + + /** + * Sets the threshold above which cleanup starts. See the class javadoc on cleanup. + * @param cleanupStartThreshhold threshold at which cleanup starts + */ + public void setCleanupStartThreshhold(Integer cleanupStartThreshhold) { + synchronized (listenerLock) { + this.cleanupStartThreshhold = cleanupStartThreshhold; + } + } + + /** + * Gets the threshold below which cleanup stops. See the class javadoc on cleanup. + * @return threshold at which cleanup stops (it may start again) + */ + public Integer getCleanupStopThreshold() { + synchronized (listenerLock) { + return cleanupStopThreshold; + } + } + + /** + * Sets the threshold below which cleanup stops. See the class javadoc on cleanup. + * @param cleanupStopThreshold threshold at which cleanup stops (it may start again). + */ + public void setCleanupStopThreshold(Integer cleanupStopThreshold) { + synchronized (listenerLock) { + this.cleanupStopThreshold = cleanupStopThreshold; + } + } + + /** + * Get the cleanup interval. See the class javadoc on cleanup. + * @return interval in milliseconds between cleanup runs. + */ + public Long getCleanupPeriodMS() { + synchronized (listenerLock) { + return cleanupPeriodMS; + } + } + + /** + * Sets the cleanup interval. See the class javadoc on cleanup. + * @param cleanupPeriodMS interval in milliseconds between cleanup runs. Passing null + * stops cleanup. + */ + public void setCleanupPeriodMS(Long cleanupPeriodMS) { + synchronized (listenerLock) { + this.cleanupPeriodMS = cleanupPeriodMS; + } + } + + /** @see EventService#subscribe(Class,EventSubscriber) */ + public boolean subscribe(Class cl, EventSubscriber eh) { + if (cl == null) { + throw new IllegalArgumentException("Event class must not be null"); + } + if (eh == null) { + throw new IllegalArgumentException("Event subscriber must not be null"); + } + if (LOG.isLoggable(Level.DEBUG)) { + LOG.debug("Subscribing by class, class:" + cl + ", subscriber:" + eh); + } + return subscribe(cl, subscribersByEventClass, new WeakReference(eh)); + } + + /** @see EventService#subscribe(java.lang.reflect.Type, EventSubscriber) */ + public boolean subscribe(Type type, EventSubscriber eh) { + return subscribe(type, subscribersByEventType, new WeakReference(eh)); + } + + /** @see EventService#subscribeExactly(Class,EventSubscriber) */ + public boolean subscribeExactly(Class cl, EventSubscriber eh) { + if (cl == null) { + throw new IllegalArgumentException("Event class must not be null"); + } + if (eh == null) { + throw new IllegalArgumentException("Event subscriber must not be null"); + } + if (LOG.isLoggable(Level.DEBUG)) { + LOG.debug("Subscribing by class, class:" + cl + ", subscriber:" + eh); + } + return subscribe(cl, subscribersByExactEventClass, new WeakReference(eh)); + } + + /** @see EventService#subscribe(String,EventTopicSubscriber) */ + public boolean subscribe(String topic, EventTopicSubscriber eh) { + if (topic == null) { + throw new IllegalArgumentException("Topic must not be null"); + } + if (eh == null) { + throw new IllegalArgumentException("Event topic subscriber must not be null"); + } + if (LOG.isLoggable(Level.DEBUG)) { + LOG.debug("Subscribing by topic name, name:" + topic + ", subscriber:" + eh); + } + return subscribe(topic, subscribersByTopic, new WeakReference(eh)); + } + + /** @see EventService#subscribe(Pattern,EventTopicSubscriber) */ + public boolean subscribe(Pattern pat, EventTopicSubscriber eh) { + if (pat == null) { + throw new IllegalArgumentException("Pattern must not be null"); + } + if (eh == null) { + throw new IllegalArgumentException("Event subscriber must not be null"); + } + if (LOG.isLoggable(Level.DEBUG)) { + LOG.debug("Subscribing by pattern, pattern:" + pat + ", subscriber:" + eh); + } + PatternWrapper patternWrapper = new PatternWrapper(pat); + return subscribe(patternWrapper, subscribersByTopicPattern, new WeakReference(eh)); + } + + /** @see EventService#subscribeStrongly(Class,EventSubscriber) */ + public boolean subscribeStrongly(Class cl, EventSubscriber eh) { + if (LOG.isLoggable(Level.DEBUG)) { + LOG.debug("Subscribing weakly by class, class:" + cl + ", subscriber:" + eh); + } + if (eh == null) { + throw new IllegalArgumentException("Subscriber cannot be null."); + } + return subscribe(cl, subscribersByEventClass, eh); + } + + /** @see EventService#subscribeExactlyStrongly(Class,EventSubscriber) */ + public boolean subscribeExactlyStrongly(Class cl, EventSubscriber eh) { + if (cl == null) { + throw new IllegalArgumentException("Event class must not be null"); + } + if (eh == null) { + throw new IllegalArgumentException("Event subscriber must not be null"); + } + if (LOG.isLoggable(Level.DEBUG)) { + LOG.debug("Subscribing by class, class:" + cl + ", subscriber:" + eh); + } + return subscribe(cl, subscribersByExactEventClass, eh); + } + + /** @see EventService#subscribeStrongly(String,EventTopicSubscriber) */ + public boolean subscribeStrongly(String name, EventTopicSubscriber eh) { + if (LOG.isLoggable(Level.DEBUG)) { + LOG.debug("Subscribing weakly by topic name, name:" + name + ", subscriber:" + eh); + } + if (eh == null) { + throw new IllegalArgumentException("Subscriber cannot be null."); + } + return subscribe(name, subscribersByTopic, eh); + } + + /** @see EventService#subscribeStrongly(Pattern,EventTopicSubscriber) */ + public boolean subscribeStrongly(Pattern pat, EventTopicSubscriber eh) { + if (pat == null) { + throw new IllegalArgumentException("Pattern must not be null"); + } + if (eh == null) { + throw new IllegalArgumentException("Event subscriber must not be null"); + } + if (LOG.isLoggable(Level.DEBUG)) { + LOG.debug("Subscribing by pattern, pattern:" + pat + ", subscriber:" + eh); + } + PatternWrapper patternWrapper = new PatternWrapper(pat); + return subscribe(patternWrapper, subscribersByTopicPattern, eh); + } + + + /** @see org.scijava.event.bushe.EventService#clearAllSubscribers() */ + public void clearAllSubscribers() { + synchronized (listenerLock) { + unsubscribeAllInMap(subscribersByEventType); + unsubscribeAllInMap(subscribersByEventClass); + unsubscribeAllInMap(subscribersByExactEventClass); + unsubscribeAllInMap(subscribersByTopic); + unsubscribeAllInMap(subscribersByTopicPattern); + unsubscribeAllInMap(vetoListenersByClass); + unsubscribeAllInMap(vetoListenersByExactClass); + unsubscribeAllInMap(vetoListenersByTopic); + unsubscribeAllInMap(vetoListenersByTopicPattern); + } + } + + private void unsubscribeAllInMap(Map subscriberMap) { + synchronized (listenerLock) { + Set subscriptionKeys = subscriberMap.keySet(); + for (Object key : subscriptionKeys) { + List subscribers = (List) subscriberMap.get(key); + while (!subscribers.isEmpty()) { + unsubscribe(key, subscriberMap, subscribers.get(0)); + } + } + } + } + + /** @see EventService#subscribeVetoListener(Class,VetoEventListener) */ + public boolean subscribeVetoListener(Class eventClass, VetoEventListener vetoListener) { + if (vetoListener == null) { + throw new IllegalArgumentException("VetoEventListener cannot be null."); + } + if (eventClass == null) { + throw new IllegalArgumentException("eventClass cannot be null."); + } + return subscribeVetoListener(eventClass, vetoListenersByClass, new WeakReference(vetoListener)); + } + + /** @see EventService#subscribeVetoListenerExactly(Class,VetoEventListener) */ + public boolean subscribeVetoListenerExactly(Class eventClass, VetoEventListener vetoListener) { + if (vetoListener == null) { + throw new IllegalArgumentException("VetoEventListener cannot be null."); + } + if (eventClass == null) { + throw new IllegalArgumentException("eventClass cannot be null."); + } + return subscribeVetoListener(eventClass, vetoListenersByExactClass, new WeakReference(vetoListener)); + } + + /** @see EventService#subscribeVetoListener(String,VetoTopicEventListener) */ + public boolean subscribeVetoListener(String topic, VetoTopicEventListener vetoListener) { + if (vetoListener == null) { + throw new IllegalArgumentException("VetoEventListener cannot be null."); + } + if (topic == null) { + throw new IllegalArgumentException("topic cannot be null."); + } + return subscribeVetoListener(topic, vetoListenersByTopic, new WeakReference(vetoListener)); + } + + /** @see EventService#subscribeVetoListener(Pattern,VetoTopicEventListener) */ + public boolean subscribeVetoListener(Pattern topicPattern, VetoTopicEventListener vetoListener) { + if (vetoListener == null) { + throw new IllegalArgumentException("VetoEventListener cannot be null."); + } + if (topicPattern == null) { + throw new IllegalArgumentException("topicPattern cannot be null."); + } + PatternWrapper patternWrapper = new PatternWrapper(topicPattern); + return subscribeVetoListener(patternWrapper, vetoListenersByTopicPattern, new WeakReference(vetoListener)); + } + + /** @see EventService#subscribeVetoListenerStrongly(Class,VetoEventListener) */ + public boolean subscribeVetoListenerStrongly(Class eventClass, VetoEventListener vetoListener) { + if (vetoListener == null) { + throw new IllegalArgumentException("VetoEventListener cannot be null."); + } + if (eventClass == null) { + throw new IllegalArgumentException("eventClass cannot be null."); + } + return subscribeVetoListener(eventClass, vetoListenersByClass, vetoListener); + } + + /** @see EventService#subscribeVetoListenerExactlyStrongly(Class,VetoEventListener) */ + public boolean subscribeVetoListenerExactlyStrongly(Class eventClass, VetoEventListener vetoListener) { + if (vetoListener == null) { + throw new IllegalArgumentException("VetoEventListener cannot be null."); + } + if (eventClass == null) { + throw new IllegalArgumentException("eventClass cannot be null."); + } + return subscribeVetoListener(eventClass, vetoListenersByExactClass, vetoListener); + } + + /** @see EventService#subscribeVetoListenerStrongly(String,VetoTopicEventListener) */ + public boolean subscribeVetoListenerStrongly(String topic, VetoTopicEventListener vetoListener) { + if (vetoListener == null) { + throw new IllegalArgumentException("VetoListener cannot be null."); + } + if (topic == null) { + throw new IllegalArgumentException("topic cannot be null."); + } + return subscribeVetoListener(topic, vetoListenersByTopic, vetoListener); + } + + /** @see EventService#subscribeVetoListenerStrongly(Pattern,VetoTopicEventListener) */ + public boolean subscribeVetoListenerStrongly(Pattern topicPattern, VetoTopicEventListener vetoListener) { + if (vetoListener == null) { + throw new IllegalArgumentException("VetoTopicEventListener cannot be null."); + } + if (topicPattern == null) { + throw new IllegalArgumentException("topicPattern cannot be null."); + } + PatternWrapper patternWrapper = new PatternWrapper(topicPattern); + return subscribeVetoListener(patternWrapper, vetoListenersByTopicPattern, vetoListener); + } + + /** + * All veto subscriptions methods call this method. Extending classes only have to override this method to subscribe + * all veto subscriptions. + * + * @param subscription the topic, Pattern, or event class to subscribe to + * @param vetoListenerMap the internal map of veto listeners to use (by topic of class) + * @param vetoListener the veto listener to subscribe, may be a VetoEventListener or a WeakReference to one + * + * @return boolean if the veto listener is subscribed (was not subscribed). + * + * @throws IllegalArgumentException if vl or o is null + */ + protected boolean subscribeVetoListener(final Object subscription, final Map vetoListenerMap, final Object vetoListener) { + if (LOG.isLoggable(Level.DEBUG)) { + LOG.debug("subscribeVetoListener(" + subscription + "," + vetoListener + ")"); + } + if (vetoListener == null) { + throw new IllegalArgumentException("Can't subscribe null veto listener to " + subscription); + } + if (subscription == null) { + throw new IllegalArgumentException("Can't subscribe veto listener to null."); + } + return subscribe(subscription, vetoListenerMap, vetoListener); + } + + /** + * All subscribe methods call this method, including veto subscriptions. + * Extending classes only have to override this method to subscribe all + * subscriber subscriptions. + *

+ * Overriding this method is only for the adventurous. This basically gives you just enough rope to hang yourself. + *

+ * + * @param classTopicOrPatternWrapper the topic String, event Class, or PatternWrapper to subscribe to + * @param subscriberMap the internal map of subscribers to use (by topic or class) + * @param subscriber the EventSubscriber or EventTopicSubscriber to subscribe, or a WeakReference to either + * + * @return boolean if the subscriber is subscribed (was not subscribed). + * + * @throws IllegalArgumentException if subscriber or topicOrClass is null + */ + protected boolean subscribe(final Object classTopicOrPatternWrapper, final Map subscriberMap, final Object subscriber) { + if (classTopicOrPatternWrapper == null) { + throw new IllegalArgumentException("Can't subscribe to null."); + } + if (subscriber == null) { + throw new IllegalArgumentException("Can't subscribe null subscriber to " + classTopicOrPatternWrapper); + } + boolean alreadyExists = false; + + //Find the real subscriber underlying weak refs and proxies + Object realSubscriber = subscriber; + boolean isWeakRef = subscriber instanceof WeakReference; + if (isWeakRef) { + realSubscriber = ((WeakReference) subscriber).get(); + } + if (realSubscriber instanceof Prioritized) { + hasEverUsedPrioritized = true; + } + boolean isWeakProxySubscriber = false; + if (subscriber instanceof ProxySubscriber) { + ProxySubscriber proxySubscriber = (ProxySubscriber) subscriber; + if (proxySubscriber instanceof Prioritized) { + hasEverUsedPrioritized = true; + } + isWeakProxySubscriber = proxySubscriber.getReferenceStrength() == ReferenceStrength.WEAK; + if (isWeakProxySubscriber) { + realSubscriber = ((ProxySubscriber) subscriber).getProxiedSubscriber(); + } + } + if (isWeakRef && isWeakProxySubscriber) { + throw new IllegalArgumentException("ProxySubscribers should always be subscribed strongly."); + } + if (realSubscriber == null) { + return false;//already garbage collected? Weird. + } + synchronized (listenerLock) { + List currentSubscribers = (List) subscriberMap.get(classTopicOrPatternWrapper); + if (currentSubscribers == null) { + if (LOG.isLoggable(Level.DEBUG)) { + LOG.debug("Creating new subscriber map for:" + classTopicOrPatternWrapper); + } + currentSubscribers = new ArrayList(); + subscriberMap.put(classTopicOrPatternWrapper, currentSubscribers); + } else { + //Double subscription check and stale subscriber cleanup + //Need to compare the underlying referents for WeakReferences and ProxySubscribers + //to make sure a weak ref and a hard ref aren't both subscribed + //to the same topic and object. + //Use the proxied subscriber for comparison if a ProxySubscribers is used + //Subscribing the same object by proxy and subscribing explicitly should + //not subscribe the same object twice + for (Iterator iterator = currentSubscribers.iterator(); iterator.hasNext();) { + Object currentSubscriber = iterator.next(); + Object realCurrentSubscriber = getRealSubscriberAndCleanStaleSubscriberIfNecessary(iterator, currentSubscriber); + if (realSubscriber.equals(realCurrentSubscriber)) { + //Already subscribed. + //Remove temporarily, to add to the end of the calling list + iterator.remove(); + alreadyExists = true; + } + } + } + currentSubscribers.add(subscriber); + if (isWeakProxySubscriber || isWeakRef) { + incWeakRefPlusProxySubscriberCount(); + } + return !alreadyExists; + } + } + + /** @see EventService#unsubscribe(Class,EventSubscriber) */ + public boolean unsubscribe(Class cl, EventSubscriber eh) { + return unsubscribe(cl, subscribersByEventClass, eh); + } + + /** @see EventService#unsubscribeExactly(Class,EventSubscriber) */ + public boolean unsubscribeExactly(Class cl, EventSubscriber eh) { + return unsubscribe(cl, subscribersByExactEventClass, eh); + } + + /** @see EventService#unsubscribe(String,EventTopicSubscriber) */ + public boolean unsubscribe(String name, EventTopicSubscriber eh) { + return unsubscribe(name, subscribersByTopic, eh); + } + + /** @see EventService#unsubscribe(String,EventTopicSubscriber) */ + public boolean unsubscribe(Pattern topicPattern, EventTopicSubscriber eh) { + PatternWrapper patternWrapper = new PatternWrapper(topicPattern); + return unsubscribe(patternWrapper, subscribersByTopicPattern, eh); + } + + /** @see EventService#unsubscribe(Class,Object) */ + public boolean unsubscribe(Class eventClass, Object subscribedByProxy) { + EventSubscriber subscriber = (EventSubscriber) getProxySubscriber(eventClass, subscribedByProxy); + if (subscriber == null) { + return false; + } else { + return unsubscribe(eventClass, subscriber); + } + } + + /** @see EventService#unsubscribeExactly(Class,Object) */ + public boolean unsubscribeExactly(Class eventClass, Object subscribedByProxy) { + EventSubscriber subscriber = (EventSubscriber) getProxySubscriber(eventClass, subscribedByProxy); + if (subscriber == null) { + return false; + } else { + return unsubscribeExactly(eventClass, subscriber); + } + } + + /** @see EventService#unsubscribe(String,Object) */ + public boolean unsubscribe(String topic, Object subscribedByProxy) { + EventTopicSubscriber subscriber = (EventTopicSubscriber) getProxySubscriber(topic, subscribedByProxy); + if (subscriber == null) { + return false; + } else { + return unsubscribe(topic, subscriber); + } + } + + /** @see EventService#unsubscribe(java.util.regex.Pattern,Object) */ + public boolean unsubscribe(Pattern pattern, Object subscribedByProxy) { + EventTopicSubscriber subscriber = (EventTopicSubscriber) getProxySubscriber(pattern, subscribedByProxy); + if (subscriber == null) { + return false; + } else { + return unsubscribe(pattern, subscriber); + } + } + + /** + * All event subscriber unsubscriptions call this method. Extending classes only have to override this method to + * subscribe all subscriber unsubscriptions. + * + * @param o the topic or event class to unsubscribe from + * @param subscriberMap the map of subscribers to use (by topic of class) + * @param subscriber the subscriber to unsubscribe, either an EventSubscriber or an EventTopicSubscriber, or a WeakReference + * to either + * + * @return boolean if the subscriber is unsubscribed (was subscribed). + */ + protected boolean unsubscribe(Object o, Map subscriberMap, Object subscriber) { + if (LOG.isLoggable(Level.DEBUG)) { + LOG.debug("unsubscribe(" + o + "," + subscriber + ")"); + } + if (o == null) { + throw new IllegalArgumentException("Can't unsubscribe to null."); + } + if (subscriber == null) { + throw new IllegalArgumentException("Can't unsubscribe null subscriber to " + o); + } + synchronized (listenerLock) { + return removeFromSetResolveWeakReferences(subscriberMap, o, subscriber); + } + } + + /** @see EventService#unsubscribeVeto(Class,Object) */ + public boolean unsubscribeVeto(Class eventClass, Object subscribedByProxy) { + VetoEventListener subscriber = (VetoEventListener) getVetoProxySubscriber(eventClass, subscribedByProxy); + if (subscriber == null) { + return false; + } else { + return unsubscribeVetoListener(eventClass, subscriber); + } + } + + /** @see EventService#unsubscribeVetoExactly(Class,Object) */ + public boolean unsubscribeVetoExactly(Class eventClass, Object subscribedByProxy) { + VetoEventListener subscriber = (VetoEventListener) getVetoProxySubscriber(eventClass, subscribedByProxy); + if (subscriber == null) { + return false; + } else { + return unsubscribeVetoListenerExactly(eventClass, subscriber); + } + } + + /** @see EventService#unsubscribeVeto(String,Object) */ + public boolean unsubscribeVeto(String topic, Object subscribedByProxy) { + VetoTopicEventListener subscriber = (VetoTopicEventListener) getVetoProxySubscriber(topic, subscribedByProxy); + if (subscriber == null) { + return false; + } else { + return unsubscribeVetoListener(topic, subscriber); + } + } + + /** @see EventService#unsubscribeVeto(java.util.regex.Pattern,Object) */ + public boolean unsubscribeVeto(Pattern pattern, Object subscribedByProxy) { + VetoTopicEventListener subscriber = (VetoTopicEventListener) getVetoProxySubscriber(pattern, subscribedByProxy); + if (subscriber == null) { + return false; + } else { + return unsubscribeVetoListener(pattern, subscriber); + } + } + + /** @see EventService#unsubscribeVetoListener(Class,VetoEventListener) */ + public boolean unsubscribeVetoListener(Class eventClass, VetoEventListener vetoListener) { + return unsubscribeVetoListener(eventClass, vetoListenersByClass, vetoListener); + } + + /** @see EventService#unsubscribeVetoListenerExactly(Class,VetoEventListener) */ + public boolean unsubscribeVetoListenerExactly(Class eventClass, VetoEventListener vetoListener) { + return unsubscribeVetoListener(eventClass, vetoListenersByExactClass, vetoListener); + } + + /** @see EventService#unsubscribeVetoListener(String,VetoTopicEventListener) */ + public boolean unsubscribeVetoListener(String topic, VetoTopicEventListener vetoListener) { + return unsubscribeVetoListener(topic, vetoListenersByTopic, vetoListener); + } + + /** @see EventService#unsubscribeVetoListener(Pattern,VetoTopicEventListener) */ + public boolean unsubscribeVetoListener(Pattern topicPattern, VetoTopicEventListener vetoListener) { + PatternWrapper patternWrapper = new PatternWrapper(topicPattern); + return unsubscribeVetoListener(patternWrapper, vetoListenersByTopicPattern, vetoListener); + } + + /** + * All veto unsubscriptions methods call this method. Extending classes only have to override this method to + * subscribe all veto unsubscriptions. + * + * @param o the topic or event class to unsubscribe from + * @param vetoListenerMap the map of veto listeners to use (by topic or class) + * @param vl the veto listener to unsubscribe, or a WeakReference to one + * + * @return boolean if the veto listener is unsubscribed (was subscribed). + */ + protected boolean unsubscribeVetoListener(Object o, Map vetoListenerMap, Object vl) { + if (LOG.isLoggable(Level.DEBUG)) { + LOG.debug("unsubscribeVetoListener(" + o + "," + vl + ")"); + } + if (o == null) { + throw new IllegalArgumentException("Can't unsubscribe veto listener to null."); + } + if (vl == null) { + throw new IllegalArgumentException("Can't unsubscribe null veto listener to " + o); + } + synchronized (listenerLock) { + return removeFromSetResolveWeakReferences(vetoListenerMap, o, vl); + } + } + + private ProxySubscriber getProxySubscriber(Class eventClass, Object subscribedByProxy) { + List subscribers = getSubscribers(eventClass); + return getProxySubscriber(subscribers, subscribedByProxy); + } + + private ProxySubscriber getProxySubscriber(String topic, Object subscribedByProxy) { + List subscribers = getSubscribers(topic); + return getProxySubscriber(subscribers, subscribedByProxy); + } + + private ProxySubscriber getProxySubscriber(Pattern pattern, Object subscribedByProxy) { + List subscribers = getSubscribersToPattern(pattern); + return getProxySubscriber(subscribers, subscribedByProxy); + } + + private ProxySubscriber getVetoProxySubscriber(Class eventClass, Object subscribedByProxy) { + List subscribers = getVetoSubscribers(eventClass); + return getProxySubscriber(subscribers, subscribedByProxy); + } + + private ProxySubscriber getVetoProxySubscriber(String topic, Object subscribedByProxy) { + List subscribers = getVetoSubscribers(topic); + return getProxySubscriber(subscribers, subscribedByProxy); + } + + private ProxySubscriber getVetoProxySubscriber(Pattern pattern, Object subscribedByProxy) { + List subscribers = getVetoSubscribers(pattern); + return getProxySubscriber(subscribers, subscribedByProxy); + } + + private ProxySubscriber getProxySubscriber(List subscribers, Object subscribedByProxy) { + for (Iterator iter = subscribers.iterator(); iter.hasNext();) { + Object subscriber = iter.next(); + if (subscriber instanceof WeakReference) { + WeakReference wr = (WeakReference) subscriber; + subscriber = wr.get(); + } + if (subscriber instanceof ProxySubscriber) { + ProxySubscriber proxy = (ProxySubscriber) subscriber; + subscriber = proxy.getProxiedSubscriber(); + if (subscriber == subscribedByProxy) { + return proxy; + } + } + } + return null; + } + + /** @see EventService#publish(Object) */ + public void publish(Object event) { + if (event == null) { + throw new IllegalArgumentException("Cannot publish null event."); + } + publish(event, null, null, getSubscribers(event.getClass()), getVetoSubscribers(event.getClass()), null); + } + + /** @see EventService#publish(java.lang.reflect.Type, Object) */ + public void publish(Type genericType, Object event) { + if (genericType == null) { + throw new IllegalArgumentException("genericType must not be null."); + } + if (event == null) { + throw new IllegalArgumentException("Cannot publish null event."); + } + publish(event, null, null, getSubscribers(genericType), null/*getVetoSubscribers(genericType)*/, null); + } + + /** @see EventService#publish(String,Object) */ + public void publish(String topicName, Object eventObj) { + publish(null, topicName, eventObj, getSubscribers(topicName), getVetoEventListeners(topicName), null); + } + + /** + * All publish methods call this method. Extending classes only have to override this method to handle all + * publishing cases. + * + * @param event the event to publish, null if publishing on a topic + * @param topic if publishing on a topic, the topic to publish on, else null + * @param eventObj if publishing on a topic, the eventObj to publish, else null + * @param subscribers the subscribers to publish to - must be a snapshot copy + * @param vetoSubscribers the veto subscribers to publish to - must be a snapshot copy. + * @param callingStack the stack that called this publication, helpful for reporting errors on other threads + * @throws IllegalArgumentException if eh or o is null + */ + protected void publish(final Object event, final String topic, final Object eventObj, + final List subscribers, final List vetoSubscribers, StackTraceElement[] callingStack) { + + if (event == null && topic == null) { + throw new IllegalArgumentException("Can't publish to null topic/event."); + } + + setStatus(PublicationStatus.Initiated, event, topic, eventObj); + //topic or event + logEvent(event, topic, eventObj); + + //Check all veto subscribers, if any veto, then don't publish or cache + if (checkVetoSubscribers(event, topic, eventObj, vetoSubscribers, callingStack)) { + setStatus(PublicationStatus.Vetoed, event, topic, eventObj); + return; + } else { + setStatus(PublicationStatus.Queued, event, topic, eventObj); + } + + addEventToCache(event, topic, eventObj); + + if (subscribers == null || subscribers.isEmpty()) { + if (LOG.isLoggable(Level.DEBUG)) { + LOG.debug("No subscribers for event or topic. Event:" + event + ", Topic:" + topic); + } + } else { + if (LOG.isLoggable(Level.DEBUG)) { + LOG.debug("Publishing to subscribers:" + subscribers); + } + setStatus(PublicationStatus.Publishing, event, topic, eventObj); + for (int i = 0; i < subscribers.size(); i++) { + Object eh = subscribers.get(i); + if (event != null) { + EventSubscriber eventSubscriber = (EventSubscriber) eh; + long start = System.currentTimeMillis(); + try { + eventSubscriber.onEvent(event); + } catch (Throwable e) { + handleException(event, e, callingStack, eventSubscriber); + } + } else { + EventTopicSubscriber eventTopicSubscriber = (EventTopicSubscriber) eh; + try { + eventTopicSubscriber.onEvent(topic, eventObj); + } catch (Throwable e) { + onEventException(topic, eventObj, e, callingStack, eventTopicSubscriber); + } + } + } + } + setStatus(PublicationStatus.Completed, event, topic, eventObj); + } + + /** + * Called during publication to set the status on an event. Can be used by subclasses + * to be notified when an event transitions from one state to another. Implementers + * are required to call setPublicationStatus + * @param status the status to set on the object + * @param event the event being published, will be null if topic is not null + * @param topic the topic eventObj is being published on, will be null if event is not null + * @param eventObj the payload being published on the topic , will be null if event is not null + */ + @SuppressWarnings({"UnusedDeclaration"}) + protected void setStatus(PublicationStatus status, Object event, String topic, Object eventObj) { + if (event instanceof PublicationStatusTracker) { + ((PublicationStatusTracker)event).setPublicationStatus(status); + } + if (eventObj instanceof PublicationStatusTracker) { + ((PublicationStatusTracker)eventObj).setPublicationStatus(status); + } + } + + /** + * Handles subscribers that are Prioritized by putting the most negative prioritized subscribers + * first, the most positive prioritized subscribers last, and leaving non-Prioritized in their + * original FIFO order. + * @param subscribers the subscribers to sort + * @return the same list if there are no prioritized subscribers in the list, otherwise a new sorted result + */ + private List sortSubscribers(List subscribers) { + if (subscribers == null) { + return null; + } + List prioritizedSubscribers = null; + Iterator iterator = subscribers.iterator(); + while (iterator.hasNext()) { + Object subscriber = iterator.next(); + if (subscriber instanceof Prioritized) { + Prioritized prioritized = ((Prioritized)subscriber); + if (prioritized.getPriority() != 0) { + iterator.remove(); + if (prioritizedSubscribers == null) { + prioritizedSubscribers = new ArrayList(); + } + prioritizedSubscribers.add(prioritized); + } + } + } + if (prioritizedSubscribers == null) { + return subscribers; + } else { + List result = new ArrayList(prioritizedSubscribers.size()+subscribers.size()); + Collections.sort(prioritizedSubscribers, PRIORITIZED_SUBSCRIBER_COMPARATOR); + boolean haveAddedFIFOSubscribers = false; + for (Prioritized prioritizedSubscriber : prioritizedSubscribers) { + if (prioritizedSubscriber.getPriority() > 0 && !haveAddedFIFOSubscribers) { + for (Object subscriber : subscribers) { + result.add(subscriber); + } + haveAddedFIFOSubscribers = true; + } + result.add(prioritizedSubscriber); + } + //Issue 26 - of all priorities are negative, then add the FIFO after processing all of them + if (!haveAddedFIFOSubscribers) { + for (Object subscriber : subscribers) { + result.add(subscriber); + } + } + return result; + } + } + + private boolean checkVetoSubscribers(Object event, String topic, Object eventObj, List vetoSubscribers, + StackTraceElement[] callingStack) { + if (vetoSubscribers != null && !vetoSubscribers.isEmpty()) { + for (Iterator vlIter = vetoSubscribers.iterator(); vlIter.hasNext();) { + Object vetoer = vlIter.next(); + VetoEventListener vl = null; + VetoTopicEventListener vtl = null; + if (event == null) { + vtl = (VetoTopicEventListener) vetoer; + } else { + vl = (VetoEventListener) vetoer; + } + long start = System.currentTimeMillis(); + try { + boolean shouldVeto = false; + if (event == null) { + shouldVeto = vtl.shouldVeto(topic, eventObj); + } else { + shouldVeto = vl.shouldVeto(event); + } + if (shouldVeto) { + handleVeto(vl, event, vtl, topic, eventObj); + if (LOG.isLoggable(Level.DEBUG)) { + LOG.debug("Publication vetoed. Event:" + event + ", Topic:" + topic + ", veto subscriber:" + vl); + } + return true; + } + } catch (Throwable ex) { + subscribeVetoException(event, topic, eventObj, ex, callingStack, vl); + } + } + } + return false; + } + + private void logEvent(Object event, String topic, Object eventObj) { + if (LOG.isLoggable(Level.DEBUG)) { + if (event != null) { + LOG.debug("Publishing event: class=" + event.getClass() + ", event=" + event); + } else if (topic != null) { + LOG.debug("Publishing event: topic=" + topic + ", eventObj=" + eventObj); + } + } + } + + /** + * Adds an event to the event cache, if appropriate. This method is called just before publication to listeners, + * after the event passes any veto listeners. + *

+ * Using protected visibility to open the caching to other implementations. + *

+ * + * @param event the event about to be published, null if topic is non-null + * @param topic the topic about to be published to, null if the event is non-null + * @param eventObj the eventObj about to be published on a topic, null if the event is non-null + */ + protected void addEventToCache(Object event, String topic, Object eventObj) { + //Taking the listener lock here, since a listener that is now subscribing will want + //this event since they are not in this subscriber list. + synchronized (listenerLock) { + if (event != null) { + int cacheSizeForEventClass = getCacheSizeForEventClass(event.getClass()); + List eventClassCache = (List) cacheByEvent.get(event.getClass()); + if (cacheSizeForEventClass <= 0) { + if (eventClassCache != null) { + //the cache threshold was lowered to 0 + cacheByEvent.remove(event.getClass()); + } + } else { + if (eventClassCache == null) { + eventClassCache = new LinkedList(); + cacheByEvent.put(event.getClass(), eventClassCache); + } + eventClassCache.add(0, event); + while (eventClassCache.size() > cacheSizeForEventClass) { + eventClassCache.remove(eventClassCache.size() - 1); + } + } + } else { + //topic + int cacheSizeForTopic = getCacheSizeForTopic(topic); + List topicCache = (List) cacheByTopic.get(topic); + if (cacheSizeForTopic <= 0) { + if (topicCache != null) { + //the cache threshold was lowered to 0 + topicCache.remove(topic); + } + } else { + if (topicCache == null) { + topicCache = new LinkedList(); + cacheByTopic.put(topic, topicCache); + } + topicCache.add(0, eventObj); + while (topicCache.size() > cacheSizeForTopic) { + topicCache.remove(topicCache.size() - 1); + } + } + } + } + } + + /** @see EventService#getSubscribers(Class) */ + public List getSubscribers(Class eventClass) { + List hierarchyMatches; + List exactMatches; + synchronized (listenerLock) { + hierarchyMatches = getSubscribersToClass(eventClass); + exactMatches = getSubscribersToExactClass(eventClass); + } + List result = new ArrayList(); + if (exactMatches != null) { + result.addAll(exactMatches); + } + if (hierarchyMatches != null) { + result.addAll(hierarchyMatches); + } + if (hasEverUsedPrioritized) { + result = sortSubscribers(result); + } + return result; + + } + + /** @see EventService#getSubscribersToClass(Class) */ + public List getSubscribersToClass(Class eventClass) { + synchronized (listenerLock) { + Map classMap = subscribersByEventClass; + List result = getEventOrVetoSubscribersToClass(classMap, eventClass); + if (hasEverUsedPrioritized) { + result = sortSubscribers(result); + } + return result; + } + } + + /** @see EventService#getSubscribersToExactClass(Class) */ + public List getSubscribersToExactClass(Class eventClass) { + synchronized (listenerLock) { + return getSubscribers(eventClass, subscribersByExactEventClass); + } + } + + /** @see EventService#getSubscribers(Type) */ + public List getSubscribers(Type eventType) { + List result; + synchronized (listenerLock) { + result = getEventOrVetoSubscribersToType(subscribersByEventType, eventType); + } + if (hasEverUsedPrioritized) { + result = sortSubscribers(result); + } + return result; + } + + /** @see EventService#getSubscribers(String) */ + public List getSubscribers(String topic) { + List result = new ArrayList(); + List exactMatches; + List patternMatches; + synchronized (listenerLock) { + exactMatches = getSubscribersToTopic(topic); + patternMatches = getSubscribersByPattern(topic); + } + if (exactMatches != null) { + result.addAll(exactMatches); + } + if (patternMatches != null) { + result.addAll(patternMatches); + } + if (hasEverUsedPrioritized) { + result = sortSubscribers(result); + } + return result; + } + + /** @see EventService#getSubscribersToTopic(String) */ + public List getSubscribersToTopic(String topic) { + synchronized (listenerLock) { + return getSubscribers(topic, subscribersByTopic); + } + } + + /** @see EventService#getSubscribers(Pattern) */ + public List getSubscribers(Pattern pattern) { + synchronized (listenerLock) { + return getSubscribers(pattern, subscribersByTopicPattern); + } + } + + /** @see EventService#getSubscribersByPattern(String) */ + public List getSubscribersByPattern(String topic) { + return getSubscribersByPattern(topic, subscribersByTopicPattern); + } + + /** @see EventService#getVetoSubscribers(Class) */ + public List getVetoSubscribers(Class eventClass) { + List result = new ArrayList(); + List exactMatches; + List hierarchyMatches; + synchronized (listenerLock) { + exactMatches = getVetoSubscribersToClass(eventClass); + hierarchyMatches = getVetoSubscribersToExactClass(eventClass); + } + if (exactMatches != null) { + result.addAll(exactMatches); + } + if (hierarchyMatches != null) { + result.addAll(hierarchyMatches); + } + if (hasEverUsedPrioritized) { + result = sortSubscribers(result); + } + return result; + } + + /** @see EventService#getVetoSubscribersToClass(Class) */ + public List getVetoSubscribersToClass(Class eventClass) { + List result; + synchronized (listenerLock) { + Map classMap = vetoListenersByClass; + result = getEventOrVetoSubscribersToClass(classMap, eventClass); + } + if (hasEverUsedPrioritized) { + result = sortSubscribers(result); + } + return result; + } + + /** @see EventService#getVetoSubscribersToExactClass(Class) */ + public List getVetoSubscribersToExactClass(Class eventClass) { + synchronized (listenerLock) { + return getSubscribers(eventClass, vetoListenersByExactClass); + } + } + + /** @see EventService#getVetoEventListeners(String) */ + public List getVetoEventListeners(String topicOrPattern) { + List result = new ArrayList(); + List exactMatches; + List patternMatches; + synchronized (listenerLock) { + exactMatches = getVetoSubscribersToTopic(topicOrPattern); + patternMatches = getVetoSubscribersByPattern(topicOrPattern); + } + if (exactMatches != null) { + result.addAll(exactMatches); + } + if (patternMatches != null) { + result.addAll(patternMatches); + } + if (hasEverUsedPrioritized) { + result = sortSubscribers(result); + } + return result; + } + + /** @see EventService#getVetoSubscribersToTopic(String) */ + public List getVetoSubscribersToTopic(String topic) { + synchronized (listenerLock) { + return getSubscribers(topic, vetoListenersByTopic); + } + } + + /** + * Note: this is inconsistent with getSubscribers(String) + * @see EventService#getVetoSubscribersToTopic(String) + * @deprecated use getVetoSubscribersToTopic instead for direct replacement, + * or use getVetoEventListeners to get topic and pattern matchers. + * In EventBus 2.0 this name will replace getVetoEventListeners() + * and have it's union functionality + */ + public List getVetoSubscribers(String topic) { + synchronized (listenerLock) { + return getVetoSubscribersToTopic(topic); + } + } + + /** @see EventService#getVetoSubscribers(Pattern) */ + public List getVetoSubscribers(Pattern topicPattern) { + synchronized (listenerLock) { + PatternWrapper patternWrapper = new PatternWrapper(topicPattern); + return getSubscribers(patternWrapper, vetoListenersByTopicPattern); + } + } + + /** @see EventService#getVetoSubscribersByPattern(String) */ + public List getVetoSubscribersByPattern(String pattern) { + return getSubscribersByPattern(pattern, vetoListenersByTopicPattern); + } + + /** Used for subscribers and veto subscribers */ + private List getSubscribersByPattern(String topic, Map subscribersByTopicPattern) { + List result = new ArrayList(); + synchronized (listenerLock) { + Set keys = subscribersByTopicPattern.keySet(); + for (Iterator iterator = keys.iterator(); iterator.hasNext();) { + PatternWrapper patternKey = (PatternWrapper) iterator.next(); + if (patternKey.matches(topic)) { + if (LOG.isLoggable(Level.DEBUG)) { + LOG.debug("Pattern " + patternKey + " matched topic name " + topic); + } + Collection subscribers = (Collection) subscribersByTopicPattern.get(patternKey); + result.addAll(createCopyOfContentsRemoveWeakRefs(subscribers)); + } + } + if (hasEverUsedPrioritized) { + result = sortSubscribers(result); + } + return result; + } + } + + protected List getSubscribersToPattern(Pattern topicPattern) { + synchronized (listenerLock) { + PatternWrapper patternWrapper = new PatternWrapper(topicPattern); + return getSubscribers(patternWrapper, subscribersByTopicPattern); + } + } + + private List getSubscribers(Object classOrTopic, Map subscriberMap) { + List result; + synchronized (listenerLock) { + List subscribers = (List) subscriberMap.get(classOrTopic); + //Make a defensive copy of subscribers and veto listeners so listeners + //can change the listener list while the listeners are being called + //Resolve WeakReferences and unsubscribe if necessary. + result = createCopyOfContentsRemoveWeakRefs(subscribers); + } + if (hasEverUsedPrioritized) { + result = sortSubscribers(result); + } + return result; + } + + private List getEventOrVetoSubscribersToClass(Map classMap, Class eventClass) { + List result = new ArrayList(); + Set keys = classMap.keySet(); + for (Iterator iterator = keys.iterator(); iterator.hasNext();) { + Class cl = (Class) iterator.next(); + if (cl.isAssignableFrom(eventClass)) { + if (LOG.isLoggable(Level.DEBUG)) { + LOG.debug("Hierarchical match " + cl + " matched event of class " + eventClass); + } + Collection subscribers = (Collection) classMap.get(cl); + result.addAll(createCopyOfContentsRemoveWeakRefs(subscribers)); + } + } + return result; + } + + private List getEventOrVetoSubscribersToType(Map typeMap, Type eventType) { + List result = new ArrayList(); + Set mapKeySet = typeMap.keySet(); + for (Object mapKey : mapKeySet) { + Type subscriberType = (Type) mapKey; + if (eventType instanceof ParameterizedType && subscriberType instanceof ParameterizedType) { + ParameterizedType subscriberPT = (ParameterizedType) subscriberType; + ParameterizedType eventPT = (ParameterizedType) eventType; + if (eventPT.getRawType().equals(subscriberPT.getRawType())) { + Type[] mapTypeArgs = subscriberPT.getActualTypeArguments(); + Type[] eventTypeArgs = eventPT.getActualTypeArguments(); + if (mapTypeArgs == null || eventTypeArgs == null || mapTypeArgs.length != eventTypeArgs.length) { + continue; + } + boolean parameterArgsMatch = true; + for (int argCount = 0; argCount < mapTypeArgs.length; argCount++) { + Type eventTypeArg = eventTypeArgs[argCount]; + if (eventTypeArg instanceof WildcardType) { + throw new IllegalArgumentException("Only simple Class parameterized types can be published, not wildcards, etc. Published attempt made for:"+eventTypeArg); + } + Type subscriberTypeArg = mapTypeArgs[argCount]; + if (subscriberTypeArg instanceof WildcardType) { + WildcardType wildcardSubscriberTypeArg = (WildcardType) subscriberTypeArg; + Type[] upperBound = wildcardSubscriberTypeArg.getUpperBounds(); + Type[] lowerBound = wildcardSubscriberTypeArg.getLowerBounds(); + if (upperBound != null && upperBound.length > 0) { + if (upperBound[0] instanceof Class) { + Class upper = (Class) upperBound[0]; + if (eventTypeArg instanceof Class) { + if (!upper.isAssignableFrom((Class) eventTypeArg)) { + parameterArgsMatch = false; + break; + } + } else { + parameterArgsMatch = false; + break; + } + } else { + throw new IllegalArgumentException("Only Class and Interface types are supported as types of wildcard subscriptions. Type:"+upperBound[0]); + } + } + if (lowerBound != null && lowerBound.length > 0) { + if (lowerBound[0] instanceof Class) { + Class lower = (Class) lowerBound[0]; + if (eventTypeArg instanceof Class) { + if (!((Class)eventTypeArg).isAssignableFrom(lower)) { + parameterArgsMatch = false; + break; + } + } else { + parameterArgsMatch = false; + break; + } + } else { + throw new IllegalArgumentException("Only Class and Interface types are supported as types of wildcard subscriptions. Type:"+upperBound[0]); + } + } + } else if (!subscriberTypeArg.equals(eventTypeArg)) { + parameterArgsMatch = false; + break; + } + } + if (parameterArgsMatch) { + if (LOG.isLoggable(Level.DEBUG)) { + LOG.debug("Exact parameterized subscriberType match for event subscriberType " + eventType); + } + Collection subscribers = (Collection) typeMap.get(subscriberType); + if (subscribers != null) { + result.addAll(createCopyOfContentsRemoveWeakRefs(subscribers)); + } + } + } + } + } + return result; +// Type o = p.getOwnerType(); +// if (o != null) { +// +// } +// p.getActualTypeArguments(); +// } + /* + } else if (type instanceof TypeVariable) { + TypeVariable v = (TypeVariable)type; + out.print(v.getName()); + } else if (type instanceof GenericArrayType) { + GenericArrayType a = (GenericArrayType)type; + printType(a.getGenericComponentType()); + out.print("[]"); + } else if (type instanceof WildcardType) { + WildcardType w = (WildcardType)type; + Type[] upper = w.getUpperBounds(); + Type[] lower = w.getLowerBounds(); + if (upper.length==1 && lower.length==0) { + out.print("? extends "); + printType(upper[0]); + } else if (upper.length==0 && lower.length==1) { + out.print("? super "); + printType(lower[0]); + } else assert false; + } + */ + } + + /** + * Handle vetos of an event or topic, by default logs finely. + * + * @param vl the veto listener for an event + * @param event the event, can be null if topic is not + * @param vtl the veto listener for a topic + * @param topic can be null if event is not + * @param eventObj the object published with the topic + */ + protected void handleVeto(VetoEventListener vl, Object event, + VetoTopicEventListener vtl, String topic, Object eventObj) { + if (LOG.isLoggable(Level.DEBUG)) { + if (event != null) { + LOG.debug("Vetoing event: class=" + event.getClass() + ", event=" + event + ", vetoer:" + vl); + } else { + LOG.debug("Vetoing event: topic=" + topic + ", eventObj=" + eventObj + ", vetoer:" + vtl); + } + } + } + + /** + * Given a Map (of Lists of subscribers or veto listeners), removes the toRemove element from the List in the map for + * the given key. The entire map is checked for WeakReferences and ProxySubscribers and they are all unsubscribed + * if stale. + * + * @param map map of lists + * @param key key for a List in the map + * @param toRemove the object to remove form the list with the key of the map + * + * @return true if toRemove was unsubscribed + */ + private boolean removeFromSetResolveWeakReferences(Map map, Object key, Object toRemove) { + List subscribers = (List) map.get(key); + if (subscribers == null) { + return false; + } + if (subscribers.remove(toRemove)) { + if (toRemove instanceof WeakReference) { + decWeakRefPlusProxySubscriberCount(); + } + if (toRemove instanceof ProxySubscriber) { + ((ProxySubscriber)toRemove).proxyUnsubscribed(); + decWeakRefPlusProxySubscriberCount(); + } + return true; + } + + //search for WeakReferences and ProxySubscribers + for (Iterator iter = subscribers.iterator(); iter.hasNext();) { + Object existingSubscriber = iter.next(); + if (existingSubscriber instanceof ProxySubscriber) { + ProxySubscriber proxy = (ProxySubscriber) existingSubscriber; + existingSubscriber = proxy.getProxiedSubscriber(); + if (existingSubscriber == toRemove) { + removeProxySubscriber(proxy, iter); + return true; + } + } + if (existingSubscriber instanceof WeakReference) { + WeakReference wr = (WeakReference) existingSubscriber; + Object realRef = wr.get(); + if (realRef == null) { + //clean up a garbage collected reference + iter.remove(); + decWeakRefPlusProxySubscriberCount(); + return true; + } else if (realRef == toRemove) { + iter.remove(); + decWeakRefPlusProxySubscriberCount(); + return true; + } else if (realRef instanceof ProxySubscriber) { + ProxySubscriber proxy = (ProxySubscriber) realRef; + existingSubscriber = proxy.getProxiedSubscriber(); + if (existingSubscriber == toRemove) { + removeProxySubscriber(proxy, iter); + return true; + } + } + } + } + return false; + } + + /** + * Given a set (or subscribers or veto listeners), makes a copy of the set, resolving WeakReferences to hard + * references, and removing garbage collected references from the original set. + * + * @param subscribersOrVetoListeners + * + * @return a copy of the set + */ + private List createCopyOfContentsRemoveWeakRefs(Collection subscribersOrVetoListeners) { + if (subscribersOrVetoListeners == null) { + return null; + } + List copyOfSubscribersOrVetolisteners = new ArrayList(subscribersOrVetoListeners.size()); + for (Iterator iter = subscribersOrVetoListeners.iterator(); iter.hasNext();) { + Object elem = iter.next(); + if (elem instanceof ProxySubscriber) { + ProxySubscriber proxy = (ProxySubscriber)elem; + elem = proxy.getProxiedSubscriber(); + if (elem == null) { + removeProxySubscriber(proxy, iter); + } else { + copyOfSubscribersOrVetolisteners.add(proxy); + } + } else if (elem instanceof WeakReference) { + Object hardRef = ((WeakReference) elem).get(); + if (hardRef == null) { + //Was reclaimed, unsubscribe + iter.remove(); + decWeakRefPlusProxySubscriberCount(); + } else { + copyOfSubscribersOrVetolisteners.add(hardRef); + } + } else { + copyOfSubscribersOrVetolisteners.add(elem); + } + } + return copyOfSubscribersOrVetolisteners; + } + + /** + * Sets the default cache size for each kind of event, default is 0 (no caching). + *

+ * If this value is set to a positive number, then when an event is published, the EventService caches the event or + * topic payload data for later retrieval. This allows subscribers to find out what has most recently happened + * before they subscribed. The cached event(s) are returned from #getLastEvent(Class), #getLastTopicData(String), + * #getCachedEvents(Class), or #getCachedTopicData(String) + *

+ *

+ * The default can be overridden on a by-event-class or by-topic basis. + *

+ * + * @param defaultCacheSizePerClassOrTopic + */ + public void setDefaultCacheSizePerClassOrTopic(int defaultCacheSizePerClassOrTopic) { + synchronized (cacheLock) { + this.defaultCacheSizePerClassOrTopic = defaultCacheSizePerClassOrTopic; + } + } + + /** @return the default number of event payloads kept per event class or topic */ + public int getDefaultCacheSizePerClassOrTopic() { + synchronized (cacheLock) { + return defaultCacheSizePerClassOrTopic; + } + } + + /** + * Set the number of events cached for a particular class of event. By default, no events are cached. + *

+ * This overrides any setting for the DefaultCacheSizePerClassOrTopic. + *

+ *

+ * Class hierarchy semantics are respected. That is, if there are three events, A, X and Y, and X and Y are both + * derived from A, then setting the cache size for A applies the cache size for all three. Setting the cache size + * for X applies to X and leaves the settings for A and Y in tact. Interfaces can be passed to this method, but they + * only take effect if the cache size of a class or it's superclasses has been set. Just like Class.getInterfaces(), + * if multiple cache sizes are set, the interface names declared earliest in the implements clause of the eventClass + * takes effect. + *

+ *

+ * The cache for an event is not adjusted until the next event of that class is published. + *

+ * + * @param eventClass the class of event + * @param cacheSize the number of published events to cache for this event + */ + public void setCacheSizeForEventClass(Class eventClass, int cacheSize) { + synchronized (cacheLock) { + if (rawCacheSizesForEventClass == null) { + rawCacheSizesForEventClass = new HashMap(); + } + rawCacheSizesForEventClass.put(eventClass, new Integer(cacheSize)); + rawCacheSizesForEventClassChanged = true; + } + } + + /** + * Returns the number of events cached for a particular class of event. By default, no events are cached. + *

+ * This result is computed for a particular class from the values passed to #setCacheSizeForEventClass(Class, int), + * and respects the class hierarchy. + *

+ * + * @param eventClass the class of event + * + * @return the maximum size of the event cache for the given event class + * + * @see #setCacheSizeForEventClass(Class,int) + */ + public int getCacheSizeForEventClass(Class eventClass) { + if (eventClass == null) { + throw new IllegalArgumentException("eventClass must not be null."); + } + synchronized (cacheLock) { + if (rawCacheSizesForEventClass == null || rawCacheSizesForEventClass.size() == 0) { + return getDefaultCacheSizePerClassOrTopic(); + } + if (cacheSizesForEventClass == null) { + cacheSizesForEventClass = new HashMap(); + } + if (rawCacheSizesForEventClassChanged) { + cacheSizesForEventClass.clear(); + cacheSizesForEventClass.putAll(rawCacheSizesForEventClass); + rawCacheSizesForEventClassChanged = false; + } + + //Has this been computed yet or set directly? + Integer size = (Integer) cacheSizesForEventClass.get(eventClass); + if (size != null) { + return size.intValue(); + } else { + //must be computed + Class parent = eventClass.getSuperclass(); + while (parent != null) { + Integer parentSize = (Integer) cacheSizesForEventClass.get(parent); + if (parentSize != null) { + cacheSizesForEventClass.put(eventClass, parentSize); + return parentSize.intValue(); + } + parent = parent.getSuperclass(); + } + //try interfaces + Class[] interfaces = eventClass.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + Class anInterface = interfaces[i]; + Integer interfaceSize = (Integer) cacheSizesForEventClass.get(anInterface); + if (interfaceSize != null) { + cacheSizesForEventClass.put(eventClass, interfaceSize); + return interfaceSize.intValue(); + } + } + } + return getDefaultCacheSizePerClassOrTopic(); + } + } + + /** + * Set the number of published data objects cached for a particular event topic. By default, no caching is done. + *

+ * This overrides any setting for the DefaultCacheSizePerClassOrTopic. + *

+ *

+ * Settings for exact topic names take precedence over pattern matching. + *

+ *

+ * The cache for a topic is not adjusted until the next publication on that topic. + *

+ * + * @param topicName the topic name + * @param cacheSize the number of published data Objects to cache for this topic + */ + public void setCacheSizeForTopic(String topicName, int cacheSize) { + synchronized (cacheLock) { + if (rawCacheSizesForTopic == null) { + rawCacheSizesForTopic = new HashMap(); + } + rawCacheSizesForTopic.put(topicName, new Integer(cacheSize)); + rawCacheSizesForTopicChanged = true; + } + } + + /** + * Set the number of published data objects cached for topics matching a pattern. By default, caching is done. + *

+ * This overrides any setting for the DefaultCacheSizePerClassOrTopic. + *

+ *

+ * Settings for exact topic names take precedence over pattern matching. If a topic matches the cache settings for + * more than one pattern, the cache size chosen is an undetermined one from one of the matched pattern settings. + *

+ *

+ * The cache for a topic is not adjusted until the next publication on that topic. + *

+ * + * @param pattern the pattern matching topic names + * @param cacheSize the number of data Objects to cache for this topic + */ + public void setCacheSizeForTopic(Pattern pattern, int cacheSize) { + synchronized (cacheLock) { + if (rawCacheSizesForPattern == null) { + rawCacheSizesForPattern = new HashMap(); + } + PatternWrapper patternWrapper = new PatternWrapper(pattern); + rawCacheSizesForPattern.put(patternWrapper, new Integer(cacheSize)); + rawCacheSizesForPatternChanged = true; + } + } + + /** + * Returns the number of cached data objects published on a particular topic. By default, no caching is performed. + *

+ * This result is computed for a particular topic from the values passed to #setCacheSizeForTopic(String, int) and + * #setCacheSizeForTopic(Pattern, int). + *

+ * + * @param topic the topic name + * + * @return the maximum size of the data Object cache for the given topic + * + * @see #setCacheSizeForTopic(String,int) + * @see #setCacheSizeForTopic(java.util.regex.Pattern,int) + */ + public int getCacheSizeForTopic(String topic) { + if (topic == null) { + throw new IllegalArgumentException("topic must not be null."); + } + synchronized (cacheLock) { + if ((rawCacheSizesForTopic == null || (rawCacheSizesForTopic != null && rawCacheSizesForTopic.size() == 0)) && + (rawCacheSizesForPattern == null || (rawCacheSizesForPattern != null && rawCacheSizesForPattern.size() == 0))) { + return getDefaultCacheSizePerClassOrTopic(); + } + if (cacheSizesForTopic == null) { + cacheSizesForTopic = new HashMap(); + } + if (rawCacheSizesForTopicChanged || rawCacheSizesForPatternChanged) { + cacheSizesForTopic.clear(); + cacheSizesForTopic.putAll(rawCacheSizesForTopic); + rawCacheSizesForTopicChanged = false; + rawCacheSizesForPatternChanged = false; + } + + //Is this an exact match or has it been matched to a pattern yet? + Integer size = cacheSizesForTopic.get(topic); + if (size != null) { + return size; + } else { + //try matching patterns + if (rawCacheSizesForPattern != null) { + Set patterns = rawCacheSizesForPattern.keySet(); + for (Iterator iterator = patterns.iterator(); iterator.hasNext();) { + PatternWrapper pattern = (PatternWrapper) iterator.next(); + if (pattern.matches(topic)) { + size = rawCacheSizesForPattern.get(pattern); + cacheSizesForTopic.put(topic, size); + return size; + } + } + } + } + return getDefaultCacheSizePerClassOrTopic(); + } + } + + /** + * @param eventClass an index into the cache, cannot be an interface + * + * @return the last event published for this event class, or null if caching is turned off (the default) + */ + public Object getLastEvent(Class eventClass) { + if (eventClass.isInterface()) { + throw new IllegalArgumentException("Interfaces are not accepted in get last event, use a specific event class."); + } + synchronized (cacheLock) { + List eventCache = cacheByEvent.get(eventClass); + if (eventCache == null || eventCache.size() == 0) { + return null; + } + return eventCache.get(0); + } + } + + /** + * @param eventClass an index into the cache, cannot be an interface + * + * @return the last events published for this event class, or null if caching is turned off (the default) + */ + public List getCachedEvents(Class eventClass) { + if (eventClass.isInterface()) { + throw new IllegalArgumentException("Interfaces are not accepted in get last event, use a specific event class."); + } + synchronized (cacheLock) { + List eventCache = cacheByEvent.get(eventClass); + if (eventCache == null || eventCache.size() == 0) { + return null; + } + return eventCache; + } + } + + /** + * @param topic an index into the cache + * + * @return the last data Object published on this topic, or null if caching is turned off (the default) + */ + public Object getLastTopicData(String topic) { + synchronized (cacheLock) { + List topicCache = cacheByTopic.get(topic); + if (topicCache == null || topicCache.size() == 0) { + return null; + } + return topicCache.get(0); + } + } + + /** + * @param topic an index into the cache + * + * @return the last data Objects published on this topic, or null if caching is turned off (the default) + */ + public List getCachedTopicData(String topic) { + synchronized (cacheLock) { + List topicCache = cacheByTopic.get(topic); + if (topicCache == null || topicCache.size() == 0) { + return null; + } + return topicCache; + } + } + + /** + * Clears the event cache for a specific event class or interface and it's any of it's subclasses or implementing + * classes. + * + * @param eventClassToClear the event class to clear the cache for + */ + public void clearCache(Class eventClassToClear) { + synchronized (cacheLock) { + Set classes = cacheByEvent.keySet(); + for (Iterator iterator = classes.iterator(); iterator.hasNext();) { + Class cachedClass = (Class) iterator.next(); + if (eventClassToClear.isAssignableFrom(cachedClass)) { + iterator.remove(); + } + } + } + } + + /** + * Clears the topic data cache for a specific topic name. + * + * @param topic the topic name to clear the cache for + */ + public void clearCache(String topic) { + synchronized (cacheLock) { + cacheByTopic.remove(topic); + } + } + + /** + * Clears the topic data cache for all topics that match a particular pattern. + * + * @param pattern the pattern to match topic caches to + */ + public void clearCache(Pattern pattern) { + synchronized (cacheLock) { + Set classes = cacheByTopic.keySet(); + for (Iterator iterator = classes.iterator(); iterator.hasNext();) { + String cachedTopic = (String) iterator.next(); + if (pattern.matcher(cachedTopic).matches()) { + iterator.remove(); + } + } + } + } + + /** Clear all event caches for all topics and event. */ + public void clearCache() { + synchronized (cacheLock) { + cacheByEvent.clear(); + cacheByTopic.clear(); + } + } + + /** Called during veto exceptions, calls handleException */ + protected void subscribeVetoException(final Object event, final String topic, final Object eventObj, + Throwable e, StackTraceElement[] callingStack, VetoEventListener vetoer) { + String str = "EventService veto event listener r:" + vetoer; + if (vetoer != null) { + str = str + ". Vetoer class:" + vetoer.getClass(); + } + handleException("vetoing", event, topic, eventObj, e, callingStack, str); + } + + /** Called during event handling exceptions, calls handleException */ + protected void onEventException(final String topic, final Object eventObj, Throwable e, + StackTraceElement[] callingStack, EventTopicSubscriber eventTopicSubscriber) { + String str = "EventService topic subscriber:" + eventTopicSubscriber; + if (eventTopicSubscriber != null) { + str = str + ". Subscriber class:" + eventTopicSubscriber.getClass(); + } + handleException("handling event", null, topic, eventObj, e, callingStack, str); + } + + /** Called during event handling exceptions, calls handleException */ + protected void handleException(final Object event, Throwable e, + StackTraceElement[] callingStack, EventSubscriber eventSubscriber) { + String str = "EventService subscriber:" + eventSubscriber; + if (eventSubscriber != null) { + str = str + ". Subscriber class:" + eventSubscriber.getClass(); + } + handleException("handling event topic", event, null, null, e, callingStack, str); + } + + /** + * All exception handling goes through this method. Logs a warning by default. + */ + protected void handleException(final String action, final Object event, final String topic, + final Object eventObj, Throwable e, StackTraceElement[] callingStack, String sourceString) { + String eventClassString = (event == null ? "none" : event.getClass().getName()); + String eventString = event + ""; + String contextMsg = "Exception " + action + " event class=" + eventClassString + + ", event=" + eventString + ", topic=" + topic + ", eventObj=" + eventObj; + SwingException clientEx = new SwingException(contextMsg, e, callingStack); + String msg = "Exception thrown by;" + sourceString; + LOG.log(Level.WARN, msg, clientEx); + } + + /** + * Unsubscribe a subscriber if it is a stale ProxySubscriber. Used during subscribe() and + * in the cleanup Timer. See the class javadoc. + *

+ * Not private since I don't claim I'm smart enough to anticipate all needs, but I + * am smart enough to doc the rules you must follow to override this method. Those + * rules may change (changes will be doc'ed), override at your own risk. + *

+ *

+ * Overriders MUST call iterator.remove() to unsubscribe the proxy if the subscriber is + * a ProxySubscriber and is stale and should be cleaned up. If the ProxySubscriber + * is unsubscribed, then implementers MUST also call proxyUnsubscribed() on the subscriber. + * Overriders MUST also remove the proxy from the weakProxySubscriber list by calling + * removeStaleProxyFromList. Method assumes caller is holding the listenerList + * lock (else how can you pass the iterator?). + *

+ * @param iterator current iterator + * @param existingSubscriber the current value of the iterator + * @return the real value of the param, or the proxied subscriber of the param if + * the param is a a ProxySubscriber + */ + protected Object getRealSubscriberAndCleanStaleSubscriberIfNecessary(Iterator iterator, Object existingSubscriber) { + ProxySubscriber existingProxySubscriber = null; + if (existingSubscriber instanceof WeakReference) { + existingSubscriber = ((WeakReference) existingSubscriber).get(); + if (existingSubscriber == null) { + iterator.remove(); + decWeakRefPlusProxySubscriberCount(); + } + } + if (existingSubscriber instanceof ProxySubscriber) { + existingProxySubscriber = (ProxySubscriber) existingSubscriber; + existingSubscriber = existingProxySubscriber.getProxiedSubscriber(); + if (existingProxySubscriber == null) { + removeProxySubscriber(existingProxySubscriber, iterator); + } + } + return existingSubscriber; + } + + protected void removeProxySubscriber(ProxySubscriber proxy, Iterator iter) { + iter.remove(); + proxy.proxyUnsubscribed(); + decWeakRefPlusProxySubscriberCount(); + } + + /** + * Increment the count of stale proxies and start a cleanup task if necessary + */ + protected void incWeakRefPlusProxySubscriberCount() { + synchronized(listenerLock) { + weakRefPlusProxySubscriberCount++; + if (cleanupStartThreshhold == null || cleanupPeriodMS == null) { + return; + } + if (weakRefPlusProxySubscriberCount >= cleanupStartThreshhold) { + startCleanup(); + } + } + } + + /** + * Decrement the count of stale proxies + */ + protected void decWeakRefPlusProxySubscriberCount() { + synchronized(listenerLock) { + weakRefPlusProxySubscriberCount--; + if (weakRefPlusProxySubscriberCount < 0) { + weakRefPlusProxySubscriberCount = 0; + } + } + } + + private void startCleanup() { + synchronized(listenerLock) { + if (cleanupTimer == null) { + cleanupTimer = new Timer(true); + } + if (cleanupTimerTask == null) { + cleanupTimerTask = new CleanupTimerTask(); + cleanupTimer.schedule(cleanupTimerTask, 0L, cleanupPeriodMS); + } + } + } + + class CleanupTimerTask extends TimerTask { + @Override + public void run() { + synchronized(listenerLock) { + if (weakRefPlusProxySubscriberCount <= cleanupStopThreshold) { + this.cancel(); + cleanupTimer = null; + cleanupTimerTask = null; + LOG.debug("Cancelled scheduled weak reference and proxy cleanup."); + return; + } + LOG.debug("Starting a weak reference and proxy cleanup."); + List allSubscriberMaps = new ArrayList(); + allSubscriberMaps.add(subscribersByEventType); + allSubscriberMaps.add(subscribersByEventClass); + allSubscriberMaps.add(subscribersByExactEventClass); + allSubscriberMaps.add(subscribersByTopic); + allSubscriberMaps.add(subscribersByTopicPattern); + allSubscriberMaps.add(vetoListenersByClass); + allSubscriberMaps.add(vetoListenersByExactClass); + allSubscriberMaps.add(vetoListenersByTopic); + allSubscriberMaps.add(vetoListenersByTopicPattern); + + int staleCount = 0; + for (Map subscriberMap : allSubscriberMaps) { + Set subscriptions = subscriberMap.keySet(); + for (Object subscription : subscriptions) { + List subscribers = (List) subscriberMap.get(subscription); + for (Iterator iter = subscribers.iterator(); iter.hasNext();) { + Object subscriber = iter.next(); + Object realSubscriber = getRealSubscriberAndCleanStaleSubscriberIfNecessary(iter, subscriber); + if (realSubscriber == null) { + staleCount++; + } + } + } + } + } + } + } + + private static class PrioritizedSubscriberComparator implements Comparator { + public int compare(Prioritized prioritized1, Prioritized prioritized2) { + if (prioritized1 == null) { + return -1; + } + if (prioritized2 == null) { + return 1; + } + if (prioritized1.getPriority() < prioritized2.getPriority()) { + return -1; + } else if (prioritized1.getPriority() > prioritized2.getPriority()) { + return 1; + } else { + return 0; + } + } + } + + /** + * Since Pattern doesn't implement equals(), we need one of these + */ + private class PatternWrapper { + private Pattern pattern; + + public PatternWrapper(Pattern pat) { + pattern = pat; + } + + public boolean matches(CharSequence input) { + return pattern.matcher(input).matches(); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + PatternWrapper that = (PatternWrapper) o; + + if (pattern != null) { + if (!pattern.equals(that.pattern)) {//give the JVM a shot for forward compatibility + return pattern.pattern() != null && this.pattern.pattern().equals(this.pattern.pattern()); + } + } else { + if (that.pattern != null) { + return false; + } + } + + return true; + } + + public int hashCode() { + if (this.pattern != null && this.pattern.pattern() != null) { + return this.pattern.pattern().hashCode(); + } + return (pattern != null ? pattern.hashCode() : 0); + } + } +} diff --git a/src/main/java/org/scijava/event/bushe/TypeReference.java b/src/main/java/org/scijava/event/bushe/TypeReference.java new file mode 100644 index 000000000..06ff73a8c --- /dev/null +++ b/src/main/java/org/scijava/event/bushe/TypeReference.java @@ -0,0 +1,52 @@ +package org.scijava.event.bushe; + +import java.lang.reflect.Type; +import java.lang.reflect.Constructor; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.InvocationTargetException; + +/** + * Courtesy of Neil Gafter's blog. + * Thanks to Curt Cox for the pointer. + */ +abstract class TypeReference { + + private final Type type; + private volatile Constructor constructor; + + protected TypeReference() { + Type superclass = getClass().getGenericSuperclass(); + if (superclass instanceof Class) { + throw new RuntimeException("Missing type parameter."); + } + this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0]; + } + + /** + * @return a new instance of {@code T} using the default, no-arg + * constructor. + * @throws IllegalAccessException on security reflection issues + * @throws NoSuchMethodException there's not getRawType on the type + * @throws java.lang.reflect.InvocationTargetException if a reflective call causes an exception in the underlying instance + * @throws InstantiationException if the instance cannot be instantiated + */ + @SuppressWarnings("unchecked") + public T newInstance() + throws NoSuchMethodException, IllegalAccessException, + InvocationTargetException, InstantiationException { + if (constructor == null) { + Class rawType = type instanceof Class + ? (Class) type + : (Class) ((ParameterizedType) type).getRawType(); + constructor = rawType.getConstructor(); + } + return (T) constructor.newInstance(); + } + + /** + * @return the referenced type. + */ + public Type getType() { + return this.type; + } +} diff --git a/src/main/java/org/scijava/event/bushe/VetoEventListener.java b/src/main/java/org/scijava/event/bushe/VetoEventListener.java new file mode 100644 index 000000000..4b377c1c1 --- /dev/null +++ b/src/main/java/org/scijava/event/bushe/VetoEventListener.java @@ -0,0 +1,39 @@ +/** + * Copyright 2005 Bushe Enterprises, Inc., Hopkinton, MA, USA, www.bushe.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.scijava.event.bushe; + +/** + * Interface for classes that can veto class-based event publication from the {@link EventService}. + * + * @author Michael Bushe michael@bushe.com + */ +interface VetoEventListener { + + /** + * Determine whether an event should be vetoed or published. + *

+ * The EventService calls this method before class-based publication of objects. If any of the + * VetoEventListeners return true, then none of the subscribers for that event are called.

Prerequisite: + * VetoEventListener has to be subscribed with the EventService for the event object's class.

Guaranteed to be + * called in the SwingEventThread when using the SwingEventService (EventBus). See {@link EventService}

+ *

+ * + * @param event The event object to veto or allow to be published. + * + * @return true if the event should be vetoed and not published, false if the event should be published. + */ + public boolean shouldVeto(T event); +} diff --git a/src/main/java/org/scijava/event/bushe/VetoTopicEventListener.java b/src/main/java/org/scijava/event/bushe/VetoTopicEventListener.java new file mode 100644 index 000000000..f44d9693a --- /dev/null +++ b/src/main/java/org/scijava/event/bushe/VetoTopicEventListener.java @@ -0,0 +1,26 @@ +package org.scijava.event.bushe; + +/** + * Interface for classes that can veto publication on topic names from the {@link org.scijava.event.bushe.EventService}. + * + * @author Michael Bushe michael@bushe.com + */ +interface VetoTopicEventListener { + + /** + * Determine whether a topic publication should be vetoed or allowed. + *

+ * The EventService calls this method before publication of on a topic name. If any of the + * VetoTopicEventListeners return true, then none of the subscribers to that topic are called.

Prerequisite: + * VetoTopicEventListener has to be subscribed with the EventService for the topic name.

Guaranteed to be + * called in the SwingEventThread when using the SwingEventService (EventBus). See {@link EventService}

+ *

+ * + * @param topic The topic name the data object is published on. + * @param data The data object being published on the topic. + * + * @return true if the publication on the topic should be vetoed and not published, false if the data should be + * published on the topic. + */ + public boolean shouldVeto(String topic, T data); +} diff --git a/src/main/java/org/scijava/input/Accelerator.java b/src/main/java/org/scijava/input/Accelerator.java index c3976e9b8..73878ca15 100644 --- a/src/main/java/org/scijava/input/Accelerator.java +++ b/src/main/java/org/scijava/input/Accelerator.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/input/DefaultInputService.java b/src/main/java/org/scijava/input/DefaultInputService.java index 5c485330c..392dc250d 100644 --- a/src/main/java/org/scijava/input/DefaultInputService.java +++ b/src/main/java/org/scijava/input/DefaultInputService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/input/InputModifiers.java b/src/main/java/org/scijava/input/InputModifiers.java index 91fce4bc5..50dba7820 100644 --- a/src/main/java/org/scijava/input/InputModifiers.java +++ b/src/main/java/org/scijava/input/InputModifiers.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/input/InputService.java b/src/main/java/org/scijava/input/InputService.java index 215256789..0c7adae95 100644 --- a/src/main/java/org/scijava/input/InputService.java +++ b/src/main/java/org/scijava/input/InputService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/input/KeyCode.java b/src/main/java/org/scijava/input/KeyCode.java index e70e63222..a8eeccaad 100644 --- a/src/main/java/org/scijava/input/KeyCode.java +++ b/src/main/java/org/scijava/input/KeyCode.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -54,7 +52,7 @@ public enum KeyCode { CANCEL(0x03), /** Clear. */ - CLEAR(0x0C), + CLEAR(0x0c), /** Shift (left or right). */ SHIFT(0x10), @@ -72,7 +70,7 @@ public enum KeyCode { CAPS_LOCK(0x14), /** Escape. */ - ESCAPE(0x1B), + ESCAPE(0x1b), /** Space (' '). */ SPACE(0x20), @@ -102,16 +100,16 @@ public enum KeyCode { DOWN(0x28), /** Comma (','). */ - COMMA(0x2C), + COMMA(0x2c), /** Minus ('-'). */ - MINUS(0x2D), + MINUS(0x2d), /** Period ('.'). */ - PERIOD(0x2E), + PERIOD(0x2e), /** Forward slash ('/'). */ - SLASH(0x2F), + SLASH(0x2f), /** Zero ('0', non-numpad). */ NUM0(0x30), @@ -144,10 +142,10 @@ public enum KeyCode { NUM9(0x39), /** Semicolon (';'). */ - SEMICOLON(0x3B), + SEMICOLON(0x3b), /** Equals ('='). */ - EQUALS(0x3D), + EQUALS(0x3d), /** The letter A. */ A(0x41), @@ -177,22 +175,22 @@ public enum KeyCode { I(0x49), /** The letter J. */ - J(0x4A), + J(0x4a), /** The letter K. */ - K(0x4B), + K(0x4b), /** The letter L. */ - L(0x4C), + L(0x4c), /** The letter M. */ - M(0x4D), + M(0x4d), /** The letter N. */ - N(0x4E), + N(0x4e), /** The letter O. */ - O(0x4F), + O(0x4f), /** The letter P. */ P(0x50), @@ -225,16 +223,16 @@ public enum KeyCode { Y(0x59), /** The letter Z. */ - Z(0x5A), + Z(0x5a), /** Left bracket ('['). */ - OPEN_BRACKET(0x5B), + OPEN_BRACKET(0x5b), /** Backslash ('\\'). */ - BACK_SLASH(0x5C), + BACK_SLASH(0x5c), /** Right bracket (']'). */ - CLOSE_BRACKET(0x5D), + CLOSE_BRACKET(0x5d), /** Zero ('0') on numeric keypad. */ NUMPAD_0(0x60), @@ -267,24 +265,24 @@ public enum KeyCode { NUMPAD_9(0x69), /** Asterisk ('*') on numeric keypad. */ - NUMPAD_ASTERISK(0x6A), + NUMPAD_ASTERISK(0x6a), /** Plus ('+') on numeric keypad. */ - NUMPAD_PLUS(0x6B), + NUMPAD_PLUS(0x6b), - NUMPAD_SEPARATOR(0x6C), + NUMPAD_SEPARATOR(0x6c), /** Minus ('-') on numeric keypad. */ - NUMPAD_MINUS(0x6D), + NUMPAD_MINUS(0x6d), /** Period ('.') on numeric keypad. */ - NUMPAD_PERIOD(0x6E), + NUMPAD_PERIOD(0x6e), /** Slash ('/') on numeric keypad. */ - NUMPAD_SLASH(0x6F), + NUMPAD_SLASH(0x6f), /** Delete (non-numpad). */ - DELETE(0x7F), + DELETE(0x7f), /** Num Lock. */ NUM_LOCK(0x90), @@ -323,76 +321,76 @@ public enum KeyCode { F10(0x79), /** F11. */ - F11(0x7A), + F11(0x7a), /** F12. */ - F12(0x7B), + F12(0x7b), /** F13. */ - F13(0xF000), + F13(0xf000), /** F14. */ - F14(0xF001), + F14(0xf001), /** F15. */ - F15(0xF002), + F15(0xf002), /** F16. */ - F16(0xF003), + F16(0xf003), /** F17. */ - F17(0xF004), + F17(0xf004), /** F18 */ - F18(0xF005), + F18(0xf005), /** F19. */ - F19(0xF006), + F19(0xf006), /** F20. */ - F20(0xF007), + F20(0xf007), /** F21. */ - F21(0xF008), + F21(0xf008), /** F22. */ - F22(0xF009), + F22(0xf009), /** F23. */ - F23(0xF00A), + F23(0xf00a), /** F24. */ - F24(0xF00B), + F24(0xf00b), /** Print Screen. */ - PRINTSCREEN(0x9A), + PRINTSCREEN(0x9a), /** Insert. */ - INSERT(0x9B), + INSERT(0x9b), /** Help. */ - HELP(0x9C), + HELP(0x9c), /** Meta. */ - META(0x9D), + META(0x9d), /** Backquote ('`'). */ - BACK_QUOTE(0xC0), + BACK_QUOTE(0xc0), /** Single quote ('\''). */ - QUOTE(0xDE), + QUOTE(0xde), /** Up arrow on numeric keypad. */ - KP_UP(0xE0), + KP_UP(0xe0), /** Down arrow on numeric keypad. */ - KP_DOWN(0xE1), + KP_DOWN(0xe1), /** Left arrow on numeric keypad. */ - KP_LEFT(0xE2), + KP_LEFT(0xe2), /** Right arrow on numeric keypad. */ - KP_RIGHT(0xE3), + KP_RIGHT(0xe3), /** TODO. */ DEAD_GRAVE(0x80), @@ -494,51 +492,51 @@ public enum KeyCode { PLUS(0x0209), /** Right parenthesis (')'). */ - RIGHT_PARENTHESIS(0x020A), + RIGHT_PARENTHESIS(0x020a), /** Underscore ('_'). */ - UNDERSCORE(0x020B), + UNDERSCORE(0x020b), /** Windows key (both left and right). */ - WINDOWS(0x020C), + WINDOWS(0x020c), /** Windows Context Menu key. */ - CONTEXT_MENU(0x020D), + CONTEXT_MENU(0x020d), FINAL(0x0018), /** Convert function key. */ - CONVERT(0x001C), + CONVERT(0x001c), /** Don't Convert function key. */ - NONCONVERT(0x001D), + NONCONVERT(0x001d), /** Accept or Commit function key. */ - ACCEPT(0x001E), + ACCEPT(0x001e), - MODECHANGE(0x001F), + MODECHANGE(0x001f), KANA(0x0015), KANJI(0x0019), /** Alphanumeric function key. */ - ALPHANUMERIC(0x00F0), + ALPHANUMERIC(0x00f0), /** Katakana function key. */ - KATAKANA(0x00F1), + KATAKANA(0x00f1), /** Hiragana function key. */ - HIRAGANA(0x00F2), + HIRAGANA(0x00f2), /** Full-Width Characters function key. */ - FULL_WIDTH(0x00F3), + FULL_WIDTH(0x00f3), /** Half-Width Characters function key. */ - HALF_WIDTH(0x00F4), + HALF_WIDTH(0x00f4), /** Roman Characters function key. */ - ROMAN_CHARACTERS(0x00F5), + ROMAN_CHARACTERS(0x00f5), /** All Candidates function key. */ ALL_CANDIDATES(0x0100), @@ -565,37 +563,37 @@ public enum KeyCode { INPUT_METHOD_ON_OFF(0x0107), /** Cut (Sun keyboard). */ - CUT(0xFFD1), + CUT(0xffd1), /** Copy (Sun keyboard). */ - COPY(0xFFCD), + COPY(0xffcd), /** Paste (Sun keyboard). */ - PASTE(0xFFCF), + PASTE(0xffcf), /** Undo (Sun keyboard). */ - UNDO(0xFFCB), + UNDO(0xffcb), /** Again (Sun keyboard). */ - AGAIN(0xFFC9), + AGAIN(0xffc9), /** Find (Sun keyboard). */ - FIND(0xFFD0), + FIND(0xffd0), /** Props (Sun keyboard). */ - PROPS(0xFFCA), + PROPS(0xffca), /** Stop (Sun keyboard). */ - STOP(0xFFC8), + STOP(0xffc8), /** Compose function key. */ - COMPOSE(0xFF20), + COMPOSE(0xff20), /** AltGraph function key. */ - ALT_GRAPH(0xFF7E), + ALT_GRAPH(0xff7e), /** Begin key. */ - BEGIN(0xFF58), + BEGIN(0xff58), /** Unknown code. */ UNDEFINED(0x0); @@ -634,14 +632,97 @@ public static KeyCode get(final int code) { return keyCode; } + /** + * Gets the KeyCode corresponding to the given character, + * or {@link #UNDEFINED} if no such code. + */ + public static KeyCode get(final char c) { + switch (c) { + case '\n': case '\r': return ENTER; + case '\b': return BACK_SPACE; + case '\t': return TAB; + case 0x1b: return ESCAPE; + case ' ': return SPACE; + case ',': return COMMA; + case '-': return MINUS; + case '.': return PERIOD; + case '/': return SLASH; + case '0': return NUM0; + case '1': return NUM1; + case '2': return NUM2; + case '3': return NUM3; + case '4': return NUM4; + case '5': return NUM5; + case '6': return NUM6; + case '7': return NUM7; + case '8': return NUM8; + case '9': return NUM9; + case ';': return SEMICOLON; + case '=': return EQUALS; + case 'a': case 'A': return A; + case 'b': case 'B': return B; + case 'c': case 'C': return C; + case 'd': case 'D': return D; + case 'e': case 'E': return E; + case 'f': case 'F': return F; + case 'g': case 'G': return G; + case 'h': case 'H': return H; + case 'i': case 'I': return I; + case 'j': case 'J': return J; + case 'k': case 'K': return K; + case 'l': case 'L': return L; + case 'm': case 'M': return M; + case 'n': case 'N': return N; + case 'o': case 'O': return O; + case 'p': case 'P': return P; + case 'q': case 'Q': return Q; + case 'r': case 'R': return R; + case 's': case 'S': return S; + case 't': case 'T': return T; + case 'u': case 'U': return U; + case 'v': case 'V': return V; + case 'w': case 'W': return W; + case 'x': case 'X': return X; + case 'y': case 'Y': return Y; + case 'z': case 'Z': return Z; + case '[': return OPEN_BRACKET; + case '\\': return BACK_SLASH; + case ']': return CLOSE_BRACKET; + case '`': return BACK_QUOTE; + case '\'': return QUOTE; + case '&': return AMPERSAND; + case '*': return ASTERISK; + case '"': return QUOTEDBL; + case '<': return LESS; + case '>': return GREATER; + case '{': return BRACELEFT; + case '}': return BRACERIGHT; + case '@': return AT; + case ':': return COLON; + case '^': return CIRCUMFLEX; + case '$': return DOLLAR; + case '€': return EURO_SIGN; + case '!': return EXCLAMATION_MARK; + case 161: return INVERTED_EXCLAMATION_MARK; + case '(': return LEFT_PARENTHESIS; + case '#': return NUMBER_SIGN; + case '+': return PLUS; + case ')': return RIGHT_PARENTHESIS; + case '_': return UNDERSCORE; + } + return UNDEFINED; + } + /** * Gets the KeyCode with the given name, or {@link #UNDEFINED} if no such * code. */ public static KeyCode get(final String name) { final KeyCode keyCode = NAMES.get(name); - if (keyCode == null) return UNDEFINED; - return keyCode; + if (keyCode != null) return keyCode; + // Not a code name, but maybe a direct character value? + if (name.length() == 1) return KeyCode.get(name.charAt(0)); + return UNDEFINED; } } diff --git a/src/main/java/org/scijava/input/MouseCursor.java b/src/main/java/org/scijava/input/MouseCursor.java index cc87247e6..15bb0ccc8 100644 --- a/src/main/java/org/scijava/input/MouseCursor.java +++ b/src/main/java/org/scijava/input/MouseCursor.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/io/AbstractIOPlugin.java b/src/main/java/org/scijava/io/AbstractIOPlugin.java index 1574e109c..3c965739a 100644 --- a/src/main/java/org/scijava/io/AbstractIOPlugin.java +++ b/src/main/java/org/scijava/io/AbstractIOPlugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -31,15 +29,60 @@ package org.scijava.io; +import org.scijava.io.location.Location; +import org.scijava.io.location.LocationService; import org.scijava.plugin.AbstractHandlerPlugin; +import org.scijava.plugin.Parameter; + +import java.io.IOException; +import java.net.URISyntaxException; /** * Abstract base class for {@link IOPlugin}s. * * @author Curtis Rueden */ -public abstract class AbstractIOPlugin extends AbstractHandlerPlugin - implements IOPlugin +public abstract class AbstractIOPlugin extends + AbstractHandlerPlugin implements IOPlugin { - // NB: No implementation needed. + + @Parameter + private LocationService locationService; + + @Override + public boolean supportsOpen(final String source) { + try { + return supportsOpen(locationService.resolve(source)); + } catch (URISyntaxException e) { + return false; + } + } + + @Override + public boolean supportsSave(final String destination) { + try { + return supportsSave(locationService.resolve(destination)); + } catch (URISyntaxException e) { + return false; + } + } + + @Override + public void save(final D data, final String destination) throws IOException { + try { + save(data, locationService.resolve(destination)); + } catch (URISyntaxException e) { + throw new IOException(e); + } + } + + @Override + public D open(final String destination) throws IOException { + try { + return open(locationService.resolve(destination)); + } catch (URISyntaxException e) { + throw new IOException(e); + } + } + } diff --git a/src/main/java/org/scijava/io/AbstractTypedIOService.java b/src/main/java/org/scijava/io/AbstractTypedIOService.java new file mode 100644 index 000000000..4d75bfb3e --- /dev/null +++ b/src/main/java/org/scijava/io/AbstractTypedIOService.java @@ -0,0 +1,140 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io; + +import org.scijava.io.location.Location; +import org.scijava.io.location.LocationService; +import org.scijava.plugin.AbstractHandlerService; +import org.scijava.plugin.Parameter; + +import java.io.IOException; +import java.net.URISyntaxException; + +/** + * Abstract base class for typed {@link IOPlugin}s. + * + * @author Curtis Rueden + * @author Deborah Schmidt + */ +public abstract class AbstractTypedIOService extends AbstractHandlerService> implements TypedIOService +{ + + @Parameter + private LocationService locationService; + + @Parameter + private IOService ioService; + + @Override + public D open(String source) throws IOException { + try { + return open(locationService.resolve(source)); + } catch (URISyntaxException e) { + throw new IOException(e); + } + } + + @Override + public D open(Location source) throws IOException { + IOPlugin opener = ioService().getOpener(source); + try { + Class ignored = (Class) opener.getDataType(); + return (D) opener.open(source); + } + catch(ClassCastException e) { + throw new UnsupportedOperationException("No compatible opener found."); + } + } + + @Override + public void save(D data, String destination) throws IOException { + try { + save(data, locationService.resolve(destination)); + } catch (URISyntaxException e) { + throw new IOException(e); + } + } + + @Override + public void save(D data, Location destination) throws IOException { + IOPlugin saver = ioService().getSaver(data, destination); + if (saver != null) { + saver.save(data, destination); + } + else { + throw new UnsupportedOperationException("No compatible saver found."); + } + } + + @Override + public boolean canOpen(String source) { + try { + return canOpen(locationService.resolve(source)); + } catch (URISyntaxException e) { + return false; + } + } + + @Override + public boolean canOpen(Location source) { + IOPlugin opener = ioService().getOpener(source); + if (opener == null) return false; + try { + Class ignored = (Class) (opener.getDataType()); + return true; + } catch(ClassCastException e) { + return false; + } + } + + @Override + public boolean canSave(D data, String source) { + try { + return canSave(data, locationService.resolve(source)); + } catch (URISyntaxException e) { + return false; + } + } + + @Override + public boolean canSave(D data, Location destination) { + IOPlugin saver = ioService.getSaver(data, destination); + if (saver == null) return false; + return saver.supportsSave(destination); + } + + protected LocationService locationService() { + return locationService; + } + + protected IOService ioService() { + return ioService; + } +} diff --git a/src/main/java/org/scijava/io/ByteArrayByteBank.java b/src/main/java/org/scijava/io/ByteArrayByteBank.java new file mode 100644 index 000000000..f0183f2d8 --- /dev/null +++ b/src/main/java/org/scijava/io/ByteArrayByteBank.java @@ -0,0 +1,154 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io; + +import org.scijava.util.ByteArray; + +/** + * {@link ByteBank} implementation backed by a {@link ByteArray}. Self-growing + * up to a maximum capacity of {@link Integer#MAX_VALUE}. + * + * @author Gabriel Einsdorf + */ +public class ByteArrayByteBank implements ByteBank { + + private final ByteArray buffer; + private long size; + + /** + * Creates a {@link ByteArrayByteBank}. + */ + public ByteArrayByteBank() { + this(new ByteArray()); + } + + /** + * Creates a {@link ByteArrayByteBank} with the specified initial capacity. + * + * @param initialCapacity the initial capacity of this {@link ByteBank} + */ + public ByteArrayByteBank(final int initialCapacity) { + this(emptyByteArrayOfCapacity(initialCapacity)); + } + + /** + * Creates a {@link ByteArrayByteBank} that wraps the provided byte array. + * + * @param bytes the bytes to wrap + */ + public ByteArrayByteBank(final byte[] bytes) { + this(new ByteArray(bytes)); + } + + /** + * Creates a {@link ByteArrayByteBank} that wraps the specified + * {@link ByteArray}. + * + * @param bytes the {@link ByteArray} to wrap + */ + public ByteArrayByteBank(final ByteArray bytes) { + buffer = bytes; + size = bytes.size(); + } + + @Override + public long getMaxBufferSize() { + return Integer.MAX_VALUE; + } + + @Override + public void setBytes(final long startpos, final byte[] bytes, + final int offset, final int length) + { + // ensure we have space + checkWritePos(startpos, startpos + length); + final int neededCapacity = (int) (size + length); + buffer.ensureCapacity(neededCapacity); + + // copy the data + System.arraycopy(bytes, offset, buffer.getArray(), (int) startpos, length); + buffer.setSize(neededCapacity); + updateSize(startpos + length); + } + + @Override + public void setByte(final long pos, final byte b) { + checkWritePos(pos, pos); + buffer.ensureCapacity((int) pos); + // NB: update the size of the underlying buffer before appending to it + if (pos == buffer.size()) { + buffer.setSize((int) (pos + 1)); + } + buffer.setValue((int) pos, b); + updateSize(pos + 1); + } + + @Override + public void clear() { + buffer.clear(); + size = 0; + } + + @Override + public byte getByte(final long pos) { + checkReadPos(pos, pos); + // the buffer might contain bytes with negative value + // we need to flip the sign to positive to satisfy the method contract + return buffer.getValue((int) pos); + } + + @Override + public int getBytes(final long startPos, final byte[] b, final int offset, + final int length) + { + checkReadPos(startPos, startPos + length); + // ensure we don't try to read data which is not in the buffer + final int readLength = (int) Math.min(size() - startPos, length); + System.arraycopy(buffer.getArray(), (int) startPos, b, offset, readLength); + return readLength; + } + + @Override + public long size() { + return size; + } + + // -- Helper methods -- + + private void updateSize(final long newSize) { + size = newSize > size ? newSize : size; + } + + private static ByteArray emptyByteArrayOfCapacity(final int capacity) { + final ByteArray byteArray = new ByteArray(new byte[capacity]); + byteArray.setSize(0); + return byteArray; + } +} diff --git a/src/main/java/org/scijava/io/ByteBank.java b/src/main/java/org/scijava/io/ByteBank.java new file mode 100644 index 000000000..8d466e8ce --- /dev/null +++ b/src/main/java/org/scijava/io/ByteBank.java @@ -0,0 +1,209 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io; + +/** + * A {@link ByteBank} is a self-growing buffer over arbitrary bytes. + * + * @author Gabriel Einsdorf + * @author Curtis Rueden + */ +public interface ByteBank { + + /** + * @param pos the position to read from + * @return the byte at the given position + */ + public byte getByte(long pos); + + /** + * @param startPos the position in the buffer to start reading from + * @param bytes the byte array to read into + * @return the number of bytes read + */ + default int getBytes(long startPos, byte[] bytes) { + return getBytes(startPos, bytes, 0, bytes.length); + } + + /** + * @param startPos the position in the buffer to start reading from + * @param bytes the byte array to read into + * @param offset the offset in the bytes array + * @param length the number of elements to read into the bytes array + * @return number of bytes read + */ + int getBytes(long startPos, byte[] bytes, int offset, int length); + + /** + * Copies part of this buffer into a newly allocated byte array. + * + * @param offset the initial position in the buffer + * @param len the number of bytes to copy + * @return The newly allocated byte array containing the data. + */ + default byte[] toByteArray(final long offset, final int len) { + if (offset < 0 || len < 0 || offset + len > size()) { + throw new IllegalArgumentException("Invalid range"); + } + final byte[] bytes = new byte[len]; + getBytes(offset, bytes); + return bytes; + } + + /** + * Copies this entire buffer into a newly allocated byte array. + * + * @return The newly allocated byte array containing the data. + */ + default byte[] toByteArray() { + long max = size(); + if (max > Integer.MAX_VALUE) { + throw new IllegalStateException( + "Byte bank is too large to store into a single byte[]"); + } + return toByteArray(0, (int) max); + } + + /** + * Sets the bytes starting form the given position to the values form the + * provided array. + * + * @param startPos the position in the buffer to start writing from + * @param bytes the byte array to write + * @param offset the offset in the bytes array + * @param length the number of bytes to read + */ + void setBytes(long startPos, byte[] bytes, int offset, int length); + + /** + * Appends the given bytes to the buffer + * + * @param bytes the array containing the bytes to append to the buffer + * @param length the number of elements to append from the bytes array + */ + default void appendBytes(byte[] bytes, int length) { + appendBytes(bytes, 0, length); + } + + /** + * Appends the given bytes to the buffer + * + * @param bytes the array containing the bytes to append to the buffer + * @param offset the offset in the bytes array + * @param length the number of elements to append from the bytes array + */ + default void appendBytes(byte[] bytes, int offset, int length) { + setBytes(size(), bytes, offset, length); + } + + /** + * Check if we can read from the specified range + * + * @param start the start position of the range + * @param end the end position of the range + */ + default void checkReadPos(final long start, final long end) { + basicRangeCheck(start, end); + if (start > size()) { + throw new IndexOutOfBoundsException("Requested position: " + start + + " is outside the buffer: " + size()); + } + } + + /** + * Check if we can write to the specified range + * + * @param start the start position of the range + * @param end the end position of the range + * @throws IndexOutOfBoundsException if + */ + default void checkWritePos(final long start, final long end) { + if (start > size() + 1) { // we can't have holes in the buffer + throw new IndexOutOfBoundsException("Requested start position: " + start + + " would leave a hole in the buffer, largest legal position is: " + + size()); + } + if (end < start) { + throw new IllegalArgumentException( + "Invalid range, end is smaller than start!"); + } + if (end > getMaxBufferSize()) { + throw new IndexOutOfBoundsException("Requested position " + end + + " is larger than the maximal buffer size: " + getMaxBufferSize()); + } + } + + /** + * Ensures that the requested range satisfies basic sanity criteria. + * + * @param start the start of the range + * @param end the end of the range + */ + default void basicRangeCheck(final long start, final long end) { + if (start > size()) { + throw new IndexOutOfBoundsException("Requested position: " + start + + " is outside the buffer: " + size()); + } + if (end < start) { + throw new IllegalArgumentException( + "Invalid range, end is smaller than start!"); + } + } + + /** + * Clears the buffer + */ + void clear(); + + /** + * @return the offset which follows the last byte stored in this ByteBank + */ + long size(); + + /** + * Sets the byte at the given position + * + * @param pos the position + * @param b the value to set + */ + void setByte(long pos, byte b); + + /** + * @return the maximal size of the buffer + */ + long getMaxBufferSize(); + + /** + * @return True iff the buffer is read-only. + */ + default boolean isReadOnly() { + return false; + } +} diff --git a/src/main/java/org/scijava/io/DataHandle.java b/src/main/java/org/scijava/io/DataHandle.java deleted file mode 100644 index f058d9241..000000000 --- a/src/main/java/org/scijava/io/DataHandle.java +++ /dev/null @@ -1,373 +0,0 @@ -/* - * #%L - * SciJava Common shared library for SciJava software. - * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ - -package org.scijava.io; - -import java.io.Closeable; -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -import org.scijava.plugin.WrapperPlugin; - -/** - * A data handle is a plugin which provides access to bytes in a data - * stream (e.g., files or arrays), identified by a {@link Location}. - * - * @author Curtis Rueden - * @see DataHandleInputStream - * @see DataHandleOutputStream - */ -public interface DataHandle extends WrapperPlugin, - DataInput, DataOutput, Closeable -{ - - /** Default block size to use when searching through the stream. */ - int DEFAULT_BLOCK_SIZE = 256 * 1024; // 256 KB - - /** Default bound on bytes to search when searching through the stream. */ - int MAX_SEARCH_SIZE = 512 * 1024 * 1024; // 512 MB - - /** Returns the current offset in the stream. */ - long offset() throws IOException; - - /** Returns the length of the stream. */ - long length() throws IOException; - - /** - * Returns the current order of the stream. - * - * @return See above. - */ - ByteOrder getOrder(); - - /** Gets the endianness of the stream. */ - default boolean isLittleEndian() { - return getOrder() == ByteOrder.LITTLE_ENDIAN; - } - - /** - * Sets the byte order of the stream. - * - * @param order Order to set. - */ - void setOrder(ByteOrder order); - - /** Sets the endianness of the stream. */ - default void setOrder(final boolean little) { - setOrder(little ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); - } - - /** Gets the native encoding of the stream. */ - String getEncoding(); - - /** Sets the native encoding of the stream. */ - void setEncoding(String encoding); - - /** - * Reads up to {@code buf.remaining()} bytes of data from the stream into a - * {@link ByteBuffer}. - */ - default int read(final ByteBuffer buf) throws IOException { - return read(buf, buf.remaining()); - } - - /** - * Reads up to {@code len} bytes of data from the stream into a - * {@link ByteBuffer}. - * - * @return the total number of bytes read into the buffer. - */ - default int read(final ByteBuffer buf, final int len) throws IOException { - final int n; - if (buf.hasArray()) { - // read directly into the array - n = read(buf.array(), buf.arrayOffset(), len); - } - else { - // read into a temporary array, then copy - final byte[] b = new byte[len]; - n = read(b); - buf.put(b, 0, n); - } - return n; - } - - /** - * Sets the stream pointer offset, measured from the beginning of the stream, - * at which the next read or write occurs. - */ - void seek(long pos) throws IOException; - - /** - * Writes up to {@code buf.remaining()} bytes of data from the given - * {@link ByteBuffer} to the stream. - */ - default void write(final ByteBuffer buf) throws IOException { - write(buf, buf.remaining()); - } - - /** - * Writes up to len bytes of data from the given ByteBuffer to the stream. - */ - default void write(final ByteBuffer buf, final int len) - throws IOException - { - if (buf.hasArray()) { - // write directly from the buffer's array - write(buf.array(), buf.arrayOffset(), len); - } - else { - // copy into a temporary array, then write - final byte[] b = new byte[len]; - buf.get(b); - write(b); - } - } - - - /** Reads a string of arbitrary length, terminated by a null char. */ - default String readCString() throws IOException { - final String line = findString("\0"); - return line.length() == 0 ? null : line; - } - - /** Reads a string of up to length n. */ - default String readString(int n) throws IOException { - final long avail = length() - offset(); - if (n > avail) n = (int) avail; - final byte[] b = new byte[n]; - readFully(b); - return new String(b, getEncoding()); - } - - /** - * Reads a string ending with one of the characters in the given string. - * - * @see #findString(String...) - */ - default String readString(final String lastChars) throws IOException { - if (lastChars.length() == 1) return findString(lastChars); - final String[] terminators = new String[lastChars.length()]; - for (int i = 0; i < terminators.length; i++) { - terminators[i] = lastChars.substring(i, i + 1); - } - return findString(terminators); - } - - /** - * Reads a string ending with one of the given terminating substrings. - * - * @param terminators The strings for which to search. - * @return The string from the initial position through the end of the - * terminating sequence, or through the end of the stream if no - * terminating sequence is found. - */ - default String findString(final String... terminators) throws IOException { - return findString(true, DEFAULT_BLOCK_SIZE, terminators); - } - - /** - * Reads or skips a string ending with one of the given terminating - * substrings. - * - * @param saveString Whether to collect the string from the current file - * pointer to the terminating bytes, and return it. If false, returns - * null. - * @param terminators The strings for which to search. - * @throws IOException If saveString flag is set and the maximum search length - * (512 MB) is exceeded. - * @return The string from the initial position through the end of the - * terminating sequence, or through the end of the stream if no - * terminating sequence is found, or null if saveString flag is unset. - */ - default String findString(final boolean saveString, - final String... terminators) throws IOException - { - return findString(saveString, DEFAULT_BLOCK_SIZE, terminators); - } - - /** - * Reads a string ending with one of the given terminating substrings, using - * the specified block size for buffering. - * - * @param blockSize The block size to use when reading bytes in chunks. - * @param terminators The strings for which to search. - * @return The string from the initial position through the end of the - * terminating sequence, or through the end of the stream if no - * terminating sequence is found. - */ - default String findString(final int blockSize, final String... terminators) - throws IOException - { - return findString(true, blockSize, terminators); - } - - /** - * Reads or skips a string ending with one of the given terminating - * substrings, using the specified block size for buffering. - * - * @param saveString Whether to collect the string from the current file - * pointer to the terminating bytes, and return it. If false, returns - * null. - * @param blockSize The block size to use when reading bytes in chunks. - * @param terminators The strings for which to search. - * @throws IOException If saveString flag is set and the maximum search length - * (512 MB) is exceeded. - * @return The string from the initial position through the end of the - * terminating sequence, or through the end of the stream if no - * terminating sequence is found, or null if saveString flag is unset. - */ - default String findString(final boolean saveString, final int blockSize, - final String... terminators) throws IOException - { - final StringBuilder out = new StringBuilder(); - final long startPos = offset(); - long bytesDropped = 0; - final long inputLen = length(); - long maxLen = inputLen - startPos; - final boolean tooLong = saveString && maxLen > MAX_SEARCH_SIZE; - if (tooLong) maxLen = MAX_SEARCH_SIZE; - boolean match = false; - int maxTermLen = 0; - for (final String term : terminators) { - final int len = term.length(); - if (len > maxTermLen) maxTermLen = len; - } - - @SuppressWarnings("resource") - final InputStreamReader in = - new InputStreamReader(new DataHandleInputStream<>(this), getEncoding()); - final char[] buf = new char[blockSize]; - long loc = 0; - while (loc < maxLen && offset() < length() - 1) { - // if we're not saving the string, drop any old, unnecessary output - if (!saveString) { - final int outLen = out.length(); - if (outLen >= maxTermLen) { - final int dropIndex = outLen - maxTermLen + 1; - final String last = out.substring(dropIndex, outLen); - out.setLength(0); - out.append(last); - bytesDropped += dropIndex; - } - } - - // read block from stream - final int r = in.read(buf, 0, blockSize); - if (r <= 0) throw new IOException("Cannot read from stream: " + r); - - // append block to output - out.append(buf, 0, r); - - // check output, returning smallest possible string - int min = Integer.MAX_VALUE, tagLen = 0; - for (final String t : terminators) { - final int len = t.length(); - final int start = (int) (loc - bytesDropped - len); - final int value = out.indexOf(t, start < 0 ? 0 : start); - if (value >= 0 && value < min) { - match = true; - min = value; - tagLen = len; - } - } - - if (match) { - // reset stream to proper location - seek(startPos + bytesDropped + min + tagLen); - - // trim output string - if (saveString) { - out.setLength(min + tagLen); - return out.toString(); - } - return null; - } - - loc += r; - } - - // no match - if (tooLong) throw new IOException("Maximum search length reached."); - return saveString ? out.toString() : null; - } - - // -- InputStream look-alikes -- - - /** - * Reads the next byte of data from the stream. - * - * @return the next byte of data, or -1 if the end of the stream is reached. - * @throws IOException - if an I/O error occurs. - */ - int read() throws IOException; - - /** - * Reads up to b.length bytes of data from the stream into an array of bytes. - * - * @return the total number of bytes read into the buffer. - */ - default int read(byte[] b) throws IOException { - return read(b, 0, b.length); - } - - /** - * Reads up to len bytes of data from the stream into an array of bytes. - * - * @return the total number of bytes read into the buffer. - */ - int read(byte[] b, int off, int len) throws IOException; - - /** - * Skips over and discards {@code n} bytes of data from the stream. The - * {@code skip} method may, for a variety of reasons, end up skipping over - * some smaller number of bytes, possibly {@code 0}. This may result from any - * of a number of conditions; reaching end of file before {@code n} bytes have - * been skipped is only one possibility. The actual number of bytes skipped is - * returned. If {@code n} is negative, no bytes are skipped. - * - * @param n - the number of bytes to be skipped. - * @return the actual number of bytes skipped. - * @throws IOException - if an I/O error occurs. - */ - default long skip(final long n) throws IOException { - if (n < 0) return 0; - final long remain = length() - offset(); - final long num = n < remain ? n : remain; - seek(offset() + num); - return num; - } - -} diff --git a/src/main/java/org/scijava/io/DefaultIOService.java b/src/main/java/org/scijava/io/DefaultIOService.java index 2e6cdc63b..0b67ce690 100644 --- a/src/main/java/org/scijava/io/DefaultIOService.java +++ b/src/main/java/org/scijava/io/DefaultIOService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -32,10 +30,13 @@ package org.scijava.io; import java.io.IOException; +import java.net.URISyntaxException; import org.scijava.event.EventService; import org.scijava.io.event.DataOpenedEvent; import org.scijava.io.event.DataSavedEvent; +import org.scijava.io.location.Location; +import org.scijava.io.location.LocationService; import org.scijava.log.LogService; import org.scijava.plugin.AbstractHandlerService; import org.scijava.plugin.Parameter; @@ -49,7 +50,7 @@ */ @Plugin(type = Service.class) public final class DefaultIOService - extends AbstractHandlerService> implements IOService + extends AbstractHandlerService> implements IOService { @Parameter @@ -58,28 +59,76 @@ public final class DefaultIOService @Parameter private EventService eventService; - // -- IOService methods -- + @Parameter + private LocationService locationService; + + @Override + public IOPlugin getOpener(final String source) throws IOException { + try { + return getOpener(locationService.resolve(source)); + } catch (URISyntaxException e) { + throw new IOException(e); + } + } + + @Override + public IOPlugin getSaver(D data, String destination) throws IOException { + try { + return getSaver(data, locationService.resolve(destination)); + } catch (URISyntaxException e) { + throw new IOException(e); + } + } @Override public Object open(final String source) throws IOException { + try { + return open(locationService.resolve(source)); + } catch (URISyntaxException e) { + throw new IOException(e); + } + } + + @Override + public void save(final Object data, final String destination) + throws IOException + { + try { + save(data, locationService.resolve(destination)); + } catch (URISyntaxException e) { + throw new IOException(e); + } + } + + @Override + public Object open(final Location source) throws IOException { final IOPlugin opener = getOpener(source); - if (opener == null) return null; // no appropriate IOPlugin + if (opener == null) { + log.error("No opener IOPlugin found for " + source + "."); + return null; + } final Object data = opener.open(source); - if (data == null) return null; // IOPlugin returned no data; canceled? + if (data == null) { + log.warn("Opener IOPlugin " + opener + " returned no data. Canceled?"); + return null; // IOPlugin returned no data; canceled? + } eventService.publish(new DataOpenedEvent(source, data)); return data; } @Override - public void save(final Object data, final String destination) + public void save(final Object data, final Location destination) throws IOException { final IOPlugin saver = getSaver(data, destination); if (saver != null) { saver.save(data, destination); eventService.publish(new DataSavedEvent(destination, data)); + } else { + log.error("No Saver IOPlugin found for " + data.toString() + "."); } } + } diff --git a/src/main/java/org/scijava/io/DefaultRecentFileService.java b/src/main/java/org/scijava/io/DefaultRecentFileService.java index 62506bb26..3cef6fb1a 100644 --- a/src/main/java/org/scijava/io/DefaultRecentFileService.java +++ b/src/main/java/org/scijava/io/DefaultRecentFileService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -43,6 +41,8 @@ import org.scijava.event.EventHandler; import org.scijava.event.EventService; import org.scijava.io.event.IOEvent; +import org.scijava.io.location.FileLocation; +import org.scijava.io.location.Location; import org.scijava.menu.MenuConstants; import org.scijava.module.ModuleInfo; import org.scijava.module.ModuleService; @@ -174,11 +174,19 @@ public void initialize() { moduleService.addModules(recentModules.values()); } + @Override + public void dispose() { + clear(); + } + // -- Event handlers -- @EventHandler protected void onEvent(final IOEvent event) { - add(event.getDescriptor()); + final Location loc = event.getLocation(); + if (!(loc instanceof FileLocation)) return; + final FileLocation fileLoc = (FileLocation) loc; + add(fileLoc.getFile().getPath()); } // -- Helper methods -- diff --git a/src/main/java/org/scijava/io/IOPlugin.java b/src/main/java/org/scijava/io/IOPlugin.java index 7de3cc315..b8c69cc18 100644 --- a/src/main/java/org/scijava/io/IOPlugin.java +++ b/src/main/java/org/scijava/io/IOPlugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -32,7 +30,10 @@ package org.scijava.io; import java.io.IOException; +import java.net.URISyntaxException; +import org.scijava.io.location.Location; +import org.scijava.io.location.LocationService; import org.scijava.plugin.HandlerPlugin; import org.scijava.plugin.Plugin; @@ -50,52 +51,91 @@ * @see Plugin * @see IOService */ -public interface IOPlugin extends HandlerPlugin { +public interface IOPlugin extends HandlerPlugin { /** The type of data opened and/or saved by the plugin. */ Class getDataType(); /** Checks whether the I/O plugin can open data from the given source. */ - @SuppressWarnings("unused") default boolean supportsOpen(final String source) { + try { + return supportsOpen(context().service(LocationService.class).resolve(source)); + } + catch (final URISyntaxException exc) { + return false; + } + } + + /** Checks whether the I/O plugin can open data from the given location. */ + @SuppressWarnings("unused") + default boolean supportsOpen(final Location source) { return false; } /** Checks whether the I/O plugin can save data to the given destination. */ - @SuppressWarnings("unused") default boolean supportsSave(final String destination) { + try { + return supportsSave(context().service(LocationService.class).resolve(destination)); + } + catch (final URISyntaxException exc) { + return false; + } + } + + /** Checks whether the I/O plugin can save data to the given location. */ + @SuppressWarnings("unused") + default boolean supportsSave(final Location destination) { return false; } /** * Checks whether the I/O plugin can save the given data to the specified - * destination. + * location. */ default boolean supportsSave(final Object data, final String destination) { return supportsSave(destination) && getDataType().isInstance(data); } + default boolean supportsSave(final Object data, final Location destination) { + return supportsSave(destination) && getDataType().isInstance(data); + } + /** Opens data from the given source. */ @SuppressWarnings("unused") default D open(final String source) throws IOException { throw new UnsupportedOperationException(); } - /** Saves the given data to the specified destination. */ + /** Opens data from the given location. */ @SuppressWarnings("unused") + default D open(final Location source) throws IOException { + throw new UnsupportedOperationException(); + } + + /** Saves the given data to the specified destination. */ default void save(final D data, final String destination) throws IOException { + try { + save(data, context().service(LocationService.class).resolve(destination)); + } + catch (final URISyntaxException exc) { + throw new UnsupportedOperationException(exc); + } + } + + /** Saves the given data to the specified location. */ + @SuppressWarnings("unused") + default void save(final D data, final Location destination) throws IOException { throw new UnsupportedOperationException(); } // -- Typed methods -- - @Override default boolean supports(final String descriptor) { return supportsOpen(descriptor) || supportsSave(descriptor); } @Override - default Class getType() { - return String.class; + default Class getType() { + return Location.class; } } diff --git a/src/main/java/org/scijava/io/IOService.java b/src/main/java/org/scijava/io/IOService.java index a4f284680..672ba9f9b 100644 --- a/src/main/java/org/scijava/io/IOService.java +++ b/src/main/java/org/scijava/io/IOService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -33,6 +31,7 @@ import java.io.IOException; +import org.scijava.io.location.Location; import org.scijava.plugin.HandlerService; import org.scijava.service.SciJavaService; @@ -40,18 +39,22 @@ * Interface for high-level data I/O: opening and saving data. * * @author Curtis Rueden - * @see DataHandleService - * @see Location */ -public interface IOService extends HandlerService>, +public interface IOService extends HandlerService>, SciJavaService { /** * Gets the most appropriate {@link IOPlugin} for opening data from the given - * source. + * location. */ - default IOPlugin getOpener(final String source) { + IOPlugin getOpener(final String source) throws IOException; + + /** + * Gets the most appropriate {@link IOPlugin} for opening data from the given + * location. + */ + default IOPlugin getOpener(Location source) { for (final IOPlugin handler : getInstances()) { if (handler.supportsOpen(source)) return handler; } @@ -60,9 +63,15 @@ default IOPlugin getOpener(final String source) { /** * Gets the most appropriate {@link IOPlugin} for saving data to the given - * destination. + * location. + */ + IOPlugin getSaver(final D data, final String destination) throws IOException; + + /** + * Gets the most appropriate {@link IOPlugin} for saving data to the given + * location. */ - default IOPlugin getSaver(final D data, final String destination) { + default IOPlugin getSaver(D data, Location destination) { for (final IOPlugin handler : getInstances()) { if (handler.supportsSave(data, destination)) { @SuppressWarnings("unchecked") @@ -81,7 +90,7 @@ default IOPlugin getSaver(final D data, final String destination) { * The opener to use is automatically determined based on available * {@link IOPlugin}s; see {@link #getOpener(String)}. *

- * + * * @param source The source (e.g., file path) from which to data should be * loaded. * @return An object representing the loaded data, or null if the source is @@ -90,6 +99,22 @@ default IOPlugin getSaver(final D data, final String destination) { */ Object open(String source) throws IOException; + /** + * Loads data from the given location. + *

+ * The opener to use is automatically determined based on available + * {@link IOPlugin}s; see {@link #getOpener(Location)}. + *

+ * + * @param source The location from which to data should be loaded. + * @return An object representing the loaded data, or null if the source is + * not supported. + * @throws IOException if something goes wrong loading the data. + */ + default Object open(Location source) throws IOException { + throw new UnsupportedOperationException(); + } + /** * Saves data to the given destination. The nature of the destination is left * intentionally general, but the most common example is a file path. @@ -97,7 +122,7 @@ default IOPlugin getSaver(final D data, final String destination) { * The saver to use is automatically determined based on available * {@link IOPlugin}s; see {@link #getSaver(Object, String)}. *

- * + * * @param data The data to be saved to the destination. * @param destination The destination (e.g., file path) to which data should * be saved. @@ -105,6 +130,21 @@ default IOPlugin getSaver(final D data, final String destination) { */ void save(Object data, String destination) throws IOException; + /** + * Saves data to the given location. + *

+ * The saver to use is automatically determined based on available + * {@link IOPlugin}s; see {@link #getSaver(Object, Location)}. + *

+ * + * @param data The data to be saved to the destination. + * @param destination The destination location to which data should be saved. + * @throws IOException if something goes wrong saving the data. + */ + default void save(Object data, Location destination) throws IOException { + throw new UnsupportedOperationException(); + } + // -- HandlerService methods -- @Override @@ -114,7 +154,7 @@ default Class> getPluginType() { } @Override - default Class getType() { - return String.class; + default Class getType() { + return Location.class; } } diff --git a/src/main/java/org/scijava/io/RecentFileService.java b/src/main/java/org/scijava/io/RecentFileService.java index 6eb0e68cd..20a6ad9c8 100644 --- a/src/main/java/org/scijava/io/RecentFileService.java +++ b/src/main/java/org/scijava/io/RecentFileService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/io/TypedIOService.java b/src/main/java/org/scijava/io/TypedIOService.java new file mode 100644 index 000000000..e6882fc95 --- /dev/null +++ b/src/main/java/org/scijava/io/TypedIOService.java @@ -0,0 +1,180 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io; + +import java.io.IOException; +import java.net.URISyntaxException; + +import org.scijava.io.location.FileLocation; +import org.scijava.io.location.Location; +import org.scijava.io.location.LocationService; +import org.scijava.plugin.HandlerService; +import org.scijava.service.SciJavaService; + +/** + * Interface for high-level data I/O: opening and saving data of a specific type. + * + * @author Curtis Rueden + * @author Deborah Schmidt + */ +public interface TypedIOService extends HandlerService>, + SciJavaService +{ + + /** + * Gets the most appropriate {@link IOPlugin} for opening data from the given + * location. + */ + default IOPlugin getOpener(final String source) { + try { + return getOpener(context().service(LocationService.class).resolve(source)); + } + catch (final URISyntaxException exc) { + return null; + } + } + + /** + * Gets the most appropriate {@link IOPlugin} for opening data from the given + * location. + */ + default IOPlugin getOpener(Location source) { + for (final IOPlugin handler : getInstances()) { + if (handler.supportsOpen(source)) return handler; + } + return null; + } + + /** + * Gets the most appropriate {@link IOPlugin} for saving data to the given + * location. + */ + default IOPlugin getSaver(final D data, final String destination) { + try { + return getSaver(data, context().service(LocationService.class).resolve(destination)); + } + catch (final URISyntaxException exc) { + return null; + } + } + + /** + * Gets the most appropriate {@link IOPlugin} for saving data to the given + * location. + */ + default IOPlugin getSaver(D data, Location destination) { + for (final IOPlugin handler : getInstances()) { + if (handler.supportsSave(data, destination)) { + return (IOPlugin) handler; + } + } + return null; + } + + /** + * Loads data from the given source. For extensibility, the nature of the + * source is left intentionally general, but two common examples include file + * paths and URLs. + *

+ * The opener to use is automatically determined based on available + * {@link IOPlugin}s; see {@link #getOpener(String)}. + *

+ * + * @param source The source (e.g., file path) from which to data should be + * loaded. + * @return An object representing the loaded data, or null if the source is + * not supported. + * @throws IOException if something goes wrong loading the data. + */ + D open(String source) throws IOException; + + /** + * Loads data from the given location. + *

+ * The opener to use is automatically determined based on available + * {@link IOPlugin}s; see {@link #getOpener(Location)}. + *

+ * + * @param source The location from which to data should be loaded. + * @return An object representing the loaded data, or null if the source is + * not supported. + * @throws IOException if something goes wrong loading the data. + */ + D open(Location source) throws IOException; + + /** + * Saves data to the given destination. The nature of the destination is left + * intentionally general, but the most common example is a file path. + *

+ * The saver to use is automatically determined based on available + * {@link IOPlugin}s; see {@link #getSaver(Object, String)}. + *

+ * + * @param data The data to be saved to the destination. + * @param destination The destination (e.g., file path) to which data should + * be saved. + * @throws IOException if something goes wrong saving the data. + */ + void save(D data, String destination) throws IOException; + + /** + * Saves data to the given location. + *

+ * The saver to use is automatically determined based on available + * {@link IOPlugin}s; see {@link #getSaver(Object, Location)}. + *

+ * + * @param data The data to be saved to the destination. + * @param destination The destination location to which data should be saved. + * @throws IOException if something goes wrong saving the data. + */ + void save(D data, Location destination) throws IOException; + + boolean canOpen(String source); + + boolean canOpen(Location source); + + boolean canSave(D data, String destination); + + boolean canSave(D data, Location destination); + + // -- HandlerService methods -- + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + default Class> getPluginType() { + return (Class) IOPlugin.class; + } + + @Override + default Class getType() { + return Location.class; + } +} diff --git a/src/main/java/org/scijava/io/console/OpenArgument.java b/src/main/java/org/scijava/io/console/OpenArgument.java index 6f0c7947e..bfdc3dbdd 100644 --- a/src/main/java/org/scijava/io/console/OpenArgument.java +++ b/src/main/java/org/scijava/io/console/OpenArgument.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -41,6 +39,7 @@ import org.scijava.log.LogService; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; +import org.scijava.startup.StartupService; /** * Handles the {@code --open} command line argument. @@ -50,13 +49,16 @@ @Plugin(type = ConsoleArgument.class) public class OpenArgument extends AbstractConsoleArgument { - @Parameter + @Parameter(required = false) private IOService ioService; - @Parameter + @Parameter(required = false) private DisplayService displayService; - @Parameter + @Parameter(required = false) + private StartupService startupService; + + @Parameter(required = false) private LogService log; // -- Constructor -- @@ -74,12 +76,23 @@ public void handle(final LinkedList args) { args.removeFirst(); // --open final String source = args.removeFirst(); - try { - final Object o = ioService.open(source); - displayService.createDisplay(o); - } - catch (IOException exc) { - log.error(exc); - } + // open the source after the UI is shown + startupService.addOperation(() -> { + try { + final Object o = ioService.open(source); + displayService.createDisplay(o); + } + catch (final IOException exc) { + if (log != null) log.error(exc); + } + }); + } + + // -- Typed methods -- + + @Override + public boolean supports(final LinkedList args) { + return startupService != null && ioService != null && + displayService != null && super.supports(args); } } diff --git a/src/main/java/org/scijava/io/event/DataOpenedEvent.java b/src/main/java/org/scijava/io/event/DataOpenedEvent.java index 08df4c44e..c177c9195 100644 --- a/src/main/java/org/scijava/io/event/DataOpenedEvent.java +++ b/src/main/java/org/scijava/io/event/DataOpenedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -31,22 +29,34 @@ package org.scijava.io.event; + +import org.scijava.io.location.FileLocation; +import org.scijava.io.location.Location; + /** - * An event indicating that data has been opened from a source. + * An event indicating that data has been opened from a location. * * @author Curtis Rueden */ public class DataOpenedEvent extends IOEvent { + public DataOpenedEvent(final Location location, final Object data) { + super(location, data); + } + + /** + * @deprecated use {@link #DataOpenedEvent(Location, Object)} instead + */ + @Deprecated public DataOpenedEvent(final String source, final Object data) { super(source, data); } - // -- DataOpenedEvent methods -- - - /** Gets the source from which data was opened. */ + /** + * @deprecated use {@link #getLocation} instead + */ + @Deprecated public String getSource() { return getDescriptor(); } - } diff --git a/src/main/java/org/scijava/io/event/DataSavedEvent.java b/src/main/java/org/scijava/io/event/DataSavedEvent.java index 73e21c01a..ec75495c9 100644 --- a/src/main/java/org/scijava/io/event/DataSavedEvent.java +++ b/src/main/java/org/scijava/io/event/DataSavedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -31,6 +29,10 @@ package org.scijava.io.event; + +import org.scijava.io.location.FileLocation; +import org.scijava.io.location.Location; + /** * An event indicating that data has been saved to a destination. * @@ -38,15 +40,23 @@ */ public class DataSavedEvent extends IOEvent { - public DataSavedEvent(final String destination, final Object data) { + public DataSavedEvent(final Location destination, final Object data) { super(destination, data); } - // -- DataSavedEvent methods -- + /** + * @deprecated use {@link #DataSavedEvent(Location, Object)} instead + */ + @Deprecated + public DataSavedEvent(final String destination, final Object data) { + super(destination, data); + } - /** Gets the destination to which data was saved. */ + /** + * @deprecated use {@link #getLocation} instead + */ + @Deprecated public String getDestination() { return getDescriptor(); } - } diff --git a/src/main/java/org/scijava/io/event/IOEvent.java b/src/main/java/org/scijava/io/event/IOEvent.java index 83bb16f7b..20a28a191 100644 --- a/src/main/java/org/scijava/io/event/IOEvent.java +++ b/src/main/java/org/scijava/io/event/IOEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -31,7 +29,12 @@ package org.scijava.io.event; +import java.net.URISyntaxException; + import org.scijava.event.SciJavaEvent; +import org.scijava.io.location.FileLocation; +import org.scijava.io.location.Location; +import org.scijava.io.location.LocationService; /** * An event indicating that I/O (e.g., opening or saving) has occurred. @@ -40,20 +43,41 @@ */ public abstract class IOEvent extends SciJavaEvent { - /** The data descriptor (source or destination). */ + /** The data location (source or destination). */ + private final Location location; + + /** @deprecated use {@link #location} instead */ + @Deprecated private final String descriptor; /** The data for which I/O took place. */ private final Object data; + /** + * @deprecated use {@link #IOEvent(Location, Object)} instead + */ + @Deprecated public IOEvent(final String descriptor, final Object data) { + this.location = null; this.descriptor = descriptor; this.data = data; } - /** Gets the data descriptor (source or destination). */ - public String getDescriptor() { - return descriptor; + public IOEvent(final Location location, final Object data) { + this.location = location; + this.descriptor = null; + this.data = data; + } + + /** Gets the data location (source or destination). */ + public Location getLocation() { + if (location != null) return location; + try { + return context().service(LocationService.class).resolve(descriptor); + } + catch (final URISyntaxException exc) { + return null; + } } /** Gets the data for which I/O took place. */ @@ -65,7 +89,20 @@ public Object getData() { @Override public String toString() { - return super.toString() + "\n\tdescriptor = " + data + "\n\tdata = " + data; + return super.toString() + "\n\tlocation = " + location + "\n\tdata = " + + data; } + /** + * @deprecated use {@link #getLocation()} instead + */ + @Deprecated + public String getDescriptor() { + if (descriptor != null) return descriptor; + if (location instanceof FileLocation) { + final FileLocation fileLocation = (FileLocation) location; + return fileLocation.getFile().getAbsolutePath(); + } + return location.getURI().toString(); + } } diff --git a/src/main/java/org/scijava/io/AbstractDataHandle.java b/src/main/java/org/scijava/io/handle/AbstractDataHandle.java similarity index 89% rename from src/main/java/org/scijava/io/AbstractDataHandle.java rename to src/main/java/org/scijava/io/handle/AbstractDataHandle.java index 7a30a6d3b..f2446f90a 100644 --- a/src/main/java/org/scijava/io/AbstractDataHandle.java +++ b/src/main/java/org/scijava/io/handle/AbstractDataHandle.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -29,10 +27,9 @@ * #L% */ -package org.scijava.io; - -import java.nio.ByteOrder; +package org.scijava.io.handle; +import org.scijava.io.location.Location; import org.scijava.plugin.AbstractWrapperPlugin; /** @@ -44,6 +41,13 @@ public abstract class AbstractDataHandle extends AbstractWrapperPlugin implements DataHandle { + private byte[] conversionBuffer = new byte[8]; + + @Override + public byte[] conversionBuffer() { + return conversionBuffer; + } + // -- Fields -- private ByteOrder order = ByteOrder.BIG_ENDIAN; diff --git a/src/main/java/org/scijava/io/handle/AbstractHigherOrderHandle.java b/src/main/java/org/scijava/io/handle/AbstractHigherOrderHandle.java new file mode 100644 index 000000000..7fa3d8df9 --- /dev/null +++ b/src/main/java/org/scijava/io/handle/AbstractHigherOrderHandle.java @@ -0,0 +1,112 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.handle; + +import java.io.IOException; + +import org.scijava.io.location.Location; + +/** + * Abstract superclass for {@link DataHandle}s that operate over other + * {@link DataHandle}s. + * + * @author Gabriel Einsdorf + */ +public abstract class AbstractHigherOrderHandle extends + AbstractDataHandle +{ + + private DataHandle handle; + private boolean closed; + + public AbstractHigherOrderHandle(final DataHandle handle) { + this.handle = handle; + set(handle.get()); // provides access to underlying location + } + + @Override + public boolean isReadable() { + return !closed && handle.isReadable(); + } + + @Override + public boolean isWritable() { + return !closed && handle.isWritable(); + } + + @Override + public long length() throws IOException { + ensureOpen(); + return handle.length(); + } + + @Override + public Class getType() { + return handle.getType(); + } + + @Override + public boolean exists() throws IOException { + return handle.exists(); + } + + @Override + public void close() throws IOException { + if (!closed) { + cleanup(); + closed = true; + handle.close(); + handle = null; + } + } + + protected void ensureOpen() throws IOException { + if (closed) { + throw new IOException("This handle is closed!"); + } + } + + /** + * Clean up data structures after a handle has been closed in the + * {@link #close()} method. + * + * @throws IOException + */ + protected abstract void cleanup() throws IOException; + + /** + * @return the {@link DataHandle} wrapped by this + * {@link AbstractHigherOrderHandle} + */ + protected DataHandle handle() { + return handle; + } + +} diff --git a/src/main/java/org/scijava/io/handle/AbstractSeekableStreamHandle.java b/src/main/java/org/scijava/io/handle/AbstractSeekableStreamHandle.java new file mode 100644 index 000000000..5b56c5e93 --- /dev/null +++ b/src/main/java/org/scijava/io/handle/AbstractSeekableStreamHandle.java @@ -0,0 +1,103 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.handle; + +import java.io.IOException; + +import org.scijava.io.location.Location; + +public abstract class AbstractSeekableStreamHandle extends + AbstractStreamHandle implements SeekableStreamHandle +{ + + private long jumpCutoff = 10000; + + @Override + public void seek(final long pos) throws IOException { + + // how much and which direction we have to jump + final long delta = pos - offset(); + + if (delta == 0) { + return; + // nothing to do + } + else if (delta > 0) { + // offset position is "downstream" + + // try to reconnect instead of linearly reading large chunks + if (recreatePossible() && delta > jumpCutoff) { + recreateStreamFromPos(pos); + } + else { + jump(delta); + } + + } + else { // delta < 0 + // need to recreate the stream + if (recreatePossible()) { + recreateStreamFromPos(pos); + } + else { + resetStream(); + jump(pos); + } + } + setOffset(pos); + } + + /** + * Recreates the internal input stream available through {@link #in()}, so + * that it starts from the specified position. + * + * @param pos + * @throws IOException + */ + protected abstract void recreateStreamFromPos(long pos) throws IOException; + + /** + * In some implementations of this class, the ability to recreate the stream + * depends on external factors (e.g. server support). This influences a + * + * @return if recreate is actually possible. + * @throws IOException + */ + protected abstract boolean recreatePossible() throws IOException; + + /** + * Sets the maximum of bytes which are read from the stream when seeking + * forward. Any larger number will result in a call to + * {@link #recreateStreamFromPos(long)}. + */ + protected void setJumpCutoff(long jumpCutoff) { + this.jumpCutoff = jumpCutoff; + } +} diff --git a/src/main/java/org/scijava/io/FileLocation.java b/src/main/java/org/scijava/io/handle/AbstractStreamHandle.java similarity index 67% rename from src/main/java/org/scijava/io/FileLocation.java rename to src/main/java/org/scijava/io/handle/AbstractStreamHandle.java index 3d93ee12c..75a9f4939 100644 --- a/src/main/java/org/scijava/io/FileLocation.java +++ b/src/main/java/org/scijava/io/handle/AbstractStreamHandle.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -29,40 +27,37 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.handle; -import java.io.File; -import java.net.URI; +import org.scijava.io.location.Location; /** - * {@link Location} backed by a {@link File} on disk. + * Abstract base class for {@link StreamHandle} implementations. * * @author Curtis Rueden + * @author Melissa Linkert */ -public class FileLocation extends AbstractLocation { +public abstract class AbstractStreamHandle extends + AbstractDataHandle implements StreamHandle +{ - private final File file; + // -- Fields -- - public FileLocation(final File file) { - this.file = file; - } - - public FileLocation(final String path) { - this(new File(path)); - } + /** Current position within the stream(s). */ + private long offset; - // -- FileLocation methods -- + // -- StreamHandle methods -- - /** Gets the associated {@link File}. */ - public File getFile() { - return file; + @Override + public void setOffset(final long offset) { + this.offset = offset; } - // -- Location methods -- + // -- DataHandle methods -- @Override - public URI getURI() { - return getFile().toURI(); + public long offset() { + return offset; } } diff --git a/src/main/java/org/scijava/io/handle/BytesHandle.java b/src/main/java/org/scijava/io/handle/BytesHandle.java new file mode 100644 index 000000000..b09228673 --- /dev/null +++ b/src/main/java/org/scijava/io/handle/BytesHandle.java @@ -0,0 +1,185 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.handle; + +import java.io.EOFException; +import java.io.IOException; + +import org.scijava.io.ByteBank; +import org.scijava.io.location.BytesLocation; +import org.scijava.plugin.Plugin; + +/** + * {@link DataHandle} for a {@link BytesLocation}. + * + * @author Curtis Rueden + * @author Melissa Linkert + * @author Gabriel Einsdorf + */ +@Plugin(type = DataHandle.class) +public class BytesHandle extends AbstractDataHandle { + + private long offset = 0; + + // -- Constructors -- + + public BytesHandle() { } + + public BytesHandle(final BytesLocation location) { + set(location); + } + + // -- DataHandle methods -- + + @Override + public boolean isReadable() { + return true; + } + + @Override + public boolean isWritable() { + return !bytes().isReadOnly(); + } + + @Override + public boolean exists() { + return true; + } + + @Override + public long offset() { + return offset; + } + + @Override + public long length() { + return bytes().size(); + } + + @Override + public void setLength(final long length) throws IOException { + // check if new length is legal + bytes().basicRangeCheck(0, length); + // TODO update the maxLength? + } + + @Override + public int read(final byte[] b, final int off, int len) throws IOException { + if(len == 0) return 0; + if (offset + len > length()) { + len = (int) (length() - offset); + } + if(len == 0) { // EOF + return -1; + } + bytes().getBytes(offset, b, off, len); + offset += len; + return len; + } + + @Override + public void seek(final long pos) throws IOException { + if (pos > length()) setLength(pos); + offset = pos; + } + + // -- DataInput methods -- + + @Override + public byte readByte() throws IOException { + ensureReadable(1); + try { + // we need to convert the bytes into the range 0-255 + return bytes().getByte(offset++); + } + catch (final Exception e) { + throw eofException(e); + } + } + + @Override + public void readFully(final byte[] b, final int off, final int len) + throws IOException + { + ensureReadable(len); + try { + bytes().getBytes(offset, b, off, len); + offset += len; + } + catch (final Exception e) { + throw eofException(e); + } + } + + // -- DataOutput methods -- + + @Override + public void write(final byte[] b, final int off, final int len) + throws IOException + { + ensureWritable(len); + bytes().setBytes(offset, b, off, len); + offset += len; + } + + @Override + public void write(final int b) throws IOException { + ensureWritable(1); + bytes().setByte(offset, (byte) b); + offset++; + } + + // -- Closeable methods -- + + @Override + public void close() { + // NB: No action needed. + } + + // -- Typed methods -- + + @Override + public Class getType() { + return BytesLocation.class; + } + + // -- Helper methods -- + + private ByteBank bytes() { + return get().getByteBank(); + } + + private EOFException eofException(final Throwable cause) { + final EOFException eof = new EOFException(); + eof.initCause(cause); + return eof; + } + +} diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java new file mode 100644 index 000000000..18cddadcb --- /dev/null +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -0,0 +1,672 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.handle; + +import java.io.Closeable; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Date; + +import org.scijava.io.location.Location; +import org.scijava.plugin.WrapperPlugin; +import org.scijava.util.Bytes; + +/** + * A data handle is a plugin which provides both streaming and random + * access to bytes at a {@link Location} (e.g., files or arrays). + * + * @author Curtis Rueden + * @see DataHandleInputStream + * @see DataHandleOutputStream + */ +public interface DataHandle extends WrapperPlugin, + DataInput, DataOutput, Closeable +{ + + public enum ByteOrder { + LITTLE_ENDIAN, BIG_ENDIAN + } + + /** Default block size to use when searching through the stream. */ + int DEFAULT_BLOCK_SIZE = 256 * 1024; // 256 KB + + /** Default bound on bytes to search when searching through the stream. */ + int MAX_SEARCH_SIZE = 512 * 1024 * 1024; // 512 MB + + /** Gets whether reading from this handle is supported. */ + boolean isReadable(); + + /** Gets whether writing to this handle is supported. */ + boolean isWritable(); + + /** + * Tests whether this handle's location actually exists at the source. + * + * @return True if the location exists; false if not. + * @throws IOException If something goes wrong with the existence check. + */ + boolean exists() throws IOException; + + /** + * Gets the last modified timestamp of the location. + * + * @return The last modified timestamp, or null if the handle does not support + * this feature or if the location does not exist. + * @throws IOException If something goes wrong with the last modified check. + */ + default Date lastModified() throws IOException { + return null; + } + + /** + * Gets a "fast" checksum which succinctly represents the contents of the data + * stream. The term "fast" here refers to the idea that the checksum be + * retrievable quickly, without actually performing a thorough computation + * across the entire data stream. Typically, such a thing is feasible because + * the checksum was calculated a priori; e.g., artifacts deployed to remote + * Maven repositories are always deployed with corresponding checksum files. + *

+ * No guarantee is made about the exact nature of the checksum (e.g., SHA-1 or + * MD5), only that the value is deterministic for this particular location + * with its current contents. In other words: if a checksum differs from a + * previous inquiry, you can be sure the contents have changed; conversely, if + * the checksum is still the same, the contents are highly likely to be + * unchanged. + *

+ * + * @return The checksum, or null if the handle does not support this feature. + * @throws IOException If something goes wrong when accessing the checksum. + */ + default String checksum() throws IOException { + return null; + } + + /** Returns the current offset in the stream. */ + long offset() throws IOException; + + /** + * Sets the stream offset, measured from the beginning of the stream, at which + * the next read or write occurs. + */ + void seek(long pos) throws IOException; + + /** + * Returns the length of the data in bytes. + * + * @return The length, or -1 if the length is unknown. + */ + long length() throws IOException; + + /** + * Sets the new length of the handle. + * + * @param length New length. + * @throws IOException If there is an error changing the handle's length. + */ + void setLength(long length) throws IOException; + + /** + * Gets the number of bytes which can be read from, or written to, the + * data handle, bounded by the specified number of bytes. + *

+ * In the case of reading, attempting to read the returned number of bytes is + * guaranteed not to throw {@link EOFException}. However, be aware that the + * following methods might still process fewer bytes than indicated + * by this method: + *

+ *
    + *
  • {@link #read(byte[])}
  • + *
  • {@link #read(byte[], int, int)}
  • + *
  • {@link #skip(long)}
  • + *
  • {@link #skipBytes(int)}
  • + *
+ *

+ * In the case of writing, attempting to write the returned number of bytes is + * guaranteed not to expand the length of the handle; i.e., the write will + * only overwrite bytes already within the handle's bounds. + *

+ * + * @param count Desired number of bytes to read/write. + * @return The actual number of bytes which could be read/written, + * which might be less than the requested value. + * @throws IOException If something goes wrong with the check. + */ + default long available(final long count) throws IOException { + final long remain = length() - offset(); + return remain < count ? remain : count; + } + + /** + * Ensures that the handle has sufficient bytes available to read. + * + * @param count Number of bytes to read. + * @see #available(long) + * @throws EOFException If there are insufficient bytes available. + * @throws IOException If the handle is write-only, or something goes wrong + * with the check. + */ + default void ensureReadable(final long count) throws IOException { + if (!isReadable()) throw new IOException("This handle is write-only."); + if (available(count) < count) throw new EOFException(); + } + + /** + * Ensures that the handle has the correct length to be written to, and + * extends it as required. + * + * @param count Number of bytes to write. + * @return {@code true} if the handle's length was sufficient, or + * {@code false} if the handle's length required an extension. + * @throws IOException If the handle is read-only, or something goes wrong + * with the check, or there is an error changing the handle's + * length. + */ + default boolean ensureWritable(final long count) throws IOException { + if (!isWritable()) throw new IOException("This handle is read-only."); + final long minLength = offset() + count; + if (length() < minLength) { + setLength(minLength); + return false; + } + return true; + } + + /** Returns the byte order of the stream. */ + ByteOrder getOrder(); + + /** + * Sets the byte order of the stream. + * + * @param order Order to set. + */ + void setOrder(ByteOrder order); + + /** + * Returns true iff the stream's order is {@link ByteOrder#BIG_ENDIAN}. + * + * @see #getOrder() + */ + default boolean isBigEndian() { + return getOrder() == ByteOrder.BIG_ENDIAN; + } + + /** + * Returns true iff the stream's order is {@link ByteOrder#LITTLE_ENDIAN}. + * + * @see #getOrder() + */ + default boolean isLittleEndian() { + return getOrder() == ByteOrder.LITTLE_ENDIAN; + } + + /** + * Sets the endianness of the stream. + * + * @param little If true, sets the order to {@link ByteOrder#LITTLE_ENDIAN}; + * otherwise, sets the order to {@link ByteOrder#BIG_ENDIAN}. + * @see #setOrder(ByteOrder) + */ + default void setLittleEndian(final boolean little) { + setOrder(little ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); + } + + /** Gets the native encoding of the stream. */ + String getEncoding(); + + /** Sets the native encoding of the stream. */ + void setEncoding(String encoding); + + /** + * @return a 8 byte long buffer array used for type conversions + */ + byte[] conversionBuffer(); + + /** Reads a string of arbitrary length, terminated by a null char. */ + default String readCString() throws IOException { + final String line = findString("\0"); + return line.length() == 0 ? null : line; + } + + /** Reads a string of up to length n. */ + default String readString(final int n) throws IOException { + final int r = (int) available(n); + final byte[] b = new byte[r]; + readFully(b); + return new String(b, getEncoding()); + } + + /** + * Reads a string ending with one of the characters in the given string. + * + * @see #findString(String...) + */ + default String readString(final String lastChars) throws IOException { + if (lastChars.length() == 1) return findString(lastChars); + final String[] terminators = new String[lastChars.length()]; + for (int i = 0; i < terminators.length; i++) { + terminators[i] = lastChars.substring(i, i + 1); + } + return findString(terminators); + } + + /** + * Reads a string ending with one of the given terminating substrings. + * + * @param terminators The strings for which to search. + * @return The string from the initial position through the end of the + * terminating sequence, or through the end of the stream if no + * terminating sequence is found. + */ + default String findString(final String... terminators) throws IOException { + return findString(true, DEFAULT_BLOCK_SIZE, terminators); + } + + /** + * Reads or skips a string ending with one of the given terminating + * substrings. + * + * @param saveString Whether to collect the string from the current offset to + * the terminating bytes, and return it. If false, returns null. + * @param terminators The strings for which to search. + * @throws IOException If saveString flag is set and the maximum search length + * (512 MB) is exceeded. + * @return The string from the initial position through the end of the + * terminating sequence, or through the end of the stream if no + * terminating sequence is found, or null if saveString flag is unset. + */ + default String findString(final boolean saveString, + final String... terminators) throws IOException + { + return findString(saveString, DEFAULT_BLOCK_SIZE, terminators); + } + + /** + * Reads a string ending with one of the given terminating substrings, using + * the specified block size for buffering. + * + * @param blockSize The block size to use when reading bytes in chunks. + * @param terminators The strings for which to search. + * @return The string from the initial position through the end of the + * terminating sequence, or through the end of the stream if no + * terminating sequence is found. + */ + default String findString(final int blockSize, final String... terminators) + throws IOException + { + return findString(true, blockSize, terminators); + } + + /** + * Reads or skips a string ending with one of the given terminating + * substrings, using the specified block size for buffering. + * + * @param saveString Whether to collect the string from the current offset to + * the terminating bytes, and return it. If false, returns null. + * @param blockSize The block size to use when reading bytes in chunks. + * @param terminators The strings for which to search. + * @throws IOException If saveString flag is set and the maximum search length + * (512 MB) is exceeded. + * @return The string from the initial position through the end of the + * terminating sequence, or through the end of the stream if no + * terminating sequence is found, or null if saveString flag is unset. + */ + default String findString(final boolean saveString, final int blockSize, + final String... terminators) throws IOException + { + final StringBuilder out = new StringBuilder(); + final long startPos = offset(); + long bytesDropped = 0; + final long maxLen = saveString ? MAX_SEARCH_SIZE : Long.MAX_VALUE; + boolean match = false; + int maxTermLen = 0; + for (final String term : terminators) { + final int len = term.length(); + if (len > maxTermLen) maxTermLen = len; + } + + @SuppressWarnings("resource") + final InputStreamReader in = new InputStreamReader( + new DataHandleInputStream<>(this), getEncoding()); + final char[] buf = new char[blockSize]; + long loc = 0; + int r = 0; + + // NB: we need at least 2 bytes to read a char + while (loc < maxLen && ((r = in.read(buf, 0, blockSize)) > 1)) { + // if we're not saving the string, drop any old, unnecessary output + if (!saveString) { + final int outLen = out.length(); + if (outLen >= maxTermLen) { + final int dropIndex = outLen - maxTermLen + 1; + final String last = out.substring(dropIndex, outLen); + out.setLength(0); + out.append(last); + bytesDropped += dropIndex; + } + } + // append block to output + out.append(buf, 0, r); + + // check output, returning smallest possible string + int min = Integer.MAX_VALUE; + int tagLen = 0; + for (final String t : terminators) { + final int len = t.length(); + final int start = (int) (loc - bytesDropped - len); + final int value = out.indexOf(t, start < 0 ? 0 : start); + if (value >= 0 && value < min) { + match = true; + min = value; + tagLen = len; + } + } + + if (match) { + // reset stream to proper location + seek(startPos + bytesDropped + min + tagLen); + + // trim output string + if (saveString) { + out.setLength(min + tagLen); + return out.toString(); + } + return null; + } + + loc += r; + } + + // no match + if (loc > MAX_SEARCH_SIZE) { + throw new IOException("Maximum search length reached."); + } + return saveString ? out.toString() : null; + } + + /** + * Writes the provided string, followed by a newline character. + * + * @param string The string to write. + * @throws IOException If an I/O error occurs. + */ + default void writeLine(final String string) throws IOException { + writeBytes(string); + writeBytes("\n"); + } + + // -- InputStream look-alikes -- + + /** + * Reads the next byte of data from the stream. + * + * @return the next byte of data, or -1 if the end of the stream is reached. + * @throws IOException - if an I/O error occurs. + */ + default int read() throws IOException { + return offset() < length() ? readByte() & 0xff : -1; + } + + /** + * Reads up to b.length bytes of data from the stream into an array of bytes. + * + * @return the total number of bytes read into the buffer. + */ + default int read(final byte[] b) throws IOException { + return read(b, 0, b.length); + } + + /** + * Reads up to {@code len} bytes of data from the stream into an array of + * bytes. + * + * @return the total number of bytes read into the buffer. + */ + int read(byte[] b, int off, int len) throws IOException; + + /** + * Skips over and discards {@code n} bytes of data from the stream. The + * {@code skip} method may, for a variety of reasons, end up skipping over + * some smaller number of bytes, possibly {@code 0}. This may result from any + * of a number of conditions; reaching end of file before {@code n} bytes have + * been skipped is only one possibility. The actual number of bytes skipped is + * returned. If {@code n} is negative, no bytes are skipped. + * + * @param n - the number of bytes to be skipped. + * @return the actual number of bytes skipped. + * @throws IOException - if an I/O error occurs. + */ + default long skip(final long n) throws IOException { + final long skip = available(n); + if (skip <= 0) return 0; + seek(offset() + skip); + return skip; + } + + // -- DataInput methods -- + + @Override + default void readFully(final byte[] b) throws IOException { + readFully(b, 0, b.length); + } + + @Override + default void readFully(final byte[] b, final int off, final int len) + throws IOException + { + // NB: Adapted from java.io.DataInputStream.readFully(byte[], int, int). + if (len < 0) throw new IndexOutOfBoundsException(); + int n = 0; + while (n < len) { + final int count = read(b, off + n, len - n); + if (count < 0) throw new EOFException(); + n += count; + } + } + + @Override + default int skipBytes(final int n) throws IOException { + // NB: Cast here is safe since the value of n bounds the result to an int. + final int skip = (int) available(n); + if (skip < 0) return 0; + seek(offset() + skip); + return skip; + } + + @Override + default boolean readBoolean() throws IOException { + return readByte() != 0; + } + + @Override + default int readUnsignedByte() throws IOException { + return readByte() & 0xff; + } + + @Override + default short readShort() throws IOException { + final byte[] buf = conversionBuffer(); + final int read = read(buf, 0, 2); + if (read < 2) throw new EOFException(); + return Bytes.toShort(buf, isLittleEndian()); + } + + @Override + default int readUnsignedShort() throws IOException { + return readShort() & 0xffff; + } + + @Override + default char readChar() throws IOException { + return (char) readShort(); + } + + @Override + default int readInt() throws IOException { + final byte[] buf = conversionBuffer(); + final int read = read(buf, 0, 4); + if (read < 4) throw new EOFException(); + return Bytes.toInt(buf, isLittleEndian()); + } + + @Override + default long readLong() throws IOException { + final byte[] buf = conversionBuffer(); + final int read = read(buf, 0, 8); + if (read < 8) { + throw new EOFException(); + } + return Bytes.toLong(buf, isLittleEndian()); + } + + @Override + default float readFloat() throws IOException { + return Float.intBitsToFloat(readInt()); + } + + @Override + default double readDouble() throws IOException { + return Double.longBitsToDouble(readLong()); + } + + @Override + default String readLine() throws IOException { + // NB: Adapted from java.io.RandomAccessFile.readLine(). + + final StringBuilder input = new StringBuilder(); + int c = -1; + boolean eol = false; + + while (!eol) { + switch (c = read()) { + case -1: + case '\n': + eol = true; + break; + case '\r': + eol = true; + final long cur = offset(); + if (read() != '\n') seek(cur); + break; + default: + input.append((char) c); + break; + } + } + + if (c == -1 && input.length() == 0) { + return null; + } + return input.toString(); + } + + @Override + default String readUTF() throws IOException { + return DataInputStream.readUTF(this); + } + + // -- DataOutput methods -- + + @Override + default void write(final byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + default void writeBoolean(final boolean v) throws IOException { + write(v ? 1 : 0); + } + + @Override + default void writeByte(final int v) throws IOException { + write(v); + } + + @Override + default void writeShort(final int v) throws IOException { + final byte[] buf = conversionBuffer(); + Bytes.unpack(v, buf, 0, 2, isLittleEndian()); + write(buf, 0, 2); + } + + @Override + default void writeChar(final int v) throws IOException { + writeShort(v); + } + + @Override + default void writeInt(final int v) throws IOException { + final byte[] buf = conversionBuffer(); + Bytes.unpack(v, buf, 0, 4, isLittleEndian()); + write(buf, 0, 4); + } + + @Override + default void writeLong(final long v) throws IOException { + final byte[] buf = conversionBuffer(); + Bytes.unpack(v, buf, 0, 8, isLittleEndian()); + write(buf, 0, 8); + } + + @Override + default void writeFloat(final float v) throws IOException { + final byte[] buf = conversionBuffer(); + Bytes.unpack(Float.floatToIntBits(v), buf, 0, 4, isLittleEndian()); + write(buf, 0, 4); + } + + @Override + default void writeDouble(final double v) throws IOException { + final byte[] buf = conversionBuffer(); + Bytes.unpack(Double.doubleToLongBits(v), buf, 0, 8, isLittleEndian()); + write(buf, 0, 8); + } + + @Override + default void writeBytes(final String s) throws IOException { + write(s.getBytes("UTF-8")); + } + + @Override + default void writeChars(final String s) throws IOException { + final int len = s.length(); + for (int i = 0; i < len; i++) { + writeChar(s.charAt(i)); + } + } + + @Override + default void writeUTF(final String str) throws IOException { + DataHandles.writeUTF(str, this); + } +} diff --git a/src/main/java/org/scijava/io/DataHandleInputStream.java b/src/main/java/org/scijava/io/handle/DataHandleInputStream.java similarity index 93% rename from src/main/java/org/scijava/io/DataHandleInputStream.java rename to src/main/java/org/scijava/io/handle/DataHandleInputStream.java index 6b2e21e4a..10e6b84e9 100644 --- a/src/main/java/org/scijava/io/DataHandleInputStream.java +++ b/src/main/java/org/scijava/io/handle/DataHandleInputStream.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -29,11 +27,13 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.handle; import java.io.IOException; import java.io.InputStream; +import org.scijava.io.location.Location; + /** * {@link InputStream} backed by a {@link DataHandle}. * diff --git a/src/main/java/org/scijava/io/DataHandleOutputStream.java b/src/main/java/org/scijava/io/handle/DataHandleOutputStream.java similarity index 91% rename from src/main/java/org/scijava/io/DataHandleOutputStream.java rename to src/main/java/org/scijava/io/handle/DataHandleOutputStream.java index 6f876694d..3328a4e0a 100644 --- a/src/main/java/org/scijava/io/DataHandleOutputStream.java +++ b/src/main/java/org/scijava/io/handle/DataHandleOutputStream.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -29,11 +27,13 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.handle; import java.io.IOException; import java.io.OutputStream; +import org.scijava.io.location.Location; + /** * {@link OutputStream} backed by a {@link DataHandle}. * diff --git a/src/main/java/org/scijava/io/handle/DataHandleService.java b/src/main/java/org/scijava/io/handle/DataHandleService.java new file mode 100644 index 000000000..fae0e7f4a --- /dev/null +++ b/src/main/java/org/scijava/io/handle/DataHandleService.java @@ -0,0 +1,121 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.handle; + +import java.io.IOException; + +import org.scijava.io.IOService; +import org.scijava.io.location.Location; +import org.scijava.plugin.WrapperService; +import org.scijava.service.SciJavaService; + +/** + * Interface for low-level data I/O: reading and writing bytes using + * {@link DataHandle}s. + * + * @author Curtis Rueden + * @see IOService + * @see Location + */ +public interface DataHandleService extends + WrapperService>, SciJavaService +{ + + // -- PTService methods -- + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + default Class> getPluginType() { + return (Class) DataHandle.class; + } + + // -- Typed methods -- + + @Override + default Class getType() { + return Location.class; + } + + /** + * Convenience method to test whether it describes an existing file. + * + * @param location the location to test + * @return The result of {@link DataHandle#exists()} on a newly created handle + * on this location. Also returns {@code false} if the handle can not + * be created. + * @throws IOException if the creation of the handle fails exceptionally + */ + default boolean exists(final Location location) throws IOException { + try (DataHandle handle = create(location)) { + return handle == null ? false : handle.exists(); + } + } + + /** + * Wraps the provided {@link DataHandle} in a read-only buffer for accelerated + * reading. + * + * @param handle the handle to wrap + * @return The handle wrapped in a read-only buffer, or {@code null} if the + * input handle is {@code null} + * @see ReadBufferDataHandle#ReadBufferDataHandle(DataHandle) + */ + default DataHandle readBuffer(final DataHandle handle) { + return handle == null ? null : new ReadBufferDataHandle(handle); + } + + /** + * Creates a {@link DataHandle} on the provided {@link Location} wrapped in a + * read-only buffer for accelerated reading. + * + * @param location the Location to create a buffered handle on. + * @return A {@link DataHandle} on the provided location wrapped in a + * read-only buffer, or {@code null} if no handle could be created for + * the location. + * @see ReadBufferDataHandle#ReadBufferDataHandle(DataHandle) + */ + default DataHandle readBuffer(final Location location) { + final DataHandle handle = create(location); + return handle == null ? null : new ReadBufferDataHandle(handle); + } + + /** + * Wraps the provided {@link DataHandle} in a write-only buffer for + * accelerated writing. + * + * @param handle the handle to wrap + * @return the handle wrapped in a write-only buffer or {@code null} if the + * provided handle is {@code null} + * @see WriteBufferDataHandle#WriteBufferDataHandle(DataHandle) + */ + default DataHandle writeBuffer(final DataHandle handle) { + return handle == null ? null : new WriteBufferDataHandle(handle); + } +} diff --git a/src/main/java/org/scijava/io/handle/DataHandles.java b/src/main/java/org/scijava/io/handle/DataHandles.java new file mode 100644 index 000000000..1464f637e --- /dev/null +++ b/src/main/java/org/scijava/io/handle/DataHandles.java @@ -0,0 +1,252 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.handle; + +import java.io.DataOutput; +import java.io.IOException; +import java.io.UTFDataFormatException; + +import org.scijava.io.location.Location; +import org.scijava.task.Task; + +/** + * Utility methods for working with {@link DataHandle}s. + * + * @author Curtis Rueden + * @author Gabriel Einsdorf + */ +public final class DataHandles { + + private DataHandles() { + // Prevent instantiation of utility class. + } + + /** + * Writes a string to the specified DataOutput using modified UTF-8 encoding + * in a machine-independent manner. + *

+ * First, two bytes are written to out as if by the {@code writeShort} method + * giving the number of bytes to follow. This value is the number of bytes + * actually written out, not the length of the string. Following the length, + * each character of the string is output, in sequence, using the modified + * UTF-8 encoding for the character. If no exception is thrown, the counter + * {@code written} is incremented by the total number of bytes written to the + * output stream. This will be at least two plus the length of {@code str}, + * and at most two plus thrice the length of {@code str}. + *

+ * + * @param str a string to be written. + * @param out destination to write to + * @return The number of bytes written out. + * @throws IOException if an I/O error occurs. + */ + public static int writeUTF(final String str, final DataOutput out) + throws IOException + { + // Encode string as modified UTF-8 per java.io.DataOutput specification. + final int strlen = str.length(); + int utflen = 0; + for (int i = 0; i < strlen; i++) { + final char c = str.charAt(i); + if (c >= '\u0001' && c <= '\u007F') utflen += 1; + else if (c <= '\u07FF') utflen += 2; + else utflen += 3; + } + if (utflen > 65535) throw new UTFDataFormatException( + "encoded string too long: " + utflen + " bytes"); + final byte[] bytes = new byte[utflen + 2]; + bytes[0] = (byte) ((utflen >>> 8) & 0xFF); + bytes[1] = (byte) (utflen & 0xFF); + int pos = 2; + for (int i = 0; i < strlen; i++) { + final char c = str.charAt(i); + if (c >= '\u0001' && c <= '\u007F') { + bytes[pos++] = (byte) c; + } + else if (c <= '\u07FF') { + bytes[pos++] = (byte) (0xC0 | ((c >> 6) & 0x1F)); + bytes[pos++] = (byte) (0x80 | (c & 0x3F)); + } + else { + bytes[pos++] = (byte) (0xE0 | ((c >> 12) & 0x0F)); + bytes[pos++] = (byte) (0x80 | ((c >> 6) & 0x3F)); + bytes[pos++] = (byte) (0x80 | (c & 0x3F)); + } + } + out.write(bytes); + return utflen + 2; + } + + protected static IOException readOnlyException() { + return new IOException("This handle is read-only!"); + } + + protected static IOException writeOnlyException() { + return new IOException("This handle is write-only!"); + } + + + /** + * Copies all bytes from the input to the output handle. Reading and writing + * start at the current positions of the handles. + * + * @param in the input handle + * @param out the output handle + * @return the number of bytes copied + * @throws IOException if an I/O error occurs. + */ + public static long copy(final DataHandle in, + final DataHandle out) throws IOException + { + return copy(in, out, 0l, null); + } + + /** + * Copies all bytes from the input to the output handle, reporting the + * progress to the provided task. Reading and writing start at the current + * positions of the handles. + * + * @param in the input handle + * @param out the output handle + * @param task task to report progress to + * @return the number of bytes copied + * @throws IOException if an I/O error occurs. + */ + public static long copy(final DataHandle in, + final DataHandle out, final Task task) throws IOException + { + return copy(in, out, 0l, task); + } + + /** + * Copies up to length bytes from the input to the output handle. + * Reading and writing start at the current positions of the handles. Stops + * early if there are no more bytes available from the input handle. + * + * @param in the input handle + * @param out the output handle + * @param length maximum number of bytes to copy; will copy all bytes if set + * to 0 + * @return the number of bytes copied + * @throws IOException if an I/O error occurs. + */ + public static long copy(final DataHandle in, + final DataHandle out, final int length) throws IOException + { + return copy(in, out, length, null); + } + + /** + * Copies up to length bytes from the input to the output handle, + * reporting the progress to the provided task. Reading and writing start at + * the current positions of the handles. Stops early if there are no more + * bytes available from the input handle. + * + * @param in input handle + * @param out the output handle + * @param length maximum number of bytes to copy; will copy all bytes if set + * to 0 + * @param task a task object to use for reporting the status of the copy + * operation. Can be null if no reporting is needed. + * @return the number of bytes copied + * @throws IOException if an I/O error occurs. + */ + public static long copy(final DataHandle in, + final DataHandle out, final long length, final Task task) + throws IOException + { + return copy(in, out, length, task, 64 * 1024); + } + + /** + * Copies up to length bytes from the input to the output handle, + * reporting the progress to the provided task. Reading and writing start at + * the current positions of the handles. Stops early if there are no more + * bytes available from the input handle. Uses a buffer of the provided size, + * instead of using the default size. + * + * @param in input handle + * @param out the output handle + * @param length maximum number of bytes to copy, will copy all bytes if set + * to 0 + * @param task a task object to use for reporting the status of the copy + * operation. Can be null if no reporting is needed. + * @return the number of bytes copied + * @throws IOException if an I/O error occurs. + */ + public static long copy(final DataHandle in, + final DataHandle out, final long length, final Task task, + final int bufferSize) throws IOException + { + + // get length of input + final long inputlength; + { + long i = 0; + try { + i = in.length(); + } + catch (final IOException exc) { + // Assume unknown length. + i = 0; + } + inputlength = i; + } + + if (task != null) { + if (length > 0) task.setProgressMaximum(length); + else if (inputlength > 0) task.setProgressMaximum(inputlength); + } + + final byte[] buffer = new byte[bufferSize]; + long totalRead = 0; + + while (true) { + if (task != null && task.isCanceled()) break; + final int r; + // ensure we do not read more than required into the buffer + if (length > 0 && totalRead + bufferSize > length) { + int remaining = (int) (length - totalRead); + r = in.read(buffer, 0, remaining); + } + else { + r = in.read(buffer); + } + if (r <= 0) break; // EOF + if (task != null && task.isCanceled()) break; + out.write(buffer, 0, r); + totalRead += r; + if (task != null) { + task.setProgressValue(task.getProgressValue() + r); + } + } + return totalRead; + } +} diff --git a/src/main/java/org/scijava/io/DefaultDataHandleService.java b/src/main/java/org/scijava/io/handle/DefaultDataHandleService.java similarity index 89% rename from src/main/java/org/scijava/io/DefaultDataHandleService.java rename to src/main/java/org/scijava/io/handle/DefaultDataHandleService.java index dc98ce28f..1cdbcdf46 100644 --- a/src/main/java/org/scijava/io/DefaultDataHandleService.java +++ b/src/main/java/org/scijava/io/handle/DefaultDataHandleService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -29,8 +27,9 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.handle; +import org.scijava.io.location.Location; import org.scijava.plugin.AbstractWrapperService; import org.scijava.plugin.Plugin; import org.scijava.service.Service; diff --git a/src/main/java/org/scijava/io/handle/DummyHandle.java b/src/main/java/org/scijava/io/handle/DummyHandle.java new file mode 100644 index 000000000..74b2d24ab --- /dev/null +++ b/src/main/java/org/scijava/io/handle/DummyHandle.java @@ -0,0 +1,146 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.handle; + +import java.io.IOException; +import java.util.Arrays; + +import org.scijava.io.location.DummyLocation; +import org.scijava.plugin.Plugin; + +/** + * A {@link DataHandle} which reads all zeroes, and writes no actual data. + * + * @author Curtis Rueden + */ +@Plugin(type = DataHandle.class) +public class DummyHandle extends AbstractDataHandle { + + // -- Fields -- + + private long offset; + private long length; + + // -- Constructors -- + + public DummyHandle() { } + + public DummyHandle(final DummyLocation location) { + set(location); + } + + // -- DataHandle methods -- + + @Override + public boolean isReadable() { + return true; + } + + @Override + public boolean isWritable() { + return true; + } + + @Override + public boolean exists() { + return true; + } + + @Override + public long offset() { + return offset; + } + + @Override + public void seek(final long pos) { + if (pos > length()) setLength(pos); + offset = pos; + } + + @Override + public long length() { + return length; + } + + @Override + public void setLength(final long length) { + this.length = length; + } + + // -- DataInput methods -- + + @Override + public byte readByte() throws IOException { + final long r = available(1); + if (r <= 0) return -1; + offset++; + return 0; + } + + @Override + public int read(final byte[] b, final int off, final int len) + throws IOException + { + final int r = (int) available(len); + offset += r; + Arrays.fill(b, off, off + r, (byte) 0); + return r; + } + + // -- DataOutput methods -- + + @Override + public void write(final int v) throws IOException { + ensureWritable(1); + offset++; + } + + @Override + public void write(final byte[] b, final int off, final int len) + throws IOException + { + ensureWritable(len); + offset += len; + } + + // -- Closeable methods -- + + @Override + public void close() { + // NB: No action needed. + } + + // -- Typed methods -- + + @Override + public Class getType() { + return DummyLocation.class; + } +} diff --git a/src/main/java/org/scijava/io/FileHandle.java b/src/main/java/org/scijava/io/handle/FileHandle.java similarity index 51% rename from src/main/java/org/scijava/io/FileHandle.java rename to src/main/java/org/scijava/io/handle/FileHandle.java index f3ac1f422..3734f7ebb 100644 --- a/src/main/java/org/scijava/io/FileHandle.java +++ b/src/main/java/org/scijava/io/handle/FileHandle.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -29,11 +27,16 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.handle; +import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Date; +import org.scijava.io.location.FileLocation; import org.scijava.plugin.Plugin; /** @@ -50,13 +53,27 @@ public class FileHandle extends AbstractDataHandle { private RandomAccessFile raf; /** The mode of the {@link RandomAccessFile}. */ - private String mode = "rw"; + private String mode; + + /** True iff the {@link #close()} has already been called. */ + private boolean closed; + + // -- Constructors -- + + public FileHandle() { } + + public FileHandle(final FileLocation location) { + set(location); + } // -- FileHandle methods -- - /** Gets the random access file object backing this FileHandle. */ + /** + * Gets the random access file object backing this FileHandle. If the + * underlying file does not exist yet, it will be created. + */ public RandomAccessFile getRandomAccessFile() throws IOException { - return raf(); + return writer(); } public String getMode() { @@ -72,196 +89,185 @@ public void setMode(final String mode) { // -- DataHandle methods -- + @Override + public boolean isReadable() { + return getMode().contains("r"); + } + + @Override + public boolean isWritable() { + return getMode().contains("w"); + } + + @Override + public boolean exists() { + return get().getFile().exists(); + } + + @Override + public Date lastModified() { + final long lastModified = get().getFile().lastModified(); + return lastModified == 0 ? null : new Date(lastModified); + } + @Override public long offset() throws IOException { - return raf().getFilePointer(); + return exists() ? reader().getFilePointer() : 0; } @Override public long length() throws IOException { - return raf().length(); + return exists() ? reader().length() : -1; + } + + @Override + public void setLength(final long length) throws IOException { + writer().setLength(length); } @Override public int read() throws IOException { - return raf().read(); + return reader().read(); } @Override public int read(final byte[] b) throws IOException { - return raf().read(b); + return reader().read(b); } @Override public int read(final byte[] b, final int off, final int len) throws IOException { - return raf().read(b, off, len); + return reader().read(b, off, len); } @Override public void seek(final long pos) throws IOException { - raf().seek(pos); + if (isWritable()) { + writer().seek(pos); + } + else { + reader().seek(pos); + } } // -- DataInput methods -- @Override public boolean readBoolean() throws IOException { - return raf().readBoolean(); + return reader().readBoolean(); } @Override public byte readByte() throws IOException { - return raf().readByte(); - } - - @Override - public char readChar() throws IOException { - return raf().readChar(); - } - - @Override - public double readDouble() throws IOException { - return raf().readDouble(); - } - - @Override - public float readFloat() throws IOException { - return raf().readFloat(); + return reader().readByte(); } @Override public void readFully(final byte[] b) throws IOException { - raf().readFully(b); + reader().readFully(b); } @Override public void readFully(final byte[] b, final int off, final int len) throws IOException { - raf().readFully(b, off, len); - } - - @Override - public int readInt() throws IOException { - return raf().readInt(); + reader().readFully(b, off, len); } @Override public String readLine() throws IOException { - return raf().readLine(); - } - - @Override - public long readLong() throws IOException { - return raf().readLong(); - } - - @Override - public short readShort() throws IOException { - return raf().readShort(); + return reader().readLine(); } @Override public int readUnsignedByte() throws IOException { - return raf().readUnsignedByte(); - } - - @Override - public int readUnsignedShort() throws IOException { - return raf().readUnsignedShort(); + return reader().readUnsignedByte(); } @Override public String readUTF() throws IOException { - return raf().readUTF(); + return reader().readUTF(); } @Override public int skipBytes(final int n) throws IOException { - return raf().skipBytes(n); + return reader().skipBytes(n); } // -- DataOutput methods -- @Override public void write(final byte[] b) throws IOException { - raf().write(b); + writer().write(b); } @Override public void write(final byte[] b, final int off, final int len) throws IOException { - raf().write(b, off, len); + writer().write(b, off, len); } @Override public void write(final int b) throws IOException { - raf().write(b); + writer().write(b); } @Override public void writeBoolean(final boolean v) throws IOException { - raf().writeBoolean(v); + writer().writeBoolean(v); } @Override public void writeByte(final int v) throws IOException { - raf().writeByte(v); + writer().writeByte(v); } @Override public void writeBytes(final String s) throws IOException { - raf().writeBytes(s); - } - - @Override - public void writeChar(final int v) throws IOException { - raf().writeChar(v); + writer().writeBytes(s); } @Override public void writeChars(final String s) throws IOException { - raf().writeChars(s); - } - - @Override - public void writeDouble(final double v) throws IOException { - raf().writeDouble(v); + writer().writeChars(s); } @Override - public void writeFloat(final float v) throws IOException { - raf().writeFloat(v); + public void writeUTF(final String str) throws IOException { + writer().writeUTF(str); } - @Override - public void writeInt(final int v) throws IOException { - raf().writeInt(v); - } + // -- Closeable methods -- @Override - public void writeLong(final long v) throws IOException { - raf().writeLong(v); + public synchronized void close() throws IOException { + if (raf != null) raf.close(); + closed = true; } - @Override - public void writeShort(final int v) throws IOException { - raf().writeShort(v); - } + // -- WrapperPlugin methods -- @Override - public void writeUTF(final String str) throws IOException { - raf().writeUTF(str); - } - - // -- Closeable methods -- + public void set(FileLocation loc) { + super.set(loc); - @Override - public void close() throws IOException { - raf().close(); + // Infer the initial mode based on file existence + permissions. + final File file = loc.getFile(); + String mode; + if (file.exists()) { + final Path path = loc.getFile().toPath(); + mode = ""; + if (Files.isReadable(path)) mode += "r"; + if (Files.isWritable(path)) mode += "w"; + } + else { + // Non-existent file; assume the intent is to create it. + mode = "rw"; + } + setMode(mode); } // -- Typed methods -- @@ -273,13 +279,49 @@ public Class getType() { // -- Helper methods -- - private RandomAccessFile raf() throws IOException { - if (raf == null) initRAF(); + /** + * Access method for the internal {@link RandomAccessFile}, that succeeds + * independently of the underlying file existing on disk. This allows us to + * create a new file for writing. + * + * @return the internal {@link RandomAccessFile} creating a new file on disk + * if needed. + * @throws IOException if the {@link RandomAccessFile} could not be created. + */ + private RandomAccessFile writer() throws IOException { + if (raf == null) initRAF(true); return raf; } - private synchronized void initRAF() throws IOException { - raf = new RandomAccessFile(get().getFile(), getMode()); + /** + * Access method for the internal {@link RandomAccessFile}, that only succeeds + * if the underlying file exists on disk. This prevents accidental creation of + * an empty file when calling read operations on a non-existent file. + * + * @return the internal {@link RandomAccessFile}. + * @throws IOException if the {@link RandomAccessFile} could not be created, + * or the backing file does not exists. + */ + private RandomAccessFile reader() throws IOException { + if (raf == null) initRAF(false); + return raf; } + /** + * Initializes the {@link RandomAccessFile}. + * + * @param create whether to create the {@link RandomAccessFile} if the + * underlying file does not exist yet. + * @throws IOException if the {@link RandomAccessFile} could not be created, + * or the backing file does not exist and the {@code create} + * parameter was set to {@code false}. + */ + private synchronized void initRAF(final boolean create) throws IOException { + if (!create && !exists()) { + throw new IOException("Trying to read from non-existent file!"); + } + if (closed) throw new IOException("Handle already closed"); + if (raf != null) return; + raf = new RandomAccessFile(get().getFile(), getMode()); + } } diff --git a/src/main/java/org/scijava/io/handle/ReadBufferDataHandle.java b/src/main/java/org/scijava/io/handle/ReadBufferDataHandle.java new file mode 100644 index 000000000..fed7de110 --- /dev/null +++ b/src/main/java/org/scijava/io/handle/ReadBufferDataHandle.java @@ -0,0 +1,319 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.handle; + +import java.io.IOException; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.scijava.io.location.Location; + +/** + * Read-only buffered {@link DataHandle}. It buffers the underlying handle into + * a fixed number of pages, swapping them out when necessary. + */ +public class ReadBufferDataHandle extends AbstractHigherOrderHandle { + + private static final int DEFAULT_PAGE_SIZE = 10_000; + private static final int DEFAULT_NUM_PAGES = 10; + + private final int pageSize; + private final List pages; + private final int[] slotToPage; + private final LRUReplacementStrategy replacementStrategy; + private final Map pageToSlot; + + /** + * Cached length value, for performance. When reading data, length is not + * expected to change, but querying it (e.g. via native filesystem access) + * can be slow, and we need to query the length frequently. + */ + private long length = -1; + private long offset = 0l; + private byte[] currentPage; + private int currentPageID = -1; + + /** + * Creates a {@link ReadBufferDataHandle} wrapping the provided handle using the + * default values for the size of the pages ({@value #DEFAULT_PAGE_SIZE} byte) + * and number of pages ({@link #DEFAULT_NUM_PAGES}). + * + * @param handle + * the handle to wrap + */ + public ReadBufferDataHandle(final DataHandle handle) { + this(handle, DEFAULT_PAGE_SIZE); + } + + /** + * Creates a {@link ReadBufferDataHandle} wrapping the provided handle using the + * default value for the number of pages ({@link #DEFAULT_NUM_PAGES}). + * + * @param handle + * the handle to wrap + * @param pageSize + * the size of the used pages + */ + public ReadBufferDataHandle(final DataHandle handle, final int pageSize) { + this(handle, pageSize, DEFAULT_NUM_PAGES); + } + + /** + * Creates a {@link ReadBufferDataHandle} wrapping the provided handle. + * + * @param handle + * the handle to wrap + * @param pageSize + * the size of the used pages + * @param numPages + * the number of pages to use + */ + public ReadBufferDataHandle(final DataHandle handle, final int pageSize, final int numPages) { + super(handle); + this.pageSize = pageSize; + + // init maps + slotToPage = new int[numPages]; + Arrays.fill(slotToPage, -1); + + pages = new ArrayList<>(numPages); + for (int i = 0; i < numPages; i++) { + pages.add(null); + } + + pageToSlot = new HashMap<>(); + replacementStrategy = new LRUReplacementStrategy(numPages); + } + + /** + * Ensures that the byte at the given offset is buffered, and sets the current + * page to be the one containing the specified location. + */ + private void ensureBuffered(final long globalOffset) throws IOException { + ensureOpen(); + final int pageID = (int) (globalOffset / pageSize); + if (pageID == currentPageID) + return; + + final int slotID = pageToSlot.computeIfAbsent(pageID, replacementStrategy::pickVictim); + final int inSlotID = slotToPage[slotID]; + + if (inSlotID != pageID) { // desired page is not buffered + // update the mappings + slotToPage[slotID] = pageID; + pageToSlot.put(pageID, slotID); + pageToSlot.put(inSlotID, null); + + // read the page + currentPage = readPage(pageID, slotID); + } else { + currentPage = pages.get(slotID); + } + replacementStrategy.accessed(slotID); + currentPageID = pageID; + } + + /** + * Reads the page with the id pageID into the slot with the id + * slotID. + * + * @param pageID + * the id of the page to read + * @param slotID + * the id of the slot to read the page into + * @return the read page + * @throws IOException + * if the reading fails + */ + private byte[] readPage(final int pageID, final int slotID) throws IOException { + replacementStrategy.accessed(slotID); + byte[] page = pages.get(slotID); + if (page == null) { + // lazy initialization + page = new byte[pageSize]; + pages.set(slotID, page); + } + + final long startOfPage = pageID * (long) pageSize; + if (handle().offset() != startOfPage) { + handle().seek(startOfPage); + } + + // NB: we read repeatedly until the page is full or EOF is reached + // handle().read(..) might read less bytes than requested + int off = 0; + while (off < pageSize) { + final int read = handle().read(page, off, pageSize - off); + if (read == -1) { // EOF + break; + } + off += read; + } + return page; + } + + /** + * Calculates the offset in the current page for the given global offset + */ + private int globalToLocalOffset(final long off) { + return (int) (off % pageSize); + } + + @Override + public void seek(final long pos) throws IOException { + this.offset = pos; + } + + @Override + public long length() throws IOException { + if (length < 0) length = super.length(); + return length; + } + + @Override + public int read(final byte[] b, final int targetOffset, final int len) + throws IOException + { + if (len == 0) return 0; + + // the last position we will read + final long endPos = offset + len; + + // the number of bytes we plan to read + final int readLength = (int) (endPos < length() ? len : length() - offset); + + int read = 0; // the number of bytes we have read + int localTargetOff = targetOffset; + + while (read < readLength) { + ensureBuffered(offset); + + // calculate local offsets + final int pageOffset = globalToLocalOffset(offset); + int localLength = pageSize - pageOffset; + localLength = Math.min(localLength, readLength - read); + localLength = Math.min(localLength, b.length - localTargetOff); + if (localLength == 0) break; // we've read all we can + + // copy the data + System.arraycopy(currentPage, pageOffset, b, localTargetOff, localLength); + + // update offsets + read += localLength; + offset += localLength; + localTargetOff += localLength; + } + // return -1 if we tried to read at least one byte but failed + return read != 0 ? read : -1; + } + + @Override + public byte readByte() throws IOException { + ensureBuffered(offset); + return currentPage[globalToLocalOffset(offset++)]; + } + + @Override + public boolean isReadable() { + return true; + } + + @Override + public long offset() throws IOException { + return offset; + } + + @Override + protected void cleanup() { + pages.clear(); + currentPage = null; + } + + @Override + public void write(final int b) throws IOException { + throw DataHandles.readOnlyException(); + } + + @Override + public void write(final byte[] b, final int off, final int len) throws IOException { + throw DataHandles.readOnlyException(); + } + + @Override + public void setLength(final long length) throws IOException { + throw DataHandles.readOnlyException(); + } + + /** + * Simple strategy to pick the slot that get's evicted from the cache. This + * strategy always picks the least recently used slot. + */ + private class LRUReplacementStrategy { + + private final Deque queue; + + /** + * Creates a {@link LRUReplacementStrategy} with the specified number of slots. + * + * @param numSlots + * the number of slots to use + */ + public LRUReplacementStrategy(final int numSlots) { + queue = new ArrayDeque<>(numSlots); + + // fill the queue + for (int i = 0; i < numSlots; i++) { + queue.add(i); + } + } + + /** + * Notifies this strategy that a slot has been accessed, pushing it to the end + * of the queue. + * + * @param slotID + * the id of the slot that has been accessed + */ + public void accessed(final int slotID) { + // put accessed element to the end of the queue + queue.remove(slotID); + queue.add(slotID); + } + + public int pickVictim(@SuppressWarnings("unused") final int pageID) { + return queue.peek(); + } + } +} diff --git a/src/main/java/org/scijava/io/handle/ResettableStreamHandle.java b/src/main/java/org/scijava/io/handle/ResettableStreamHandle.java new file mode 100644 index 000000000..b4a7310f8 --- /dev/null +++ b/src/main/java/org/scijava/io/handle/ResettableStreamHandle.java @@ -0,0 +1,70 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.handle; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.scijava.io.location.Location; + +/** + * A {@link DataHandle} backed by an {@link InputStream} and/or + * {@link OutputStream}. Supports resetting the handle to the start of the + * internal stream(s). + */ +public interface ResettableStreamHandle extends + StreamHandle +{ + + @Override + default void seek(final long pos) throws IOException { + final long off = offset(); + if (pos == off) return; // nothing to do + if (pos > off) { + // jump from the current offset + jump(pos - off); + } + else { + // jump from the beginning of the stream + resetStream(); + jump(pos); + } + setOffset(pos); + } + + /** + * Resets the stream to its start. + * + * @throws IOException If something goes wrong with the reset + */ + @Override + void resetStream() throws IOException; +} diff --git a/src/main/java/org/scijava/io/handle/SeekableStreamHandle.java b/src/main/java/org/scijava/io/handle/SeekableStreamHandle.java new file mode 100644 index 000000000..6b4746f56 --- /dev/null +++ b/src/main/java/org/scijava/io/handle/SeekableStreamHandle.java @@ -0,0 +1,51 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.handle; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.scijava.io.location.Location; + +/** + * A {@link DataHandle} backed by an {@link InputStream} and/or + * {@link OutputStream}. Supports seeking to an arbitrary position within the + * stream. + * + * @author Gabriel Einsdorf + */ +public interface SeekableStreamHandle extends + ResettableStreamHandle +{ + + @Override + void seek(long pos) throws IOException; +} diff --git a/src/main/java/org/scijava/io/handle/StreamHandle.java b/src/main/java/org/scijava/io/handle/StreamHandle.java new file mode 100644 index 000000000..30a3e88ba --- /dev/null +++ b/src/main/java/org/scijava/io/handle/StreamHandle.java @@ -0,0 +1,190 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.handle; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.scijava.io.location.Location; + +/** + * A {@link DataHandle} backed by an {@link InputStream} and/or + * {@link OutputStream}. + * + * @author Curtis Rueden + * @author Melissa Linkert + * @author Gabriel Einsdorf + */ +public interface StreamHandle extends DataHandle { + + // -- StreamHandle methods -- + + /** + * Gets an input stream for reading data, positioned at the current offset. + * + * @return the appropriate input stream, or null if the handle is write-only. + * @throws IOException + */ + InputStream in() throws IOException; + + /** + * Gets an output stream for writing data, positioned at the current offset. + * + * @return the appropriate output stream, or null if the handle is read-only. + */ + OutputStream out() throws IOException; + + /** + * Sets the offset of the handle to the given position. + *

+ * This method is intended to be called only in conjunction with reading from + * the input stream, or writing to the output stream. Otherwise, the contents + * may get out of sync. + *

+ */ + void setOffset(long offset); + + /** + * Increments the handle's offset by the given amount. + *

+ * This method is intended to be called only in conjunction with reading from + * the input stream, or writing to the output stream. Otherwise, the contents + * may get out of sync. + *

+ */ + default void advance(final long bytes) throws IOException { + setOffset(offset() + bytes); + } + + // -- DataHandle methods -- + + @Override + default void seek(final long pos) throws IOException { + if (pos == offset()) return; + if (pos > offset()) { + jump(pos - offset()); + } + else { + throw new UnsupportedOperationException( + "Can't seek backwards through this StreamHandle"); + } + } + + /** + * Resets the stream to its start. + * + * @throws IOException If something goes wrong with the reset + */ + void resetStream() throws IOException; + + default void jump(final long n) throws IOException, EOFException { + long remain = n; + while (remain > 0) { + final long r = in().skip(remain); + if (r < 0) throw new EOFException(); + remain -= r; + } + } + + @Override + default void ensureReadable(final long count) throws IOException { + if (in() == null) throw new IOException("This handle is write-only."); + DataHandle.super.ensureReadable(count); + } + + @Override + default boolean ensureWritable(final long count) throws IOException { + if (out() == null) throw new IOException("This handle is read-only."); + return DataHandle.super.ensureWritable(count); + } + + @Override + default int read() throws IOException { + ensureReadable(0); + final int v = in().read(); + if (v >= 0) advance(1); + return v; + } + + @Override + default byte readByte() throws IOException { + int ch = this.read(); + if (ch < 0) throw new EOFException(); + return (byte) (ch); + } + + @Override + default int read(final byte[] b, final int off, final int len) + throws IOException + { + final int n = in().read(b, off, len); + if (n >= 0) advance(n); + return n; + } + + // -- DataOutput methods -- + + @Override + default void write(final int v) throws IOException { + ensureWritable(1); + out().write(v); + advance(1); + } + + @Override + default void writeByte(int v) throws IOException { + write(v); + } + + @Override + default void write(final byte[] b, final int off, final int len) + throws IOException + { + ensureWritable(len); + out().write(b, off, len); + advance(len); + } + + // -- Closeable methods -- + + @Override + default void close() throws IOException { + // TODO: Double check this logic. + try (final InputStream in = in()) { + if (in != null) in.close(); + } + try (final OutputStream out = out()) { + if (out != null) out.close(); + } + } + +} diff --git a/src/main/java/org/scijava/io/handle/WriteBufferDataHandle.java b/src/main/java/org/scijava/io/handle/WriteBufferDataHandle.java new file mode 100644 index 000000000..f1549aa30 --- /dev/null +++ b/src/main/java/org/scijava/io/handle/WriteBufferDataHandle.java @@ -0,0 +1,203 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.handle; + +import java.io.EOFException; +import java.io.IOException; + +import org.scijava.io.location.Location; + +/** + * Wraps a {@link DataHandle} and acts as a write buffer. + * + * @author Gabriel Einsdorf + */ +public class WriteBufferDataHandle extends AbstractHigherOrderHandle { + + private static final int DEFAULT_BUFFERSIZE = 10_000; + private long offset = 0; + private int nextPos = 0; + + private byte[] buffer; + private final int bufferSize; + + /** + * Creates a {@link WriteBufferDataHandle} that wraps the given + * {@link DataHandle}, the default size for the buffer is used + * ({@value #DEFAULT_BUFFERSIZE} bytes). + * + * @param handle the handle to wrap + */ + public WriteBufferDataHandle(final DataHandle handle) { + this(handle, DEFAULT_BUFFERSIZE); + } + + /** + * Creates a {@link WriteBufferDataHandle} that wraps the given + * {@link DataHandle} + * + * @param handle the handle to wrap + * @param bufferSize the size of the write buffer in bytes + */ + public WriteBufferDataHandle(final DataHandle handle, + final int bufferSize) + { + super(handle); + this.bufferSize = bufferSize; + } + + @Override + public void write(final int b) throws IOException { + ensureOpen(); + // if buffer is full flush + if (nextPos >= buffer.length) { + flush(); + } + // buffer the byte + buffer[nextPos] = (byte) b; + nextPos++; + offset++; + } + + @Override + public void write(final byte[] b, final int off, final int len) + throws IOException + { + ensureOpen(); + // ensure the range is valid + if ((off < 0) || (off > b.length) || (len < 0) || ((off + + len) > b.length) || ((off + len) < 0)) + { + throw new IndexOutOfBoundsException(); + } + else if (len == 0) { + return; // nothing to do + } + + // skip the buffering and write directly to the handle + if (len > buffer.length) { + flush(); + handle().write(b, off, len); + offset += len; + return; + } + + // copy to buffer / flush if necessary + int start = off; + final int total = off + len; + while (start < total) { + final int numItems = Math.min(buffer.length - nextPos, total - start); + System.arraycopy(b, start, buffer, nextPos, numItems); + start += numItems; + nextPos += numItems; + if (nextPos >= buffer.length) { + flush(); + } + } + } + + /** + * Write the buffer content to the underlying handle + */ + private void flush() throws IOException { + ensureOpen(); + if (nextPos == 0) return; + + handle().write(buffer, 0, nextPos); + nextPos = 0; + } + + @Override + public long length() throws IOException { + // data written out + data in the buffer + return handle().length() + nextPos - 1; + } + + @Override + public void setLength(final long length) throws IOException { + ensureOpen(); + handle().setLength(length); + } + + @Override + public boolean isReadable() { + return false; + } + + /** + * @throws IOException if this handle has been closed + */ + @Override + protected void ensureOpen() throws IOException { + super.ensureOpen(); + if (buffer == null) { + buffer = new byte[bufferSize]; + } + } + + @Override + public long offset() throws IOException { + return offset; + } + + @Override + public void seek(final long pos) throws IOException { + ensureOpen(); + if (pos >= length()) { + throw new EOFException(); + } + flush(); + offset = pos; + handle().seek(offset); + } + + @Override + public long skip(final long n) throws IOException { + throw new IOException("Operation 'skip' is not supported!"); + } + + @Override + public byte readByte() throws IOException { + throw DataHandles.writeOnlyException(); + } + + @Override + public int read(final byte[] b, final int off, final int len) + throws IOException + { + throw DataHandles.writeOnlyException(); + } + + @Override + protected void cleanup() throws IOException { + flush(); + buffer = null; + } +} diff --git a/src/main/java/org/scijava/io/location/AbstractLocation.java b/src/main/java/org/scijava/io/location/AbstractLocation.java new file mode 100644 index 000000000..007a1a8a9 --- /dev/null +++ b/src/main/java/org/scijava/io/location/AbstractLocation.java @@ -0,0 +1,68 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.location; + +import java.net.URI; +import java.util.Objects; + +/** + * Abstract base class for {@link Location} implementations. + * + * @author Curtis Rueden + */ +public abstract class AbstractLocation implements Location { + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((getURI() == null) ? 0 : getURI().hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + final Location other = (Location) obj; + return Objects.equals(getURI(), other.getURI()); + } + + @Override + public String toString() { + final String prefix = getClass().getSimpleName() + ":"; + final URI uri = getURI(); + if (uri != null) return prefix + uri; + final String name = getName(); + if (name != null) return prefix + name; + return prefix + defaultName(); + } +} diff --git a/src/main/java/org/scijava/io/location/AbstractLocationResolver.java b/src/main/java/org/scijava/io/location/AbstractLocationResolver.java new file mode 100644 index 000000000..b40f6c0d9 --- /dev/null +++ b/src/main/java/org/scijava/io/location/AbstractLocationResolver.java @@ -0,0 +1,69 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.location; + +import java.net.URI; + +import org.scijava.plugin.AbstractHandlerPlugin; + +/** + * Abstract super class for {@link LocationResolver} plugins. + * + * @author Gabriel Einsdorf + */ +public abstract class AbstractLocationResolver extends + AbstractHandlerPlugin implements LocationResolver +{ + + private final String[] schemes; + + /** + * @param schemes the uri schmemes that the implementing sub-type supports + */ + public AbstractLocationResolver(String... schemes) { + assert schemes.length > 0; + this.schemes = schemes; + } + + @Override + public boolean supports(URI uri) { + boolean supports = false; + for (final String scheme : schemes) { + supports = supports || scheme.equals(uri.getScheme()); + } + return supports; + } + + @Override + public Class getType() { + return URI.class; + } + +} diff --git a/src/main/java/org/scijava/io/location/AbstractRemoteLocation.java b/src/main/java/org/scijava/io/location/AbstractRemoteLocation.java new file mode 100644 index 000000000..61b3f9735 --- /dev/null +++ b/src/main/java/org/scijava/io/location/AbstractRemoteLocation.java @@ -0,0 +1,55 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.location; + +/** + * Abstract base class for {@link RemoteLocation} implementations. + * + * @author Gabriel Einsdorf + */ +public abstract class AbstractRemoteLocation extends AbstractLocation implements + RemoteLocation +{ + + /** + * The {@link RemoteLocation}s timeout in milliseconds. + */ + private long timeout = 15_000l; + + @Override + public long getTimeout() { + return timeout; + } + + @Override + public void setTimeout(final long timeout) { + this.timeout = timeout; + } +} diff --git a/src/main/java/org/scijava/io/location/BrowsableLocation.java b/src/main/java/org/scijava/io/location/BrowsableLocation.java new file mode 100644 index 000000000..f6efc3b99 --- /dev/null +++ b/src/main/java/org/scijava/io/location/BrowsableLocation.java @@ -0,0 +1,100 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.location; + +import java.io.IOException; +import java.util.Collections; +import java.util.Set; + +/** + * A {@link Location} that offers methods to browse other locations relative to + * it. + * + * @author Gabriel Einsdorf + * @author Curtis Rueden + */ +public interface BrowsableLocation extends Location { + + /** + * Obtains a location pointing to the parent directory of this one. + * + * @return the parent location of this one, or null if this + * location has no parent. + * @throws IOException if something goes wrong obtaining the parent. + */ + BrowsableLocation parent() throws IOException; + + /** + * Obtains a collection of locations for whom this location is the parent. + * Note that this will only succeed if calls to {@link #isDirectory()} on this + * location return true. + * + * @return A set containing the children of this location, or + * {@link Collections#EMPTY_SET} if this location has no children. + * @throws IOException if something goes wrong obtaining the children. + * @throws IllegalArgumentException if this location is not a directory (i.e., + * {@link #isDirectory()} returns false). + */ + Set children() throws IOException; + + /** + * Obtains a location relative to this one, which will be configured + * like the current location, but point to a the file specified by the + * path parameter. + * + * @param path the relative path of the desired location. + * @return A location that points to the specified file location. + * @throws IOException if something goes wrong obtaining the sibling + */ + BrowsableLocation sibling(String path) throws IOException; + + /** + * Tests whether this location is a directory, meaning that it can have + * children. It is recommended to use this method before calling + * {@link #child(String)} or {@link #children()}, to ensure those calls + * succeed. + * + * @return True iff the location represents a directory. + */ + boolean isDirectory(); + + /** + * Obtains a location with the given name, for whom this location is the + * parent. Note that this will only succeed if calls to {@link #isDirectory()} + * on this location return true. + * + * @param name the name of the child + * @return a location pointing to the child + * @throws IOException if something goes wrong obtaining the child. + * @throws IllegalArgumentException if this location is not a directory (i.e., + * {@link #isDirectory()} returns false). + */ + BrowsableLocation child(String name) throws IOException; +} diff --git a/src/main/java/org/scijava/io/location/BytesLocation.java b/src/main/java/org/scijava/io/location/BytesLocation.java new file mode 100644 index 000000000..0b7f604ed --- /dev/null +++ b/src/main/java/org/scijava/io/location/BytesLocation.java @@ -0,0 +1,183 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.location; + +import org.scijava.io.ByteArrayByteBank; +import org.scijava.io.ByteBank; +import org.scijava.util.ByteArray; + +/** + * {@link Location} backed by a {@link ByteBank}. + * + * @author Curtis Rueden + * @author Gabriel Einsdorf + */ +public class BytesLocation extends AbstractLocation { + + private final ByteBank bytes; + + private final String name; + + /** + * Creates a {@link BytesLocation} backed by the specified + * {@link ByteBank}. + * + * @param bytes the {@link ByteBank} that will back this {@link Location} + */ + public BytesLocation(final ByteBank bytes) { + this(bytes, null); + } + + /** + * Creates a {@link BytesLocation} backed by the specified {@link ByteBank}. + * + * @param bytes the {@link ByteBank} that will back this {@link Location} + * @param name the name of this {@link Location} + */ + public BytesLocation(final ByteBank bytes, final String name) { + this.bytes = bytes; + this.name = name; + } + + /** + * Creates a {@link BytesLocation} backed by a {@link ByteArrayByteBank} with + * the specified initial capacity, but with a reported size of 0. This method + * can be used to avoid needing to grow the underlying {@link ByteBank}. + */ + public BytesLocation(final int initialCapacity) { + this(initialCapacity, null); + } + + /** + * Creates a {@link BytesLocation} backed by a {@link ByteArrayByteBank} with + * the specified initial capacity, but with a reported size of 0. This method + * can be used to avoid needing to grow the underlying {@link ByteBank}. + * + * @param name the name of this {@link Location} + */ + public BytesLocation(final int initialCapacity, final String name) { + this.bytes = new ByteArrayByteBank(initialCapacity); + this.name = name; + } + + /** + * Creates a {@link BytesLocation} backed by a {@link ByteArrayByteBank} + * that wraps the specified {@link ByteArray}. + */ + public BytesLocation(final ByteArray bytes) { + this(bytes, null); + } + + /** + * Creates a {@link BytesLocation} backed by a {@link ByteArrayByteBank} that + * wraps the specified {@link ByteArray}. + * + * @param name the name of this Location. + */ + public BytesLocation(final ByteArray bytes, final String name) { + this.bytes = new ByteArrayByteBank(bytes); + this.name = name; + } + + /** + * Creates a {@link BytesLocation} backed by a {@link ByteArrayByteBank} + * which wraps the specified array. + * + * @param bytes the array to wrap + */ + public BytesLocation(final byte[] bytes) { + this(bytes, null); + } + + /** + * Creates a {@link BytesLocation} backed by a {@link ByteArrayByteBank} which + * wraps the specified array. + * + * @param bytes the array to wrap + * @param name the name of this Location. + */ + public BytesLocation(final byte[] bytes, final String name) { + this.bytes = new ByteArrayByteBank(bytes); + this.name = name; + } + + /** + * Creates a {@link BytesLocation} backed by a {@link ByteArrayByteBank} with + * the specified initial capacity and the provided data. + * + * @param bytes the bytes to copy into the new {@link BytesLocation} + * @param offset the offset in the bytes array to start copying from + * @param length the number of bytes to copy, starting from the offset + */ + public BytesLocation(final byte[] bytes, final int offset, final int length) { + this(bytes, offset, length, null); + } + + /** + * Creates a {@link BytesLocation} backed by a {@link ByteArrayByteBank} with + * the specified initial capacity and the provided data. + * + * @param bytes the bytes to copy into the new {@link BytesLocation} + * @param offset the offset in the bytes array to start copying from + * @param length the number of bytes to copy, starting from the offset + * @param name the name of this Location. + */ + public BytesLocation(final byte[] bytes, final int offset, final int length, + final String name) + { + this.bytes = new ByteArrayByteBank(length); + this.bytes.setBytes(0l, bytes, offset, length); + this.name = name; + } + + // -- BytesLocation methods -- + + /** Gets the backing {@link ByteBank}. */ + public ByteBank getByteBank() { + return bytes; + } + + @Override + public String getName() { + return name != null ? name : defaultName(); + } + + // -- Object methods -- + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public boolean equals(final Object obj) { + return obj == this; + } +} diff --git a/src/main/java/org/scijava/io/location/DefaultLocationService.java b/src/main/java/org/scijava/io/location/DefaultLocationService.java new file mode 100644 index 000000000..028dd807e --- /dev/null +++ b/src/main/java/org/scijava/io/location/DefaultLocationService.java @@ -0,0 +1,49 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.location; + +import java.net.URI; + +import org.scijava.plugin.AbstractHandlerService; +import org.scijava.plugin.Plugin; +import org.scijava.service.Service; + +/** + * Default {@link LocationService} implementation. + * + * @author Gabriel Einsdorf + */ +@Plugin(type = Service.class) +public class DefaultLocationService extends + AbstractHandlerService implements + LocationService +{ + // NB: No implementation needed. +} diff --git a/src/main/java/org/scijava/io/AbstractLocation.java b/src/main/java/org/scijava/io/location/DummyLocation.java similarity index 81% rename from src/main/java/org/scijava/io/AbstractLocation.java rename to src/main/java/org/scijava/io/location/DummyLocation.java index 8937d3847..0a9dd546f 100644 --- a/src/main/java/org/scijava/io/AbstractLocation.java +++ b/src/main/java/org/scijava/io/location/DummyLocation.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -29,13 +27,13 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.location; /** - * Abstract base class for {@link Location} implementations. - * + * {@link Location} backed by nothing whatsoever. + * * @author Curtis Rueden */ -public abstract class AbstractLocation implements Location { +public class DummyLocation extends AbstractLocation { // NB: No implementation needed. } diff --git a/src/main/java/org/scijava/io/location/FileLocation.java b/src/main/java/org/scijava/io/location/FileLocation.java new file mode 100644 index 000000000..4cf688d3b --- /dev/null +++ b/src/main/java/org/scijava/io/location/FileLocation.java @@ -0,0 +1,127 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.location; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * {@link Location} backed by a {@link File} on disk. + * + * @author Curtis Rueden + * @author Gabriel Einsdorf + */ +public class FileLocation extends AbstractLocation implements + BrowsableLocation +{ + + private final File file; + + public FileLocation(final File file) { + Objects.requireNonNull(file); + this.file = file; + } + + public FileLocation(final String path) { + this(new File(path)); + } + + public FileLocation(final URI path) { + this(new File(path)); + } + + // -- FileLocation methods -- + + /** Gets the associated {@link File}. */ + public File getFile() { + return file; + } + + // -- Location methods -- + + @Override + public URI getURI() { + return getFile().toURI(); + } + + @Override + public String getName() { + return file.getName(); + } + + // -- BrowsableLocation methods -- + + @Override + public FileLocation parent() throws IOException { + return new FileLocation(file.getParentFile()); + } + + @Override + public Set children() throws IOException { + validateDirectory(); + final File[] files = file.listFiles(); + if (files == null) return Collections.emptySet(); + + final Set out = new HashSet<>(files.length); + for (final File child : files) { + out.add(new FileLocation(child)); + } + return out; + } + + @Override + public FileLocation sibling(final String path) { + return new FileLocation(new File(file.getParentFile(), path)); + } + + @Override + public FileLocation child(final String name) { + validateDirectory(); + return new FileLocation(new File(file, name)); + } + + @Override + public boolean isDirectory() { + return file.isDirectory(); + } + + // -- Helper methods -- + + private void validateDirectory() { + if (isDirectory()) return; + throw new IllegalArgumentException( + "This location does not point to a directory!"); + } +} diff --git a/src/main/java/org/scijava/io/location/FileLocationResolver.java b/src/main/java/org/scijava/io/location/FileLocationResolver.java new file mode 100644 index 000000000..5e6b19df0 --- /dev/null +++ b/src/main/java/org/scijava/io/location/FileLocationResolver.java @@ -0,0 +1,52 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.location; + +import java.net.URI; + +import org.scijava.plugin.Plugin; + +/** + * Implementation of {@link LocationResolver} for {@link FileLocation}. + * + * @author Gabriel Einsdorf + */ +@Plugin(type = LocationResolver.class) +public class FileLocationResolver extends AbstractLocationResolver { + + public FileLocationResolver() { + super("file"); + } + + @Override + public Location resolve(URI uri) { + return new FileLocation(uri); + } +} diff --git a/src/main/java/org/scijava/io/Location.java b/src/main/java/org/scijava/io/location/Location.java similarity index 65% rename from src/main/java/org/scijava/io/Location.java rename to src/main/java/org/scijava/io/location/Location.java index 34b79f63b..2c579ad7e 100644 --- a/src/main/java/org/scijava/io/Location.java +++ b/src/main/java/org/scijava/io/location/Location.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -29,10 +27,12 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.location; import java.net.URI; +import org.scijava.io.handle.DataHandle; + /** * A location is a data descriptor, such as a file on disk, a remote * URL, or a database connection. @@ -42,10 +42,11 @@ * resource identifier ({@link URI}), a location identifies where * the data resides, without necessarily specifying how to access that * data. The {@link DataHandle} interface defines a plugin that knows how to - * provide a stream of bytes for a particular kind of location. + * read and/or write bytes for a particular kind of location. *

* * @author Curtis Rueden + * @author Gabriel Einsdorf */ public interface Location { @@ -57,4 +58,30 @@ default URI getURI() { return null; } + /** + * Gets a (typically short) name expressing this location. This string is not + * intended to unambiguously identify the location, but rather act as a + * friendly, human-readable name. The precise behavior will depend on the + * implementation, but as an example, a file-based location could return the + * name of the associated file without its full path. + * + * @return The name, or an empty string if no name is available. + */ + default String getName() { + return defaultName(); + } + + /** + * Gets the default name used when no explicit name is assigned. + *

+ * Note: this is mostly intended for debugging, since most kinds of + * {@code Location} will assign some non-default name. But in cases where that + * does not occur, this value can be useful to detect the situation. + *

+ * + * @return The default name string. + */ + default String defaultName() { + return "Location.defaultName"; + } } diff --git a/src/main/java/org/scijava/io/location/LocationResolver.java b/src/main/java/org/scijava/io/location/LocationResolver.java new file mode 100644 index 000000000..e4c60eada --- /dev/null +++ b/src/main/java/org/scijava/io/location/LocationResolver.java @@ -0,0 +1,53 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.location; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.scijava.plugin.HandlerPlugin; + +/** + * {@link LocationResolver} plugins allow resolving an {@link URI} to a + * {@link Location}. Extending {@link AbstractLocationResolver} is recommended + * for easy implementation. + * + * @author Gabriel Einsdorf + */ +public interface LocationResolver extends HandlerPlugin { + + /** + * Resolves the given {@link URI} to a {@link Location} + * + * @return the resolved Location + * @throws URISyntaxException + */ + Location resolve(URI uri) throws URISyntaxException; +} diff --git a/src/main/java/org/scijava/io/location/LocationService.java b/src/main/java/org/scijava/io/location/LocationService.java new file mode 100644 index 000000000..078eca44c --- /dev/null +++ b/src/main/java/org/scijava/io/location/LocationService.java @@ -0,0 +1,106 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.location; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; + +import org.scijava.plugin.HandlerService; +import org.scijava.service.SciJavaService; + +/** + * A service that allows resolving of URIs to Locations, using + * {@link LocationResolver} plugins for translation. + * + * @author Gabriel Einsdorf + */ +public interface LocationService extends HandlerService, + SciJavaService +{ + + /** + * Turns the given string into a {@link URI}, then resolves it to a + * {@link Location}. + * + * @param uriString the uri to resolve + * @return the resolved {@link Location} + * @throws URISyntaxException if the URI is malformed + */ + default Location resolve(final String uriString) throws URISyntaxException { + try { + Location loc = resolve(new URI(uriString)); + if (loc != null) return loc; + } + catch (final URISyntaxException exc) { + // In general, filenames are not valid URI strings. + // Particularly on Windows, there are backslashes, which are invalid in URIs. + // So we explicitly turn this string into a file if an error happens above. + } + return resolve(new File(uriString).toURI()); + } + + /** + * Resolves the given {@link URI} to a location. If the {@code scheme} part of + * the URI is {@code null} the path component is resolved as a local file. + * + * @param uri the uri to resolve + * @return the resolved {@link Location} or null if no resolver + * could be found. + * @throws URISyntaxException if the URI is malformed + */ + default Location resolve(URI uri) throws URISyntaxException { + if (uri.getScheme() == null) { // Fallback for local files + uri = new File(uri.getPath()).toURI(); + } + final LocationResolver resolver = getResolver(uri); + return resolver != null ? resolver.resolve(uri) : null; + } + + /** @deprecated Use {@link #getHandler} instead. */ + @Deprecated + default LocationResolver getResolver(URI uri) { + return getHandler(uri); + } + + // -- PTService methods -- + + @Override + default Class getPluginType() { + return LocationResolver.class; + } + + // -- Typed methods -- + + @Override + default Class getType() { + return URI.class; + } +} diff --git a/src/main/java/org/scijava/io/location/RemoteLocation.java b/src/main/java/org/scijava/io/location/RemoteLocation.java new file mode 100644 index 000000000..20fd65fef --- /dev/null +++ b/src/main/java/org/scijava/io/location/RemoteLocation.java @@ -0,0 +1,53 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.location; + +/** + * A {@link Location} which resides on a remote machine. + * + * @author Gabriel Einsdorf + */ +public interface RemoteLocation extends Location { + + /** + * Sets the connection timeout for this location. + * + * @param timeout The timeout in milliseconds. + */ + void setTimeout(long timeout); + + /** + * Gets the connection timeout for this location. + * + * @return The connection timeout in milliseconds. + */ + long getTimeout(); + +} diff --git a/src/main/java/org/scijava/io/URILocation.java b/src/main/java/org/scijava/io/location/URILocation.java similarity index 93% rename from src/main/java/org/scijava/io/URILocation.java rename to src/main/java/org/scijava/io/location/URILocation.java index 819fb2282..52621ea91 100644 --- a/src/main/java/org/scijava/io/URILocation.java +++ b/src/main/java/org/scijava/io/location/URILocation.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -29,7 +27,7 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.location; import java.io.UnsupportedEncodingException; import java.net.URI; @@ -114,7 +112,7 @@ private Map decodeQuery(final String query) { * @see URLDecoder */ private String decode(final String s) { - // http://stackoverflow.com/a/6926987 + // https://stackoverflow.com/a/6926987 try { return URLDecoder.decode(s.replace("+", "%2B"), "UTF-8"); } diff --git a/src/main/java/org/scijava/io/URLLocation.java b/src/main/java/org/scijava/io/location/URLLocation.java similarity index 87% rename from src/main/java/org/scijava/io/URLLocation.java rename to src/main/java/org/scijava/io/location/URLLocation.java index 1b7490894..396dedeae 100644 --- a/src/main/java/org/scijava/io/URLLocation.java +++ b/src/main/java/org/scijava/io/location/URLLocation.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -29,7 +27,7 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.location; import java.net.URI; import java.net.URISyntaxException; @@ -60,7 +58,7 @@ public URL getURL() { /** * Gets the associated {@link URI}, or null if this URL is not formatted - * strictly according to to RFC2396 and cannot be converted to a URI. + * strictly according to RFC2396 and cannot be converted to a URI. */ @Override public URI getURI() { diff --git a/src/main/java/org/scijava/io/nio/ByteBufferByteBank.java b/src/main/java/org/scijava/io/nio/ByteBufferByteBank.java new file mode 100644 index 000000000..6f9ec504d --- /dev/null +++ b/src/main/java/org/scijava/io/nio/ByteBufferByteBank.java @@ -0,0 +1,180 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.nio; + +import java.nio.ByteBuffer; +import java.util.function.Function; + +import org.scijava.io.ByteBank; + +/** + * A {@link ByteBank} backed by a {@link ByteBuffer}. Self-growing up to a + * maximal capacity of {@link Integer#MAX_VALUE} + * + * @author Gabriel Einsdorf + */ +public class ByteBufferByteBank implements ByteBank { + + private static final int DEFAULT_CAPACITY = 10_000; + + private ByteBuffer buffer; + + private int size; + + private Function provider; + + public ByteBufferByteBank() { + provider = ByteBuffer::allocate; + buffer = provider.apply(DEFAULT_CAPACITY); + } + + public ByteBufferByteBank(final Function provider) { + this.provider = provider; + buffer = provider.apply(DEFAULT_CAPACITY); + } + + public ByteBufferByteBank(final Function provider, + final int initialCapacity) + { + this.provider = provider; + buffer = provider.apply(initialCapacity); + } + + public ByteBufferByteBank(final int initialCapacity) { + provider = ByteBuffer::allocate; + buffer = provider.apply(initialCapacity); + } + + @Override + public long getMaxBufferSize() { + return Integer.MAX_VALUE; + } + + @Override + public void setBytes(final long startpos, final byte[] bytes, + final int offset, final int length) + { + // ensure we have space + checkWritePos(startpos, startpos + length); + final int neededCapacity = size + length; + ensureCapacity(neededCapacity); + + // copy the data + buffer.position((int) startpos); + buffer.put(bytes, offset, length); + + // update the maxpos + updateSize(startpos + length); + } + + @Override + public void setByte(final long pos, final byte b) { + checkWritePos(pos, pos); + if (pos == buffer.capacity()) { + ensureCapacity((int) pos + 1); + } + buffer.put((int) pos, b); + updateSize(pos + 1); + } + + @Override + public void clear() { + buffer.clear(); + size = 0; + } + + @Override + public byte getByte(final long pos) { + checkReadPos(pos, pos); + // the buffer might contain bytes with negative value + // we need to flip the sign to positive to satisfy the contract of this + // method + return buffer.get((int) pos); + } + + @Override + public int getBytes(final long startPos, final byte[] b, final int offset, + final int length) + { + checkReadPos(startPos, startPos + length); + // ensure we don't try to read data which is not in the buffer + final int readLength = (int) Math.min(size() - startPos, length); + buffer.position((int) startPos); + buffer.get(b, offset, readLength); + + return readLength; + } + + @Override + public long size() { + return size; + } + + @Override + public boolean isReadOnly() { + // NB: Some ByteBuffers are read-only. But there is no API to check it. + // Therefore, we make a "best effort" guess based on known read-only types. + // Since these read-only types are not public, we compare class names rather + // than checking for type equality or using instanceof. + final String className = buffer.getClass().getName(); + return className.equals("java.nio.HeapByteBufferR") || + className.equals("java.nio.DirectByteBufferR"); + } + + // -- Helper methods -- + + private void ensureCapacity(final int minCapacity) { + final int oldCapacity = buffer.capacity(); + if (minCapacity <= oldCapacity) return; // no need to grow + + // grow the array by up to 50% (plus a small constant) + final int growth = Math.min(oldCapacity / 2 + 16, Integer.MAX_VALUE); + final int newCapacity; + if (growth > Integer.MAX_VALUE - oldCapacity) { + // growth would push array over the maximum array size + newCapacity = Integer.MAX_VALUE; + } + else newCapacity = oldCapacity + growth; + // ensure the array grows by at least the requested minimum capacity + final int newLength = Math.max(minCapacity, newCapacity); + + // copy the data into a new array + buffer.position(0); + final ByteBuffer newBuffer = ByteBuffer.allocate(newLength); + newBuffer.order(buffer.order()); + newBuffer.put(buffer); + buffer = newBuffer; + } + + private void updateSize(final long newSize) { + size = (int) (newSize > size ? newSize : size); + } + +} diff --git a/src/main/java/org/scijava/io/nio/DefaultNIOService.java b/src/main/java/org/scijava/io/nio/DefaultNIOService.java new file mode 100644 index 000000000..2550caab8 --- /dev/null +++ b/src/main/java/org/scijava/io/nio/DefaultNIOService.java @@ -0,0 +1,117 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.nio; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileChannel.MapMode; + +import org.scijava.log.LogService; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.service.AbstractService; +import org.scijava.service.Service; + +/** + * Default service for working with the {@link java.nio} package, particularly + * NIO {@link ByteBuffer} objects. + * + * @author Chris Allan + * @author Curtis Rueden + */ +@Plugin(type = Service.class) +public class DefaultNIOService extends AbstractService implements NIOService { + + // -- Fields -- + + @Parameter + private LogService log; + + /** Whether or not we are to use memory mapped I/O. */ + private final boolean useMappedByteBuffer = Boolean.parseBoolean(System + .getProperty("mappedBuffers")); + + // -- NIOService API methods -- + + @Override + public ByteBuffer allocate(final FileChannel channel, final MapMode mapMode, + final long bufferStartPosition, final int newSize) throws IOException + { + log.debug("NIO: allocate: mapped=" + useMappedByteBuffer + ", start=" + + bufferStartPosition + ", size=" + newSize); + if (useMappedByteBuffer) { + return allocateMappedByteBuffer(channel, mapMode, bufferStartPosition, + newSize); + } + return allocateDirect(channel, bufferStartPosition, newSize); + } + + // -- Helper methods -- + + /** + * Allocates memory and copies the desired file data into it. + * + * @param channel File channel to allocate or map byte buffers from. + * @param bufferStartPosition The absolute position of the start of the + * buffer. + * @param newSize The buffer size. + * @return A newly allocated NIO byte buffer. + * @throws IOException If there is an issue aligning or allocating the buffer. + */ + private ByteBuffer allocateDirect(final FileChannel channel, + final long bufferStartPosition, final int newSize) throws IOException + { + final ByteBuffer buffer = ByteBuffer.allocate(newSize); + channel.read(buffer, bufferStartPosition); + return buffer; + } + + /** + * Memory maps the desired file data into memory. + * + * @param channel File channel to allocate or map byte buffers from. + * @param mapMode The map mode. Required but only used if memory mapped I/O is + * to occur. + * @param bufferStartPosition The absolute position of the start of the + * buffer. + * @param newSize The buffer size. + * @return A newly mapped NIO byte buffer. + * @throws IOException If there is an issue mapping, aligning or allocating + * the buffer. + */ + private ByteBuffer allocateMappedByteBuffer(final FileChannel channel, + final MapMode mapMode, final long bufferStartPosition, final int newSize) + throws IOException + { + return channel.map(mapMode, bufferStartPosition, newSize); + } + +} diff --git a/src/main/java/org/scijava/io/nio/NIOService.java b/src/main/java/org/scijava/io/nio/NIOService.java new file mode 100644 index 000000000..5e90ced72 --- /dev/null +++ b/src/main/java/org/scijava/io/nio/NIOService.java @@ -0,0 +1,71 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.nio; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileChannel.MapMode; + +import org.scijava.service.SciJavaService; + +/** + * Interface for services that work with the {@link java.nio} package, + * particularly NIO {@link ByteBuffer} objects. + * + * @author Chris Allan + * @author Curtis Rueden + */ +public interface NIOService extends SciJavaService { + + /** + * Allocates or maps the desired file data into memory. + *

+ * This method provides a facade to byte buffer allocation that enables + * FileChannel.map() usage on platforms where it's unlikely to + * give us problems and heap allocation where it is. + *

+ * + * @param channel File channel to allocate or map byte buffers from. + * @param mapMode The map mode. Required but only used if memory mapped I/O is + * to occur. + * @param bufferStartPosition The absolute position of the start of the + * buffer. + * @param newSize The buffer size. + * @return A newly allocated or mapped NIO byte buffer. + * @see "https://bugs.java.com/bugdatabase/view_bug.do?bug_id=5092131" + * @see "https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6417205" + * @throws IOException If there is an issue mapping, aligning or allocating + * the buffer. + */ + ByteBuffer allocate(FileChannel channel, MapMode mapMode, + long bufferStartPosition, int newSize) throws IOException; + +} diff --git a/src/main/java/org/scijava/log/AbstractLogService.java b/src/main/java/org/scijava/log/AbstractLogService.java index 69eddd8c0..099750f95 100644 --- a/src/main/java/org/scijava/log/AbstractLogService.java +++ b/src/main/java/org/scijava/log/AbstractLogService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -38,263 +36,160 @@ import org.scijava.service.AbstractService; /** - * Base class for {@link LogService} implementationst. + * Base class for {@link LogService} implementations. * * @author Johannes Schindelin + * @author Curtis Rueden + * @author Matthias Arzt */ +@IgnoreAsCallingClass public abstract class AbstractLogService extends AbstractService implements LogService { - private int currentLevel = System.getenv("DEBUG") == null ? INFO : DEBUG; + private int currentLevel = levelFromEnvironment(); - private final Map classAndPackageLevels = - new HashMap<>(); + private final Map classAndPackageLevels; - // -- abstract methods -- - - /** - * Displays a message. - * - * @param msg the message to display. - */ - protected abstract void log(final String msg); - - /** - * Displays an exception. - * - * @param t the exception to display. - */ - protected abstract void log(final Throwable t); + private final Logger rootLogger; // -- constructor -- public AbstractLogService() { - // check SciJava log level system properties for initial logging levels - - // global log level property - final String logProp = System.getProperty(LOG_LEVEL_PROPERTY); - final int level = level(logProp); - if (level >= 0) setLevel(level); - - if (getLevel() == 0) { - // use the default, which is WARN unless the DEBUG env. variable is set - setLevel(System.getenv("DEBUG") == null ? INFO : DEBUG); - } - - // populate custom class- and package-specific log level properties - final String logLevelPrefix = LOG_LEVEL_PROPERTY + ":"; - final Properties props = System.getProperties(); - for (final Object propKey : props.keySet()) { - if (!(propKey instanceof String)) continue; - final String propName = (String) propKey; - if (!propName.startsWith(logLevelPrefix)) continue; - final String classOrPackageName = - propName.substring(logLevelPrefix.length()); - setLevel(classOrPackageName, level(props.getProperty(propName))); - } - + this(System.getProperties()); } - // -- helper methods -- - - protected void log(final int level, final Object msg, final Throwable t) { - if (level > getLevel()) return; - - if (msg != null || t == null) { - log(level, msg); - } - if (t != null) log(t); - } - - protected void log(final int level, final Object msg) { - final String prefix = getPrefix(level); - log((prefix == null ? "" : prefix + " ") + msg); - } - - protected String getPrefix(final int level) { - switch (level) { - case ERROR: - return "[ERROR]"; - case WARN: - return "[WARNING]"; - case INFO: - return "[INFO]"; - case DEBUG: - return "[DEBUG]"; - case TRACE: - return "[TRACE]"; - default: - return null; - } + public AbstractLogService(final Properties properties) { + rootLogger = new RootLogger(); + // provide this constructor to enable unit tests + final int level = LogLevel.value(properties.getProperty( + LogService.LOG_LEVEL_PROPERTY)); + if (level >= 0) currentLevel = level; + classAndPackageLevels = setupMapFromProperties(properties, + LogService.LOG_LEVEL_PROPERTY + ":"); + initLogSourceLevels(properties); } - // -- LogService methods -- + // -- AbstractLogService methods -- @Override - public void debug(final Object msg) { - log(DEBUG, msg, null); + public void setLevel(final int level) { + currentLevel = level; } @Override - public void debug(final Throwable t) { - log(DEBUG, null, t); + public void setLevel(final String classOrPackageName, final int level) { + classAndPackageLevels.put(classOrPackageName, level); } @Override - public void debug(final Object msg, final Throwable t) { - log(DEBUG, msg, t); + public void setLevelForLogger(final String source, final int level) { + rootLogger.getSource().subSource(source).setLogLevel(level); } - @Override - public void error(final Object msg) { - log(ERROR, msg, null); - } + abstract protected void messageLogged(LogMessage message); - @Override - public void error(final Throwable t) { - log(ERROR, null, t); - } + // -- Logger methods -- @Override - public void error(final Object msg, final Throwable t) { - log(ERROR, msg, t); + public void alwaysLog(final int level, final Object msg, final Throwable t) { + rootLogger.alwaysLog(level, msg, t); } @Override - public void info(final Object msg) { - log(INFO, msg, null); + public LogSource getSource() { + return rootLogger.getSource(); } @Override - public void info(final Throwable t) { - log(INFO, null, t); - } - - @Override - public void info(final Object msg, final Throwable t) { - log(INFO, msg, t); + public int getLevel() { + if (classAndPackageLevels.isEmpty()) return currentLevel; + return getLevelForClass(CallingClassUtils.getCallingClassName(), + currentLevel); } @Override - public void trace(final Object msg) { - log(TRACE, msg, null); + public Logger subLogger(String name, int level) { + return rootLogger.subLogger(name, level); } @Override - public void trace(final Throwable t) { - log(TRACE, null, t); + public void addLogListener(final LogListener listener) { + rootLogger.addLogListener(listener); } @Override - public void trace(final Object msg, final Throwable t) { - log(TRACE, msg, t); + public void removeLogListener(final LogListener listener) { + rootLogger.removeLogListener(listener); } @Override - public void warn(final Object msg) { - log(WARN, msg, null); + public void notifyListeners(final LogMessage event) { + rootLogger.notifyListeners(event); } - @Override - public void warn(final Throwable t) { - log(WARN, null, t); - } + // -- Deprecated -- - @Override - public void warn(final Object msg, final Throwable t) { - log(WARN, msg, t); + /** @deprecated Use {@link LogLevel#prefix(int)} instead. */ + @Deprecated + protected String getPrefix(final int level) { + return "[" + LogLevel.prefix(level) + "]"; } - @Override - public boolean isDebug() { - return getLevel() >= DEBUG; - } + // -- Helper methods -- - @Override - public boolean isError() { - return getLevel() >= ERROR; + private void initLogSourceLevels(Properties properties) { + Map nameLevels = setupMapFromProperties(properties, + LOG_LEVEL_BY_SOURCE_PROPERTY + ":"); + nameLevels.forEach(this::setLevelForLogger); } - @Override - public boolean isInfo() { - return getLevel() >= INFO; + private int getLevelForClass(String classOrPackageName, int defaultLevel) { + // check for a custom log level for calling class or its parent packages + while (classOrPackageName != null) { + final Integer level = classAndPackageLevels.get(classOrPackageName); + if (level != null) return level; + classOrPackageName = parentPackage(classOrPackageName); + } + return defaultLevel; } - @Override - public boolean isTrace() { - return getLevel() >= TRACE; + private String parentPackage(final String classOrPackageName) { + final int dot = classOrPackageName.lastIndexOf("."); + if (dot < 0) return null; + return classOrPackageName.substring(0, dot); } - @Override - public boolean isWarn() { - return getLevel() >= WARN; + private int levelFromEnvironment() { + // use the default, which is INFO unless the DEBUG env. variable is set + return System.getenv("DEBUG") == null ? LogLevel.INFO : LogLevel.DEBUG; } - @Override - public int getLevel() { - if (!classAndPackageLevels.isEmpty()) { - // check for a custom log level for calling class or its parent packages - String classOrPackageName = callingClass(); - while (classOrPackageName != null) { - final Integer level = classAndPackageLevels.get(classOrPackageName); - if (level != null) return level; - classOrPackageName = parentPackage(classOrPackageName); + private Map setupMapFromProperties(Properties properties, + String prefix) + { + final HashMap map = new HashMap<>(); + for (final String propName : properties.stringPropertyNames()) + if (propName.startsWith(prefix)) { + final String key = propName.substring(prefix.length()); + map.put(key, LogLevel.value(properties.getProperty(propName))); } - } - // no custom log level; return the global log level - return currentLevel; + return map; } - @Override - public void setLevel(final int level) { - currentLevel = level; - } - - @Override - public void setLevel(final String classOrPackageName, final int level) { - classAndPackageLevels.put(classOrPackageName, level); - } + // -- Helper classes -- - // -- Helper methods -- + @IgnoreAsCallingClass + private class RootLogger extends DefaultLogger { - /** Extracts the log level value from a string. */ - private int level(final String logProp) { - if (logProp == null) return -1; - - // check whether it's a string label (e.g., "debug") - final String log = logProp.trim().toLowerCase(); - if (log.startsWith("n")) return NONE; - if (log.startsWith("e")) return ERROR; - if (log.startsWith("w")) return WARN; - if (log.startsWith("i")) return INFO; - if (log.startsWith("d")) return DEBUG; - if (log.startsWith("t")) return TRACE; - - // check whether it's a numerical value (e.g., 5) - try { - return Integer.parseInt(log); - } - catch (final NumberFormatException exc) { - // nope! + public RootLogger() { + super(AbstractLogService.this::messageLogged, LogSource.newRoot(), + LogLevel.NONE); } - return -1; - } - private String callingClass() { - final String thisClass = AbstractLogService.class.getName(); - for (final StackTraceElement element : new Exception().getStackTrace()) { - final String className = element.getClassName(); - // NB: Skip stack trace elements from other methods of this class. - if (!thisClass.equals(className)) return className; + @Override + public int getLevel() { + return AbstractLogService.this.getLevel(); } - return null; - } - - private String parentPackage(final String classOrPackageName) { - final int dot = classOrPackageName.lastIndexOf("."); - if (dot < 0) return null; - return classOrPackageName.substring(0, dot); } - } diff --git a/src/main/java/org/scijava/log/CallingClassUtils.java b/src/main/java/org/scijava/log/CallingClassUtils.java new file mode 100644 index 000000000..3a615a5b2 --- /dev/null +++ b/src/main/java/org/scijava/log/CallingClassUtils.java @@ -0,0 +1,99 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.log; + +import org.scijava.Context; + +/** + * Utility class for getting the calling class of a method. + * + * @author Matthias Arzt + */ + +@IgnoreAsCallingClass +public final class CallingClassUtils { + + private CallingClassUtils() { + // prevent instantiation of utility class + } + + /** + * Inspects the stack trace to return the name of the class that calls + * this method, but ignores every class annotated with @IgnoreAsCallingClass. + *

+ * If every class on the stack trace is annotated, then the class at the + * root of the stack trace is returned. + */ + public static String getCallingClassName() { + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + for (int i = 1; i < stackTrace.length - 2; i++) { + String className = stackTrace[i].getClassName(); + if (!hasIgnoreAsCallingClassAnnotation(className)) return className; + } + return stackTrace[stackTrace.length - 1].getClassName(); + } + + private static boolean hasIgnoreAsCallingClassAnnotation(String className) { + try { + Class< ? > clazz = Context.getClassLoader().loadClass(className); + return clazz.isAnnotationPresent(IgnoreAsCallingClass.class); + } + catch (ClassNotFoundException ignore) { + return false; + } + } + + /** + * @deprecated Use {@link #getCallingClassName()} instead. + * + * Warning: This method throws a IllegalStateException as soon as it comes + * across a class that can't be loaded with the default class loader. + * + * Inspects the stack trace to return the class that calls this method, but + * ignores every class annotated with @IgnoreAsCallingClass. + * + * @throws IllegalStateException if every method on the stack, is in a class + * annotated with @IgnoreAsCallingClass. + */ + @Deprecated + public static Class getCallingClass() { + try { + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + for (int i = 1; i < stackTrace.length - 1; i++) { + Class clazz = Class.forName(stackTrace[i].getClassName()); + if (!clazz.isAnnotationPresent(IgnoreAsCallingClass.class)) + return clazz; + } + } + catch (ClassNotFoundException ignore) {} + throw new IllegalStateException(); + } + +} diff --git a/src/main/java/org/scijava/log/DefaultLogger.java b/src/main/java/org/scijava/log/DefaultLogger.java new file mode 100644 index 000000000..a5522aa50 --- /dev/null +++ b/src/main/java/org/scijava/log/DefaultLogger.java @@ -0,0 +1,107 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.log; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Default implementation of {@link Logger}. + * + * @author Matthias Arzt + * @author Curtis Rueden + */ +@IgnoreAsCallingClass +public class DefaultLogger implements Logger, LogListener { + + private final LogListener destination; + + private final LogSource source; + + private final int level; + + private final List listeners = new CopyOnWriteArrayList<>(); + + public DefaultLogger(final LogListener destination, + final LogSource source, final int level) + { + this.destination = destination; + this.source = source; + this.level = level; + } + + // -- Logger methods -- + + @Override + public LogSource getSource() { + return source; + } + + @Override + public int getLevel() { + return level; + } + + @Override + public void alwaysLog(final int level, final Object msg, final Throwable t) { + messageLogged(new LogMessage(source, level, msg, t)); + } + + @Override + public Logger subLogger(final String name, final int level) { + LogSource source = getSource().subSource(name); + int actualLevel = source.hasLogLevel() ? source.logLevel() : level; + return new DefaultLogger(this, source, actualLevel); + } + + @Override + public void addLogListener(final LogListener listener) { + listeners.add(listener); + } + + @Override + public void removeLogListener(final LogListener listener) { + listeners.remove(listener); + } + + @Override + public void notifyListeners(final LogMessage message) { + for (LogListener listener : listeners) + listener.messageLogged(message); + } + + // -- LogListener methods -- + + @Override + public void messageLogged(final LogMessage message) { + notifyListeners(message); + destination.messageLogged(message); + } +} diff --git a/src/main/java/org/scijava/log/DefaultUncaughtExceptionHandler.java b/src/main/java/org/scijava/log/DefaultUncaughtExceptionHandler.java index ab28a4515..d562921e5 100644 --- a/src/main/java/org/scijava/log/DefaultUncaughtExceptionHandler.java +++ b/src/main/java/org/scijava/log/DefaultUncaughtExceptionHandler.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -68,10 +66,6 @@ public static void install(final LogService log) { final UncaughtExceptionHandler handler = new DefaultUncaughtExceptionHandler(log); Thread.setDefaultUncaughtExceptionHandler(handler); - - // Needed for modal dialog handling before Java7: - System.setProperty("sun.awt.exception.handler", - DefaultUncaughtExceptionHandler.class.getName()); } } diff --git a/src/main/java/org/scijava/log/IgnoreAsCallingClass.java b/src/main/java/org/scijava/log/IgnoreAsCallingClass.java new file mode 100644 index 000000000..5aa4a690a --- /dev/null +++ b/src/main/java/org/scijava/log/IgnoreAsCallingClass.java @@ -0,0 +1,42 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Classes annotated with {@link IgnoreAsCallingClass} are ignored by + * {@link CallingClassUtils#getCallingClassName()}. + * + * @author Matthias Arzt + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface IgnoreAsCallingClass {} diff --git a/src/main/java/org/scijava/log/LogLevel.java b/src/main/java/org/scijava/log/LogLevel.java new file mode 100644 index 000000000..aabab92aa --- /dev/null +++ b/src/main/java/org/scijava/log/LogLevel.java @@ -0,0 +1,94 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.log; + +/** + * Constants for specifying a logger's level of verbosity. + * + * @author Curtis Rueden + */ +public final class LogLevel { + + private LogLevel() { + // prevent instantiation of utility class + } + + public static final int NONE = 0; + public static final int ERROR = 1; + public static final int WARN = 2; + public static final int INFO = 3; + public static final int DEBUG = 4; + public static final int TRACE = 5; + + public static String prefix(final int level) { + switch (level) { + case ERROR: + return "ERROR"; + case WARN: + return "WARNING"; + case INFO: + return "INFO"; + case DEBUG: + return "DEBUG"; + case TRACE: + return "TRACE"; + default: + return "LEVEL" + level; + } + } + + /** + * Extracts the log level value from a string. + * + * @return The log level, or -1 if the level cannot be parsed. + */ + public static int value(final String s) { + if (s == null) return -1; + + // check whether it's a string label (e.g., "debug") + final String log = s.trim().toLowerCase(); + if (log.startsWith("n")) return LogLevel.NONE; + if (log.startsWith("e")) return LogLevel.ERROR; + if (log.startsWith("w")) return LogLevel.WARN; + if (log.startsWith("i")) return LogLevel.INFO; + if (log.startsWith("d")) return LogLevel.DEBUG; + if (log.startsWith("t")) return LogLevel.TRACE; + + // check whether it's a numerical value (e.g., 5) + try { + return Integer.parseInt(log); + } + catch (final NumberFormatException exc) { + // nope! + } + return -1; + } + +} diff --git a/src/main/java/org/scijava/log/LogListener.java b/src/main/java/org/scijava/log/LogListener.java new file mode 100644 index 000000000..19c03c71f --- /dev/null +++ b/src/main/java/org/scijava/log/LogListener.java @@ -0,0 +1,47 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.log; + +/** + * Callback function used by {@link Logger}. + * + * @author Matthias Arzt + * @see Logger + * @see LogMessage + */ +public interface LogListener { + + /** + * This method is normally called from many threads in parallel. It must be + * implemented highly thread safe and must not use any kind of locks. + */ + void messageLogged(LogMessage message); + +} diff --git a/src/main/java/org/scijava/log/LogMessage.java b/src/main/java/org/scijava/log/LogMessage.java new file mode 100644 index 000000000..dce591878 --- /dev/null +++ b/src/main/java/org/scijava/log/LogMessage.java @@ -0,0 +1,139 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.log; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.LinkedList; + +import org.scijava.event.EventService; + +/** + * A log message broadcast by a {@link Logger}. + *

+ * NB: The message is published on the calling thread by + * {@link Logger#notifyListeners}, not on a dedicated event dispatch + * thread by the {@link EventService}. This is done to avoid the overhead of the + * event service's synchronized pub/sub implementation, as well as to avoid + * potential infinite loops caused by debugging log messages surrounding event + * publication. + *

+ * + * @author Matthias Arzt + */ +public class LogMessage { + + private final LogSource source; + private final int level; + private final String message; + private final Throwable throwable; + private final Date time; + + private Collection attachments; + + public LogMessage(LogSource source, int level, Object message, + Throwable throwable) + { + this.source = source; + this.attachments = null; + this.level = level; + this.message = message == null ? null : message.toString(); + this.throwable = throwable; + this.time = new Date(); + } + + public LogMessage(LogSource source, int level, Object msg) { + this(source, level, msg, null); + } + + /** Represents the source of the message. */ + public LogSource source() { + return source; + } + + /** + * Log level of the message. + * + * @see LogLevel + */ + public int level() { + return level; + } + + /** The content of this log message. */ + public String text() { + return message; + } + + /** Exception associated with the log message. */ + public Throwable throwable() { + return throwable; + } + + /** Time of the creation of the log message. */ + public Date time() { + return time; + } + + /** + * Collection of objects that have been attached to this message with + * {@link #attach(Object)}. + */ + public Collection attachments() { + if (attachments == null) return Collections.emptyList(); + return Collections.unmodifiableCollection(attachments); + } + + /** + * Attach object to this log message. This can be used to attach additional + * information to the log message. + */ + public void attach(Object value) { + if (attachments == null) attachments = new LinkedList<>(); + attachments.add(value); + } + + // -- Object methods -- + + @Override + public String toString() { + final StringWriter sw = new StringWriter(); + final PrintWriter printer = new PrintWriter(sw); + printer.print("[" + LogLevel.prefix(level()) + "] "); + if(text() != null) printer.println(text()); + if (throwable() != null) { + throwable().printStackTrace(printer); + } + return sw.toString(); + } +} diff --git a/src/main/java/org/scijava/log/LogService.java b/src/main/java/org/scijava/log/LogService.java index c86563871..ad9cdb40b 100644 --- a/src/main/java/org/scijava/log/LogService.java +++ b/src/main/java/org/scijava/log/LogService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -37,69 +35,61 @@ * Interface for the logging service. *

* The service supports five common logging levels: {@link #ERROR}, - * {@link #WARN}, {@link #INFO}, {@link #TRACE} and {@link #DEBUG}. It provides - * methods for logging messages, exception stack traces and combinations of the - * two. + * {@link #WARN}, {@link #INFO}, {@link #TRACE} and {@link #DEBUG}. It is + * extensible to additional levels as needed. It provides methods for logging + * messages, exception stack traces and combinations of the two. *

* * @author Curtis Rueden + * @author Matthias Arzt */ -public interface LogService extends SciJavaService { +public interface LogService extends SciJavaService, Logger { /** System property to set for overriding the default logging level. */ String LOG_LEVEL_PROPERTY = "scijava.log.level"; - int NONE = 0; - int ERROR = 1; - int WARN = 2; - int INFO = 3; - int DEBUG = 4; - int TRACE = 5; - - void debug(Object msg); - - void debug(Throwable t); - - void debug(Object msg, Throwable t); - - void error(Object msg); - - void error(Throwable t); - - void error(Object msg, Throwable t); - - void info(Object msg); - - void info(Throwable t); - - void info(Object msg, Throwable t); - - void trace(Object msg); - - void trace(Throwable t); - - void trace(Object msg, Throwable t); - - void warn(Object msg); - - void warn(Throwable t); - - void warn(Object msg, Throwable t); - - boolean isDebug(); - - boolean isError(); - - boolean isInfo(); - - boolean isTrace(); - - boolean isWarn(); - - int getLevel(); + String LOG_LEVEL_BY_SOURCE_PROPERTY = "scijava.log.level.source"; + /** Changes the log level of the root logger. */ void setLevel(int level); + /** + * For messages that are logged directly to the LogService. The log level can + * be set depending on the class that makes the log. + * + * @param classOrPackageName If this is the name of a class. Messages logged + * directly by this class are logged, if the message's level is less + * or equal to the given level. If this is a package, the same holds + * for all classes in this package. + * @param level Given level. + */ void setLevel(String classOrPackageName, int level); + /** + * Setting the log level for loggers depending on their {@link LogSource}. + * This will only affect loggers that are created after this method has been + * called. + */ + void setLevelForLogger(String source, int level); + + // -- Deprecated -- + + /** @deprecated Use {@link LogLevel#NONE}. */ + @Deprecated + int NONE = LogLevel.NONE; + /** @deprecated Use {@link LogLevel#ERROR}. */ + @Deprecated + int ERROR = LogLevel.ERROR; + /** @deprecated Use {@link LogLevel#WARN}. */ + @Deprecated + int WARN = LogLevel.WARN; + /** @deprecated Use {@link LogLevel#INFO}. */ + @Deprecated + int INFO = LogLevel.INFO; + /** @deprecated Use {@link LogLevel#DEBUG}. */ + @Deprecated + int DEBUG = LogLevel.DEBUG; + /** @deprecated Use {@link LogLevel#TRACE}. */ + @Deprecated + int TRACE = LogLevel.TRACE; } diff --git a/src/main/java/org/scijava/log/LogSource.java b/src/main/java/org/scijava/log/LogSource.java new file mode 100644 index 000000000..5f03b1870 --- /dev/null +++ b/src/main/java/org/scijava/log/LogSource.java @@ -0,0 +1,143 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.StringJoiner; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentSkipListMap; + +/** + * Identifies where a {@link LogMessage} came from. + * + * @author Matthias Arzt + */ +public class LogSource { + + public static final String SEPARATOR = ":"; + + private final LogSource parent; + + private final List path; + + private final ConcurrentMap children = + new ConcurrentSkipListMap<>(); + + private String formatted = null; + + private Integer logLevel; + + private LogSource(LogSource parent, String name) { + this.parent = parent; + List parentPath = parent.path(); + List list = new ArrayList<>(parentPath.size() + 1); + list.addAll(parentPath); + list.add(name); + this.path = Collections.unmodifiableList(list); + } + + private LogSource() { + this.parent = null; + this.path = Collections.emptyList(); + } + + /** Returns the root log source. This LogSource represents the empty list. */ + public static LogSource newRoot() { + return new LogSource(); + } + + /** + * Returns a log source with the given path. + * + * @param subPath Relative path to the source, divided by + * {@link LogSource#SEPARATOR}. + */ + public LogSource subSource(final String subPath) { + LogSource result = this; + for (final String name : subPath.split(SEPARATOR)) + result = result.child(name); + return result; + } + + /** Returns the list of strings which is represented by this LogSource. */ + public List path() { + return path; + } + + /** Returns the last entry in the list of strings. */ + public String name() { + if (path.isEmpty()) return ""; + return path.get(path.size() - 1); + } + + @Override + public String toString() { + if (formatted != null) return formatted; + StringJoiner joiner = new StringJoiner(SEPARATOR); + path.forEach(s -> joiner.add(s)); + formatted = joiner.toString(); + return formatted; + } + + public boolean isRoot() { + return parent == null; + } + + /** Gets the parent of this source, or null if the source is a root. */ + public LogSource parent() { + return parent; + } + + public void setLogLevel(int logLevel) { + this.logLevel = logLevel; + } + + public boolean hasLogLevel() { + return logLevel != null; + } + + public int logLevel() { + if (!hasLogLevel()) throw new IllegalStateException(); + return logLevel; + } + + // -- Helper methods -- + + private LogSource child(final String name) { + if (name.isEmpty()) return this; + LogSource child = children.get(name); + if (child != null) return child; + child = new LogSource(this, name); + children.putIfAbsent(name, child); + return children.get(name); + } +} diff --git a/src/main/java/org/scijava/log/Logged.java b/src/main/java/org/scijava/log/Logged.java index cb73be8c5..bee37e0fa 100644 --- a/src/main/java/org/scijava/log/Logged.java +++ b/src/main/java/org/scijava/log/Logged.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -39,5 +37,6 @@ public interface Logged { /** Gets the {@link LogService} to use when logging activities. */ + // TODO: SJC3: Generalize to Logger instead of LogService. LogService log(); } diff --git a/src/main/java/org/scijava/log/Logger.java b/src/main/java/org/scijava/log/Logger.java new file mode 100644 index 000000000..c2657b0b0 --- /dev/null +++ b/src/main/java/org/scijava/log/Logger.java @@ -0,0 +1,218 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.log; + +import static org.scijava.log.LogLevel.DEBUG; +import static org.scijava.log.LogLevel.ERROR; +import static org.scijava.log.LogLevel.INFO; +import static org.scijava.log.LogLevel.TRACE; +import static org.scijava.log.LogLevel.WARN; + +/** + * Interface for objects which can produce log messages. + *

+ * It provides methods for logging messages, exception stack traces and + * combinations of the two. + *

+ * + * @author Curtis Rueden + * @see LogLevel + * @see LogService + */ +@IgnoreAsCallingClass +public interface Logger { + + default void debug(final Object msg) { + log(DEBUG, msg); + } + + default void debug(final Throwable t) { + log(DEBUG, t); + } + + default void debug(final Object msg, final Throwable t) { + log(DEBUG, msg, t); + } + + default void error(final Object msg) { + log(ERROR, msg); + } + + default void error(final Throwable t) { + log(ERROR, t); + } + + default void error(final Object msg, final Throwable t) { + log(ERROR, msg, t); + } + + default void info(final Object msg) { + log(INFO, msg); + } + + default void info(final Throwable t) { + log(INFO, t); + } + + default void info(final Object msg, final Throwable t) { + log(INFO, msg, t); + } + + default void trace(final Object msg) { + log(TRACE, msg); + } + + default void trace(final Throwable t) { + log(TRACE, t); + } + + default void trace(final Object msg, final Throwable t) { + log(TRACE, msg, t); + } + + default void warn(final Object msg) { + log(WARN, msg); + } + + default void warn(final Throwable t) { + log(WARN, t); + } + + default void warn(final Object msg, final Throwable t) { + log(WARN, msg, t); + } + + default boolean isDebug() { + return isLevel(DEBUG); + } + + default boolean isError() { + return isLevel(ERROR); + } + + default boolean isInfo() { + return isLevel(INFO); + } + + default boolean isTrace() { + return isLevel(TRACE); + } + + default boolean isWarn() { + return isLevel(WARN); + } + + default boolean isLevel(final int level) { + return getLevel() >= level; + } + + /** + * Logs a message. + * + * @param level The level at which the message will be logged. If the current + * level (given by {@link #getLevel()} is below this one, no logging + * is performed. + * @param msg The message to log. + */ + default void log(final int level, final Object msg) { + log(level, msg, null); + } + + /** + * Logs an exception. + * + * @param level The level at which the exception will be logged. If the + * current level (given by {@link #getLevel()} is below this one, no + * logging is performed. + * @param t The exception to log. + */ + default void log(final int level, final Throwable t) { + log(level, null, t); + } + + /** + * Logs a message with an exception. + * + * @param level The level at which the information will be logged. If the + * current level (given by {@link #getLevel()} is below this one, no + * logging is performed. + * @param msg The message to log. + * @param t The exception to log. + */ + default void log(final int level, final Object msg, final Throwable t) { + if (isLevel(level)) alwaysLog(level, msg, t); + } + + /** + * Logs a message with an exception. This message will always be logged even + * if its level is above the current level (given by {@link #getLevel()}). + * + * @param level The level at which the information will be logged. + * @param msg The message to log. + * @param t The exception to log. + */ + void alwaysLog(int level, Object msg, Throwable t); + + /** Returns the name of this logger. */ + default String getName() { + return getSource().name(); + } + + /** Returns the {@link LogSource} associated with this logger. */ + LogSource getSource(); + + /** Returns the log level of this logger. see {@link LogLevel} */ + int getLevel(); + + /** + * Creates a sub logger, that forwards the message it gets to this logger. The + * sub logger will have the same log level as this logger. + */ + default Logger subLogger(String name) { + return subLogger(name, getLevel()); + } + + /** + * Creates a sub logger, that forwards the message it gets to this logger. + * + * @param name The name of the sub logger. + * @param level The log level of the sub logger. + */ + Logger subLogger(String name, int level); + + /** Adds an item to the list of registered listeners. */ + void addLogListener(LogListener listener); + + /** Removes an item from the list of registered listeners. */ + void removeLogListener(LogListener listener); + + /** Broadcasts the given log message to the registered listeners. */ + void notifyListeners(final LogMessage message); +} diff --git a/src/main/java/org/scijava/log/StderrLogService.java b/src/main/java/org/scijava/log/StderrLogService.java index f79baab50..8c67b082b 100644 --- a/src/main/java/org/scijava/log/StderrLogService.java +++ b/src/main/java/org/scijava/log/StderrLogService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -31,6 +29,9 @@ package org.scijava.log; +import java.io.PrintStream; +import java.util.function.Function; + import org.scijava.Priority; import org.scijava.plugin.Plugin; import org.scijava.service.Service; @@ -46,36 +47,19 @@ * @author Johannes Schindelin * @author Curtis Rueden */ -@Plugin(type = Service.class, priority = Priority.LOW_PRIORITY) +@Plugin(type = Service.class, priority = Priority.LOW) public class StderrLogService extends AbstractLogService { - @Override - protected void log(final int level, final Object msg) { - final String prefix = getPrefix(level); - final String message = (prefix == null ? "" : prefix + " ") + msg; - // NB: Emit severe messages to stderr, and less severe ones to stdout. - if (level <= WARN) System.err.println(message); - else System.out.println(message); - } + private Function levelToStream = + level -> (level <= LogLevel.WARN) ? System.err : System.out; - /** - * Prints a message to stderr. - * - * @param message the message - */ - @Override - protected void log(final String message) { - System.err.println(message); + public void setPrintStreams(Function levelToStream) { + this.levelToStream = levelToStream; } - /** - * Prints an exception to stderr. - * - * @param t the exception - */ @Override - protected void log(final Throwable t) { - t.printStackTrace(); + protected void messageLogged(LogMessage message) { + final PrintStream out = levelToStream.apply(message.level()); + out.print(message); } - } diff --git a/src/main/java/org/scijava/main/DefaultMainService.java b/src/main/java/org/scijava/main/DefaultMainService.java index f8fcfc99d..699a78132 100644 --- a/src/main/java/org/scijava/main/DefaultMainService.java +++ b/src/main/java/org/scijava/main/DefaultMainService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -41,7 +39,7 @@ import org.scijava.plugin.Plugin; import org.scijava.service.AbstractService; import org.scijava.service.Service; -import org.scijava.util.ClassUtils; +import org.scijava.util.Types; /** * Default implementation of {@link MainService}. @@ -101,7 +99,7 @@ public String[] args() { @Override public void exec() { try { - final Class mainClass = ClassUtils.loadClass(className, false); + final Class mainClass = Types.load(className, false); final Method main = mainClass.getMethod("main", String[].class); main.invoke(null, new Object[] { args }); } diff --git a/src/main/java/org/scijava/main/MainService.java b/src/main/java/org/scijava/main/MainService.java index d865ec95c..36b92b89b 100644 --- a/src/main/java/org/scijava/main/MainService.java +++ b/src/main/java/org/scijava/main/MainService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/main/console/MainArgument.java b/src/main/java/org/scijava/main/console/MainArgument.java index d33c64121..0e0ebe29d 100644 --- a/src/main/java/org/scijava/main/console/MainArgument.java +++ b/src/main/java/org/scijava/main/console/MainArgument.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/main/run/MainCodeRunner.java b/src/main/java/org/scijava/main/run/MainCodeRunner.java index 153d5303d..a18ae6b3b 100644 --- a/src/main/java/org/scijava/main/run/MainCodeRunner.java +++ b/src/main/java/org/scijava/main/run/MainCodeRunner.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -41,14 +39,14 @@ import org.scijava.plugin.Plugin; import org.scijava.run.AbstractCodeRunner; import org.scijava.run.CodeRunner; -import org.scijava.util.ClassUtils; +import org.scijava.util.Types; /** * Executes the given class's {@code main} method. * * @author Curtis Rueden */ -@Plugin(type = CodeRunner.class, priority = Priority.LOW_PRIORITY) +@Plugin(type = CodeRunner.class, priority = Priority.LOW) public class MainCodeRunner extends AbstractCodeRunner { @Parameter(required = false) @@ -107,7 +105,7 @@ private Method getMain(final Object code) { private Class getClass(final Object code) { if (code instanceof Class) return (Class) code; - if (code instanceof String) return ClassUtils.loadClass((String) code); + if (code instanceof String) return Types.load((String) code); return null; } diff --git a/src/main/java/org/scijava/menu/AbstractMenuCreator.java b/src/main/java/org/scijava/menu/AbstractMenuCreator.java index d3835cdca..726639ae8 100644 --- a/src/main/java/org/scijava/menu/AbstractMenuCreator.java +++ b/src/main/java/org/scijava/menu/AbstractMenuCreator.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/menu/DefaultMenuService.java b/src/main/java/org/scijava/menu/DefaultMenuService.java index 99e6c8c9e..74ed27285 100644 --- a/src/main/java/org/scijava/menu/DefaultMenuService.java +++ b/src/main/java/org/scijava/menu/DefaultMenuService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -78,25 +76,22 @@ public ShadowMenu getMenu(final String menuRoot) { // -- Event handlers -- @EventHandler - protected void onEvent(final ModulesAddedEvent event) { - if (rootMenus == null) { - // add *all* known modules, which includes the ones given here - rootMenus(); - return; - } - // data structure already exists; add *these* modules only + protected synchronized void onEvent(final ModulesAddedEvent event) { + if (rootMenus == null) return; // menus not yet initialized addModules(event.getItems()); } @EventHandler - protected void onEvent(final ModulesRemovedEvent event) { + protected synchronized void onEvent(final ModulesRemovedEvent event) { + if (rootMenus == null) return; // menus not yet initialized for (final ShadowMenu menu : rootMenus().values()) { menu.removeAll(event.getItems()); } } @EventHandler - protected void onEvent(final ModulesUpdatedEvent event) { + protected synchronized void onEvent(final ModulesUpdatedEvent event) { + if (rootMenus == null) return; // menus not yet initialized for (final ShadowMenu menu : rootMenus().values()) { menu.updateAll(event.getItems()); } @@ -164,9 +159,7 @@ private synchronized void addModules(final Collection items, *

*/ private HashMap rootMenus() { - if (rootMenus == null) { - initRootMenus(); - } + if (rootMenus == null) initRootMenus(); return rootMenus; } diff --git a/src/main/java/org/scijava/menu/MenuConstants.java b/src/main/java/org/scijava/menu/MenuConstants.java index 6ee40aa5f..8c38942c9 100644 --- a/src/main/java/org/scijava/menu/MenuConstants.java +++ b/src/main/java/org/scijava/menu/MenuConstants.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/menu/MenuCreator.java b/src/main/java/org/scijava/menu/MenuCreator.java index 84691ad71..386771672 100644 --- a/src/main/java/org/scijava/menu/MenuCreator.java +++ b/src/main/java/org/scijava/menu/MenuCreator.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/menu/MenuService.java b/src/main/java/org/scijava/menu/MenuService.java index b0b611c13..ce9a5d36c 100644 --- a/src/main/java/org/scijava/menu/MenuService.java +++ b/src/main/java/org/scijava/menu/MenuService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/menu/ShadowMenu.java b/src/main/java/org/scijava/menu/ShadowMenu.java index 529ba1f81..c5eaea4cd 100644 --- a/src/main/java/org/scijava/menu/ShadowMenu.java +++ b/src/main/java/org/scijava/menu/ShadowMenu.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -56,6 +54,7 @@ import org.scijava.plugin.Parameter; import org.scijava.util.ClassUtils; import org.scijava.util.MiscUtils; +import org.scijava.util.Types; /** * A tree representing a menu structure independent of any particular user @@ -231,17 +230,24 @@ public URL getIconURL() { if (isLeaf()) iconPath = DEFAULT_ICON_PATH; else return null; } - final String className = moduleInfo.getDelegateClassName(); try { - final Class c = ClassUtils.loadClass(className, false); + final Class c = moduleInfo.loadDelegateClass(); final URL iconURL = c.getResource(iconPath); if (iconURL == null) { if (log != null) log.error("Could not load icon: " + iconPath); } return iconURL; } + catch (final ClassNotFoundException exc) { + final String message = "Failed to load class: " + + moduleInfo.getDelegateClassName(); + if (log.isDebug()) log.debug(message, exc); + else log.error(message); + return null; + } catch (final IllegalArgumentException exc) { - final String message = "Could not load icon for class: " + className; + final String message = "Could not load icon for class: " + + moduleInfo.getDelegateClassName(); if (log.isDebug()) log.debug(message, exc); else log.error(message); return null; @@ -491,7 +497,7 @@ public T[] toArray(final T[] a) { // -- Helper methods -- private ShadowMenu addInternal(final ModuleInfo o) { - if (o.getMenuPath().isEmpty()) return null; // no menu + if (o.getMenuPath() == null || o.getMenuPath().isEmpty()) return null; // no menu return addChild(o, 0); } @@ -547,14 +553,8 @@ private ShadowMenu addChild(final ModuleInfo info, final int depth) { else if (existingChild != null) { if (log != null) { final ModuleInfo childInfo = existingChild.getModuleInfo(); - if (childInfo != null && info.getPriority() == childInfo.getPriority()) - { - log.warn("ShadowMenu: menu item already exists:\n" + // - "\texisting: " + details(childInfo) + "\n" + // - "\t ignored: " + details(info)); - } - else { - log.debug("ShadowMenu: higher-priority menu item already exists:\n" + + if (log.isDebug()) { + log.debug("ShadowMenu: menu item already exists:\n" + // "\texisting: " + details(childInfo) + "\n" + // "\t ignored: " + details(info)); } @@ -573,7 +573,7 @@ private String details(final ModuleInfo info) { try { final Class c = info.loadDelegateClass(); className = c.getName(); - classLocation = ClassUtils.getLocation(c).toString(); + classLocation = Types.location(c).toString(); } catch (final ClassNotFoundException exc) { className = info.getDelegateClassName(); diff --git a/src/main/java/org/scijava/menu/ShadowMenuIterator.java b/src/main/java/org/scijava/menu/ShadowMenuIterator.java index bb1784cba..ea69e7a2f 100644 --- a/src/main/java/org/scijava/menu/ShadowMenuIterator.java +++ b/src/main/java/org/scijava/menu/ShadowMenuIterator.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/menu/event/MenuEvent.java b/src/main/java/org/scijava/menu/event/MenuEvent.java index e3220196f..ae0deaa13 100644 --- a/src/main/java/org/scijava/menu/event/MenuEvent.java +++ b/src/main/java/org/scijava/menu/event/MenuEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/menu/event/MenusAddedEvent.java b/src/main/java/org/scijava/menu/event/MenusAddedEvent.java index c22b02cf6..a3fd70e2f 100644 --- a/src/main/java/org/scijava/menu/event/MenusAddedEvent.java +++ b/src/main/java/org/scijava/menu/event/MenusAddedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/menu/event/MenusRemovedEvent.java b/src/main/java/org/scijava/menu/event/MenusRemovedEvent.java index b1a1ac583..24e3a8f93 100644 --- a/src/main/java/org/scijava/menu/event/MenusRemovedEvent.java +++ b/src/main/java/org/scijava/menu/event/MenusRemovedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/menu/event/MenusUpdatedEvent.java b/src/main/java/org/scijava/menu/event/MenusUpdatedEvent.java index c7063001f..951daace8 100644 --- a/src/main/java/org/scijava/menu/event/MenusUpdatedEvent.java +++ b/src/main/java/org/scijava/menu/event/MenusUpdatedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/AbstractModule.java b/src/main/java/org/scijava/module/AbstractModule.java index e50a162e7..4ca6a4528 100644 --- a/src/main/java/org/scijava/module/AbstractModule.java +++ b/src/main/java/org/scijava/module/AbstractModule.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -162,8 +160,13 @@ public void resolveInput(final String name) { item.validate(this); } catch (final MethodCallException exc) { - // NB: Hacky, but avoids changing the API signature. - throw new RuntimeException(exc); + // NB: resolveInput cannot declare checked exceptions, so we wrap. + // Prefer the cause's message (the user-facing validation error) when + // available; otherwise fall back to the MethodCallException's message. + final Throwable cause = exc.getCause(); + final String message = (cause != null && cause.getMessage() != null && + !cause.getMessage().isEmpty()) ? cause.getMessage() : exc.getMessage(); + throw new RuntimeException(message, exc); } } resolvedInputs.add(name); diff --git a/src/main/java/org/scijava/module/AbstractModuleInfo.java b/src/main/java/org/scijava/module/AbstractModuleInfo.java index ffd48cf37..704ac4cd3 100644 --- a/src/main/java/org/scijava/module/AbstractModuleInfo.java +++ b/src/main/java/org/scijava/module/AbstractModuleInfo.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -38,7 +36,7 @@ import java.util.Map; import org.scijava.AbstractUIDetails; -import org.scijava.util.ConversionUtils; +import org.scijava.util.Types; /** * Abstract superclass of {@link ModuleInfo} implementation. @@ -167,8 +165,8 @@ private ModuleItem castItem(final ModuleItem item, { final Class itemType = item.getType(); // if (!type.isAssignableFrom(itemType)) { - final Class saneItemType = ConversionUtils.getNonprimitiveType(itemType); - if (!ConversionUtils.canCast(type, saneItemType)) { + final Class saneItemType = Types.box(itemType); + if (!Types.isAssignable(type, saneItemType)) { throw new IllegalArgumentException("Type " + type.getName() + " is incompatible with item of type " + itemType.getName()); } diff --git a/src/main/java/org/scijava/module/AbstractModuleItem.java b/src/main/java/org/scijava/module/AbstractModuleItem.java index e7ce1f0d2..e2648cd2f 100644 --- a/src/main/java/org/scijava/module/AbstractModuleItem.java +++ b/src/main/java/org/scijava/module/AbstractModuleItem.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -38,11 +36,11 @@ import org.scijava.AbstractBasicDetails; import org.scijava.ItemIO; import org.scijava.ItemVisibility; -import org.scijava.util.ClassUtils; import org.scijava.util.ConversionUtils; import org.scijava.util.NumberUtils; import org.scijava.util.Prefs; import org.scijava.util.StringMaker; +import org.scijava.util.Types; /** * Abstract superclass of {@link ModuleItem} implementations. @@ -212,7 +210,12 @@ public void validate(final Module module) throws MethodCallException { if (validaterRef == null) { validaterRef = new MethodRef(delegateObject.getClass(), getValidater()); } - validaterRef.execute(module.getDelegateObject()); + final Object result = validaterRef.executeWithResult(module.getDelegateObject()); + // If the validater returns a non-empty String, treat it as an error message. + if (result instanceof String) { + final String message = (String) result; + if (!message.isEmpty()) throw new MethodCallException(message); + } } @Override @@ -263,7 +266,7 @@ public T getSoftMaximum() { @Override public Number getStepSize() { - if (!ClassUtils.isNumber(getType())) return null; + if (!Types.isNumber(getType())) return null; return NumberUtils.toNumber("1", getType()); } diff --git a/src/main/java/org/scijava/module/DefaultModuleService.java b/src/main/java/org/scijava/module/DefaultModuleService.java index f7a7f7a46..c6dee7f42 100644 --- a/src/main/java/org/scijava/module/DefaultModuleService.java +++ b/src/main/java/org/scijava/module/DefaultModuleService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -62,7 +60,7 @@ import org.scijava.service.AbstractService; import org.scijava.service.Service; import org.scijava.thread.ThreadService; -import org.scijava.util.ClassUtils; +import org.scijava.util.Types; /** * Default service for keeping track of and executing available modules. @@ -76,7 +74,7 @@ public class DefaultModuleService extends AbstractService implements ModuleService { - @Parameter + @Parameter(required = false) private LogService log; @Parameter @@ -172,7 +170,7 @@ public Module createModule(final ModuleInfo info) { return module; } catch (final ModuleException exc) { - log.error("Cannot create module: " + info.getDelegateClassName(), exc); + if (log != null) log.error("Cannot create module: " + info.getDelegateClassName(), exc); } return null; } @@ -253,10 +251,10 @@ public M waitFor(final Future future) { return future.get(); } catch (final InterruptedException e) { - log.error("Module execution interrupted", e); + if (log != null) log.error("Module execution interrupted", e); } catch (final ExecutionException e) { - log.error("Error during module execution", e); + if (log != null) log.error("Error during module execution", e); } return null; } @@ -293,21 +291,16 @@ public void save(final ModuleItem item, final T value) { // NB: Do not persist the value if it is the default. // This is nice if the default value might change later, // such as when iteratively developing a script. + prefService.remove(prefClass(item), prefKey(item)); return; } - final String sValue = value == null ? "" : value.toString(); + final String sValue = value == null ? "" : convertService.convert(value, String.class); // do not persist if object cannot be converted back from a string if (!convertService.supports(sValue, item.getType())) return; - final String persistKey = item.getPersistKey(); - if (persistKey == null || persistKey.isEmpty()) { - final Class prefClass = delegateClass(item); - final String prefKey = item.getName(); - prefService.put(prefClass, prefKey, sValue); - } - else prefService.put(persistKey, sValue); + prefService.put(prefClass(item), prefKey(item), sValue); } @Override @@ -315,14 +308,7 @@ public T load(final ModuleItem item) { // if there is nothing to load from persistence return nothing if (!item.isPersisted()) return null; - final String sValue; - final String persistKey = item.getPersistKey(); - if (persistKey == null || persistKey.isEmpty()) { - final Class prefClass = delegateClass(item); - final String prefKey = item.getName(); - sValue = prefService.get(prefClass, prefKey); - } - else sValue = prefService.get(persistKey); + final String sValue = prefService.get(prefClass(item), prefKey(item)); // if persisted value has never been set before return null if (sValue == null) return null; @@ -350,6 +336,16 @@ public T getDefaultValue(final ModuleItem item) { return null; } + @Override + public void saveInputs(final Module module) { + module.getInfo().inputs().forEach(item -> saveInput(module, item)); + } + + @Override + public void loadInputs(final Module module) { + module.getInfo().inputs().forEach(item -> loadInput(module, item)); + } + // -- Service methods -- @Override @@ -381,7 +377,7 @@ private List post(final boolean process) { * {@link ModuleInfo} argument is called). */ private Module getRegisteredModuleInstance(final ModuleInfo info) { - final Class type = ClassUtils.loadClass(info.getDelegateClassName()); + final Class type = Types.load(info.getDelegateClassName()); if (type == null || !Module.class.isAssignableFrom(type)) return null; // the module metadata's delegate class extends Module, so there is hope @@ -396,8 +392,10 @@ private Module getRegisteredModuleInstance(final ModuleInfo info) { } if (objects.size() > 1) { // there are multiple instances; it's not clear which one to use - log.warn("Ignoring multiple candidate module instances for class: " + - type.getName()); + if (log != null) { + log.warn("Ignoring multiple candidate module instances for class: " + + type.getName()); + } return null; } // found exactly one instance; return it! @@ -420,7 +418,7 @@ private Map createMap(final Object[] values) { final Map valueMap = (Map) values[0]; for (final Object key : valueMap.keySet()) { if (!(key instanceof String)) { - log.error("Invalid input name: " + key); + if (log != null) log.error("Invalid input name: " + key); continue; } final String name = (String) key; @@ -431,7 +429,7 @@ private Map createMap(final Object[] values) { } if (values.length % 2 != 0) { - log.error("Ignoring extraneous argument: " + values[values.length - 1]); + if (log != null) log.error("Ignoring extraneous argument: " + values[values.length - 1]); } // loop over list of key/value pairs @@ -440,7 +438,7 @@ private Map createMap(final Object[] values) { final Object key = values[2 * i]; final Object value = values[2 * i + 1]; if (!(key instanceof String)) { - log.error("Invalid input name: " + key); + if (log != null) log.error("Invalid input name: " + key); continue; } final String name = (String) key; @@ -463,7 +461,7 @@ private void assignInputs(final Module module, if (input == null) { // inputs whose name starts with a dot are implicitly known by convention if (!name.startsWith(".")) { - log.warn("Unmatched input: " + name); + if (log != null) log.warn("Unmatched input: " + name); } converted = value; } @@ -471,8 +469,10 @@ private void assignInputs(final Module module, final Class type = input.getType(); converted = convertService.convert(value, type); if (value != null && converted == null) { - log.error("For input " + name + ": incompatible object " + - value.getClass().getName() + " for type " + type.getName()); + if (log != null) { + log.error("For input " + name + ": incompatible object " + + value.getClass().getName() + " for type " + type.getName()); + } continue; } } @@ -498,9 +498,8 @@ private ModuleItem getSingleItem(final Module module, for (final ModuleItem item : items) { final String name = item.getName(); - final boolean resolved = module.isInputResolved(name); - if (resolved) continue; // skip resolved inputs if (!item.isAutoFill()) continue; // skip unfillable inputs + if (module.isInputResolved(name)) continue; // skip resolved inputs final Class itemType = item.getType(); for (final Class type : types) { if (type.isAssignableFrom(itemType)) { @@ -523,4 +522,43 @@ private Class delegateClass(final ModuleItem item) { } } + private Class prefClass(final ModuleItem item) { + final String persistKey = item.getPersistKey(); + return persistKey == null || persistKey.isEmpty() ? // + delegateClass(item) : null; + } + + private String prefKey(final ModuleItem item) { + final String persistKey = item.getPersistKey(); + return persistKey == null || persistKey.isEmpty() ? // + item.getName() : persistKey; + } + + /** Saves the value of the given module item to persistent storage. */ + private void saveInput(final Module module, final ModuleItem item) { + final T value = item.getValue(module); + save(item, value); + } + + /** Loads the value of the given module item from persistent storage. */ + private void loadInput(final Module module, final ModuleItem item) { + // skip input that has already been resolved + if (module.isInputResolved(item.getName())) return; + + final T prefValue = load(item); + final Class type = item.getType(); + final T defaultValue = item.getValue(module); + final T value = getBestValue(prefValue, defaultValue, type); + item.setValue(module, value); + } + + private T getBestValue(final Object prefValue, + final Object defaultValue, final Class type) + { + if (prefValue != null) return convertService.convert(prefValue, type); + if (defaultValue != null) { + return convertService.convert(defaultValue, type); + } + return Types.nullValue(type); + } } diff --git a/src/main/java/org/scijava/module/DefaultMutableModule.java b/src/main/java/org/scijava/module/DefaultMutableModule.java index c368a602c..0e646a212 100644 --- a/src/main/java/org/scijava/module/DefaultMutableModule.java +++ b/src/main/java/org/scijava/module/DefaultMutableModule.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/DefaultMutableModuleInfo.java b/src/main/java/org/scijava/module/DefaultMutableModuleInfo.java index ff6a07fb1..372776ab9 100644 --- a/src/main/java/org/scijava/module/DefaultMutableModuleInfo.java +++ b/src/main/java/org/scijava/module/DefaultMutableModuleInfo.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/DefaultMutableModuleItem.java b/src/main/java/org/scijava/module/DefaultMutableModuleItem.java index 363388587..c16ea80ec 100644 --- a/src/main/java/org/scijava/module/DefaultMutableModuleItem.java +++ b/src/main/java/org/scijava/module/DefaultMutableModuleItem.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -53,6 +51,7 @@ public class DefaultMutableModuleItem extends AbstractModuleItem private final Type genericType; private ItemIO ioType; private ItemVisibility visibility; + private boolean autoFill; private boolean required; private boolean persisted; private String persistKey; @@ -87,10 +86,12 @@ public DefaultMutableModuleItem(final ModuleInfo info, final String name, genericType = type; ioType = super.getIOType(); visibility = super.getVisibility(); + autoFill = super.isAutoFill(); required = super.isRequired(); persisted = super.isPersisted(); persistKey = super.getPersistKey(); initializer = super.getInitializer(); + validater = super.getValidater(); callback = super.getCallback(); widgetStyle = super.getWidgetStyle(); minimumValue = super.getMinimumValue(); @@ -113,10 +114,12 @@ public DefaultMutableModuleItem(final ModuleInfo info, genericType = item.getGenericType(); ioType = item.getIOType(); visibility = item.getVisibility(); + autoFill = item.isAutoFill(); required = item.isRequired(); persisted = item.isPersisted(); persistKey = item.getPersistKey(); initializer = item.getInitializer(); + validater = item.getValidater(); callback = item.getCallback(); widgetStyle = item.getWidgetStyle(); minimumValue = item.getMinimumValue(); @@ -143,6 +146,11 @@ public void setVisibility(final ItemVisibility visibility) { this.visibility = visibility; } + @Override + public void setAutoFill(final boolean autoFill) { + this.autoFill = autoFill; + } + @Override public void setRequired(final boolean required) { this.required = required; @@ -241,6 +249,11 @@ public ItemVisibility getVisibility() { return visibility; } + @Override + public boolean isAutoFill() { + return autoFill; + } + @Override public boolean isRequired() { return required; diff --git a/src/main/java/org/scijava/module/MethodCallException.java b/src/main/java/org/scijava/module/MethodCallException.java index 25058c382..fc37d1313 100644 --- a/src/main/java/org/scijava/module/MethodCallException.java +++ b/src/main/java/org/scijava/module/MethodCallException.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/MethodRef.java b/src/main/java/org/scijava/module/MethodRef.java index 35d44b0dd..f77883204 100644 --- a/src/main/java/org/scijava/module/MethodRef.java +++ b/src/main/java/org/scijava/module/MethodRef.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -64,9 +62,15 @@ public MethodRef(final Class clazz, final String methodName, public void execute(final Object obj, final Object... args) throws MethodCallException { - if (method == null) return; + executeWithResult(obj, args); + } + + public Object executeWithResult(final Object obj, final Object... args) + throws MethodCallException + { + if (method == null) return null; try { - method.invoke(obj, args); + return method.invoke(obj, args); } catch (final Exception exc) { // NB: Several types of exceptions; simpler to handle them all the same. diff --git a/src/main/java/org/scijava/module/Module.java b/src/main/java/org/scijava/module/Module.java index 3cb81187e..21e6730bf 100644 --- a/src/main/java/org/scijava/module/Module.java +++ b/src/main/java/org/scijava/module/Module.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/ModuleCanceledException.java b/src/main/java/org/scijava/module/ModuleCanceledException.java index 1eb8c6b8f..f500a4167 100644 --- a/src/main/java/org/scijava/module/ModuleCanceledException.java +++ b/src/main/java/org/scijava/module/ModuleCanceledException.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/ModuleException.java b/src/main/java/org/scijava/module/ModuleException.java index 01d8612f8..9f9f3cb7c 100644 --- a/src/main/java/org/scijava/module/ModuleException.java +++ b/src/main/java/org/scijava/module/ModuleException.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/ModuleIndex.java b/src/main/java/org/scijava/module/ModuleIndex.java index b4a8e146d..998568f10 100644 --- a/src/main/java/org/scijava/module/ModuleIndex.java +++ b/src/main/java/org/scijava/module/ModuleIndex.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/ModuleInfo.java b/src/main/java/org/scijava/module/ModuleInfo.java index f6972555d..c9b687177 100644 --- a/src/main/java/org/scijava/module/ModuleInfo.java +++ b/src/main/java/org/scijava/module/ModuleInfo.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -41,7 +39,7 @@ import org.scijava.Versioned; import org.scijava.event.EventService; import org.scijava.module.event.ModulesUpdatedEvent; -import org.scijava.util.ClassUtils; +import org.scijava.util.Types; import org.scijava.util.VersionUtils; /** @@ -219,7 +217,7 @@ default String getLocation() { // If the same delegate class is used for more than one module, though, // it may need to override this method to indicate a different location. try { - return ClassUtils.getLocation(loadDelegateClass()).toExternalForm(); + return Types.location(loadDelegateClass()).toExternalForm(); } catch (final ClassNotFoundException exc) { return null; diff --git a/src/main/java/org/scijava/module/ModuleItem.java b/src/main/java/org/scijava/module/ModuleItem.java index 6def93c65..95ea748b4 100644 --- a/src/main/java/org/scijava/module/ModuleItem.java +++ b/src/main/java/org/scijava/module/ModuleItem.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -128,11 +126,52 @@ public interface ModuleItem extends BasicDetails { /** * Invokes this item's validation function, if any, on the given module. - * + *

+ * The validation function may signal failure either by throwing an exception + * or by returning a non-empty {@link String} error message. + *

+ * + * @throws MethodCallException if validation fails or the method cannot be + * invoked. When the validater returns a non-empty String, a + * {@link MethodCallException} is thrown with that string as its + * message. * @see #getValidater() + * @see #validateMessage(Module) */ void validate(Module module) throws MethodCallException; + /** + * Validates this item's value in the given module, returning any error + * message rather than throwing. + *

+ * The validation function may signal failure either by throwing an exception + * or by returning a non-empty {@link String} error message. This method + * catches both cases and returns the error message as a string, or + * {@code null} if the value is valid. + *

+ * + * @return an error message if the value is invalid, or {@code null} if valid. + * @see #getValidater() + * @see #validate(Module) + */ + default String validateMessage(final Module module) { + try { + validate(module); + return null; + } + catch (final MethodCallException exc) { + // Unwrap to find the most informative message. + final Throwable cause = exc.getCause(); + if (cause != null && cause.getMessage() != null && + !cause.getMessage().isEmpty()) + { + return cause.getMessage(); + } + final String msg = exc.getMessage(); + return msg != null && !msg.isEmpty() ? msg : exc.toString(); + } + } + /** * Gets the function that is called whenever this item changes. *

diff --git a/src/main/java/org/scijava/module/ModuleRunner.java b/src/main/java/org/scijava/module/ModuleRunner.java index a3a1253b6..ece995d8a 100644 --- a/src/main/java/org/scijava/module/ModuleRunner.java +++ b/src/main/java/org/scijava/module/ModuleRunner.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -41,6 +39,7 @@ import org.scijava.event.EventService; import org.scijava.log.LogService; import org.scijava.module.event.ModuleCanceledEvent; +import org.scijava.module.event.ModuleErroredEvent; import org.scijava.module.event.ModuleExecutedEvent; import org.scijava.module.event.ModuleExecutingEvent; import org.scijava.module.event.ModuleFinishedEvent; @@ -126,12 +125,10 @@ public Module call() { run(); } catch (final RuntimeException exc) { - if (log != null) log.error("Module threw exception", exc); - throw exc; + throw new RuntimeException("Module threw exception", exc); } catch (final Error err) { - if (log != null) log.error("Module threw error", err); - throw err; + throw new RuntimeException("Module threw error", err); } return module; } @@ -148,36 +145,42 @@ public void run() { final String title = module.getInfo().getTitle(); - // announce start of execution process - if (ss != null) ss.showStatus("Running command: " + title); - if (es != null) es.publish(new ModuleStartedEvent(module)); - - // execute preprocessors - final ModulePreprocessor canceler = preProcess(); - if (canceler != null) { - // module execution was canceled by preprocessor - final String reason = canceler.getCancelReason(); - cancel(reason); - cleanupAndBroadcastCancelation(title, reason); - return; + try { + // announce start of execution process + if (ss != null) ss.showStatus("Running command: " + title); + if (es != null) es.publish(new ModuleStartedEvent(module)); + + // execute preprocessors + final ModulePreprocessor canceler = preProcess(); + if (canceler != null) { + // module execution was canceled by preprocessor + final String reason = canceler.getCancelReason(); + cancel(reason); + cleanupAndBroadcastCancelation(title, reason); + return; + } + + // execute module + if (es != null) es.publish(new ModuleExecutingEvent(module)); + module.run(); + if (isCanceled()) { + // module execution was canceled by the module itself + cleanupAndBroadcastCancelation(title, getCancelReason()); + return; + } + if (es != null) es.publish(new ModuleExecutedEvent(module)); + + // execute postprocessors + postProcess(); + + // announce completion of execution process + if (es != null) es.publish(new ModuleFinishedEvent(module)); + if (ss != null) ss.showStatus("Command finished: " + title); } - - // execute module - if (es != null) es.publish(new ModuleExecutingEvent(module)); - module.run(); - if (isCanceled()) { - // module execution was canceled by the module itself - cleanupAndBroadcastCancelation(title, getCancelReason()); - return; + catch (final Throwable t) { + cleanupAndBroadcastException(title, t); + throw t; } - if (es != null) es.publish(new ModuleExecutedEvent(module)); - - // execute postprocessors - postProcess(); - - // announce completion of execution process - if (es != null) es.publish(new ModuleFinishedEvent(module)); - if (ss != null) ss.showStatus("Command finished: " + title); } // -- Helper methods -- @@ -194,6 +197,17 @@ private void cleanupAndBroadcastCancelation(final String title, } } + private void cleanupAndBroadcastException(final String title, + final Throwable t) + { + final ModuleErroredEvent evt = new ModuleErroredEvent(module, t); + if (es != null) es.publish(evt); + if (log != null && !evt.isConsumed()) { + // Nothing else handled the error, so log it. + log.error("Command errored: " + title, t); + } + } + private boolean isCanceled() { return module instanceof Cancelable && ((Cancelable) module).isCanceled(); } diff --git a/src/main/java/org/scijava/module/ModuleService.java b/src/main/java/org/scijava/module/ModuleService.java index 165d83ffe..3f97a56e4 100644 --- a/src/main/java/org/scijava/module/ModuleService.java +++ b/src/main/java/org/scijava/module/ModuleService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -310,4 +308,9 @@ Future run(M module, /** Gets the default value of the given {@link ModuleItem}. */ T getDefaultValue(final ModuleItem item); + /** Saves values to persistent storage from the given {@link Module}. */ + void saveInputs(final Module module); + + /** Loads values from persistent storage into the given {@link Module}. */ + void loadInputs(final Module module); } diff --git a/src/main/java/org/scijava/module/MutableModule.java b/src/main/java/org/scijava/module/MutableModule.java index 63771f2ca..3a907c854 100644 --- a/src/main/java/org/scijava/module/MutableModule.java +++ b/src/main/java/org/scijava/module/MutableModule.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/MutableModuleInfo.java b/src/main/java/org/scijava/module/MutableModuleInfo.java index b4b6d3ca1..31aeb7280 100644 --- a/src/main/java/org/scijava/module/MutableModuleInfo.java +++ b/src/main/java/org/scijava/module/MutableModuleInfo.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/MutableModuleItem.java b/src/main/java/org/scijava/module/MutableModuleItem.java index c546fb8ef..413950216 100644 --- a/src/main/java/org/scijava/module/MutableModuleItem.java +++ b/src/main/java/org/scijava/module/MutableModuleItem.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -48,6 +46,8 @@ public interface MutableModuleItem extends ModuleItem { void setVisibility(ItemVisibility visibility); + void setAutoFill(boolean autoFill); + void setRequired(boolean required); void setPersisted(boolean persisted); diff --git a/src/main/java/org/scijava/module/event/ModuleCanceledEvent.java b/src/main/java/org/scijava/module/event/ModuleCanceledEvent.java index e6955c597..baa6ce737 100644 --- a/src/main/java/org/scijava/module/event/ModuleCanceledEvent.java +++ b/src/main/java/org/scijava/module/event/ModuleCanceledEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/event/ModuleErroredEvent.java b/src/main/java/org/scijava/module/event/ModuleErroredEvent.java new file mode 100644 index 000000000..381cc0a4e --- /dev/null +++ b/src/main/java/org/scijava/module/event/ModuleErroredEvent.java @@ -0,0 +1,52 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.module.event; + +import org.scijava.module.Module; + +/** + * An event indicating a module execution has thrown an exception. + * + * @author Gabriel Selzer + */ +public class ModuleErroredEvent extends ModuleExecutionEvent { + + private final Throwable exc; + + public ModuleErroredEvent(final Module module, final Throwable exc) { + super(module); + this.exc = exc; + } + + public Throwable getException() { + return exc; + } + +} diff --git a/src/main/java/org/scijava/module/event/ModuleEvent.java b/src/main/java/org/scijava/module/event/ModuleEvent.java index f17936668..9d402399a 100644 --- a/src/main/java/org/scijava/module/event/ModuleEvent.java +++ b/src/main/java/org/scijava/module/event/ModuleEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/event/ModuleExecutedEvent.java b/src/main/java/org/scijava/module/event/ModuleExecutedEvent.java index 095ee07b6..c8ec50ca5 100644 --- a/src/main/java/org/scijava/module/event/ModuleExecutedEvent.java +++ b/src/main/java/org/scijava/module/event/ModuleExecutedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/event/ModuleExecutingEvent.java b/src/main/java/org/scijava/module/event/ModuleExecutingEvent.java index e24bd4f4f..5110625e8 100644 --- a/src/main/java/org/scijava/module/event/ModuleExecutingEvent.java +++ b/src/main/java/org/scijava/module/event/ModuleExecutingEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/event/ModuleExecutionEvent.java b/src/main/java/org/scijava/module/event/ModuleExecutionEvent.java index 1dcc35616..73bfa9d14 100644 --- a/src/main/java/org/scijava/module/event/ModuleExecutionEvent.java +++ b/src/main/java/org/scijava/module/event/ModuleExecutionEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/event/ModuleFinishedEvent.java b/src/main/java/org/scijava/module/event/ModuleFinishedEvent.java index 294cd8637..4c0f71dbf 100644 --- a/src/main/java/org/scijava/module/event/ModuleFinishedEvent.java +++ b/src/main/java/org/scijava/module/event/ModuleFinishedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/event/ModulePostprocessEvent.java b/src/main/java/org/scijava/module/event/ModulePostprocessEvent.java index 95b2379a1..e59f94ff2 100644 --- a/src/main/java/org/scijava/module/event/ModulePostprocessEvent.java +++ b/src/main/java/org/scijava/module/event/ModulePostprocessEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/event/ModulePreprocessEvent.java b/src/main/java/org/scijava/module/event/ModulePreprocessEvent.java index b81f1ffa1..7efdc11c7 100644 --- a/src/main/java/org/scijava/module/event/ModulePreprocessEvent.java +++ b/src/main/java/org/scijava/module/event/ModulePreprocessEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/event/ModuleProcessEvent.java b/src/main/java/org/scijava/module/event/ModuleProcessEvent.java index 55305f2af..f5ac20fe8 100644 --- a/src/main/java/org/scijava/module/event/ModuleProcessEvent.java +++ b/src/main/java/org/scijava/module/event/ModuleProcessEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/event/ModuleStartedEvent.java b/src/main/java/org/scijava/module/event/ModuleStartedEvent.java index 7a5df739f..2b8f59393 100644 --- a/src/main/java/org/scijava/module/event/ModuleStartedEvent.java +++ b/src/main/java/org/scijava/module/event/ModuleStartedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/event/ModulesAddedEvent.java b/src/main/java/org/scijava/module/event/ModulesAddedEvent.java index d695e8d27..30107c4e7 100644 --- a/src/main/java/org/scijava/module/event/ModulesAddedEvent.java +++ b/src/main/java/org/scijava/module/event/ModulesAddedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/event/ModulesListEvent.java b/src/main/java/org/scijava/module/event/ModulesListEvent.java index 2bd2b150d..051edef8d 100644 --- a/src/main/java/org/scijava/module/event/ModulesListEvent.java +++ b/src/main/java/org/scijava/module/event/ModulesListEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/event/ModulesRemovedEvent.java b/src/main/java/org/scijava/module/event/ModulesRemovedEvent.java index cc9038d05..495f9e7e2 100644 --- a/src/main/java/org/scijava/module/event/ModulesRemovedEvent.java +++ b/src/main/java/org/scijava/module/event/ModulesRemovedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/event/ModulesUpdatedEvent.java b/src/main/java/org/scijava/module/event/ModulesUpdatedEvent.java index 3278acceb..df5a9065d 100644 --- a/src/main/java/org/scijava/module/event/ModulesUpdatedEvent.java +++ b/src/main/java/org/scijava/module/event/ModulesUpdatedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/process/AbstractPostprocessorPlugin.java b/src/main/java/org/scijava/module/process/AbstractPostprocessorPlugin.java index cc33882a2..6e786753b 100644 --- a/src/main/java/org/scijava/module/process/AbstractPostprocessorPlugin.java +++ b/src/main/java/org/scijava/module/process/AbstractPostprocessorPlugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/process/AbstractPreprocessorPlugin.java b/src/main/java/org/scijava/module/process/AbstractPreprocessorPlugin.java index a0f3e4ab1..b9cb5de9d 100644 --- a/src/main/java/org/scijava/module/process/AbstractPreprocessorPlugin.java +++ b/src/main/java/org/scijava/module/process/AbstractPreprocessorPlugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/process/AbstractSingleInputPreprocessor.java b/src/main/java/org/scijava/module/process/AbstractSingleInputPreprocessor.java index a6f69dc0d..63e07cd2b 100644 --- a/src/main/java/org/scijava/module/process/AbstractSingleInputPreprocessor.java +++ b/src/main/java/org/scijava/module/process/AbstractSingleInputPreprocessor.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/process/CheckInputsPreprocessor.java b/src/main/java/org/scijava/module/process/CheckInputsPreprocessor.java index 117a1e7fe..8bbfac881 100644 --- a/src/main/java/org/scijava/module/process/CheckInputsPreprocessor.java +++ b/src/main/java/org/scijava/module/process/CheckInputsPreprocessor.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/process/DebugPostprocessor.java b/src/main/java/org/scijava/module/process/DebugPostprocessor.java index 368702df6..e614afada 100644 --- a/src/main/java/org/scijava/module/process/DebugPostprocessor.java +++ b/src/main/java/org/scijava/module/process/DebugPostprocessor.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -44,7 +42,7 @@ * * @author Curtis Rueden */ -@Plugin(type = PostprocessorPlugin.class, priority = Priority.FIRST_PRIORITY) +@Plugin(type = PostprocessorPlugin.class, priority = Priority.FIRST) public class DebugPostprocessor extends AbstractPostprocessorPlugin { @Parameter(required = false) diff --git a/src/main/java/org/scijava/module/process/DebugPreprocessor.java b/src/main/java/org/scijava/module/process/DebugPreprocessor.java index 43f4498ac..15f6afe1f 100644 --- a/src/main/java/org/scijava/module/process/DebugPreprocessor.java +++ b/src/main/java/org/scijava/module/process/DebugPreprocessor.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -42,7 +40,7 @@ * * @author Curtis Rueden */ -@Plugin(type = PreprocessorPlugin.class, priority = Priority.FIRST_PRIORITY) +@Plugin(type = PreprocessorPlugin.class, priority = Priority.FIRST) public class DebugPreprocessor extends AbstractPreprocessorPlugin { @Parameter(required = false) diff --git a/src/main/java/org/scijava/module/process/DefaultValuePreprocessor.java b/src/main/java/org/scijava/module/process/DefaultValuePreprocessor.java index 6d1cecf01..fed93f7e9 100644 --- a/src/main/java/org/scijava/module/process/DefaultValuePreprocessor.java +++ b/src/main/java/org/scijava/module/process/DefaultValuePreprocessor.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -39,7 +37,7 @@ import org.scijava.module.ModuleService; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; -import org.scijava.util.ConversionUtils; +import org.scijava.util.Types; /** * A preprocessor plugin that populates default parameter values. @@ -49,7 +47,7 @@ * * @author Curtis Rueden */ -@Plugin(type = PreprocessorPlugin.class, priority = Priority.VERY_HIGH_PRIORITY) +@Plugin(type = PreprocessorPlugin.class, priority = Priority.VERY_HIGH) public class DefaultValuePreprocessor extends AbstractPreprocessorPlugin { @Parameter @@ -73,7 +71,7 @@ private void assignDefaultValue(final Module module, final ModuleItem item) { if (module.isInputResolved(item.getName())) return; - final T nullValue = ConversionUtils.getNullValue(item.getType()); + final T nullValue = Types.nullValue(item.getType()); if (!Objects.equals(item.getValue(module), nullValue)) return; final T defaultValue = moduleService.getDefaultValue(item); if (defaultValue == null) return; diff --git a/src/main/java/org/scijava/module/process/GatewayPreprocessor.java b/src/main/java/org/scijava/module/process/GatewayPreprocessor.java index 3cc9d45ac..b3889d479 100644 --- a/src/main/java/org/scijava/module/process/GatewayPreprocessor.java +++ b/src/main/java/org/scijava/module/process/GatewayPreprocessor.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -52,8 +50,7 @@ * * @author Curtis Rueden */ -@Plugin(type = PreprocessorPlugin.class, // - priority = 2 * Priority.VERY_HIGH_PRIORITY) +@Plugin(type = PreprocessorPlugin.class, priority = 2 * Priority.VERY_HIGH) public class GatewayPreprocessor extends AbstractPreprocessorPlugin { @Parameter @@ -64,7 +61,8 @@ public class GatewayPreprocessor extends AbstractPreprocessorPlugin { @Override public void process(final Module module) { for (final ModuleItem input : module.getInfo().inputs()) { - if (!input.isAutoFill()) continue; // cannot auto-fill this input + if (!input.isAutoFill()) continue; // skip unfillable inputs + if (module.isInputResolved(input.getName())) continue; // skip resolved inputs final Class type = input.getType(); if (Gateway.class.isAssignableFrom(type)) { // input is a gateway @@ -87,22 +85,10 @@ private void setGatewayValue(final Context context, try { gateway = type.getConstructor(Context.class).newInstance(context); } - catch (final IllegalArgumentException exc) { - exception = exc; - } - catch (final SecurityException exc) { - exception = exc; - } - catch (final InstantiationException exc) { - exception = exc; - } - catch (final IllegalAccessException exc) { - exception = exc; - } - catch (final InvocationTargetException exc) { - exception = exc; - } - catch (final NoSuchMethodException exc) { + catch (final IllegalArgumentException | SecurityException + | InstantiationException | IllegalAccessException + | InvocationTargetException | NoSuchMethodException exc) + { exception = exc; } if (exception != null) { diff --git a/src/main/java/org/scijava/module/process/InitPreprocessor.java b/src/main/java/org/scijava/module/process/InitPreprocessor.java index a853ba785..d135614d6 100644 --- a/src/main/java/org/scijava/module/process/InitPreprocessor.java +++ b/src/main/java/org/scijava/module/process/InitPreprocessor.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -47,7 +45,7 @@ * * @author Curtis Rueden */ -@Plugin(type = PreprocessorPlugin.class, priority = Priority.HIGH_PRIORITY) +@Plugin(type = PreprocessorPlugin.class, priority = Priority.HIGH) public class InitPreprocessor extends AbstractPreprocessorPlugin { @Parameter(required = false) diff --git a/src/main/java/org/scijava/module/process/LoadInputsPreprocessor.java b/src/main/java/org/scijava/module/process/LoadInputsPreprocessor.java index 426be0bed..eb405b424 100644 --- a/src/main/java/org/scijava/module/process/LoadInputsPreprocessor.java +++ b/src/main/java/org/scijava/module/process/LoadInputsPreprocessor.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -31,13 +29,10 @@ package org.scijava.module.process; -import org.scijava.convert.ConvertService; import org.scijava.module.Module; -import org.scijava.module.ModuleItem; import org.scijava.module.ModuleService; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; -import org.scijava.util.ConversionUtils; import org.scijava.widget.InputHarvester; /** @@ -58,41 +53,8 @@ public class LoadInputsPreprocessor extends AbstractPreprocessorPlugin { @Parameter private ModuleService moduleService; - @Parameter - private ConvertService conversionService; - - // -- ModuleProcessor methods -- - @Override public void process(final Module module) { - final Iterable> inputs = module.getInfo().inputs(); - for (final ModuleItem item : inputs) { - loadValue(module, item); - } - } - - // -- Helper methods -- - - /** Loads the value of the given module item from persistent storage. */ - private void loadValue(final Module module, final ModuleItem item) { - // skip input that has already been resolved - if (module.isInputResolved(item.getName())) return; - - final T prefValue = moduleService.load(item); - final Class type = item.getType(); - final T defaultValue = item.getValue(module); - final T value = getBestValue(prefValue, defaultValue, type); - item.setValue(module, value); + moduleService.loadInputs(module); } - - private T getBestValue(final Object prefValue, - final Object defaultValue, final Class type) - { - if (prefValue != null) return conversionService.convert(prefValue, type); - if (defaultValue != null) { - return conversionService.convert(defaultValue, type); - } - return ConversionUtils.getNullValue(type); - } - } diff --git a/src/main/java/org/scijava/module/process/LoggerPreprocessor.java b/src/main/java/org/scijava/module/process/LoggerPreprocessor.java new file mode 100644 index 000000000..b096c5593 --- /dev/null +++ b/src/main/java/org/scijava/module/process/LoggerPreprocessor.java @@ -0,0 +1,77 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.module.process; + +import org.scijava.Priority; +import org.scijava.log.LogService; +import org.scijava.log.Logger; +import org.scijava.module.Module; +import org.scijava.module.ModuleItem; +import org.scijava.module.ModuleService; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; + +/** + * This {@link PreprocessorPlugin} affects {@link Module}s with a single + * {@link Parameter} of type {@link Logger}. It will assign a Logger to that + * Parameter, that is named like the modules class. + * + * @author Matthias Arzt + */ +@Plugin(type = PreprocessorPlugin.class, priority = Priority.VERY_HIGH) +public class LoggerPreprocessor extends AbstractPreprocessorPlugin { + + @Parameter(required = false) + private LogService logService; + + @Parameter(required = false) + private ModuleService moduleService; + + // -- ModuleProcessor methods -- + + @Override + public void process(final Module module) { + if (logService == null || moduleService == null) return; + + final ModuleItem loggerInput = moduleService.getSingleInput(module, + Logger.class); + if (loggerInput == null || !loggerInput.isAutoFill()) return; + + String loggerName = loggerInput.getLabel(); + if(loggerName == null || loggerName.isEmpty()) + loggerName = module.getDelegateObject().getClass().getSimpleName(); + Logger logger = logService.subLogger(loggerName); + + final String name = loggerInput.getName(); + module.setInput(name, logger); + module.resolveInput(name); + } + +} diff --git a/src/main/java/org/scijava/module/process/ModulePostprocessor.java b/src/main/java/org/scijava/module/process/ModulePostprocessor.java index 7f991b220..50a82900e 100644 --- a/src/main/java/org/scijava/module/process/ModulePostprocessor.java +++ b/src/main/java/org/scijava/module/process/ModulePostprocessor.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/process/ModulePreprocessor.java b/src/main/java/org/scijava/module/process/ModulePreprocessor.java index 7f7df3348..ee9d2af22 100644 --- a/src/main/java/org/scijava/module/process/ModulePreprocessor.java +++ b/src/main/java/org/scijava/module/process/ModulePreprocessor.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/process/ModuleProcessor.java b/src/main/java/org/scijava/module/process/ModuleProcessor.java index 5bda98f14..605194d03 100644 --- a/src/main/java/org/scijava/module/process/ModuleProcessor.java +++ b/src/main/java/org/scijava/module/process/ModuleProcessor.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/process/PostprocessorPlugin.java b/src/main/java/org/scijava/module/process/PostprocessorPlugin.java index e86f1b471..a010e0a6f 100644 --- a/src/main/java/org/scijava/module/process/PostprocessorPlugin.java +++ b/src/main/java/org/scijava/module/process/PostprocessorPlugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/process/PreprocessorPlugin.java b/src/main/java/org/scijava/module/process/PreprocessorPlugin.java index 8ab370ef5..57ccdc92f 100644 --- a/src/main/java/org/scijava/module/process/PreprocessorPlugin.java +++ b/src/main/java/org/scijava/module/process/PreprocessorPlugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/module/process/SaveInputsPreprocessor.java b/src/main/java/org/scijava/module/process/SaveInputsPreprocessor.java index 0784574c1..8f4596f76 100644 --- a/src/main/java/org/scijava/module/process/SaveInputsPreprocessor.java +++ b/src/main/java/org/scijava/module/process/SaveInputsPreprocessor.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -33,7 +31,6 @@ import org.scijava.Priority; import org.scijava.module.Module; -import org.scijava.module.ModuleItem; import org.scijava.module.ModuleService; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; @@ -50,30 +47,16 @@ * @author Curtis Rueden */ @Plugin(type = PreprocessorPlugin.class, - priority = Priority.VERY_LOW_PRIORITY - 1) + priority = SaveInputsPreprocessor.PRIORITY) public class SaveInputsPreprocessor extends AbstractPreprocessorPlugin { - public static final double PRIORITY = Priority.VERY_LOW_PRIORITY - 1; + public static final double PRIORITY = Priority.VERY_LOW - 1; @Parameter private ModuleService moduleService; - // -- ModuleProcessor methods -- - @Override public void process(final Module module) { - final Iterable> inputs = module.getInfo().inputs(); - for (final ModuleItem item : inputs) { - saveValue(module, item); - } + moduleService.saveInputs(module); } - - // -- Helper methods -- - - /** Saves the value of the given module item to persistent storage. */ - private void saveValue(final Module module, final ModuleItem item) { - final T value = item.getValue(module); - moduleService.save(item, value); - } - } diff --git a/src/main/java/org/scijava/module/process/ServicePreprocessor.java b/src/main/java/org/scijava/module/process/ServicePreprocessor.java index d268eb8b2..15ac626c5 100644 --- a/src/main/java/org/scijava/module/process/ServicePreprocessor.java +++ b/src/main/java/org/scijava/module/process/ServicePreprocessor.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -50,7 +48,7 @@ *

* Many modules (e.g., most {@link Command}s) use @{@link Parameter}-annotated * service fields, resulting in those parameters being populated when the - * SciJava application context is injected (via {@link Context#inject(Object)}. + * SciJava application context is injected (via {@link Context#inject(Object)}). * However, some modules may have service parameters which are programmatically * generated (i.e., returned directly as inputs from {@link ModuleInfo#inputs()} * and as such not populated by context injection. E.g., this situation is the @@ -61,8 +59,7 @@ * * @author Curtis Rueden */ -@Plugin(type = PreprocessorPlugin.class, // - priority = 2 * Priority.VERY_HIGH_PRIORITY) +@Plugin(type = PreprocessorPlugin.class, priority = 2 * Priority.VERY_HIGH) public class ServicePreprocessor extends AbstractPreprocessorPlugin { // -- ModuleProcessor methods -- @@ -70,7 +67,8 @@ public class ServicePreprocessor extends AbstractPreprocessorPlugin { @Override public void process(final Module module) { for (final ModuleItem input : module.getInfo().inputs()) { - if (!input.isAutoFill()) continue; // cannot auto-fill this input + if (!input.isAutoFill()) continue; // skip unfillable inputs + if (module.isInputResolved(input.getName())) continue; // skip resolved inputs final Class type = input.getType(); if (Service.class.isAssignableFrom(type)) { // input is a service @@ -79,7 +77,9 @@ public void process(final Module module) { (ModuleItem) input; setServiceValue(getContext(), module, serviceInput); } - if (type.isAssignableFrom(getContext().getClass())) { + if (Context.class.isAssignableFrom(type) && // + type.isAssignableFrom(getContext().getClass())) + { // input is a compatible context final String name = input.getName(); module.setInput(name, getContext()); diff --git a/src/main/java/org/scijava/module/process/ValidityPreprocessor.java b/src/main/java/org/scijava/module/process/ValidityPreprocessor.java index 2ccb94e36..e1cbdb29e 100644 --- a/src/main/java/org/scijava/module/process/ValidityPreprocessor.java +++ b/src/main/java/org/scijava/module/process/ValidityPreprocessor.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -43,8 +41,7 @@ * * @author Curtis Rueden */ -@Plugin(type = PreprocessorPlugin.class, - priority = 3 * Priority.VERY_HIGH_PRIORITY) +@Plugin(type = PreprocessorPlugin.class, priority = 3 * Priority.VERY_HIGH) public class ValidityPreprocessor extends AbstractPreprocessorPlugin { // -- ModuleProcessor methods -- diff --git a/src/main/java/org/scijava/module/run/ModuleCodeRunner.java b/src/main/java/org/scijava/module/run/ModuleCodeRunner.java index 3f4fc437f..0369dee67 100644 --- a/src/main/java/org/scijava/module/run/ModuleCodeRunner.java +++ b/src/main/java/org/scijava/module/run/ModuleCodeRunner.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/object/DefaultObjectService.java b/src/main/java/org/scijava/object/DefaultObjectService.java index 7567a4630..885f2025c 100644 --- a/src/main/java/org/scijava/object/DefaultObjectService.java +++ b/src/main/java/org/scijava/object/DefaultObjectService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -31,14 +29,10 @@ package org.scijava.object; -import java.util.List; - import org.scijava.event.EventHandler; import org.scijava.event.EventService; import org.scijava.object.event.ObjectCreatedEvent; import org.scijava.object.event.ObjectDeletedEvent; -import org.scijava.object.event.ObjectsAddedEvent; -import org.scijava.object.event.ObjectsRemovedEvent; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; import org.scijava.service.AbstractService; @@ -67,7 +61,7 @@ public final class DefaultObjectService extends AbstractService implements private EventService eventService; /** Index of registered objects. */ - private ObjectIndex objectIndex; + private NamedObjectIndex objectIndex; // -- ObjectService methods -- @@ -77,35 +71,15 @@ public EventService eventService() { } @Override - public ObjectIndex getIndex() { + public NamedObjectIndex getIndex() { return objectIndex; } - @Override - public List getObjects(final Class type) { - final List list = objectIndex.get(type); - @SuppressWarnings("unchecked") - final List result = (List) list; - return result; - } - - @Override - public void addObject(final Object obj) { - objectIndex.add(obj); - eventService.publish(new ObjectsAddedEvent(obj)); - } - - @Override - public void removeObject(final Object obj) { - objectIndex.remove(obj); - eventService.publish(new ObjectsRemovedEvent(obj)); - } - // -- Service methods -- @Override public void initialize() { - objectIndex = new ObjectIndex<>(Object.class); + objectIndex = new NamedObjectIndex<>(Object.class); } // -- Event handlers -- @@ -119,5 +93,4 @@ protected void onEvent(final ObjectCreatedEvent event) { protected void onEvent(final ObjectDeletedEvent event) { removeObject(event.getObject()); } - } diff --git a/src/main/java/org/scijava/object/LazyObjects.java b/src/main/java/org/scijava/object/LazyObjects.java index 715ef0887..3676085bb 100644 --- a/src/main/java/org/scijava/object/LazyObjects.java +++ b/src/main/java/org/scijava/object/LazyObjects.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -32,6 +30,7 @@ package org.scijava.object; import java.util.Collection; +import java.util.function.Supplier; /** * Interface for objects created lazily. This interface provides a mechanism to @@ -40,9 +39,10 @@ * * @author Curtis Rueden */ -public interface LazyObjects { +public interface LazyObjects extends Supplier> { /** Gets the collection of objects. */ + @Override Collection get(); } diff --git a/src/main/java/org/scijava/object/NamedObjectIndex.java b/src/main/java/org/scijava/object/NamedObjectIndex.java new file mode 100644 index 000000000..0d807d166 --- /dev/null +++ b/src/main/java/org/scijava/object/NamedObjectIndex.java @@ -0,0 +1,62 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.scijava.object; + +import java.util.WeakHashMap; + +/** + * An {@link ObjectIndex} where each object can have an associated name. + * + * @author Jan Eglinger + */ +public class NamedObjectIndex extends ObjectIndex { + + private WeakHashMap nameMap; + + public NamedObjectIndex(final Class baseClass) { + super(baseClass); + nameMap = new WeakHashMap<>(); + } + + public boolean add(E object, String name) { + if (name != null) + nameMap.put(object, name); + return add(object); + } + + public boolean add(E object, Class type, String name, boolean batch) { + if (name != null) + nameMap.put(object, name); + return add(object, type, batch); + } + + public String getName(E object) { + return nameMap.get(object); + } +} diff --git a/src/main/java/org/scijava/object/ObjectIndex.java b/src/main/java/org/scijava/object/ObjectIndex.java index f172e3822..5eb81dde1 100644 --- a/src/main/java/org/scijava/object/ObjectIndex.java +++ b/src/main/java/org/scijava/object/ObjectIndex.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/object/ObjectService.java b/src/main/java/org/scijava/object/ObjectService.java index 5b8833a85..fac395e5d 100644 --- a/src/main/java/org/scijava/object/ObjectService.java +++ b/src/main/java/org/scijava/object/ObjectService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -33,7 +31,10 @@ import java.util.List; +import org.scijava.Named; import org.scijava.event.EventService; +import org.scijava.object.event.ObjectsAddedEvent; +import org.scijava.object.event.ObjectsRemovedEvent; import org.scijava.service.SciJavaService; /** @@ -48,16 +49,54 @@ default EventService eventService() { } /** Gets the index of available objects. */ - ObjectIndex getIndex(); + NamedObjectIndex getIndex(); /** Gets a list of all registered objects compatible with the given type. */ - List getObjects(Class type); + default List getObjects(final Class type) { + final List list = getIndex().get(type); + @SuppressWarnings("unchecked") + final List result = (List) list; + return result; + } + + /** + * Gets the name belonging to a given object. + *

+ * If no explicit name was provided at registration time, the name will be + * derived from {@link Named#getName()} if the object implements + * {@link Named}, or from the {@link Object#toString()} otherwise. It is + * guaranteed that this method will not return {@code null}. + *

+ **/ + default String getName(final Object obj) { + if (obj == null) throw new NullPointerException(); + final String name = getIndex().getName(obj); + if (name != null) return name; + if (obj instanceof Named) { + final String n = ((Named) obj).getName(); + if (n != null) return n; + } + final String s = obj.toString(); + if (s != null) return s; + return obj.getClass().getName() + "@" + Integer.toHexString(obj.hashCode()); + } /** Registers an object with the object service. */ - void addObject(Object obj); + default void addObject(Object obj) { + addObject(obj, null); + } + + /** Registers a named object with the object service. */ + default void addObject(final Object obj, final String name) { + getIndex().add(obj, name); + eventService().publish(new ObjectsAddedEvent(obj)); + } /** Deregisters an object with the object service. */ - void removeObject(Object obj); + default void removeObject(final Object obj) { + getIndex().remove(obj); + eventService().publish(new ObjectsRemovedEvent(obj)); + } // -- Deprecated methods -- diff --git a/src/main/java/org/scijava/object/SortedObjectIndex.java b/src/main/java/org/scijava/object/SortedObjectIndex.java index ab5de4579..a9d2d1f21 100644 --- a/src/main/java/org/scijava/object/SortedObjectIndex.java +++ b/src/main/java/org/scijava/object/SortedObjectIndex.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/object/event/ListEvent.java b/src/main/java/org/scijava/object/event/ListEvent.java index 20043b4fa..e7e5214f7 100644 --- a/src/main/java/org/scijava/object/event/ListEvent.java +++ b/src/main/java/org/scijava/object/event/ListEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/object/event/ObjectCreatedEvent.java b/src/main/java/org/scijava/object/event/ObjectCreatedEvent.java index a2f823b7e..0cbd3dad0 100644 --- a/src/main/java/org/scijava/object/event/ObjectCreatedEvent.java +++ b/src/main/java/org/scijava/object/event/ObjectCreatedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/object/event/ObjectDeletedEvent.java b/src/main/java/org/scijava/object/event/ObjectDeletedEvent.java index 7e96da519..793a20c02 100644 --- a/src/main/java/org/scijava/object/event/ObjectDeletedEvent.java +++ b/src/main/java/org/scijava/object/event/ObjectDeletedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/object/event/ObjectEvent.java b/src/main/java/org/scijava/object/event/ObjectEvent.java index f4265bad5..7b1370612 100644 --- a/src/main/java/org/scijava/object/event/ObjectEvent.java +++ b/src/main/java/org/scijava/object/event/ObjectEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/object/event/ObjectModifiedEvent.java b/src/main/java/org/scijava/object/event/ObjectModifiedEvent.java index 03cb59af2..6e51bb9d2 100644 --- a/src/main/java/org/scijava/object/event/ObjectModifiedEvent.java +++ b/src/main/java/org/scijava/object/event/ObjectModifiedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/object/event/ObjectsAddedEvent.java b/src/main/java/org/scijava/object/event/ObjectsAddedEvent.java index 990622fc4..c4ce0e854 100644 --- a/src/main/java/org/scijava/object/event/ObjectsAddedEvent.java +++ b/src/main/java/org/scijava/object/event/ObjectsAddedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/object/event/ObjectsListEvent.java b/src/main/java/org/scijava/object/event/ObjectsListEvent.java index 86eee883e..9bab39b51 100644 --- a/src/main/java/org/scijava/object/event/ObjectsListEvent.java +++ b/src/main/java/org/scijava/object/event/ObjectsListEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/object/event/ObjectsRemovedEvent.java b/src/main/java/org/scijava/object/event/ObjectsRemovedEvent.java index 47df9da30..c042a73f8 100644 --- a/src/main/java/org/scijava/object/event/ObjectsRemovedEvent.java +++ b/src/main/java/org/scijava/object/event/ObjectsRemovedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/options/DefaultOptionsService.java b/src/main/java/org/scijava/options/DefaultOptionsService.java index 5d1b9fc9f..aa6792276 100644 --- a/src/main/java/org/scijava/options/DefaultOptionsService.java +++ b/src/main/java/org/scijava/options/DefaultOptionsService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/options/OptionsPlugin.java b/src/main/java/org/scijava/options/OptionsPlugin.java index 0c432e835..24208055c 100644 --- a/src/main/java/org/scijava/options/OptionsPlugin.java +++ b/src/main/java/org/scijava/options/OptionsPlugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -34,7 +32,6 @@ import org.scijava.command.DynamicCommand; import org.scijava.event.EventService; import org.scijava.module.ModuleItem; -import org.scijava.module.ModuleService; import org.scijava.options.event.OptionsEvent; import org.scijava.plugin.Parameter; import org.scijava.plugin.SingletonPlugin; @@ -83,9 +80,6 @@ public abstract class OptionsPlugin extends DynamicCommand implements @Parameter private PrefService prefService; - @Parameter - private ModuleService moduleService; - // -- OptionsPlugin methods -- /** Loads option values from persistent storage. */ @@ -107,19 +101,20 @@ public void reset() { prefService.clear(getClass()); } + // -- Module methods -- + + @Override + public void cancel() { + resetState(); + } + // -- Runnable methods -- @Override public void run() { save(); - - // NB: Clear "resolved" status of all inputs. - // Otherwise, no inputs are harvested on next run. - for (final ModuleItem input : getInfo().inputs()) { - unresolveInput(input.getName()); - } - eventService.publish(new OptionsEvent(this)); + resetState(); } // -- Helper methods -- @@ -134,4 +129,15 @@ private void saveInput(final ModuleItem input) { moduleService.save(input, value); } + private void resetState() { + // NB: Clear "resolved" status of all inputs. + // Otherwise, no inputs are harvested on next run. + for (final ModuleItem input : getInfo().inputs()) { + unresolveInput(input.getName()); + } + + // NB: Clear "canceled" status. + // Otherwise, the command cannot run again. + uncancel(); + } } diff --git a/src/main/java/org/scijava/options/OptionsService.java b/src/main/java/org/scijava/options/OptionsService.java index 84a7ee761..6e659e1c9 100644 --- a/src/main/java/org/scijava/options/OptionsService.java +++ b/src/main/java/org/scijava/options/OptionsService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/options/event/OptionsEvent.java b/src/main/java/org/scijava/options/event/OptionsEvent.java index b01199d23..9ecea6af8 100644 --- a/src/main/java/org/scijava/options/event/OptionsEvent.java +++ b/src/main/java/org/scijava/options/event/OptionsEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/parse/DefaultParseService.java b/src/main/java/org/scijava/parse/DefaultParseService.java index 457a19ac9..0924ab884 100644 --- a/src/main/java/org/scijava/parse/DefaultParseService.java +++ b/src/main/java/org/scijava/parse/DefaultParseService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -36,7 +34,8 @@ import java.util.List; import java.util.Map; -import org.scijava.parse.eval.DefaultEvaluator; +import org.scijava.parsington.Variable; +import org.scijava.parsington.eval.DefaultTreeEvaluator; import org.scijava.plugin.Plugin; import org.scijava.service.AbstractService; import org.scijava.service.Service; @@ -61,9 +60,8 @@ public Items parse(final String arg, final boolean strict) { // -- Helper classes -- /** - * {@link Items} implementation backed by the - * SciJava - * Expression Parser. + * {@link Items} implementation backed by + * Parsington. */ private static class ItemsList extends ObjectArray implements Items { @@ -99,7 +97,7 @@ public boolean isList() { } private void parseItems(final String arg, final boolean strict) { - final DefaultEvaluator e = new DefaultEvaluator(); + final DefaultTreeEvaluator e = new DefaultTreeEvaluator(); e.setStrict(strict); final Object result = e.evaluate("(" + arg + ")"); if (result == null) { diff --git a/src/main/java/org/scijava/parse/Item.java b/src/main/java/org/scijava/parse/Item.java index e58f0fec9..416538777 100644 --- a/src/main/java/org/scijava/parse/Item.java +++ b/src/main/java/org/scijava/parse/Item.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/parse/Items.java b/src/main/java/org/scijava/parse/Items.java index 687ef817d..0b3b6becb 100644 --- a/src/main/java/org/scijava/parse/Items.java +++ b/src/main/java/org/scijava/parse/Items.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/parse/ParseService.java b/src/main/java/org/scijava/parse/ParseService.java index 68cc81156..fe7ff2946 100644 --- a/src/main/java/org/scijava/parse/ParseService.java +++ b/src/main/java/org/scijava/parse/ParseService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/platform/AbstractPlatform.java b/src/main/java/org/scijava/platform/AbstractPlatform.java index 4364fa93d..9005f3024 100644 --- a/src/main/java/org/scijava/platform/AbstractPlatform.java +++ b/src/main/java/org/scijava/platform/AbstractPlatform.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/platform/AppEventService.java b/src/main/java/org/scijava/platform/AppEventService.java index aa030cd95..6227d6563 100644 --- a/src/main/java/org/scijava/platform/AppEventService.java +++ b/src/main/java/org/scijava/platform/AppEventService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/platform/DefaultAppEventService.java b/src/main/java/org/scijava/platform/DefaultAppEventService.java index 807b8d871..07809297d 100644 --- a/src/main/java/org/scijava/platform/DefaultAppEventService.java +++ b/src/main/java/org/scijava/platform/DefaultAppEventService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -48,7 +46,7 @@ /** @deprecated Use {@link AppService} and {@link App} instead. */ @Deprecated -@Plugin(type = Service.class, priority = Priority.LOW_PRIORITY) +@Plugin(type = Service.class, priority = Priority.LOW) public class DefaultAppEventService extends AbstractService implements AppEventService { diff --git a/src/main/java/org/scijava/platform/DefaultPlatform.java b/src/main/java/org/scijava/platform/DefaultPlatform.java index 630672060..18473f650 100644 --- a/src/main/java/org/scijava/platform/DefaultPlatform.java +++ b/src/main/java/org/scijava/platform/DefaultPlatform.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -35,6 +33,8 @@ import java.net.URL; import org.scijava.Priority; +import org.scijava.log.LogService; +import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; /** @@ -43,10 +43,12 @@ * @author Curtis Rueden * @author Johannes Schindelin */ -@Plugin(type = Platform.class, name = "Default", - priority = Priority.VERY_LOW_PRIORITY) +@Plugin(type = Platform.class, name = "Default", priority = Priority.VERY_LOW) public class DefaultPlatform extends AbstractPlatform { + @Parameter(required = false) + private LogService log; + // -- PlatformHandler methods -- /** @@ -69,9 +71,14 @@ public void open(final URL url) throws IOException { try { final int exitCode = getPlatformService().exec(browser, url.toString()); if (exitCode == 0) return; + else if (log != null) { + log.debug("Command '" + browser + + "' failed with exit code " + exitCode); + } } catch (final IOException e) { // browser executable was invalid; try the next one + if (log != null) log.debug("Command '" + browser + "' failed", e); } } throw new IOException("Could not open " + url); diff --git a/src/main/java/org/scijava/platform/DefaultPlatformService.java b/src/main/java/org/scijava/platform/DefaultPlatformService.java index 9b6b11047..bb8c962a6 100644 --- a/src/main/java/org/scijava/platform/DefaultPlatformService.java +++ b/src/main/java/org/scijava/platform/DefaultPlatformService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/platform/Platform.java b/src/main/java/org/scijava/platform/Platform.java index 1b19de6fa..31cc2e86a 100644 --- a/src/main/java/org/scijava/platform/Platform.java +++ b/src/main/java/org/scijava/platform/Platform.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/platform/PlatformService.java b/src/main/java/org/scijava/platform/PlatformService.java index 65282f726..ff66f8939 100644 --- a/src/main/java/org/scijava/platform/PlatformService.java +++ b/src/main/java/org/scijava/platform/PlatformService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/platform/event/AppAboutEvent.java b/src/main/java/org/scijava/platform/event/AppAboutEvent.java index 19f58943a..cd39d4764 100644 --- a/src/main/java/org/scijava/platform/event/AppAboutEvent.java +++ b/src/main/java/org/scijava/platform/event/AppAboutEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/platform/event/AppFocusEvent.java b/src/main/java/org/scijava/platform/event/AppFocusEvent.java index 4e2cac09f..e18e88a14 100644 --- a/src/main/java/org/scijava/platform/event/AppFocusEvent.java +++ b/src/main/java/org/scijava/platform/event/AppFocusEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/platform/event/AppMenusCreatedEvent.java b/src/main/java/org/scijava/platform/event/AppMenusCreatedEvent.java index cc29c24c4..3f8b80e0f 100644 --- a/src/main/java/org/scijava/platform/event/AppMenusCreatedEvent.java +++ b/src/main/java/org/scijava/platform/event/AppMenusCreatedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/platform/event/AppOpenFilesEvent.java b/src/main/java/org/scijava/platform/event/AppOpenFilesEvent.java index b4d81c1e3..cf42e0a73 100644 --- a/src/main/java/org/scijava/platform/event/AppOpenFilesEvent.java +++ b/src/main/java/org/scijava/platform/event/AppOpenFilesEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/platform/event/AppPreferencesEvent.java b/src/main/java/org/scijava/platform/event/AppPreferencesEvent.java index 71bc04fd0..f6cf5ce81 100644 --- a/src/main/java/org/scijava/platform/event/AppPreferencesEvent.java +++ b/src/main/java/org/scijava/platform/event/AppPreferencesEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/platform/event/AppPrintEvent.java b/src/main/java/org/scijava/platform/event/AppPrintEvent.java index da6acd058..2cfd3a56e 100644 --- a/src/main/java/org/scijava/platform/event/AppPrintEvent.java +++ b/src/main/java/org/scijava/platform/event/AppPrintEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/platform/event/AppQuitEvent.java b/src/main/java/org/scijava/platform/event/AppQuitEvent.java index dee21cabd..0b5f5f1ac 100644 --- a/src/main/java/org/scijava/platform/event/AppQuitEvent.java +++ b/src/main/java/org/scijava/platform/event/AppQuitEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/platform/event/AppReOpenEvent.java b/src/main/java/org/scijava/platform/event/AppReOpenEvent.java index 6e0f9a291..a9db284a5 100644 --- a/src/main/java/org/scijava/platform/event/AppReOpenEvent.java +++ b/src/main/java/org/scijava/platform/event/AppReOpenEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/platform/event/AppScreenSleepEvent.java b/src/main/java/org/scijava/platform/event/AppScreenSleepEvent.java index 4c0a273b5..d499a62db 100644 --- a/src/main/java/org/scijava/platform/event/AppScreenSleepEvent.java +++ b/src/main/java/org/scijava/platform/event/AppScreenSleepEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/platform/event/AppSleepEvent.java b/src/main/java/org/scijava/platform/event/AppSleepEvent.java index 7c9d6de2b..5ba427317 100644 --- a/src/main/java/org/scijava/platform/event/AppSleepEvent.java +++ b/src/main/java/org/scijava/platform/event/AppSleepEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/platform/event/AppSystemSleepEvent.java b/src/main/java/org/scijava/platform/event/AppSystemSleepEvent.java index d0a1386d3..9e86cadf2 100644 --- a/src/main/java/org/scijava/platform/event/AppSystemSleepEvent.java +++ b/src/main/java/org/scijava/platform/event/AppSystemSleepEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/platform/event/AppUserSessionEvent.java b/src/main/java/org/scijava/platform/event/AppUserSessionEvent.java index 60c9e6825..fb202cbc3 100644 --- a/src/main/java/org/scijava/platform/event/AppUserSessionEvent.java +++ b/src/main/java/org/scijava/platform/event/AppUserSessionEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/platform/event/AppVisibleEvent.java b/src/main/java/org/scijava/platform/event/AppVisibleEvent.java index 133d763c8..1b6fd77d2 100644 --- a/src/main/java/org/scijava/platform/event/AppVisibleEvent.java +++ b/src/main/java/org/scijava/platform/event/AppVisibleEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/platform/event/ApplicationEvent.java b/src/main/java/org/scijava/platform/event/ApplicationEvent.java index 365fc1d11..da0a2f3a1 100644 --- a/src/main/java/org/scijava/platform/event/ApplicationEvent.java +++ b/src/main/java/org/scijava/platform/event/ApplicationEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/plugin/AbstractHandlerPlugin.java b/src/main/java/org/scijava/plugin/AbstractHandlerPlugin.java index ebbd663df..2077a94c0 100644 --- a/src/main/java/org/scijava/plugin/AbstractHandlerPlugin.java +++ b/src/main/java/org/scijava/plugin/AbstractHandlerPlugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/plugin/AbstractHandlerService.java b/src/main/java/org/scijava/plugin/AbstractHandlerService.java index 0e18aca5a..65873e6eb 100644 --- a/src/main/java/org/scijava/plugin/AbstractHandlerService.java +++ b/src/main/java/org/scijava/plugin/AbstractHandlerService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/plugin/AbstractPTService.java b/src/main/java/org/scijava/plugin/AbstractPTService.java index 07c44d43f..5273d007b 100644 --- a/src/main/java/org/scijava/plugin/AbstractPTService.java +++ b/src/main/java/org/scijava/plugin/AbstractPTService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/plugin/AbstractRichPlugin.java b/src/main/java/org/scijava/plugin/AbstractRichPlugin.java index 3aa264d28..205d8ce8c 100644 --- a/src/main/java/org/scijava/plugin/AbstractRichPlugin.java +++ b/src/main/java/org/scijava/plugin/AbstractRichPlugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -44,7 +42,7 @@ public abstract class AbstractRichPlugin extends AbstractContextual implements { /** The priority of the plugin. */ - private double priority = Priority.NORMAL_PRIORITY; + private double priority = Priority.NORMAL; /** The metadata associated with the plugin. */ private PluginInfo info; diff --git a/src/main/java/org/scijava/plugin/AbstractSingletonService.java b/src/main/java/org/scijava/plugin/AbstractSingletonService.java index 65fdf2a13..b7f65c684 100644 --- a/src/main/java/org/scijava/plugin/AbstractSingletonService.java +++ b/src/main/java/org/scijava/plugin/AbstractSingletonService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -31,13 +29,17 @@ package org.scijava.plugin; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.scijava.event.EventHandler; import org.scijava.log.LogService; import org.scijava.object.ObjectService; +import org.scijava.plugin.event.PluginsAddedEvent; +import org.scijava.plugin.event.PluginsRemovedEvent; /** * Abstract base class for {@link SingletonService}s. @@ -55,9 +57,6 @@ public abstract class AbstractSingletonService @Parameter private ObjectService objectService; - // TODO: Listen for PluginsAddedEvent and PluginsRemovedEvent - // and update the list of singletons accordingly. - /** List of singleton plugin instances. */ private List instances; @@ -73,7 +72,7 @@ public ObjectService objectService() { @Override public List getInstances() { if (instances == null) initInstances(); - return instances; + return Collections.unmodifiableList(instances); } @SuppressWarnings("unchecked") @@ -83,20 +82,63 @@ public

P getInstance(final Class

pluginClass) { return (P) instanceMap.get(pluginClass); } +//-- Event handlers -- + + @EventHandler + protected void onEvent(final PluginsRemovedEvent event) { + if (instanceMap == null) return; + for (final PluginInfo info : event.getItems()) { + final PT obj = instanceMap.remove(info.getPluginClass()); + if (obj != null) { // we actually removed a plugin + instances.remove(obj); + objectService.removeObject(obj); + } + } + } + + @EventHandler + protected void onEvent(final PluginsAddedEvent event) { + if (instanceMap == null) return; + // collect singleton plugins + final List> singletons = new ArrayList<>(); + for (final PluginInfo pluginInfo : event.getItems()) { + if (getPluginType().isAssignableFrom(pluginInfo.getPluginType())) { + @SuppressWarnings("unchecked") + final PT plugin = pluginService().createInstance( + (PluginInfo) pluginInfo); + @SuppressWarnings("unchecked") + final Class pluginClass = (Class) plugin + .getClass(); + instanceMap.put(pluginClass, plugin); + instances.add(plugin); + } + } + + for (final PluginInfo pluginInfo : singletons) { + final PT plugin = pluginService().createInstance(pluginInfo); + @SuppressWarnings("unchecked") + final Class pluginClass = (Class) plugin + .getClass(); + instanceMap.put(pluginClass, plugin); + instances.add(plugin); + } + + } + // -- Helper methods -- private synchronized void initInstances() { if (instances != null) return; - final List list = Collections.unmodifiableList(filterInstances( - pluginService().createInstancesOfType(getPluginType()))); + @SuppressWarnings("unchecked") + final List list = (List) filterInstances(pluginService() + .createInstancesOfType(getPluginType())); - final HashMap, PT> map = - new HashMap<>(); + final Map, PT> map = new HashMap<>(); for (final PT plugin : list) { @SuppressWarnings("unchecked") - final Class ptClass = + final Class ptClass = // (Class) plugin.getClass(); map.put(ptClass, plugin); } diff --git a/src/main/java/org/scijava/plugin/AbstractTypedPlugin.java b/src/main/java/org/scijava/plugin/AbstractTypedPlugin.java index e8b3ff338..a9a12fb89 100644 --- a/src/main/java/org/scijava/plugin/AbstractTypedPlugin.java +++ b/src/main/java/org/scijava/plugin/AbstractTypedPlugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -43,5 +41,12 @@ public abstract class AbstractTypedPlugin extends AbstractRichPlugin implements TypedPlugin { - // NB: No implementation needed. + // -- Typed methods -- + + @Override + public boolean supports(final D data) { + // NB: Overridden just for backwards compatibility, so that + // downstream classes which call super.supports continue to work. + return TypedPlugin.super.supports(data); + } } diff --git a/src/main/java/org/scijava/plugin/AbstractTypedService.java b/src/main/java/org/scijava/plugin/AbstractTypedService.java index 4e5fc5029..c747223f3 100644 --- a/src/main/java/org/scijava/plugin/AbstractTypedService.java +++ b/src/main/java/org/scijava/plugin/AbstractTypedService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/plugin/AbstractWrapperPlugin.java b/src/main/java/org/scijava/plugin/AbstractWrapperPlugin.java index dae79f675..4a8d11412 100644 --- a/src/main/java/org/scijava/plugin/AbstractWrapperPlugin.java +++ b/src/main/java/org/scijava/plugin/AbstractWrapperPlugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/plugin/AbstractWrapperService.java b/src/main/java/org/scijava/plugin/AbstractWrapperService.java index 2e4b933d9..9bd224855 100644 --- a/src/main/java/org/scijava/plugin/AbstractWrapperService.java +++ b/src/main/java/org/scijava/plugin/AbstractWrapperService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/plugin/Attr.java b/src/main/java/org/scijava/plugin/Attr.java index 5c8f262e4..1dce1f4d7 100644 --- a/src/main/java/org/scijava/plugin/Attr.java +++ b/src/main/java/org/scijava/plugin/Attr.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/plugin/DefaultPluginFinder.java b/src/main/java/org/scijava/plugin/DefaultPluginFinder.java index e258bff85..bd3a0eb08 100644 --- a/src/main/java/org/scijava/plugin/DefaultPluginFinder.java +++ b/src/main/java/org/scijava/plugin/DefaultPluginFinder.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -37,6 +35,7 @@ import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; +import org.scijava.Context; import org.scijava.annotations.Index; import org.scijava.annotations.IndexItem; @@ -54,7 +53,7 @@ public class DefaultPluginFinder implements PluginFinder { /** Class loader to use when querying the annotation indexes. */ private final ClassLoader customClassLoader; - private final PluginBlacklist blacklist; + private final PluginBlocklist blocklist; // -- Constructors -- @@ -64,7 +63,7 @@ public DefaultPluginFinder() { public DefaultPluginFinder(final ClassLoader classLoader) { customClassLoader = classLoader; - blacklist = new SysPropBlacklist(); + blocklist = new SysPropBlocklist(); } // -- PluginFinder methods -- @@ -83,7 +82,7 @@ public HashMap findPlugins( // create a PluginInfo object for each item in the index for (final IndexItem item : annotationIndex) { - if (blacklist.contains(item.className())) continue; + if (blocklist.contains(item.className())) continue; try { final PluginInfo info = createInfo(item, classLoader); plugins.add(info); @@ -112,29 +111,29 @@ private PluginInfo createInfo( } private ClassLoader getClassLoader() { - if (customClassLoader != null) return customClassLoader; - return Thread.currentThread().getContextClassLoader(); + return customClassLoader != null ? // + customClassLoader : Context.getClassLoader(); } // -- Helper classes -- - private interface PluginBlacklist { + private interface PluginBlocklist { boolean contains(String className); } /** - * A blacklist defined by the {@code scijava.plugin.blacklist} system + * A blocklist defined by the {@code scijava.plugin.blocklist} system * property, formatted as a colon-separated list of regexes. *

* If a plugin class matches any of the regexes, it is excluded from the * plugin index. *

*/ - private class SysPropBlacklist implements PluginBlacklist { + private class SysPropBlocklist implements PluginBlocklist { private final List patterns; - public SysPropBlacklist() { - final String sysProp = System.getProperty("scijava.plugin.blacklist"); + public SysPropBlocklist() { + final String sysProp = System.getProperty("scijava.plugin.blocklist"); final String[] regexes = // sysProp == null ? new String[0] : sysProp.split(":"); patterns = new ArrayList<>(regexes.length); @@ -148,7 +147,7 @@ public SysPropBlacklist() { } } - // -- PluginBlacklist methods -- + // -- PluginBlocklist methods -- @Override public boolean contains(final String className) { diff --git a/src/main/java/org/scijava/plugin/DefaultPluginService.java b/src/main/java/org/scijava/plugin/DefaultPluginService.java index 16c94a132..096e75c92 100644 --- a/src/main/java/org/scijava/plugin/DefaultPluginService.java +++ b/src/main/java/org/scijava/plugin/DefaultPluginService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -238,7 +236,10 @@ public List createInstances( return p; } catch (final Throwable t) { - log.error("Cannot create plugin: " + info, t); + final String errorMessage = // + "Cannot create plugin: " + info.getClassName(); + if (log.isDebug()) log.debug(errorMessage, t); + else log.error(errorMessage); } return null; } diff --git a/src/main/java/org/scijava/plugin/HandlerPlugin.java b/src/main/java/org/scijava/plugin/HandlerPlugin.java index 1c0700565..8abcba382 100644 --- a/src/main/java/org/scijava/plugin/HandlerPlugin.java +++ b/src/main/java/org/scijava/plugin/HandlerPlugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/plugin/HandlerService.java b/src/main/java/org/scijava/plugin/HandlerService.java index 21724b2a1..dec2d71b5 100644 --- a/src/main/java/org/scijava/plugin/HandlerService.java +++ b/src/main/java/org/scijava/plugin/HandlerService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -56,7 +54,12 @@ public interface HandlerService> extends */ default PT getHandler(final DT data) { for (final PT handler : getInstances()) { - if (handler.supports(data)) return handler; + try { + if (handler.supports(data)) return handler; + } + catch (final Throwable t) { + log().error("Malfunctioning plugin: " + handler.getClass().getName(), t); + } } return null; } diff --git a/src/main/java/org/scijava/plugin/HasPluginInfo.java b/src/main/java/org/scijava/plugin/HasPluginInfo.java index 804a325d9..622429d98 100644 --- a/src/main/java/org/scijava/plugin/HasPluginInfo.java +++ b/src/main/java/org/scijava/plugin/HasPluginInfo.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/plugin/Menu.java b/src/main/java/org/scijava/plugin/Menu.java index 63a3285ba..ce861c6b6 100644 --- a/src/main/java/org/scijava/plugin/Menu.java +++ b/src/main/java/org/scijava/plugin/Menu.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/plugin/PTService.java b/src/main/java/org/scijava/plugin/PTService.java index b131a8600..d0e61a997 100644 --- a/src/main/java/org/scijava/plugin/PTService.java +++ b/src/main/java/org/scijava/plugin/PTService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -40,7 +38,7 @@ *

* There are many kinds of services, but most of them share one common * characteristic: they provide API specific to a particular type of plugin. A - * few examples from ImageJ: + * few examples: *

*
    *
  • The {@link org.scijava.command.CommandService} works with diff --git a/src/main/java/org/scijava/plugin/Parameter.java b/src/main/java/org/scijava/plugin/Parameter.java index c8591c48a..edc1da1af 100644 --- a/src/main/java/org/scijava/plugin/Parameter.java +++ b/src/main/java/org/scijava/plugin/Parameter.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -73,7 +71,7 @@ *
*/ // NB: We use the fully qualified name to work around a javac bug: - // http://bugs.sun.com/view_bug.do?bug_id=6512707 + // https://bugs.java.com/view_bug.do?bug_id=6512707 // See: // http://groups.google.com/group/project-lombok/browse_thread/thread/c5568eb659cab203 ItemIO type() default org.scijava.ItemIO.INPUT; @@ -96,11 +94,7 @@ * the user nor included as an input or output parameter. * */ - // NB: We use the fully qualified name to work around a javac bug: - // http://bugs.sun.com/view_bug.do?bug_id=6512707 - // See: - // http://groups.google.com/group/project-lombok/browse_thread/thread/c5568eb659cab203 - ItemVisibility visibility() default org.scijava.ItemVisibility.NORMAL; + ItemVisibility visibility() default ItemVisibility.NORMAL; /** * Defines whether the parameter value should be filled programmatically, if @@ -158,12 +152,6 @@ /** Defines the step size to use (numeric parameters only). */ String stepSize() default ""; - /** - * Defines the width of the input field in characters (text field parameters - * only). - */ - int columns() default 6; - /** Defines the list of possible values (multiple choice text fields only). */ String[] choices() default {}; @@ -173,4 +161,7 @@ */ Attr[] attrs() default {}; + /** @deprecated Replaced by {@link #style()}. */ + @Deprecated + int columns() default 6; } diff --git a/src/main/java/org/scijava/plugin/Plugin.java b/src/main/java/org/scijava/plugin/Plugin.java index 3a3223574..cd1701881 100644 --- a/src/main/java/org/scijava/plugin/Plugin.java +++ b/src/main/java/org/scijava/plugin/Plugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -115,18 +113,18 @@ * Any double value is allowed, but for convenience, there are some presets: *

*
    - *
  • {@link Priority#FIRST_PRIORITY}
  • - *
  • {@link Priority#VERY_HIGH_PRIORITY}
  • - *
  • {@link Priority#HIGH_PRIORITY}
  • - *
  • {@link Priority#NORMAL_PRIORITY}
  • - *
  • {@link Priority#LOW_PRIORITY}
  • - *
  • {@link Priority#VERY_LOW_PRIORITY}
  • - *
  • {@link Priority#LAST_PRIORITY}
  • + *
  • {@link Priority#FIRST}
  • + *
  • {@link Priority#VERY_HIGH}
  • + *
  • {@link Priority#HIGH}
  • + *
  • {@link Priority#NORMAL}
  • + *
  • {@link Priority#LOW}
  • + *
  • {@link Priority#VERY_LOW}
  • + *
  • {@link Priority#LAST}
  • *
* * @see org.scijava.service.Service */ - double priority() default Priority.NORMAL_PRIORITY; + double priority() default Priority.NORMAL; /** * Whether the plugin can be selected in the user interface. A plugin's diff --git a/src/main/java/org/scijava/plugin/PluginFinder.java b/src/main/java/org/scijava/plugin/PluginFinder.java index ab261521b..9f193a90e 100644 --- a/src/main/java/org/scijava/plugin/PluginFinder.java +++ b/src/main/java/org/scijava/plugin/PluginFinder.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/plugin/PluginIndex.java b/src/main/java/org/scijava/plugin/PluginIndex.java index bba5410be..ea3aa8fdc 100644 --- a/src/main/java/org/scijava/plugin/PluginIndex.java +++ b/src/main/java/org/scijava/plugin/PluginIndex.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -90,7 +88,7 @@ public PluginIndex() { */ @SuppressWarnings({ "rawtypes", "unchecked" }) public PluginIndex(final PluginFinder pluginFinder) { - // NB: See: http://stackoverflow.com/questions/4765520/ + // NB: See: https://stackoverflow.com/questions/4765520/ super((Class) PluginInfo.class); this.pluginFinder = pluginFinder; } diff --git a/src/main/java/org/scijava/plugin/PluginInfo.java b/src/main/java/org/scijava/plugin/PluginInfo.java index d9ad9141a..85bab5954 100644 --- a/src/main/java/org/scijava/plugin/PluginInfo.java +++ b/src/main/java/org/scijava/plugin/PluginInfo.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -32,6 +30,8 @@ package org.scijava.plugin; import java.net.URL; +import java.util.Collection; +import java.util.Optional; import org.scijava.AbstractUIDetails; import org.scijava.Identifiable; @@ -44,8 +44,8 @@ import org.scijava.UIDetails; import org.scijava.Versioned; import org.scijava.input.Accelerator; -import org.scijava.util.ClassUtils; import org.scijava.util.StringMaker; +import org.scijava.util.Types; import org.scijava.util.VersionUtils; /** @@ -281,7 +281,7 @@ public String getClassName() { public Class loadClass() throws InstantiableException { if (pluginClass == null) { try { - final Class c = ClassUtils.loadClass(className, classLoader, false); + final Class c = Types.load(className, classLoader, false); @SuppressWarnings("unchecked") final Class typedClass = (Class) c; pluginClass = typedClass; @@ -318,12 +318,7 @@ public PT createInstance() throws InstantiableException { @Override public String getIdentifier() { - try { - return "plugin:" + loadClass(); - } - catch (final InstantiableException exc) { - return null; - } + return "plugin:" + getClassName(); } // -- Locatable methods -- @@ -331,7 +326,7 @@ public String getIdentifier() { @Override public String getLocation() { try { - return ClassUtils.getLocation(loadClass()).toExternalForm(); + return Types.location(loadClass()).toExternalForm(); } catch (InstantiableException exc) { return null; @@ -350,6 +345,143 @@ public String getVersion() { } } + // -- Utility methods -- + + /** + * Finds a {@link PluginInfo} of the given plugin class in the specified + * {@link PluginIndex}. Note that to avoid loading plugin classes, class + * identity is determined by class name equality only. + * + * @param pluginClass The concrete class of the plugin whose + * {@link PluginInfo} is desired. + * @param pluginIndex The {@link PluginIndex} to search for a matching + * {@link PluginInfo}. + * @return The matching {@link PluginInfo}, or null if none found. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static

PluginInfo get( + final Class

pluginClass, final PluginIndex pluginIndex) + { + return get(pluginClass, (Collection) pluginIndex.getAll()); + } + + /** + * Finds a {@link PluginInfo} of the given plugin class and plugin type in the + * specified {@link PluginIndex}. Note that to avoid loading plugin + * classes, class identity is determined by class name equality only. + * + * @param pluginClass The concrete class of the plugin whose + * {@link PluginInfo} is desired. + * @param pluginType The type of the plugin; see {@link #getPluginType()}. + * @param pluginIndex The {@link PluginIndex} to search for a matching + * {@link PluginInfo}. + * @return The matching {@link PluginInfo}, or null if none found. + */ + public static

PluginInfo get( + final Class

pluginClass, final Class pluginType, + final PluginIndex pluginIndex) + { + return get(pluginClass, pluginIndex.getPlugins(pluginType)); + } + + /** + * Finds a {@link PluginInfo} of the given plugin class in the specified list + * of plugins. Note that to avoid loading plugin classes, class identity + * is determined by class name equality only. + * + * @param pluginClass The concrete class of the plugin whose + * {@link PluginInfo} is desired. + * @param plugins The list of plugins to search for a match. + * @return The matching {@link PluginInfo}, or null if none found. + */ + public static

PluginInfo get( + final Class

pluginClass, + final Collection> plugins) + { + final String className = pluginClass.getName(); + final Optional> result = plugins.stream() // + .filter(info -> info.getClassName().equals(className)) // + .findFirst(); + return result.isPresent() ? result.get() : null; + } + + /** + * Creates a {@link PluginInfo} for the given plugin class. The class must be + * a concrete class annotated with the @{@link Plugin} annotation, from which + * the plugin type will be inferred. + * + * @param pluginClass The concrete class of the plugin for which a new + * {@link PluginInfo} is desired. + * @return A newly created {@link PluginInfo} for the given plugin class. + * @throws IllegalArgumentException if the given class is not annotated + * with @{@link Plugin}, or its annotated {@link Plugin#type() + * type()} is not a supertype of the plugin class. + */ + public static PluginInfo create( + final Class pluginClass) + { + @SuppressWarnings({ "rawtypes", "unchecked" }) + final PluginInfo info = new PluginInfo(pluginClass, // + pluginType(pluginClass)); + return info; + } + + /** + * Creates a {@link PluginInfo} for the given plugin class of the specified + * plugin type. + * + * @param pluginClass The concrete class of the plugin for which a new + * {@link PluginInfo} is desired. + * @param pluginType The type of the plugin; see {@link #getPluginType()}. + * @return A newly created {@link PluginInfo} for the given plugin class. + */ + public static

PluginInfo create( + final Class

pluginClass, final Class pluginType) + { + return new PluginInfo<>(pluginClass, pluginType); + } + + /** + * Obtains a {@link PluginInfo} for the given plugin class. If one already + * exists in the specified {@link PluginIndex}, it is retrieved (see + * {@link #get(Class, PluginIndex)}); otherwise, a new one is created (see + * {@link #create(Class)}) but not added to the index. + * + * @param pluginClass The concrete class of the plugin whose + * {@link PluginInfo} is desired. + * @param pluginIndex The {@link PluginIndex} to search for a matching + * {@link PluginInfo}. + * @throws IllegalArgumentException when creating a new {@link PluginInfo} if + * the associated plugin type cannot be inferred; see + * {@link #create(Class)}. + */ + public static

PluginInfo getOrCreate( + final Class

pluginClass, final PluginIndex pluginIndex) + { + final PluginInfo existing = get(pluginClass, pluginIndex); + return existing == null ? create(pluginClass) : existing; + } + + /** + * Obtains a {@link PluginInfo} for the given plugin class. If one already + * exists in the specified {@link PluginIndex}, it is retrieved (see + * {@link #get(Class, PluginIndex)}); otherwise, a new one is created (see + * {@link #create(Class)}) but not added to the index. + * + * @param pluginClass The concrete class of the plugin whose + * {@link PluginInfo} is desired. + * @param pluginType The type of the plugin; see {@link #getPluginType()}. + * @param pluginIndex The {@link PluginIndex} to search for a matching + * {@link PluginInfo}. + */ + public static

PluginInfo + getOrCreate(final Class

pluginClass, final Class pluginType, + final PluginIndex pluginIndex) + { + final PluginInfo existing = get(pluginClass, pluginType, pluginIndex); + return existing == null ? create(pluginClass, pluginType) : existing; + } + // -- Helper methods -- /** Populates the entry to match the associated @{@link Plugin} annotation. */ @@ -411,4 +543,23 @@ private MenuPath parseMenuPath(final Menu[] menu) { return menuPath; } + /** Extracts the plugin type from a class's @{@link Plugin} annotation. */ + private static

Class pluginType( + final Class

pluginClass) + { + final Plugin annotation = pluginClass.getAnnotation(Plugin.class); + if (annotation == null) { + throw new IllegalArgumentException( + "Cannot infer plugin type from class '" + pluginClass.getName() + + "' with no @Plugin annotation."); + } + final Class type = annotation.type(); + if (!type.isAssignableFrom(pluginClass)) { + throw new IllegalArgumentException("Invalid plugin type '" + // + type.getName() + "' for class '" + pluginClass.getName() + "'"); + } + @SuppressWarnings("unchecked") + final Class pluginType = (Class) type; + return pluginType; + } } diff --git a/src/main/java/org/scijava/plugin/PluginService.java b/src/main/java/org/scijava/plugin/PluginService.java index f68c4db65..644268b87 100644 --- a/src/main/java/org/scijava/plugin/PluginService.java +++ b/src/main/java/org/scijava/plugin/PluginService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -33,8 +31,14 @@ import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.scijava.Priority; import org.scijava.service.SciJavaService; /** @@ -253,4 +257,23 @@ public interface PluginService extends SciJavaService { */ PT createInstance(PluginInfo info); + /** + * Sorts the given list of plugin instances by priority. + * + * @param instances List of plugin instances to sort. + * @param type The type of plugin these instances represent. + */ + default void sort(final List instances, + final Class type) + { + // Create a mapping from plugin classes to priorities. + final List> plugins = getPluginsOfType(type); + final Map, PluginInfo> infos = plugins.stream().collect(// + Collectors.toMap(PluginInfo::getPluginClass, Function.identity())); + + // Compare plugin instances by priority via the mapping. + final Comparator comparator = (o1, o2) -> Priority.compare(// + infos.get(o1.getClass()), infos.get(o2.getClass())); + Collections.sort(instances, comparator); + } } diff --git a/src/main/java/org/scijava/plugin/RichPlugin.java b/src/main/java/org/scijava/plugin/RichPlugin.java index ce7ced233..891cc7822 100644 --- a/src/main/java/org/scijava/plugin/RichPlugin.java +++ b/src/main/java/org/scijava/plugin/RichPlugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/plugin/SciJavaPlugin.java b/src/main/java/org/scijava/plugin/SciJavaPlugin.java index 65da01d77..97675e8bc 100644 --- a/src/main/java/org/scijava/plugin/SciJavaPlugin.java +++ b/src/main/java/org/scijava/plugin/SciJavaPlugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/plugin/SingletonPlugin.java b/src/main/java/org/scijava/plugin/SingletonPlugin.java index 84f0f41b0..72b5da303 100644 --- a/src/main/java/org/scijava/plugin/SingletonPlugin.java +++ b/src/main/java/org/scijava/plugin/SingletonPlugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/plugin/SingletonService.java b/src/main/java/org/scijava/plugin/SingletonService.java index bf22ba95c..a60622eac 100644 --- a/src/main/java/org/scijava/plugin/SingletonService.java +++ b/src/main/java/org/scijava/plugin/SingletonService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -34,7 +32,6 @@ import java.util.ArrayList; import java.util.List; -import org.scijava.object.LazyObjects; import org.scijava.object.ObjectService; /** @@ -94,13 +91,7 @@ default

P create(final Class

pluginClass) { @Override default void initialize() { // add singleton instances to the object index... IN THE FUTURE! - objectService().getIndex().addLater(new LazyObjects() { - - @Override - public ArrayList get() { - return new ArrayList<>(getInstances()); - } - }); + objectService().getIndex().addLater(() -> new ArrayList<>(getInstances())); } } diff --git a/src/main/java/org/scijava/plugin/SortablePlugin.java b/src/main/java/org/scijava/plugin/SortablePlugin.java index 0ba87ea57..e7f005db9 100644 --- a/src/main/java/org/scijava/plugin/SortablePlugin.java +++ b/src/main/java/org/scijava/plugin/SortablePlugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/plugin/TypedPlugin.java b/src/main/java/org/scijava/plugin/TypedPlugin.java index 806f04016..31153089a 100644 --- a/src/main/java/org/scijava/plugin/TypedPlugin.java +++ b/src/main/java/org/scijava/plugin/TypedPlugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/plugin/TypedService.java b/src/main/java/org/scijava/plugin/TypedService.java index f8cb8e887..74701f46c 100644 --- a/src/main/java/org/scijava/plugin/TypedService.java +++ b/src/main/java/org/scijava/plugin/TypedService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -67,8 +65,13 @@ public interface TypedService> extends */ default PT find(final DT data) { for (final PluginInfo plugin : getPlugins()) { - final PT instance = pluginService().createInstance(plugin); - if (instance != null && instance.supports(data)) return instance; + try { + final PT instance = pluginService().createInstance(plugin); + if (instance != null && instance.supports(data)) return instance; + } + catch (final Throwable t) { + log().error("Malfunctioning plugin: " + plugin.getClassName(), t); + } } return null; } diff --git a/src/main/java/org/scijava/plugin/WrapperPlugin.java b/src/main/java/org/scijava/plugin/WrapperPlugin.java index 9ed8c969d..651bf634e 100644 --- a/src/main/java/org/scijava/plugin/WrapperPlugin.java +++ b/src/main/java/org/scijava/plugin/WrapperPlugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/plugin/WrapperService.java b/src/main/java/org/scijava/plugin/WrapperService.java index 98dc1dc68..4274f16e8 100644 --- a/src/main/java/org/scijava/plugin/WrapperService.java +++ b/src/main/java/org/scijava/plugin/WrapperService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/plugin/event/PluginsAddedEvent.java b/src/main/java/org/scijava/plugin/event/PluginsAddedEvent.java index d15e620b3..edfc5b32e 100644 --- a/src/main/java/org/scijava/plugin/event/PluginsAddedEvent.java +++ b/src/main/java/org/scijava/plugin/event/PluginsAddedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/plugin/event/PluginsListEvent.java b/src/main/java/org/scijava/plugin/event/PluginsListEvent.java index 8e78e58b7..af6bf4dc3 100644 --- a/src/main/java/org/scijava/plugin/event/PluginsListEvent.java +++ b/src/main/java/org/scijava/plugin/event/PluginsListEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/plugin/event/PluginsRemovedEvent.java b/src/main/java/org/scijava/plugin/event/PluginsRemovedEvent.java index a5dd1deb0..baf9961a3 100644 --- a/src/main/java/org/scijava/plugin/event/PluginsRemovedEvent.java +++ b/src/main/java/org/scijava/plugin/event/PluginsRemovedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/prefs/AbstractPrefService.java b/src/main/java/org/scijava/prefs/AbstractPrefService.java index 401632185..052603404 100644 --- a/src/main/java/org/scijava/prefs/AbstractPrefService.java +++ b/src/main/java/org/scijava/prefs/AbstractPrefService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/prefs/DefaultPrefService.java b/src/main/java/org/scijava/prefs/DefaultPrefService.java index 288d2f2dc..3c192f62b 100644 --- a/src/main/java/org/scijava/prefs/DefaultPrefService.java +++ b/src/main/java/org/scijava/prefs/DefaultPrefService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -57,161 +55,111 @@ public class DefaultPrefService extends AbstractPrefService { @Parameter(required = false) private LogService log; - // -- Global preferences -- - - @Override - public String get(final String name) { - return get((Class) null, name); - } - - @Override - public String get(final String name, final String defaultValue) { - return get(null, name, defaultValue); - } - - @Override - public boolean getBoolean(final String name, final boolean defaultValue) { - return getBoolean(null, name, defaultValue); - } - - @Override - public double getDouble(final String name, final double defaultValue) { - return getDouble(null, name, defaultValue); - } - - @Override - public float getFloat(final String name, final float defaultValue) { - return getFloat(null, name, defaultValue); - } - - @Override - public int getInt(final String name, final int defaultValue) { - return getInt(null, name, defaultValue); - } - - @Override - public long getLong(final String name, final long defaultValue) { - return getLong(null, name, defaultValue); - } - - @Override - public void put(final String name, final String value) { - put(null, name, value); - } - - @Override - public void put(final String name, final boolean value) { - put(null, name, value); - } - - @Override - public void put(final String name, final double value) { - put(null, name, value); - } - - @Override - public void put(final String name, final float value) { - put(null, name, value); - } - - @Override - public void put(final String name, final int value) { - put(null, name, value); - } - - @Override - public void put(final String name, final long value) { - put(null, name, value); - } - - // -- Class-specific preferences -- - - @Override - public String get(final Class c, final String name) { - return get(c, name, null); - } - @Override public String get(final Class c, final String name, final String defaultValue) { - return prefs(c).get(key(c, name), defaultValue); + return prefs(c).get(name, defaultValue); } @Override public boolean getBoolean(final Class c, final String name, final boolean defaultValue) { - return prefs(c).getBoolean(key(c, name), defaultValue); + return prefs(c).getBoolean(name, defaultValue); } @Override public double getDouble(final Class c, final String name, final double defaultValue) { - return prefs(c).getDouble(key(c, name), defaultValue); + return prefs(c).getDouble(name, defaultValue); } @Override public float getFloat(final Class c, final String name, final float defaultValue) { - return prefs(c).getFloat(key(c, name), defaultValue); + return prefs(c).getFloat(name, defaultValue); } @Override public int getInt(final Class c, final String name, final int defaultValue) { - return prefs(c).getInt(key(c, name), defaultValue); + return prefs(c).getInt(name, defaultValue); } @Override public long getLong(final Class c, final String name, final long defaultValue) { - return prefs(c).getLong(key(c, name), defaultValue); + return prefs(c).getLong(name, defaultValue); + } + + @Override + public Map getMap(final Class c, final String name) { + return prefs(c).node(name).getMap(); + } + + @Override + public List getList(final Class c, final String name) { + return prefs(c).node(name).getList(); } @Override public void put(final Class c, final String name, final String value) { - prefs(c).put(key(c, name), value); + prefs(c).put(name, value); } @Override public void put(final Class c, final String name, final boolean value) { - prefs(c).putBoolean(key(c, name), value); + prefs(c).putBoolean(name, value); } @Override public void put(final Class c, final String name, final double value) { - prefs(c).putDouble(key(c, name), value); + prefs(c).putDouble(name, value); } @Override public void put(final Class c, final String name, final float value) { - prefs(c).putFloat(key(c, name), value); + prefs(c).putFloat(name, value); } @Override public void put(final Class c, final String name, final int value) { - prefs(c).putInt(key(c, name), value); + prefs(c).putInt(name, value); } @Override public void put(final Class c, final String name, final long value) { - prefs(c).putLong(key(c, name), value); + prefs(c).putLong(name, value); } @Override - public void clear(final Class c) { - prefs(c).clear(); + public void put(final Class c, final String name, + final Map value) + { + prefs(c).node(name).putMap(value); + } + + @Override + public void put(final Class c, final String name, + final Iterable value) + { + prefs(c).node(name).putList(value); } - // -- Other/unsorted -- + @Override + public void remove(final Class c, final String name) { + prefs(c).remove(name); + } - // TODO - Evaluate which of these methods are really needed, and which are - // duplicate of similar functionality above. + @Override + public void clear(final Class c) { + prefs(c).removeNode(); + } @Override public void clearAll() { @@ -219,43 +167,51 @@ public void clearAll() { prefs(name).removeNode(); } + // -- Deprecated methods -- + + @Deprecated @Override - public void clear(final String key) { - clear((Class) null, key); + public void putMap(final Class c, final Map map) { + prefs(c).putMap(map); } + @Deprecated @Override - public void clear(final Class prefClass, final String key) { - prefs(prefClass).clear(key); + public Map getMap(final Class c) { + return prefs(c).getMap(); } + @Deprecated @Override - public void clear(final String absolutePath, final String key) { - prefs(absolutePath).clear(key); + public void putList(final Class c, final List list) { + prefs(c).putList(list); } + @Deprecated @Override - public void remove(final Class prefClass, final String key) { - prefs(prefClass).remove(key); + public List getList(final Class c) { + return prefs(c).getList(); } + @Deprecated @Override - public void remove(final String absolutePath, final String key) { - prefs(absolutePath).remove(key); + public Iterable getIterable(final Class c, final String name) { + return prefs(c).node(name).getIterable(); } + @Deprecated @Override - public void putMap(final Map map, final String key) { - putMap((Class) null, map, key); + public void clear(final String absolutePath, final String key) { + prefs(absolutePath).clear(key); } + @Deprecated @Override - public void putMap(final Class prefClass, final Map map, - final String key) - { - prefs(prefClass).node(key).putMap(map); + public void remove(final String absolutePath, final String key) { + prefs(absolutePath).remove(key); } + @Deprecated @Override public void putMap(final String absolutePath, final Map map, final String key) @@ -263,27 +219,13 @@ public void putMap(final String absolutePath, final Map map, prefs(absolutePath).node(key).putMap(map); } - @Override - public void putMap(final Class prefClass, final Map map) { - prefs(prefClass).putMap(map); - } - + @Deprecated @Override public void putMap(final String absolutePath, final Map map) { prefs(absolutePath).putMap(map); } - @Override - public Map getMap(final String key) { - return getMap((Class) null, key); - } - - @Override - public Map getMap(final Class prefClass, final String key) - { - return prefs(prefClass).node(key).getMap(); - } - + @Deprecated @Override public Map getMap(final String absolutePath, final String key) @@ -291,23 +233,7 @@ public Map getMap(final Class prefClass, final String key) return prefs(absolutePath).node(key).getMap(); } - @Override - public Map getMap(final Class prefClass) { - return prefs(prefClass).getMap(); - } - - @Override - public void putList(final List list, final String key) { - putList((Class) null, list, key); - } - - @Override - public void putList(final Class prefClass, final List list, - final String key) - { - prefs(prefClass).node(key).putList(list); - } - + @Deprecated @Override public void putList(final String absolutePath, final List list, final String key) @@ -315,65 +241,30 @@ public void putList(final String absolutePath, final List list, prefs(absolutePath).node(key).putList(list); } - @Override - public void putList(final Class prefClass, final List list) { - prefs(prefClass).putList(list); - } - + @Deprecated @Override public void putList(final String absolutePath, final List list) { prefs(absolutePath).putList(list); } - @Override - public List getList(final String key) { - return getList((Class) null, key); - } - - @Override - public List getList(final Class prefClass, final String key) { - return prefs(prefClass).node(key).getList(); - } - + @Deprecated @Override public List getList(final String absolutePath, final String key) { return prefs(absolutePath).node(key).getList(); } + @Deprecated @Override - public List getList(final Class prefClass) { - return prefs(prefClass).getList(); - } - - @Override - public Iterable getIterable(final String key) { - return getIterable((Class) null, key); - } - - @Override - public Iterable getIterable(final Class prefClass, final String key) { - return prefs(prefClass).node(key).getIterable(); - } - - @Override - public void putIterable(final Iterable iterable, final String key) { - putIterable((Class) null, iterable, key); - } - - @Override - public void putIterable(final Class prefClass, final Iterable iterable, final String key) { - prefs(prefClass).node(key).node(key).putIterable(iterable); + public void clear(final Class c, final String name) { + prefs(c).clear(name); } // -- Helper methods -- - private static String key(final Class c, final String name) { - return c == null ? name : c.getSimpleName() + "." + name; - } - private SmartPrefs prefs(final Class c) { + final Class nodeClass = c == null ? PrefService.class : c; return new SmartPrefs(java.util.prefs.Preferences.userNodeForPackage( - c == null ? PrefService.class : c), log); + nodeClass).node(nodeClass.getSimpleName()), log); } private SmartPrefs prefs(final String absolutePath) { @@ -411,12 +302,9 @@ public SmartPrefs(final java.util.prefs.Preferences p, // -- SmartPrefs methods -- - public void clear(final String key) { - if (nodeExists(key)) node(key).clear(); - } - public void remove(final String key) { if (nodeExists(key)) node(key).removeNode(); + p.remove(safeKey(key)); } public void putMap(final Map map) { @@ -439,32 +327,24 @@ public Map getMap() { return map; } - public void putList(final List list) { - for (int index = 0; list != null && index < list.size(); index++) { - final Object value = list.get(index); - put("" + index, value); + public void putList(final Iterable list) { + int index = 0; + for (final String value : list) { + put("" + index++, value); } } + public List getList() { final List list = new ArrayList<>(); - for (int index = 0; index < 1000; index++) { + for (int index = 0; index < Integer.MAX_VALUE; index++) { final String value = get("" + index); - if (value == null) { - break; - } + if (value == null) break; list.add(value); } return list; } - public void putIterable(final Iterable iterable) { - int index = 0; - for (final String value : iterable) { - put("" + index++, value); - } - } - public Iterable getIterable() { return new Iterable() { @Override @@ -653,11 +533,16 @@ private String safeName(final String name) { * */ private String makeSafe(final String s, final int max) { + if (s == null) return ""; // Java Preferences API hates nulls. final int len = s.length(); if (len < max) return s; return "..." + s.substring(len - max + 3, len); } + @Deprecated + public void clear(final String key) { + if (nodeExists(key)) node(key).clear(); + } } } diff --git a/src/main/java/org/scijava/prefs/PrefService.java b/src/main/java/org/scijava/prefs/PrefService.java index 3e9c2429b..98115b323 100644 --- a/src/main/java/org/scijava/prefs/PrefService.java +++ b/src/main/java/org/scijava/prefs/PrefService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -44,174 +42,397 @@ */ public interface PrefService extends SciJavaService { - String get(String name); - - String get(String name, String defaultValue); - - boolean getBoolean(String name, boolean defaultValue); - - double getDouble(String name, double defaultValue); - - float getFloat(String name, float defaultValue); - - int getInt(String name, int defaultValue); - - long getLong(String name, long defaultValue); - - void put(String name, String value); - - void put(String name, boolean value); - - void put(String name, double value); - - void put(String name, float value); - - void put(String name, int value); - - void put(String name, long value); - - String get(Class c, String name); + /** + * Gets a persisted key as a {@link String}. + * + * @param c The class with which the key is associated. + * @param name The key to retrieve. + * @return The value of the key as a {@link String}, or null if the key is not + * present. + */ + default String get(final Class c, final String name) { + return get(c, name, null); + } + /** + * Gets a persisted key as a {@link String}. + * + * @param c The class with which the key is associated. + * @param name The key to retrieve. + * @param defaultValue The value to return if the key is not present. + * @return The value of the key as a {@link String}. + */ String get(Class c, String name, String defaultValue); + /** + * Gets a persisted key as a {@code boolean}. + * + * @param c The class with which the key is associated. + * @param name The key to retrieve. + * @param defaultValue The value to return if the key is not present. + * @return The value of the key as a {@code boolean}. + */ boolean getBoolean(Class c, String name, boolean defaultValue); + /** + * Gets a persisted key as a {@code double}. + * + * @param c The class with which the key is associated. + * @param name The key to retrieve. + * @param defaultValue The value to return if the key is not present. + * @return The value of the key as a {@code double}. + */ double getDouble(Class c, String name, double defaultValue); + /** + * Gets a persisted key as a {@code float}. + * + * @param c The class with which the key is associated. + * @param name The key to retrieve. + * @param defaultValue The value to return if the key is not present. + * @return The value of the key as a {@code float}. + */ float getFloat(Class c, String name, float defaultValue); + /** + * Gets a persisted key as an {@code int}. + * + * @param c The class with which the key is associated. + * @param name The key to retrieve. + * @param defaultValue The value to return if the key is not present. + * @return The value of the key as an {@code int}. + */ int getInt(Class c, String name, int defaultValue); + /** + * Gets a persisted key as a {@code long}. + * + * @param c The class with which the key is associated. + * @param name The key to retrieve. + * @param defaultValue The value to return if the key is not present. + * @return The value of the key as an {@code long}. + */ long getLong(Class c, String name, long defaultValue); + /** + * Gets a persisted key as a {@code Map}. + * + * @param c The class with which the key is associated. + * @param name The key to retrieve. + * @return The value of the key as an {@code Map}, or null if the key is not + * present. + */ + Map getMap(Class c, String name); + + /** + * Gets a persisted key as a {@code List}. + * + * @param c The class with which the key is associated. + * @param name The key to retrieve. + * @return The value of the key as an {@code List}, or null if the key is not + * present. + */ + List getList(Class c, String name); + + /** + * Saves a key/value pair in persistent storage. + * + * @param c The class with which the key/value pair is associated. + * @param name The key where the value should be stored. + * @param value The value to store. + * @see #get(Class, String) + * @see #get(Class, String, String) + */ void put(Class c, String name, String value); + /** + * Saves a key/value pair in persistent storage. + * + * @param c The class with which the key/value pair is associated. + * @param name The key where the value should be stored. + * @param value The value to store. + * @see #getBoolean(Class, String, boolean) + */ void put(Class c, String name, boolean value); + /** + * Saves a key/value pair in persistent storage. + * + * @param c The class with which the key/value pair is associated. + * @param name The key where the value should be stored. + * @param value The value to store. + * @see #getDouble(Class, String, double) + */ void put(Class c, String name, double value); - void put(Class c, String name, float value); - - void put(Class c, String name, int value); - - void put(Class c, String name, long value); - - void clear(Class c); - - /** Clears everything. */ - void clearAll(); - - /** Clears the node. */ - void clear(String key); - /** - * Clears the node indexed under the given class. + * Saves a key/value pair in persistent storage. + * + * @param c The class with which the key/value pair is associated. + * @param name The key where the value should be stored. + * @param value The value to store. + * @see #getFloat(Class, String, float) */ - void clear(Class prefClass, String key); + void put(Class c, String name, float value); /** - * Clears the ndoe indexed under the given path. + * Saves a key/value pair in persistent storage. + * + * @param c The class with which the key/value pair is associated. + * @param name The key where the value should be stored. + * @param value The value to store. + * @see #getInt(Class, String, int) */ - void clear(String absolutePath, String key); - - /** Removes the node. */ - void remove(Class prefClass, String key); - - void remove(String absolutePath, String key); - - /** Puts a Map into the preferences. */ - void putMap(Map map, String key); + void put(Class c, String name, int value); /** - * Puts a Map into the preferences, indexed under the specified class. + * Saves a key/value pair in persistent storage. + * + * @param c The class with which the key/value pair is associated. + * @param name The key where the value should be stored. + * @param value The value to store. + * @see #getLong(Class, String, long) */ - void putMap(Class prefClass, Map map, String key); + void put(Class c, String name, long value); /** - * Puts a Map into the preferences, indexed under the given path. + * Saves a key/value pair in persistent storage. + * + * @param c The class with which the key/value pair is associated. + * @param name The key where the value should be stored. + * @param value The value to store. + * @see #getMap(Class, String) */ - void putMap(String absolutePath, Map map); + void put(Class c, String name, Map value); /** - * Puts a Map into the preferences, indexed under the given class. + * Saves a key/value pair in persistent storage. + * + * @param c The class with which the key/value pair is associated. + * @param name The key where the value should be stored. + * @param value The value to store. + * @see #getList(Class, String) */ - void putMap(Class prefClass, Map map); + void put(Class c, String name, Iterable value); /** - * Puts a Map into the preferences, indexed under the given path and - * relative key path. + * Deletes a key from persistent storage. + * + * @param c The class with which the key is associated. + * @param name The key to remove. */ - void putMap(String absolutePath, Map map, String key); - - /** Gets a Map from the preferences. */ - Map getMap(String key); + void remove(Class c, String name); /** - * Gets a map from the preferences, indexed under the specified class. + * Deletes all of the given {@link Class}'s keys from persistent storage. + * @param c The class whose keys should be removed. */ - Map getMap(Class prefClass, String key); - - /** Gets a Map from the preferences. */ - Map getMap(Class prefClass); + void clear(Class c); - Map getMap(String absolutePath, String key); + /** Deletes all information from the data store. Use with care! */ + void clearAll(); - /** Puts a list into the preferences. */ - void putList(List list, String key); + // -- Deprecated methods -- + + /** @deprecated Use {@link #put(Class, String, Map)}. */ + @Deprecated + default void putMap(final Class c, final Map map, + final String name) + { + put(c, name, map); + } + + /** @deprecated Use {@link #put(Class, String, Map)}. */ + @Deprecated + void putMap(Class c, Map map); + + /** @deprecated Use {@link #getMap(Class, String)}. */ + @Deprecated + Map getMap(Class c); + + /** @deprecated Use {@link #put(Class, String, Iterable)}. */ + @Deprecated + default void putList(final Class c, final List list, + final String name) + { + put(c, name, list); + } + + /** @deprecated Use {@link #put(Class, String, Iterable)}. */ + @Deprecated + void putList(Class c, List list); + + /** @deprecated Use {@link #getList(Class, String)}. */ + @Deprecated + List getList(Class c); + + /** @deprecated Use {@link #put(Class, String, Iterable)}. */ + @Deprecated + default void putIterable(final Class c, final Iterable iterable, + final String name) + { + put(c, name, iterable); + } + + /** @deprecated Use {@link #getList(Class, String)}. */ + @Deprecated + Iterable getIterable(Class c, String name); + + /** @deprecated Use {@link #get(Class, String)}. */ + @Deprecated + default String get(final String name) { + return get((Class) null, name); + } + + /** @deprecated Use {@link #get(Class, String, String)}. */ + @Deprecated + default String get(final String name, final String defaultValue) { + return get(null, name, defaultValue); + } + + /** @deprecated Use {@link #getBoolean(Class, String, boolean)}. */ + @Deprecated + default boolean getBoolean(final String name, final boolean defaultValue) { + return getBoolean(null, name, defaultValue); + } + + /** @deprecated Use {@link #getDouble(Class, String, double)}. */ + @Deprecated + default double getDouble(final String name, final double defaultValue) { + return getDouble(null, name, defaultValue); + } + + /** @deprecated Use {@link #getFloat(Class, String, float)}. */ + @Deprecated + default float getFloat(final String name, final float defaultValue) { + return getFloat(null, name, defaultValue); + } + + /** @deprecated Use {@link #getInt(Class, String, int)}. */ + @Deprecated + default int getInt(final String name, final int defaultValue) { + return getInt(null, name, defaultValue); + } + + /** @deprecated Use {@link #getLong(Class, String, long)}. */ + @Deprecated + default long getLong(final String name, final long defaultValue) { + return getLong(null, name, defaultValue); + } + + /** @deprecated Use {@link #put(Class, String, String)}. */ + @Deprecated + default void put(final String name, final String value) { + put(null, name, value); + } + + /** @deprecated Use {@link #put(Class, String, boolean)}. */ + @Deprecated + default void put(final String name, final boolean value) { + put(null, name, value); + } + + /** @deprecated Use {@link #put(Class, String, double)}. */ + @Deprecated + default void put(final String name, final double value) { + put(null, name, value); + } + + /** @deprecated Use {@link #put(Class, String, float)}. */ + @Deprecated + default void put(final String name, final float value) { + put(null, name, value); + } + + /** @deprecated Use {@link #put(Class, String, int)}. */ + @Deprecated + default void put(final String name, final int value) { + put(null, name, value); + } + + /** @deprecated Use {@link #put(Class, String, long)}. */ + @Deprecated + default void put(final String name, final long value) { + put(null, name, value); + } /** - * Puts a list into the preferences, indexed under the specified class. + * @deprecated Use {@link #remove(Class, String)} or {@link #clear(Class)}. */ - void putList(Class prefClass, List list, String key); + @Deprecated + default void clear(final String key) { + clear((Class) null, key); + } - /** - * Puts a list into the preferences, indexed under the specified path and - * relative key. - */ - void putList(String absolutePath, List list, String key); + /** @deprecated Use {@link #remove(Class, String)}. */ + @Deprecated + void clear(String absolutePath, String key); - /** Puts a list into the preferences. */ - void putList(Class prefClass, List list); + /** @deprecated Use {@link #remove(Class, String)}. */ + @Deprecated + void remove(String absolutePath, String key); - /** Puts a list into the preferences, indexed under the specified path. */ - void putList(String absolutePath, List list); + /** @deprecated Use {@link #put(Class, String, Map)}. */ + @Deprecated + default void putMap(final Map map, final String key) { + putMap((Class) null, map, key); + } - /** Gets a List from the preferences. */ - List getList(String key); + /** @deprecated Use {@link #put(Class, String, Map)}. */ + @Deprecated + void putMap(String absolutePath, Map map); - /** - * Gets a List from the preferences, indexed under the specified path. - */ - List getList(String absolutePath, String key); + /** @deprecated Use {@link #put(Class, String, Map)}. */ + @Deprecated + void putMap(String absolutePath, Map map, String key); - /** - * Gets a List from the preferences, indexed under the specified class. - */ - List getList(Class prefClass, String key); + /** @deprecated Use {@link #getMap(Class, String)}. */ + @Deprecated + default Map getMap(final String key) { + return getMap((Class) null, key); + } - /** - * Gets a List from the preferences. Returns an empty list if nothing in - * prefs. - */ - List getList(Class prefClass); + /** @deprecated Use {@link #getMap(Class, String)}. */ + @Deprecated + Map getMap(String absolutePath, String key); - /** - * Puts an iterable into the preferences. - */ - void putIterable(Iterable iterable, String key); + /** @deprecated Use {@link #put(Class, String, Iterable)}. */ + @Deprecated + default void putList(final List list, final String key) { + putList((Class) null, list, key); + } - /** - * Puts an iterable into the preferences. - */ - void putIterable(Class prefClass, Iterable iterable, String key); + /** @deprecated Use {@link #put(Class, String, Iterable)}. */ + @Deprecated + void putList(String absolutePath, List list, String key); - /** - * Gets an iterable from the preferences. - */ - Iterable getIterable(String key); + /** @deprecated Use {@link #put(Class, String, Iterable)}. */ + @Deprecated + void putList(String absolutePath, List list); - /** - * Gets an iterable from the preferences. - */ - Iterable getIterable(Class prefClass, String key); + /** @deprecated Use {@link #getList(Class, String)}. */ + @Deprecated + default List getList(final String key) { + return getList((Class) null, key); + } + + /** @deprecated Use {@link #getList(Class, String)}. */ + @Deprecated + List getList(String absolutePath, String key); + + /** @deprecated Use {@link #put(Class, String, Iterable)}. */ + @Deprecated + default void putIterable(final Iterable iterable, final String key) { + putIterable((Class) null, iterable, key); + } + + /** @deprecated User {@link #getList(Class, String)}. */ + @Deprecated + default Iterable getIterable(final String key) { + return getIterable((Class) null, key); + } + + /** @deprecated Use {@link #remove(Class, String)}. */ + @Deprecated + void clear(Class c, String name); } diff --git a/src/main/java/org/scijava/run/AbstractCodeRunner.java b/src/main/java/org/scijava/run/AbstractCodeRunner.java index b1382ec8d..a10bd9205 100644 --- a/src/main/java/org/scijava/run/AbstractCodeRunner.java +++ b/src/main/java/org/scijava/run/AbstractCodeRunner.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/run/CodeRunner.java b/src/main/java/org/scijava/run/CodeRunner.java index c8124ee67..2efa316e2 100644 --- a/src/main/java/org/scijava/run/CodeRunner.java +++ b/src/main/java/org/scijava/run/CodeRunner.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/run/DefaultRunService.java b/src/main/java/org/scijava/run/DefaultRunService.java index 67366a0e0..68239a4d7 100644 --- a/src/main/java/org/scijava/run/DefaultRunService.java +++ b/src/main/java/org/scijava/run/DefaultRunService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/run/RunService.java b/src/main/java/org/scijava/run/RunService.java index 7b287413c..738cfb078 100644 --- a/src/main/java/org/scijava/run/RunService.java +++ b/src/main/java/org/scijava/run/RunService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/run/console/RunArgument.java b/src/main/java/org/scijava/run/console/RunArgument.java index 5e2d5673d..028440a5e 100644 --- a/src/main/java/org/scijava/run/console/RunArgument.java +++ b/src/main/java/org/scijava/run/console/RunArgument.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -42,6 +40,7 @@ import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; import org.scijava.run.RunService; +import org.scijava.startup.StartupService; /** * Handles the {@code --run} command line argument. @@ -54,6 +53,9 @@ public class RunArgument extends AbstractConsoleArgument { @Parameter private RunService runService; + @Parameter + private StartupService startupService; + @Parameter private ParseService parser; @@ -77,22 +79,24 @@ public void handle(final LinkedList args) { final String arg = getParam(args); if (arg != null) args.removeFirst(); // argument list was given - try { - if (arg == null) runService.run(code); - else { - final Items items = parser.parse(arg); - if (items.isMap()) runService.run(code, items.asMap()); - else if (items.isList()) runService.run(code, items.toArray()); + startupService.addOperation(() -> { + try { + if (arg == null) runService.run(code); else { - throw new IllegalArgumentException("Arguments are inconsistent. " + - "Please pass either a list of key/value pairs, " + - "or a list of values."); + final Items items = parser.parse(arg); + if (items.isMap()) runService.run(code, items.asMap()); + else if (items.isList()) runService.run(code, items.toArray()); + else { + throw new IllegalArgumentException("Arguments are inconsistent. " + + "Please pass either a list of key/value pairs, " + + "or a list of values."); + } } } - } - catch (final InvocationTargetException exc) { - throw new RuntimeException(exc); - } + catch (final InvocationTargetException exc) { + throw new RuntimeException(exc); + } + }); } // -- Typed methods -- diff --git a/src/main/java/org/scijava/script/AbstractAutoCompleter.java b/src/main/java/org/scijava/script/AbstractAutoCompleter.java index 5155ba784..ae255f5a2 100644 --- a/src/main/java/org/scijava/script/AbstractAutoCompleter.java +++ b/src/main/java/org/scijava/script/AbstractAutoCompleter.java @@ -2,19 +2,17 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. - * + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE diff --git a/src/main/java/org/scijava/script/AbstractScriptContext.java b/src/main/java/org/scijava/script/AbstractScriptContext.java index 46b475f0e..3f69d608a 100644 --- a/src/main/java/org/scijava/script/AbstractScriptContext.java +++ b/src/main/java/org/scijava/script/AbstractScriptContext.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/script/AbstractScriptEngine.java b/src/main/java/org/scijava/script/AbstractScriptEngine.java index ab6ab7de7..c64f967c3 100644 --- a/src/main/java/org/scijava/script/AbstractScriptEngine.java +++ b/src/main/java/org/scijava/script/AbstractScriptEngine.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/script/AbstractScriptHeader.java b/src/main/java/org/scijava/script/AbstractScriptHeader.java index 1f5c6f9e1..6dcf2650c 100644 --- a/src/main/java/org/scijava/script/AbstractScriptHeader.java +++ b/src/main/java/org/scijava/script/AbstractScriptHeader.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/script/AbstractScriptLanguage.java b/src/main/java/org/scijava/script/AbstractScriptLanguage.java index 5f52e9ac7..6f048fc89 100644 --- a/src/main/java/org/scijava/script/AbstractScriptLanguage.java +++ b/src/main/java/org/scijava/script/AbstractScriptLanguage.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/script/AdaptedScriptEngine.java b/src/main/java/org/scijava/script/AdaptedScriptEngine.java index 97bd240ee..14e0d5a72 100644 --- a/src/main/java/org/scijava/script/AdaptedScriptEngine.java +++ b/src/main/java/org/scijava/script/AdaptedScriptEngine.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/script/AdaptedScriptLanguage.java b/src/main/java/org/scijava/script/AdaptedScriptLanguage.java index f0344cdb9..695c214a9 100644 --- a/src/main/java/org/scijava/script/AdaptedScriptLanguage.java +++ b/src/main/java/org/scijava/script/AdaptedScriptLanguage.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -37,6 +35,7 @@ import javax.script.ScriptEngineFactory; import javax.script.ScriptEngineManager; +import org.scijava.Context; import org.scijava.plugin.PluginInfo; /** @@ -142,7 +141,7 @@ public ScriptEngine getScriptEngine() { // -- Helper methods -- private static ScriptEngineFactory findFactory(final String factoryName) { - final ScriptEngineManager manager = new ScriptEngineManager(); + final ScriptEngineManager manager = new ScriptEngineManager(Context.getClassLoader()); for (final ScriptEngineFactory factory : manager.getEngineFactories()) { for (final String name : factory.getNames()) { if (factoryName.equals(name)) return factory; diff --git a/src/main/java/org/scijava/script/AutoCompleter.java b/src/main/java/org/scijava/script/AutoCompleter.java index 55590081e..334addd58 100644 --- a/src/main/java/org/scijava/script/AutoCompleter.java +++ b/src/main/java/org/scijava/script/AutoCompleter.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/script/AutoCompletionResult.java b/src/main/java/org/scijava/script/AutoCompletionResult.java index 6ea4dbcc3..8f5ac4d5c 100644 --- a/src/main/java/org/scijava/script/AutoCompletionResult.java +++ b/src/main/java/org/scijava/script/AutoCompletionResult.java @@ -1,7 +1,30 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% */ package org.scijava.script; diff --git a/src/main/java/org/scijava/script/CodeGenerator.java b/src/main/java/org/scijava/script/CodeGenerator.java index d79c7f6c4..894ce8f54 100644 --- a/src/main/java/org/scijava/script/CodeGenerator.java +++ b/src/main/java/org/scijava/script/CodeGenerator.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -35,7 +33,9 @@ * Code Generator Interface * * @author Grant Harris + * @deprecated To be removed in SciJava Common 3.0.0. */ +@Deprecated public interface CodeGenerator { /** Adds delimiter character between arguments (typically a ','). */ diff --git a/src/main/java/org/scijava/script/CodeGeneratorJava.java b/src/main/java/org/scijava/script/CodeGeneratorJava.java index 750aa55b3..84e9e09c1 100644 --- a/src/main/java/org/scijava/script/CodeGeneratorJava.java +++ b/src/main/java/org/scijava/script/CodeGeneratorJava.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -35,7 +33,9 @@ * {@link CodeGenerator} for Java. * * @author Grant Harris + * @deprecated To be removed in SciJava Common 3.0.0. */ +@Deprecated public class CodeGeneratorJava implements CodeGenerator { static final String lsep = System.getProperty("line.separator"); diff --git a/src/main/java/org/scijava/script/DefaultAutoCompleter.java b/src/main/java/org/scijava/script/DefaultAutoCompleter.java index 9d2681d79..2de11644b 100644 --- a/src/main/java/org/scijava/script/DefaultAutoCompleter.java +++ b/src/main/java/org/scijava/script/DefaultAutoCompleter.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/script/DefaultScriptHeaderService.java b/src/main/java/org/scijava/script/DefaultScriptHeaderService.java index 9281fd122..284479134 100644 --- a/src/main/java/org/scijava/script/DefaultScriptHeaderService.java +++ b/src/main/java/org/scijava/script/DefaultScriptHeaderService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/script/DefaultScriptInterpreter.java b/src/main/java/org/scijava/script/DefaultScriptInterpreter.java index 543ea506a..d3448d373 100644 --- a/src/main/java/org/scijava/script/DefaultScriptInterpreter.java +++ b/src/main/java/org/scijava/script/DefaultScriptInterpreter.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -42,12 +40,13 @@ import org.scijava.log.LogService; import org.scijava.plugin.Parameter; import org.scijava.prefs.PrefService; +import org.scijava.util.LastRecentlyUsed; /** * The default implementation of a {@link ScriptInterpreter}. *

* Credit to Jason Sachs for the multi-line evaluation (see - * his post on StackOverflow). + * his post on StackOverflow). *

* * @author Johannes Schindelin @@ -175,7 +174,7 @@ public Object eval(final String command) throws ScriptException { * fact that there was a syntax error in the 2nd line. * *

- * For further details, see SO + * For further details, see SO * #5584674. *

*/ @@ -363,4 +362,113 @@ private static T callMethod(final Object object, final String methodName, return null; } + // -- Helper classes -- + + /** Container for a script language's interpreter history. */ + private static class History { + + @SuppressWarnings("unused") + protected static final long serialVersionUID = 2L; + + private static final String PREFIX = "History."; + private final int MAX_ENTRIES = 1000; + + private final PrefService prefs; + private final String name; + private final LastRecentlyUsed entries = + new LastRecentlyUsed<>(MAX_ENTRIES); + private String currentCommand = ""; + private int position = -1; + + /** + * Constructs a history object for a given scripting language. + * + * @param name the name of the scripting language + */ + public History(final PrefService prefs, final String name) { + this.prefs = prefs; + this.name = name; + } + + /** + * Read back a persisted history. + */ + public void read() { + entries.clear(); + for (final String item : prefs.getIterable(getClass(), PREFIX + name)) { + entries.addToEnd(item); + } + } + + /** + * Persist the history. + * + * @see PrefService + */ + public void write() { + prefs.putIterable(getClass(), entries, PREFIX + name); + } + + /** + * Adds the most recently issued command. + * + * @param command the most recent command to add to the history + */ + public void add(final String command) { + entries.add(command); + position = -1; + currentCommand = ""; + } + + public boolean replace(final String command) { + if (position < 0) { + currentCommand = command; + return false; + } + return entries.replace(position, command); + } + + /** + * Navigates to the next (more recent) command. + *

+ * This method wraps around, i.e. it returns {@code null} when there is no + * more-recent command in the history. + *

+ * + * @return the next command + */ + public String next() { + position = entries.next(position); + return position < 0 ? currentCommand : entries.get(position); + } + + /** + * Navigates to the previous (i.e less recent) command. + *

+ * This method wraps around, i.e. it returns {@code null} when there is no + * less-recent command in the history. + *

+ * + * @return the previous command + */ + public String previous() { + position = entries.previous(position); + return position < 0 ? currentCommand : entries.get(position); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + int pos = -1; + for (;;) { + pos = entries.previous(pos); + if (pos < 0) break; + if (builder.length() > 0) builder.append(" -> "); + if (this.position == pos) builder.append("["); + builder.append(entries.get(pos)); + if (this.position == pos) builder.append("]"); + } + return builder.toString(); + } + } } diff --git a/src/main/java/org/scijava/script/DefaultScriptService.java b/src/main/java/org/scijava/script/DefaultScriptService.java index 41ec47dab..72b2403d3 100644 --- a/src/main/java/org/scijava/script/DefaultScriptService.java +++ b/src/main/java/org/scijava/script/DefaultScriptService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -49,7 +47,6 @@ import org.scijava.Gateway; import org.scijava.InstantiableException; import org.scijava.MenuPath; -import org.scijava.Priority; import org.scijava.app.AppService; import org.scijava.command.CommandService; import org.scijava.log.LogService; @@ -62,10 +59,11 @@ import org.scijava.plugin.Plugin; import org.scijava.plugin.PluginService; import org.scijava.plugin.SciJavaPlugin; +import org.scijava.script.process.ScriptProcessorService; import org.scijava.service.Service; -import org.scijava.util.ClassUtils; import org.scijava.util.ColorRGB; import org.scijava.util.ColorRGBA; +import org.scijava.util.Types; /** * Default service for working with scripts. @@ -73,7 +71,7 @@ * @author Johannes Schindelin * @author Curtis Rueden */ -@Plugin(type = Service.class, priority = Priority.HIGH_PRIORITY) +@Plugin(type = Service.class) public class DefaultScriptService extends AbstractSingletonService implements ScriptService { @@ -90,6 +88,9 @@ public class DefaultScriptService extends @Parameter private AppService appService; + @Parameter + private ScriptProcessorService scriptProcessorService; + @Parameter private ParseService parser; @@ -191,6 +192,11 @@ public void addAlias(final String alias, final Class type) { aliasMap().put(alias, type); } + @Override + public Map> getAliases() { + return Collections.unmodifiableMap(aliasMap()); + } + @Override public synchronized Class lookupClass(final String alias) throws ScriptException @@ -199,11 +205,11 @@ public synchronized Class lookupClass(final String alias) final Class type = aliasMap().get(componentAlias); if (type != null) { final int arrayDim = (alias.length() - componentAlias.length()) / 2; - return makeArrayType(type, arrayDim); + return Types.array(type, arrayDim); } try { - final Class c = ClassUtils.loadClass(alias, false); + final Class c = Types.load(alias, false); aliasMap().put(alias, c); return c; } @@ -221,14 +227,8 @@ public void initialize() { super.initialize(); // add scripts to the module index... only when needed! - moduleService.getIndex().addLater(new LazyObjects() { - - @Override - public Collection get() { - return scripts().values(); - } - - }); + final LazyObjects lazyScripts = () -> scripts().values(); + moduleService.getIndex().addLater(lazyScripts); } // -- Helper methods - lazy initialization -- @@ -354,7 +354,7 @@ private synchronized void initAliasMap() { * are registered with the service. */ private ScriptInfo getOrCreate(final File file) { - final ScriptInfo info = scripts().get(file); + final ScriptInfo info = scripts().get(file.getAbsolutePath()); if (info != null) return info; return new ScriptInfo(getContext(), file); } @@ -385,7 +385,10 @@ private void addAliases(final HashMap> map, } private Class[] pluginClasses(final Class type) { - return pluginService.getPluginsOfType(type).stream().map(info -> { + return pluginService.getPluginsOfType(type).stream() // + .filter(info -> !info.is("noAlias")) // + .map(info -> + { try { return info.loadClass(); } @@ -400,10 +403,4 @@ private String stripArrayNotation(final String alias) { if (!alias.endsWith("[]")) return alias; return stripArrayNotation(alias.substring(0, alias.length() - 2)); } - - private Class makeArrayType(final Class type, final int arrayDim) { - if (arrayDim <= 0) return type; - return makeArrayType(ClassUtils.getArrayClass(type), arrayDim - 1); - } - } diff --git a/src/main/java/org/scijava/script/History.java b/src/main/java/org/scijava/script/History.java deleted file mode 100644 index 648a466c9..000000000 --- a/src/main/java/org/scijava/script/History.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * #%L - * SciJava Common shared library for SciJava software. - * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ - -package org.scijava.script; - -import org.scijava.prefs.PrefService; -import org.scijava.util.LastRecentlyUsed; - -/** - * Container for a script language's interpreter history. - * - * @author Johannes Schindelin - */ -class History { - - protected static final long serialVersionUID = 1L; - - private static final String PREFIX = "History."; - private final int MAX_ENTRIES = 1000; - - private final PrefService prefs; - private final String name; - private final LastRecentlyUsed entries = new LastRecentlyUsed<>(MAX_ENTRIES); - private String currentCommand = ""; - private int position = -1; - - /** - * Constructs a history object for a given scripting language. - * - * @param name the name of the scripting language - */ - public History(final PrefService prefs, final String name) { - this.prefs = prefs; - this.name = name; - } - - /** - * Read back a persisted history. - */ - public void read() { - entries.clear(); - for (final String item : prefs.getIterable(getClass(), PREFIX + name)) { - entries.addToEnd(item); - } - } - - /** - * Persist the history. - * - * @see PrefService - */ - public void write() { - prefs.putIterable(getClass(), entries, PREFIX + name); - } - - /** - * Adds the most recently issued command. - * - * @param command the most recent command to add to the history - */ - public void add(final String command) { - entries.add(command); - position = -1; - currentCommand = ""; - } - - public boolean replace(final String command) { - if (position < 0) { - currentCommand = command; - return false; - } - return entries.replace(position, command); - } - - /** - * Navigates to the next (more recent) command. - *

- * This method wraps around, i.e. it returns {@code null} when there is no - * more-recent command in the history. - *

- * - * @return the next command - */ - public String next() { - position = entries.next(position); - return position < 0 ? currentCommand : entries.get(position); - } - - /** - * Navigates to the previous (i.e less recent) command. - *

- * This method wraps around, i.e. it returns {@code null} when there is no - * less-recent command in the history. - *

- * - * @return the previous command - */ - public String previous() { - position = entries.previous(position); - return position < 0 ? currentCommand : entries.get(position); - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - int pos = -1; - for (;;) { - pos = entries.previous(pos); - if (pos < 0) break; - if (builder.length() > 0) builder.append(" -> "); - if (this.position == pos) builder.append("["); - builder.append(entries.get(pos)); - if (this.position == pos) builder.append("]"); - } - return builder.toString(); - } -} diff --git a/src/main/java/org/scijava/script/InvocationObject.java b/src/main/java/org/scijava/script/InvocationObject.java index 4ace6b083..3d9c0ce55 100644 --- a/src/main/java/org/scijava/script/InvocationObject.java +++ b/src/main/java/org/scijava/script/InvocationObject.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -38,7 +36,9 @@ * the parameters that were passed to it. * * @author Grant Harris + * @deprecated To be removed in SciJava Common 3.0.0. */ +@Deprecated public class InvocationObject { public String moduleCalled; diff --git a/src/main/java/org/scijava/script/ParameterObject.java b/src/main/java/org/scijava/script/ParameterObject.java index 7f02b1d39..2f18323b8 100644 --- a/src/main/java/org/scijava/script/ParameterObject.java +++ b/src/main/java/org/scijava/script/ParameterObject.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -35,7 +33,9 @@ * Holds a parameter, its type and value, for a recorded macro. * * @author Grant Harris + * @deprecated To be removed in SciJava Common 3.0.0. */ +@Deprecated public class ParameterObject { public ParameterObject(final String param, final Class type, diff --git a/src/main/java/org/scijava/script/ScriptCLI.java b/src/main/java/org/scijava/script/ScriptCLI.java new file mode 100644 index 000000000..e20cf8918 --- /dev/null +++ b/src/main/java/org/scijava/script/ScriptCLI.java @@ -0,0 +1,169 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.script; + +import java.io.File; +import java.io.FileReader; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.scijava.Context; +import org.scijava.log.LogLevel; +import org.scijava.log.LogService; +import org.scijava.module.Module; + +/** + * A command-line entry point for running SciJava scripts. + * + * @author Curtis Rueden + */ +public class ScriptCLI { + + private static final String USAGE = "" + // + "Usage: " + ScriptCLI.class.getSimpleName() + // + " [-d] [-h] [-l language] [-o] [-r] /path/to/script [script-args]\n" + // + "\n" + // + "Options:\n" + // + " -d, --debug : enable debug-level log output\n" + // + " -h, --help : display this help message\n" + // + " -l, --language : specify language of script to execute\n" + // + " otherwise, inferred from script extension\n" + // + " -o, --print-outputs : print output values\n" + // + " -r, --print-return-value : print return value\n" + // + "\n" + // + "To read from stdin, use a dash (-) symbol for the script path.\n" + // + "\n" + // + "For script-args, give space-separated key=value pairs,\n" + // + "while will be passed in as SciJava script arguments."; + + public static void main(String... args) throws Exception { + final Map inputs = new HashMap<>(); + File file = null; + String language = null; + boolean printOutputs = false; + boolean printReturnValue = false; + boolean parsingOptions = true; + try (final Context context = new Context()) { + // Parse command-line arguments. + if (args.length == 0) args = new String[] {"-h"}; + for (int i = 0; i < args.length; i++) { + if (parsingOptions) { + // Parse options and filename. + if (args[i].equals("-d") || args[i].equals("--debug")) { + final LogService log = context.getService(LogService.class); + if (log != null) log.setLevel(LogLevel.DEBUG); + } + else if (args[i].equals("-h") || args[i].equals("--help")) { + System.err.println(USAGE); + System.exit(1); + } + else if (i < args.length - 1 && // + args[i].equals("-l") || args[i].equals("--language")) + { + language = args[++i]; + } + else if (args[i].equals("-o") || args[i].equals("--print-outputs")) { + printOutputs = true; + } + else if (args[i].equals("-r") || args[i].equals("--print-return-value")) { + printReturnValue = true; + } + else if (args[i].equals("-")) { + // read from stdin + parsingOptions = false; + } + else if (i < args.length - 1 && args[i].equals("--")) { + // argument after the -- separator must be the filename. + file = new File(args[++i]); + parsingOptions = false; + } + else if (new File(args[i]).exists()) { + file = new File(args[i]); + parsingOptions = false; + } + else { + System.err.println("Invalid argument: " + args[i]); + System.exit(2); + } + } + else { + // Parse script arguments. + final int equals = args[i].indexOf("="); + if (equals < 0) { + System.err.println("Invalid argument: " + args[i]); + System.exit(3); + } + final String key = args[i].substring(0, equals); + final String val = args[i].substring(equals + 1); + inputs.put(key, val); + } + } + + final ScriptService ss = context.getService(ScriptService.class); + if (ss == null) { + System.err.println("Error: No script service available."); + System.exit(4); + } + if (file == null && language == null) { + System.err.println("Error: Must specify language when using stdin."); + System.exit(5); + } + + final Module m; + if (language == null) { + m = ss.run(file, true, inputs).get(); + } + else { + ScriptLanguage lang = ss.getLanguageByName(language); + if (lang == null) lang = ss.getLanguageByExtension(language); + if (lang == null) { + System.err.println("Error: Unsupported language: " + language); + System.exit(6); + } + final Reader reader = file == null ? // + new InputStreamReader(System.in) : // + new FileReader(file); + m = ss.run("." + language, reader, true, inputs).get(); + } + if (printOutputs) { + for (Entry output : m.getOutputs().entrySet()) { + System.out.println(output.getKey() + " = " + output.getValue()); + } + } + if (printReturnValue && m instanceof ScriptModule) { + System.out.println(((ScriptModule) m).getReturnValue()); + } + } + System.exit(0); + } +} diff --git a/src/main/java/org/scijava/script/ScriptFinder.java b/src/main/java/org/scijava/script/ScriptFinder.java index d51709bbc..89e4c1b16 100644 --- a/src/main/java/org/scijava/script/ScriptFinder.java +++ b/src/main/java/org/scijava/script/ScriptFinder.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -43,6 +41,7 @@ import org.scijava.AbstractContextual; import org.scijava.Context; +import org.scijava.MenuEntry; import org.scijava.MenuPath; import org.scijava.log.LogService; import org.scijava.plugin.Parameter; @@ -180,8 +179,11 @@ private int createInfos(final List scripts, final Set urls, // friendlyPath = "File/Import/Movie File..." // menuPath = File > Import > Movie File... - // NB: Ignore base-level scripts (not nested in any menu). - if (menuPath.size() == 1) continue; + // Place base-level scripts in the "Plugins>Scripts" submenu + if (menuPath.size() == 1){ + menuPath.add(0, new MenuEntry("Plugins")); + menuPath.add(1, new MenuEntry("Scripts")); + } final URL url = scriptMap.get(path); diff --git a/src/main/java/org/scijava/script/ScriptHeader.java b/src/main/java/org/scijava/script/ScriptHeader.java index 3e61a9fd9..417551358 100644 --- a/src/main/java/org/scijava/script/ScriptHeader.java +++ b/src/main/java/org/scijava/script/ScriptHeader.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/script/ScriptHeaderService.java b/src/main/java/org/scijava/script/ScriptHeaderService.java index 1adb82576..a58dd7f81 100644 --- a/src/main/java/org/scijava/script/ScriptHeaderService.java +++ b/src/main/java/org/scijava/script/ScriptHeaderService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/script/ScriptInfo.java b/src/main/java/org/scijava/script/ScriptInfo.java index d22e3902a..8b9f1734e 100644 --- a/src/main/java/org/scijava/script/ScriptInfo.java +++ b/src/main/java/org/scijava/script/ScriptInfo.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -33,44 +31,34 @@ import java.io.BufferedReader; import java.io.File; -import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.net.MalformedURLException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; -import java.util.HashMap; import java.util.List; -import java.util.Map; - -import javax.script.ScriptException; import org.scijava.Context; import org.scijava.Contextual; -import org.scijava.ItemIO; -import org.scijava.ItemVisibility; import org.scijava.NullContextException; -import org.scijava.command.Command; -import org.scijava.convert.ConvertService; import org.scijava.log.LogService; import org.scijava.module.AbstractModuleInfo; -import org.scijava.module.DefaultMutableModuleItem; import org.scijava.module.ModuleException; -import org.scijava.parse.ParseService; +import org.scijava.module.ModuleItem; import org.scijava.plugin.Parameter; +import org.scijava.script.process.ParameterScriptProcessor; +import org.scijava.script.process.ScriptCallback; +import org.scijava.script.process.ScriptProcessorService; import org.scijava.util.DigestUtils; import org.scijava.util.FileUtils; /** * Metadata about a script. - *

- * This class is responsible for parsing the script for parameters. See - * {@link #parseParameters()} for details. - *

* * @author Curtis Rueden * @author Johannes Schindelin @@ -93,14 +81,20 @@ public class ScriptInfo extends AbstractModuleInfo implements Contextual { private ScriptService scriptService; @Parameter - private ParseService parser; + private ScriptProcessorService scriptProcessorService; - @Parameter - private ConvertService convertService; + /** Final version of the script, after script processing. */ + private String processedScript; /** True iff the return value should be appended as an output. */ private boolean appendReturnValue; + /** Script language in which the script should be executed. */ + private ScriptLanguage scriptLanguage; + + /** Routines to be invoked prior to script execution. */ + private ArrayList callbacks; + /** * Creates a script metadata object which describes the given script file. * @@ -136,7 +130,7 @@ public ScriptInfo(final Context context, final String path) { public ScriptInfo(final Context context, final URL url, final String path) throws IOException { - this(context, url, path, new InputStreamReader(url.openStream())); + this(context, url, path, new InputStreamReader(url.openStream(), StandardCharsets.UTF_8)); } /** @@ -225,92 +219,98 @@ public BufferedReader getReader() { } /** - * Parses the script's input and output parameters from the script header. - *

- * This method is called automatically the first time any parameter accessor - * method is called ({@link #getInput}, {@link #getOutput}, {@link #inputs()}, - * {@link #outputs()}, etc.). Subsequent calls will reparse the parameters. - *

- * SciJava's scripting framework supports specifying @{@link Parameter}-style - * inputs and outputs in a preamble. The format is a simplified version of the - * Java @{@link Parameter} annotation syntax. The following syntaxes are - * supported: - *

- *
    - *
  • {@code // @ }
  • - *
  • {@code // @(=, ..., =) } - *
  • - *
  • {@code // @ }
  • - *
  • {@code // @(=, ..., =) - * }
  • - *
- *

- * Where: - *

- *
    - *
  • {@code //} = the comment style of the scripting language, so that the - * parameter line is ignored by the script engine itself.
  • - *
  • {@code } = one of {@code INPUT}, {@code OUTPUT}, or - * {@code BOTH}.
  • - *
  • {@code } = the name of the input or output variable.
  • - *
  • {@code } = the Java {@link Class} of the variable.
  • - *
  • {@code } = an attribute key.
  • - *
  • {@code } = an attribute value.
  • - *
- *

- * See the @{@link Parameter} annotation for a list of valid attributes. - *

- *

- * Here are a few examples: - *

- *
    - *
  • {@code // @Dataset dataset}
  • - *
  • {@code // @double(type=OUTPUT) result}
  • - *
  • {@code // @BOTH ImageDisplay display}
  • - *
  • {@code // @INPUT(persist=false, visibility=INVISIBLE) boolean verbose} - *
  • - *
- *

- * Parameters will be parsed and filled just like @{@link Parameter}-annotated - * fields in {@link Command}s. - *

+ * Gets the script contents after script processing. + * + * @return The processed script. + * @see ScriptProcessorService#process + */ + public String getProcessedScript() { + return processedScript; + } + + /** Gets the scripting language of the script. */ + public ScriptLanguage getLanguage() { + if (scriptLanguage == null) { + // infer the language from the script path's extension + final String scriptPath = getPath(); + if (scriptPath != null) { + // use language associated with the script path extension + final String extension = FileUtils.getExtension(scriptPath); + scriptLanguage = scriptService.getLanguageByExtension(extension); + } + else { + // use the highest priority language + final List langs = scriptService.getLanguages(); + if (langs != null && !langs.isEmpty()) scriptLanguage = langs.get(0); + } + } + return scriptLanguage; + } + + /** Overrides the script language to use when executing the script. */ + public void setLanguage(final ScriptLanguage scriptLanguage) { + this.scriptLanguage = scriptLanguage; + } + + /** Gets whether the return value is appended as an additional output. */ + public boolean isReturnValueAppended() { + return appendReturnValue; + } + + /** Gets whether the return value is appended as an additional output. */ + public void setReturnValueAppended(final boolean appendReturnValue) { + this.appendReturnValue = appendReturnValue; + } + + /** + * Gets the list of routines which should be invoked each time the script is + * about to execute. + * + * @return Reference to the mutable list of {@link Runnable} objects which the + * {@link ScriptModule} will run prior to executing the script itself. + */ + public List callbacks() { + if (callbacks == null) callbacks = new ArrayList<>(); + return callbacks; + } + + // -- AbstractModuleInfo methods -- + + /** + * Performs script processing. In particular, parses the script parameters. + * + * @see ParameterScriptProcessor + * @see ScriptProcessorService#process */ // NB: Widened visibility from AbstractModuleInfo. @Override public void parseParameters() { clearParameters(); - appendReturnValue = true; - - try (final BufferedReader in = script == null ? // - new BufferedReader(new FileReader(getPath())) : getReader()) // - { - while (true) { - final String line = in.readLine(); - if (line == null) break; - - // NB: Scan for lines containing an '@' with no prior alphameric - // characters. This assumes that only non-alphanumeric characters can - // be used as comment line markers. - if (line.matches("^[^\\w]*@.*")) { - final int at = line.indexOf('@'); - parseParam(line.substring(at + 1)); - } - else if (line.matches(".*\\w.*")) break; - } - - if (appendReturnValue) addReturnValue(); + try { + processedScript = scriptProcessorService.process(this); } catch (final IOException exc) { - log.error("Error reading script: " + path, exc); - } - catch (final ScriptException exc) { - log.error("Invalid parameter syntax for script: " + path, exc); + // TODO: Consider a better error handling approach. + throw new RuntimeException(exc); } } - /** Gets whether the return value is appended as an additional output. */ - public boolean isReturnValueAppended() { - return appendReturnValue; + // NB: Widened visibility from AbstractModuleInfo. + @Override + public void clearParameters() { + super.clearParameters(); + } + + // NB: Widened visibility from AbstractModuleInfo. + @Override + public void registerInput(final ModuleItem input) { + super.registerInput(input); + } + + // NB: Widened visibility from AbstractModuleInfo. + @Override + public void registerOutput(final ModuleItem output) { + super.registerOutput(output); } // -- ModuleInfo methods -- @@ -330,6 +330,11 @@ public ScriptModule createModule() throws ModuleException { return new ScriptModule(this); } + @Override + public boolean canRunHeadless() { + return is("headless"); + } + // -- Contextual methods -- @Override @@ -352,7 +357,12 @@ public void setContext(final Context context) { @Override public String getIdentifier() { - return "script:" + path; + final String name = getName(); + final String prefix = "script:"; + if (name != null) return prefix + name; + if (path != null) return prefix + path; + if (script != null) return prefix + "<" + DigestUtils.bestHex(script) + ">"; + return prefix + ""; } // -- Locatable methods -- @@ -384,6 +394,7 @@ public String getVersion() { private URL url(final URL u, final String p) { if (u != null) return u; + if (p == null) return null; try { return new File(p).toURI().toURL(); } @@ -394,144 +405,8 @@ private URL url(final URL u, final String p) { } private String path(final URL u, final String p) { - return p == null ? u.getPath() : p; - } - - private void parseParam(final String param) throws ScriptException { - final int lParen = param.indexOf("("); - final int rParen = param.lastIndexOf(")"); - if (rParen < lParen) { - throw new ScriptException("Invalid parameter: " + param); - } - if (lParen < 0) parseParam(param, parseAttrs("()")); - else { - final String cutParam = - param.substring(0, lParen) + param.substring(rParen + 1); - final String attrs = param.substring(lParen + 1, rParen); - parseParam(cutParam, parseAttrs(attrs)); - } - } - - private void parseParam(final String param, - final Map attrs) throws ScriptException - { - final String[] tokens = param.trim().split("[ \t\n]+"); - checkValid(tokens.length >= 1, param); - final String typeName, varName; - if (isIOType(tokens[0])) { - // assume syntax: - checkValid(tokens.length >= 3, param); - attrs.put("type", tokens[0]); - typeName = tokens[1]; - varName = tokens[2]; - } - else { - // assume syntax: - checkValid(tokens.length >= 2, param); - typeName = tokens[0]; - varName = tokens[1]; - } - final Class type = scriptService.lookupClass(typeName); - addItem(varName, type, attrs); - - if (ScriptModule.RETURN_VALUE.equals(varName)) { - // NB: The return value variable is declared as an explicit OUTPUT. - // So we should not append the return value as an extra output. - appendReturnValue = false; - } - } - - /** Parses a comma-delimited list of {@code key=value} pairs into a map. */ - private Map parseAttrs(final String attrs) { - return parser.parse(attrs, false).asMap(); - } - - private boolean isIOType(final String token) { - return convertService.convert(token, ItemIO.class) != null; - } - - private void checkValid(final boolean valid, final String param) - throws ScriptException - { - if (!valid) throw new ScriptException("Invalid parameter: " + param); - } - - /** Adds an output for the value returned by the script itself. */ - private void addReturnValue() { - final HashMap attrs = new HashMap<>(); - attrs.put("type", "OUTPUT"); - addItem(ScriptModule.RETURN_VALUE, Object.class, attrs); - } - - private void addItem(final String name, final Class type, - final Map attrs) - { - final DefaultMutableModuleItem item = - new DefaultMutableModuleItem<>(this, name, type); - for (final String key : attrs.keySet()) { - final Object value = attrs.get(key); - assignAttribute(item, key, value); - } - if (item.isInput()) registerInput(item); - if (item.isOutput()) { - registerOutput(item); - // NB: Only append the return value as an extra - // output when no explicit outputs are declared. - appendReturnValue = false; - } - } - - private void assignAttribute(final DefaultMutableModuleItem item, - final String k, final Object v) - { - // CTR: There must be an easier way to do this. - // Just compile the thing using javac? Or parse via javascript, maybe? - if (is(k, "callback")) item.setCallback(as(v, String.class)); - else if (is(k, "choices")) item.setChoices(asList(v, item.getType())); - else if (is(k, "columns")) item.setColumnCount(as(v, int.class)); - else if (is(k, "description")) item.setDescription(as(v, String.class)); - else if (is(k, "initializer")) item.setInitializer(as(v, String.class)); - else if (is(k, "validater")) item.setValidater(as(v, String.class)); - else if (is(k, "type")) item.setIOType(as(v, ItemIO.class)); - else if (is(k, "label")) item.setLabel(as(v, String.class)); - else if (is(k, "max")) item.setMaximumValue(as(v, item.getType())); - else if (is(k, "min")) item.setMinimumValue(as(v, item.getType())); - else if (is(k, "name")) item.setName(as(v, String.class)); - else if (is(k, "persist")) item.setPersisted(as(v, boolean.class)); - else if (is(k, "persistKey")) item.setPersistKey(as(v, String.class)); - else if (is(k, "required")) item.setRequired(as(v, boolean.class)); - else if (is(k, "softMax")) item.setSoftMaximum(as(v, item.getType())); - else if (is(k, "softMin")) item.setSoftMinimum(as(v, item.getType())); - else if (is(k, "stepSize")) item.setStepSize(as(v, double.class)); - else if (is(k, "style")) item.setWidgetStyle(as(v, String.class)); - else if (is(k, "visibility")) item.setVisibility(as(v, ItemVisibility.class)); - else if (is(k, "value")) item.setDefaultValue(as(v, item.getType())); - else item.set(k, v.toString()); - } - - /** Super terse comparison helper method. */ - private boolean is(final String key, final String desired) { - return desired.equalsIgnoreCase(key); - } - - /** Super terse conversion helper method. */ - private T as(final Object v, final Class type) { - final T converted = convertService.convert(v, type); - if (converted != null) return converted; - // NB: Attempt to convert via string. - // This is useful in cases where a weird type of object came back - // (e.g., org.scijava.parse.eval.Unresolved), but which happens to have a - // nice string representation which ultimately is expressible as the type. - return convertService.convert(v.toString(), type); - } - - private List asList(final Object v, final Class type) { - final ArrayList result = new ArrayList<>(); - final List list = as(v, List.class); - for (final Object item : list) { - result.add(as(item, type)); - } - return result; + if (p != null) return p; + return u == null ? null : u.getPath(); } /** diff --git a/src/main/java/org/scijava/script/ScriptInterpreter.java b/src/main/java/org/scijava/script/ScriptInterpreter.java index 9c08ebcf5..b48ad3254 100644 --- a/src/main/java/org/scijava/script/ScriptInterpreter.java +++ b/src/main/java/org/scijava/script/ScriptInterpreter.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/script/ScriptLanguage.java b/src/main/java/org/scijava/script/ScriptLanguage.java index 2cdb474e1..af1c27fc5 100644 --- a/src/main/java/org/scijava/script/ScriptLanguage.java +++ b/src/main/java/org/scijava/script/ScriptLanguage.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -158,7 +156,6 @@ else if (key.equals(ScriptEngine.LANGUAGE_VERSION)) { @Override default String getEngineVersion() { - return "0.0"; + return VersionUtils.getVersion(getClass()); } - } diff --git a/src/main/java/org/scijava/script/ScriptLanguageIndex.java b/src/main/java/org/scijava/script/ScriptLanguageIndex.java index f032b8356..b9e07d602 100644 --- a/src/main/java/org/scijava/script/ScriptLanguageIndex.java +++ b/src/main/java/org/scijava/script/ScriptLanguageIndex.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -40,8 +38,8 @@ import javax.script.ScriptEngineFactory; import org.scijava.log.LogService; -import org.scijava.util.ClassUtils; import org.scijava.util.FileUtils; +import org.scijava.util.Types; /** * Data structure for managing registered scripting languages. @@ -148,8 +146,8 @@ private boolean put(final String type, final Map map, // Conflicting value; behavior depends on mode. if (gently) { // Do not overwrite the previous value. - if (log != null && log.isWarn()) { - log.warn(overwriteMessage(false, type, key, value, existing)); + if (log != null && log.isDebug()) { + log.debug(overwriteMessage(false, type, key, value, existing)); } return false; } @@ -183,6 +181,6 @@ private String overwriteMessage(final boolean overwrite, final String type, /** Helper method of {@link #overwriteMessage}. */ private String details(final ScriptLanguage language) { final Class c = language.getClass(); - return c.getName() + " [" + ClassUtils.getLocation(c); + return c.getName() + " [" + Types.location(c); } } diff --git a/src/main/java/org/scijava/script/ScriptModule.java b/src/main/java/org/scijava/script/ScriptModule.java index 92cf3ced9..87fc5fb0d 100644 --- a/src/main/java/org/scijava/script/ScriptModule.java +++ b/src/main/java/org/scijava/script/ScriptModule.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -31,10 +29,8 @@ package org.scijava.script; -import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; -import java.io.Reader; import java.io.Writer; import javax.script.ScriptContext; @@ -50,7 +46,7 @@ import org.scijava.module.Module; import org.scijava.module.ModuleItem; import org.scijava.plugin.Parameter; -import org.scijava.util.FileUtils; +import org.scijava.script.process.ScriptCallback; /** * A {@link Module} which executes a script. @@ -76,9 +72,6 @@ public class ScriptModule extends AbstractModule implements Contextual { @Parameter private LogService log; - /** Script language in which the script should be executed. */ - private ScriptLanguage scriptLanguage; - /** Script engine with which the script should be executed. */ private ScriptEngine scriptEngine; @@ -96,22 +89,6 @@ public ScriptModule(final ScriptInfo info) { // -- ScriptModule methods -- - /** Gets the scripting language of the script. */ - public ScriptLanguage getLanguage() { - if (scriptLanguage == null) { - // infer the language from the script path's extension - final String path = getInfo().getPath(); - final String extension = FileUtils.getExtension(path); - scriptLanguage = scriptService.getLanguageByExtension(extension); - } - return scriptLanguage; - } - - /** Overrides the script language to use when executing the script. */ - public void setLanguage(final ScriptLanguage scriptLanguage) { - this.scriptLanguage = scriptLanguage; - } - /** Sets the writer used to record the standard output stream. */ public void setOutputWriter(final Writer output) { this.output = output; @@ -125,7 +102,16 @@ public void setErrorWriter(final Writer error) { /** Gets the script engine used to execute the script. */ public ScriptEngine getEngine() { if (scriptEngine == null) { - scriptEngine = getLanguage().getScriptEngine(); + final ScriptInfo scriptInfo = getInfo(); + if (scriptInfo == null) { + throw new IllegalArgumentException("Invalid script"); + } + final ScriptLanguage scriptLang = scriptInfo.getLanguage(); + if (scriptLang == null) { + throw new IllegalArgumentException( + "No compatible script language available"); + } + scriptEngine = scriptLang.getScriptEngine(); } return scriptEngine; } @@ -146,6 +132,13 @@ public ScriptInfo getInfo() { @Override public void run() { + // HACK: Work around code (Groovy!) assuming + // context class loader can't be null. + final ClassLoader cl = Thread.currentThread().getContextClassLoader(); + if (cl == null) { + Thread.currentThread().setContextClassLoader(Context.getClassLoader()); + } + final ScriptEngine engine = getEngine(); final String path = getInfo().getPath(); @@ -169,12 +162,15 @@ public void run() { engine.put(name, getInput(name)); } - // execute script! returnValue = null; try { - final Reader reader = getInfo().getReader(); - if (reader == null) returnValue = engine.eval(new FileReader(path)); - else returnValue = engine.eval(reader); + // invoke the callbacks + for (final ScriptCallback c : getInfo().callbacks()) { + c.invoke(this); + } + + // execute script! + returnValue = engine.eval(getInfo().getProcessedScript()); } catch (Throwable e) { while (e instanceof ScriptException && e.getCause() != null) { @@ -185,7 +181,7 @@ public void run() { } // populate output values - final ScriptLanguage language = getLanguage(); + final ScriptLanguage language = getInfo().getLanguage(); for (final ModuleItem item : getInfo().outputs()) { final String name = item.getName(); final Object value; @@ -230,4 +226,17 @@ public void setContext(final Context context) { context.inject(this); } + // -- Deprecated methods -- + + /** @deprecated Use {@link ScriptInfo#getLanguage()} instead. */ + @Deprecated + public ScriptLanguage getLanguage() { + return getInfo().getLanguage(); + } + + /** @deprecated Use {@link ScriptInfo#setLanguage(ScriptLanguage)} instead. */ + @Deprecated + public void setLanguage(final ScriptLanguage scriptLanguage) { + getInfo().setLanguage(scriptLanguage); + } } diff --git a/src/main/java/org/scijava/script/ScriptREPL.java b/src/main/java/org/scijava/script/ScriptREPL.java index 7ff8c1a07..ec9e088c4 100644 --- a/src/main/java/org/scijava/script/ScriptREPL.java +++ b/src/main/java/org/scijava/script/ScriptREPL.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -32,6 +30,7 @@ package org.scijava.script; import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -40,6 +39,8 @@ import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; import javax.script.Bindings; import javax.script.ScriptException; @@ -69,7 +70,9 @@ public class ScriptREPL { @Parameter(required = false) private PluginService pluginService; - private final PrintStream out; + private final Consumer out; + + private String languagePreference; /** List of interpreter-friendly script languages. */ private List languages; @@ -84,10 +87,26 @@ public ScriptREPL(final Context context) { this(context, System.out); } + public ScriptREPL(final Context context, final String language) { + this(context, language, System.out); + } + public ScriptREPL(final Context context, final OutputStream out) { + this(context, null, out); + } + + public ScriptREPL(final Context context, final String language, + final OutputStream out) + { + this(context, language, outputStreamConsumer(out)); + } + + public ScriptREPL(final Context context, final String language, + final Consumer out) + { context.inject(this); - this.out = out instanceof PrintStream ? - (PrintStream) out : new PrintStream(out); + languagePreference = language; + this.out = out; } /** @@ -123,39 +142,93 @@ public void loop() throws IOException { * @param in Input stream from which commands are read. */ public void loop(final InputStream in) throws IOException { - initialize(); final BufferedReader bin = new BufferedReader(new InputStreamReader(in)); + try { + loop(() -> { + try { + return bin.readLine(); + } + catch (final IOException exc) { + throw new RuntimeException(exc); + } + }); + } + catch (final RuntimeException exc) { + // NB: This convolution lets us throw IOException from inside a + // Supplier.get implementation, by wrapping in a RuntimeException. + // We then unwrap it again and throw it here, where we said we would. + final Throwable cause = exc.getCause(); + if (cause instanceof IOException) throw (IOException) cause; + else throw exc; + } + } + + /** + * Starts a Read-Eval-Print-Loop from the given source, returning when + * the loop terminates. + * + * @param in Source from which commands are read. + */ + public void loop(final Supplier in) { + initialize(); while (true) { prompt(); - final String line = bin.readLine(); + final Object input = in.get(); + final String line = input == null ? null : input.toString(); if (line == null) break; if (!evaluate(line)) return; } } - /** Outputs a greeting, and sets up the initial language of the REPL. */ + /** + * Outputs a greeting, and sets up the initial language and variables of the + * REPL. + */ public void initialize() { - out.println("Welcome to the SciJava REPL!"); - out.println(); - help(); + initialize(true); + } + + /** + * Sets up the initial language and variables of the REPL. + * + * @param verbose Whether to output an initial greeting. + */ + public void initialize(final boolean verbose) { + if (verbose) { + println("Welcome to the SciJava REPL!"); + println(); + help(); + } final List langs = getInterpretedLanguages(); - if (langs.isEmpty()) { - out.println("--------------------------------------------------------------"); - out.println("Uh oh! There are no SciJava script languages available!"); - out.println("Are any on your classpath? E.g.: org.scijava:scripting-groovy?"); - out.println("--------------------------------------------------------------"); - out.println(); - return; + if (verbose) { + if (langs.isEmpty()) { + println("--------------------------------------------------------------"); + println("Uh oh! There are no SciJava script languages available!"); + println("Are any on your classpath? E.g.: org.scijava:scripting-groovy?"); + println("--------------------------------------------------------------"); + println(); + return; + } + println("Have fun!"); + println(); + } + if (!langs.isEmpty()) { + if (languagePreference != null) selectPreferredLanguage(langs); + else lang(langs.get(0)); } - out.println("Have fun!"); - out.println(); - lang(langs.get(0).getLanguageName()); populateBindings(interpreter.getBindings()); } + private void selectPreferredLanguage(List langs) { + final ScriptLanguage preference = langs.stream() + .filter(lang -> languagePreference.equals(lang.getLanguageName())) + .findFirst().orElse(langs.get(0)); + lang(preference); + } + /** Outputs the prompt. */ public void prompt() { - out.print(interpreter == null || interpreter.isReady() ? "> " : "\\ "); + print(interpreter == null || interpreter.isReady() ? "> " : "\\ "); } /** @@ -181,22 +254,22 @@ public boolean evaluate(final String line) { // pass the input to the current interpreter for evaluation final Object result = interpreter.interpret(line); if (result != ScriptInterpreter.MORE_INPUT_PENDING) { - out.println(s(result)); + println(s(result)); } } } catch (final ScriptException exc) { // NB: Something went wrong interpreting the line of code. // Let's just display the error message, unless we are in debug mode. - if (debug) exc.printStackTrace(out); + if (debug) printStackTrace(exc); else { final String msg = exc.getMessage(); - out.println(msg == null ? exc.getClass().getName() : msg); + println(msg == null ? exc.getClass().getName() : msg); } } catch (final Throwable exc) { // NB: Something unusual went wrong. Dump the whole exception always. - exc.printStackTrace(out); + printStackTrace(exc); } return true; } @@ -205,17 +278,17 @@ public boolean evaluate(final String line) { /** Prints a usage guide. */ public void help() { - out.println("Available built-in commands:"); - out.println(); - out.println(" :help | this handy list of commands"); - out.println(" :vars | dump a list of variables"); - out.println(" :lang | switch the active language"); - out.println(" :langs | list available languages"); - out.println(" :debug | toggle full stack traces"); - out.println(" :quit | exit the REPL"); - out.println(); - out.println("Or type a statement to evaluate it with the active language."); - out.println(); + println("Available built-in commands:"); + println(); + println(" :help | this handy list of commands"); + println(" :vars | dump a list of variables"); + println(" :lang | switch the active language"); + println(" :langs | list available languages"); + println(" :debug | toggle full stack traces"); + println(" :quit | exit the REPL"); + println(); + println("Or type a statement to evaluate it with the active language."); + println(); } /** Lists variables in the script context. */ @@ -245,9 +318,20 @@ public void lang(final String langName) { // create the new interpreter final ScriptLanguage language = scriptService.getLanguageByName(langName); if (language == null) { - out.println("No such language: " + langName); + println("No such language: " + langName); return; } + lang(language); + println("language -> " + interpreter.getLanguage().getLanguageName()); + } + + /** + * Creates a new {@link ScriptInterpreter} to interpret statements, preserving + * existing variables from the previous interpreter. + * + * @param language The script language of the new interpreter. + */ + public void lang(final ScriptLanguage language) { final ScriptInterpreter newInterpreter = new DefaultScriptInterpreter(language); @@ -256,10 +340,8 @@ public void lang(final String langName) { copyBindings(interpreter, newInterpreter); } catch (final Throwable t) { - t.printStackTrace(out); + printStackTrace(t); } - out.println("language -> " + - newInterpreter.getLanguage().getLanguageName()); interpreter = newInterpreter; } @@ -277,7 +359,7 @@ public void langs() { public void debug() { debug = !debug; - out.println("debug mode -> " + debug); + println("debug mode -> " + debug); } // -- Main method -- @@ -286,8 +368,15 @@ public static void main(final String... args) throws Exception { // make a SciJava application context final Context context = new Context(); - // create the script interpreter - final ScriptREPL scriptCLI = new ScriptREPL(context); + // see if we have a preferred language + // and create the script interpreter + final ScriptREPL scriptCLI; + if(args.length > 0) { + final String preference = args[0]; + scriptCLI = new ScriptREPL(context, preference); + } else { + scriptCLI = new ScriptREPL(context); + } // start the REPL scriptCLI.loop(); @@ -352,13 +441,18 @@ private List gateways() { gateways.add(gateway); } catch (final Throwable t) { - t.printStackTrace(out); + printStackTrace(t); } } return gateways; } private String serviceName(final Service service) { + final PluginInfo info = service.getInfo(); + final String pluginName = info == null ? null : info.getName(); + // Name was explicitly given in the @Plugin annotation. + if (pluginName != null && !pluginName.isEmpty()) return pluginName; + // No name was given; synthesize one from the class name. final String serviceName = service.getClass().getSimpleName(); final String shortName = lowerCamelCase( serviceName.replaceAll("^(Default)?(.*)Service$", "$2")); @@ -372,6 +466,17 @@ private String type(final Object value) { return "[" + decoded.getClass().getName() + "]"; } + private static final String NL = System.getProperty("line.separator"); + private void print(String s) { out.accept(s); } + private void println() { print(NL); } + private void println(final String s) { print(s + NL); } + + private void printStackTrace(final Throwable t) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + t.printStackTrace(new PrintStream(baos)); + println(baos.toString()); + } + private void printColumns(final List... columns) { final int pad = 2; @@ -386,18 +491,28 @@ private void printColumns(final List... columns) { } // output the columns + final StringBuilder sb = new StringBuilder(); for (int i = 0; i < columns[0].size(); i++) { + sb.setLength(0); for (int c = 0; c < columns.length; c++) { final String s = s(columns[c].get(i)); - out.print(s); - for (int p = s.length(); p < widths[c] + pad; p++) { - out.print(' '); + sb.append(s); + if (c < columns.length - 1) { + for (int p = s.length(); p < widths[c] + pad; p++) { + sb.append(' '); + } } } - out.println(); + println(sb.toString()); } } + private static Consumer outputStreamConsumer(final OutputStream out) { + final PrintStream ps = out instanceof PrintStream ? + (PrintStream) out : new PrintStream(out); + return s -> { ps.print(s); ps.flush(); }; + } + private static String lowerCamelCase(final String s) { final StringBuilder sb = new StringBuilder(s); for (int i=0; i type) { /** TODO */ void addAlias(String alias, Class type); + /** TODO */ + Map> getAliases(); + /** TODO */ Class lookupClass(String typeName) throws ScriptException; diff --git a/src/main/java/org/scijava/script/console/RunScriptArgument.java b/src/main/java/org/scijava/script/console/RunScriptArgument.java index 4b9ad3ba6..de6f3cbc6 100644 --- a/src/main/java/org/scijava/script/console/RunScriptArgument.java +++ b/src/main/java/org/scijava/script/console/RunScriptArgument.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/script/io/ScriptIOPlugin.java b/src/main/java/org/scijava/script/io/ScriptIOPlugin.java index 14810c80a..6f466accd 100644 --- a/src/main/java/org/scijava/script/io/ScriptIOPlugin.java +++ b/src/main/java/org/scijava/script/io/ScriptIOPlugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -35,8 +33,9 @@ import org.scijava.io.AbstractIOPlugin; import org.scijava.io.IOPlugin; +import org.scijava.io.location.FileLocation; +import org.scijava.io.location.Location; import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; import org.scijava.script.ScriptService; /** @@ -45,7 +44,6 @@ * @author Curtis Rueden * @see ScriptService */ -@Plugin(type = IOPlugin.class) public class ScriptIOPlugin extends AbstractIOPlugin { @Parameter(required = false) @@ -59,13 +57,16 @@ public Class getDataType() { } @Override - public boolean supportsOpen(final String source) { + public boolean supportsOpen(final Location source) { if (scriptService == null) return false; // no service for opening scripts - return scriptService.canHandleFile(source); + // TODO: Update ScriptService to use Location instead of File. + if (!(source instanceof FileLocation)) return false; + final FileLocation loc = (FileLocation) source; + return scriptService.canHandleFile(loc.getFile()); } @Override - public String open(final String source) throws IOException { + public String open(final Location source) throws IOException { if (scriptService == null) return null; // no service for opening scripts // TODO: Use the script service to open the file in the script editor. return null; diff --git a/src/main/java/org/scijava/script/process/DefaultScriptProcessorService.java b/src/main/java/org/scijava/script/process/DefaultScriptProcessorService.java new file mode 100644 index 000000000..5995b618e --- /dev/null +++ b/src/main/java/org/scijava/script/process/DefaultScriptProcessorService.java @@ -0,0 +1,46 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.script.process; + +import org.scijava.plugin.AbstractPTService; +import org.scijava.plugin.Plugin; +import org.scijava.service.Service; + +/** + * Default implementation of {@link ScriptProcessorService}. + * + * @author Curtis Rueden + */ +@Plugin(type = Service.class) +public class DefaultScriptProcessorService extends + AbstractPTService implements ScriptProcessorService +{ + // NB: No implementation needed. +} diff --git a/src/main/java/org/scijava/script/process/DirectiveScriptProcessor.java b/src/main/java/org/scijava/script/process/DirectiveScriptProcessor.java new file mode 100644 index 000000000..c3646466e --- /dev/null +++ b/src/main/java/org/scijava/script/process/DirectiveScriptProcessor.java @@ -0,0 +1,137 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.script.process; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.scijava.convert.ConvertService; +import org.scijava.parse.ParseService; +import org.scijava.plugin.Parameter; +import org.scijava.script.ScriptInfo; + +/** + * Abstract base class for {@link ScriptProcessor} plugins that parse lines + * of the form {@code #@directive(...) ...}. + * + * @author Curtis Rueden + */ +public abstract class DirectiveScriptProcessor implements ScriptProcessor { + + private final Pattern p = // + Pattern.compile("^#@(\\w*)\\s*(\\((.*)\\))?\\s*(.*)$"); + + @Parameter + private ConvertService convertService; + + @Parameter + private ParseService parser; + + private ScriptInfo info; + + private Predicate directivesToMatch; + + public DirectiveScriptProcessor(final Predicate directivesToMatch) { + this.directivesToMatch = directivesToMatch; + } + + // -- ScriptProcessor methods -- + + @Override + public void begin(final ScriptInfo scriptInfo) { + info = scriptInfo; + } + + @Override + public String process(final String line) { + // as quickly as possible, verify that this line is a directive + if (!line.startsWith("#@")) return line; + + // parse the directive, and ensure it is well-formed + final Matcher m = p.matcher(line); + if (!m.matches()) return line; + + // ensure directive is relevant + final String directive = m.group(1); + if (!directivesToMatch.test(directive)) return line; + + // parse attributes (inner match without parentheses) + final String attrString = m.group(3); + final Map attrs = attrString == null ? // + Collections.emptyMap() : parser.parse(attrString, false).asMap(); + + // retain the rest of the string + final String theRest = m.group(4); + + return process(directive, attrs, theRest); + } + + // -- Internal methods -- + + /** Processes the given directive. */ + protected abstract String process(final String directive, + final Map attrs, final String theRest); + + /** Gets the active {@link ScriptInfo} instance. */ + protected ScriptInfo info() { + return info; + } + + /** Checks whether some key matches the desired value, ignoring case. */ + protected boolean is(final String key, final String desired) { + return desired.equalsIgnoreCase(key); + } + + /** Coerces some object into another object of the given type. */ + protected T as(final Object v, final Class type) { + final T converted = convertService.convert(v, type); + if (converted != null) return converted; + // NB: Attempt to convert via string. + // This is useful in cases where a weird type of object came back + // (e.g., org.scijava.parse.eval.Unresolved), but which happens to have a + // nice string representation which ultimately is expressible as the type. + return convertService.convert(v.toString(), type); + } + + /** Coerces some object into a list of objects of the given type. */ + protected List asList(final Object v, final Class type) { + final ArrayList result = new ArrayList<>(); + final List list = as(v, List.class); + for (final Object item : list) { + result.add(as(item, type)); + } + return result; + } +} diff --git a/src/main/java/org/scijava/script/process/ParameterScriptProcessor.java b/src/main/java/org/scijava/script/process/ParameterScriptProcessor.java new file mode 100644 index 000000000..cb030c8d2 --- /dev/null +++ b/src/main/java/org/scijava/script/process/ParameterScriptProcessor.java @@ -0,0 +1,310 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.script.process; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.script.ScriptException; + +import org.scijava.ItemIO; +import org.scijava.ItemVisibility; +import org.scijava.command.Command; +import org.scijava.convert.ConvertService; +import org.scijava.log.LogService; +import org.scijava.module.DefaultMutableModuleItem; +import org.scijava.parse.ParseService; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.script.ScriptInfo; +import org.scijava.script.ScriptModule; +import org.scijava.script.ScriptService; + +/** + * A {@link ScriptProcessor} which parses the script's input and output + * parameters from the script header. + *

+ * SciJava's scripting framework supports specifying @{@link Parameter}-style + * inputs and outputs in a preamble. The format is a simplified version of the + * Java @{@link Parameter} annotation syntax. The following syntaxes are + * supported: + *

+ *
    + *
  • {@code #@ }
  • + *
  • {@code #@ (=, ..., =) }
  • + *
  • {@code #@ }
  • + *
  • {@code #@ }
  • + *
  • {@code #@(=, ..., =) + * }
  • + *
+ *

+ * Where: + *

+ *
    + *
  • {@code #@} - signals a special script processing instruction, so that the + * parameter line is ignored by the script engine itself.
  • + *
  • {@code } - one of {@code INPUT}, {@code OUTPUT}, or {@code BOTH}. + *
  • + *
  • {@code } - the name of the input or output variable.
  • + *
  • {@code } - the Java {@link Class} of the variable, or + * {@link Object} if none specified.
  • + *
  • {@code } - an attribute key.
  • + *
  • {@code } - an attribute value.
  • + *
+ *

+ * See the @{@link Parameter} annotation for a list of valid attributes. + *

+ *

+ * Here are a few examples: + *

+ *
    + *
  • {@code #@Dataset dataset}
  • + *
  • {@code #@double(type=OUTPUT) result}
  • + *
  • {@code #@both ImageDisplay display}
  • + *
  • {@code #@input(persist=false, visibility=INVISIBLE) boolean verbose}
  • + *
  • {@code #@output thing}
  • + *
+ *

+ * Parameters will be parsed and filled just like @{@link Parameter}-annotated + * fields in {@link Command}s. + *

+ * + * @author Curtis Rueden + */ +@Plugin(type = ScriptProcessor.class) +public class ParameterScriptProcessor implements ScriptProcessor { + + @Parameter + private ScriptService scriptService; + + @Parameter + private ConvertService convertService; + + @Parameter + private ParseService parser; + + @Parameter + private LogService log; + + private ScriptInfo info; + private boolean header; + + // -- ScriptProcessor methods -- + + @Override + public void begin(final ScriptInfo scriptInfo) { + info = scriptInfo; + info.setReturnValueAppended(true); + header = true; + } + + @Override + public String process(final String line) { + // parse new-style parameters starting with #@ anywhere in the script. + if (line.matches("^#@.*")) { + final int at = line.indexOf('@'); + return process(line, line.substring(at + 1)); + } + + // parse old-style parameters in the initial script header + if (header) { + // NB: Check if line contains an '@' with no prior alphameric + // characters. This assumes that only non-alphanumeric characters can + // be used as comment line markers. + // NB: In addition, to allow for commented-out new-style parameters, we exclude + // lines that have the new-style #@ preceded by non-alphanumeric characters. + if (line.matches("^[^\\w]*[^\\w#]@.*")) { + final int at = line.indexOf('@'); + return process(line, line.substring(at + 1)); + } + else if (line.matches(".*\\w.*")) header = false; + } + + return line; + } + + @Override + public void end() { + if (info.isReturnValueAppended()) { + // add an output for the value returned by the script itself + final HashMap attrs = new HashMap<>(); + attrs.put("type", "OUTPUT"); + addItem(ScriptModule.RETURN_VALUE, Object.class, attrs, false); + } + } + + // -- Helper methods -- + + private String process(final String line, final String param) { + if (parseParam(param)) return ""; + log.warn("Ignoring invalid parameter: " + param); + return line; + } + + private boolean parseParam(final String param) { + final int lParen = param.indexOf("("); + final int rParen = param.lastIndexOf(")"); + if (rParen < lParen) return false; + if (lParen < 0) return parseParam(param, parseAttrs("()")); + final String cutParam = + param.substring(0, lParen) + param.substring(rParen + 1); + final String attrs = param.substring(lParen + 1, rParen); + return parseParam(cutParam, parseAttrs(attrs)); + } + + private boolean parseParam(final String param, + final Map attrs) + { + final String[] tokens = param.trim().split("[ \t\n]+"); + if (tokens.length < 1) return false; + final String typeName, varName; + final String maybeIOType = tokens[0].toUpperCase(); + if (isIOType(maybeIOType)) { + if (tokens.length == 2) { + // + typeName = "Object"; + varName = tokens[1]; + } + else if (tokens.length == 3) { + // + typeName = tokens[1]; + varName = tokens[2]; + } + else return false; + attrs.put("type", maybeIOType); + } + else { + // assume syntax: + if (tokens.length < 2) return false; + typeName = tokens[0]; + varName = tokens[1]; + } + try { + final Class type = scriptService.lookupClass(typeName); + addItem(varName, type, attrs, true); + } + catch (final ScriptException exc) { + log.warn("Invalid class: " + typeName, exc); + return false; + } + + if (ScriptModule.RETURN_VALUE.equals(varName)) { + // NB: The return value variable is declared as an explicit parameter. + // So we should not append the return value as an extra output. + info.setReturnValueAppended(false); + } + + return true; + } + + /** Parses a comma-delimited list of {@code key=value} pairs into a map. */ + private Map parseAttrs(final String attrs) { + return parser.parse(attrs, false).asMap(); + } + + private boolean isIOType(final String token) { + return convertService.convert(token.toUpperCase(), ItemIO.class) != null; + } + + private void addItem(final String name, final Class type, + final Map attrs, final boolean explicit) + { + final DefaultMutableModuleItem item = + new DefaultMutableModuleItem<>(info, name, type); + for (final String key : attrs.keySet()) { + final Object value = attrs.get(key); + assignAttribute(item, key, value); + } + if (item.isInput()) info.registerInput(item); + if (item.isOutput()) { + info.registerOutput(item); + // NB: Only append the return value as an extra + // output when no explicit outputs are declared. + if (explicit) info.setReturnValueAppended(false); + } + } + + private void assignAttribute(final DefaultMutableModuleItem item, + final String k, final Object v) + { + // CTR: There must be an easier way to do this. + // Just compile the thing using javac? Or parse via javascript, maybe? + if (is(k, "callback")) item.setCallback(as(v, String.class)); + else if (is(k, "choices")) item.setChoices(asList(v, item.getType())); + else if (is(k, "columns")) item.setColumnCount(as(v, int.class)); + else if (is(k, "description")) item.setDescription(as(v, String.class)); + else if (is(k, "initializer")) item.setInitializer(as(v, String.class)); + else if (is(k, "validater")) item.setValidater(as(v, String.class)); + else if (is(k, "type")) item.setIOType(as(v, ItemIO.class)); + else if (is(k, "label")) item.setLabel(as(v, String.class)); + else if (is(k, "max")) item.setMaximumValue(as(v, item.getType())); + else if (is(k, "min")) item.setMinimumValue(as(v, item.getType())); + else if (is(k, "name")) item.setName(as(v, String.class)); + else if (is(k, "autoFill")) item.setAutoFill(as(v, boolean.class)); + else if (is(k, "persist")) item.setPersisted(as(v, boolean.class)); + else if (is(k, "persistKey")) item.setPersistKey(as(v, String.class)); + else if (is(k, "required")) item.setRequired(as(v, boolean.class)); + else if (is(k, "softMax")) item.setSoftMaximum(as(v, item.getType())); + else if (is(k, "softMin")) item.setSoftMinimum(as(v, item.getType())); + else if (is(k, "stepSize")) item.setStepSize(as(v, double.class)); + else if (is(k, "style")) item.setWidgetStyle(as(v, String.class)); + else if (is(k, "visibility")) item.setVisibility(as(v, ItemVisibility.class)); + else if (is(k, "value")) item.setDefaultValue(as(v, item.getType())); + else item.set(k, v.toString()); + } + + /** Super terse comparison helper method. */ + private boolean is(final String key, final String desired) { + return desired.equalsIgnoreCase(key); + } + + /** Super terse conversion helper method. */ + private T as(final Object v, final Class type) { + final T converted = convertService.convert(v, type); + if (converted != null) return converted; + // NB: Attempt to convert via string. + // This is useful in cases where a weird type of object came back + // (e.g., org.scijava.parse.eval.Unresolved), but which happens to have a + // nice string representation which ultimately is expressible as the type. + return convertService.convert(v.toString(), type); + } + + private List asList(final Object v, final Class type) { + final ArrayList result = new ArrayList<>(); + final List list = as(v, List.class); + for (final Object item : list) { + result.add(as(item, type)); + } + return result; + } + +} diff --git a/src/main/java/org/scijava/io/BytesLocation.java b/src/main/java/org/scijava/script/process/ScriptCallback.java similarity index 63% rename from src/main/java/org/scijava/io/BytesLocation.java rename to src/main/java/org/scijava/script/process/ScriptCallback.java index 8070dc225..849ab5f27 100644 --- a/src/main/java/org/scijava/io/BytesLocation.java +++ b/src/main/java/org/scijava/script/process/ScriptCallback.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -29,36 +27,24 @@ * #L% */ -package org.scijava.io; +package org.scijava.script.process; -import java.nio.ByteBuffer; +import javax.script.ScriptException; + +import org.scijava.script.ScriptModule; /** - * {@link Location} backed by a {@link ByteBuffer}. + * A routine which will be invoked just prior to script execution. * * @author Curtis Rueden */ -public class BytesLocation extends AbstractLocation { - - private final ByteBuffer bytes; - - public BytesLocation(final ByteBuffer bytes) { - this.bytes = bytes; - } - - public BytesLocation(final byte[] bytes) { - this(ByteBuffer.wrap(bytes)); - } - - public BytesLocation(final byte[] bytes, final int offset, final int length) { - this(ByteBuffer.wrap(bytes, offset, length)); - } - - // -- ByteArrayLocation methods -- - - /** Gets the associated {@link ByteBuffer}. */ - public ByteBuffer getByteBuffer() { - return bytes; - } - +public interface ScriptCallback { + + /** + * Invokes the callback routine. + * + * @param module The {@link ScriptModule} instance which will + * execute the script. + */ + void invoke(final ScriptModule module) throws ScriptException; } diff --git a/src/main/java/org/scijava/script/process/ScriptDirectiveScriptProcessor.java b/src/main/java/org/scijava/script/process/ScriptDirectiveScriptProcessor.java new file mode 100644 index 000000000..51c891acb --- /dev/null +++ b/src/main/java/org/scijava/script/process/ScriptDirectiveScriptProcessor.java @@ -0,0 +1,181 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.script.process; + +import java.util.Map; + +import org.scijava.MenuPath; +import org.scijava.Priority; +import org.scijava.log.LogService; +import org.scijava.module.ModuleInfo; +import org.scijava.module.ModuleService; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.script.ScriptLanguage; +import org.scijava.script.ScriptService; + +/** + * A {@link ScriptProcessor} which parses the {@code #@script} directive. + *

+ * The syntax is: + *

+ * + *
+ * #@script(key1=value1, key2=value2, ...)
+ * 
+ *

+ * Supported keys include: + *

+ *
    + *
  • {@code name} - The name of the script.
  • + *
  • {@code label} - The human-readable label to use (e.g., in the menu + * structure).
  • + *
  • {@code description} - A longer description of the script (e.g., for use + * as a tool tip).
  • + *
  • {@code menuPath} - Abbreviated menu path defining where the script is + * shown in the menu structure. Use greater than sign ({@code >}) as a + * separator.
  • + *
  • {@code menuRoot} - String identifier naming the menu to which this script + * belongs.
  • + *
  • {@code iconPath} - Path to the plugin's icon (e.g., shown in the menu + * structure).
  • + *
  • {@code priority} - Priority of the script. Larger values are higher + * priority. Value can be written as a {@code double} constant, or as one of the + * following convenient shorthands: {@code first}, {@code extremely-high}, + * {@code very-high}, {@code high}, {@code normal}, {@code low}, + * {@code very-low}, {@code extremely-low}, {@code last}.
  • + *
  • {@code headless} - Provides a "hint" as to whether the script would + * behave correctly in a headless context. Do not specify + * {@code headless = true} unless the script refrains from using any UI-specific + * features (e.g., AWT or Swing calls).
  • + *
+ *

+ * Any other key-value pairs encountered are stored as properties via the + * {@link ModuleInfo#set(String, String)} method. + *

+ *

+ * See also the @{@link Plugin} annotation, which mostly lines up with this list + * of attributes. + *

+ *

+ * Here are a few examples: + *

+ *
    + *
  • {@code #@script(name = "extra-functions")}
  • + *
  • {@code #@script(headless = true)}
  • + *
  • {@code #@script(menuPath = "Image > Import > Text...")}
  • + *
+ * + * @author Curtis Rueden + */ +@Plugin(type = ScriptProcessor.class, priority=Priority.HIGH) +public class ScriptDirectiveScriptProcessor extends DirectiveScriptProcessor { + + public ScriptDirectiveScriptProcessor() { + super("script"::equals); + } + + @Parameter + private LogService log; + + @Parameter + private ModuleService moduleService; + + @Parameter + private ScriptService scriptService; + + // -- Internal DirectiveScriptProcessor methods -- + + @Override + protected String process(final String directive, + final Map attrs, final String theRest) + { + for (final String k : attrs.keySet()) { + assignAttribute(k == null ? "name" : k, attrs.get(k)); + } + moduleService.addModule(info()); // TODO how to handle duplicate names? + return ""; + } + + // -- Helper methods -- + + private void assignAttribute(final String k, final Object v) { + if (is(k, "name")) info().setName(as(v, String.class)); + else if (is(k, "label")) info().setLabel(as(v, String.class)); + else if (is(k, "description")) info().setDescription(as(v, String.class)); + else if (is(k, "language")) { + ScriptLanguage lang = parseScriptLanguage(v.toString()); + if (lang != null) info().setLanguage(lang); + } + else if (is(k, "menuPath")) { + info().setMenuPath(new MenuPath(as(v, String.class))); + } + else if (is(k, "menuRoot")) info().setMenuRoot(as(v, String.class)); + else if (is(k, "iconPath")) info().setIconPath(as(v, String.class)); + else if (is(k, "priority")) { + final Double priority = priority(v); + if (priority != null) info().setPriority(priority); + } + else if (is(k, "headless") && as(v, boolean.class)) { + // NB: There is no ModuleInfo#setHeadless(boolean). + // So we add a "headless" property; see ScriptInfo#canRunHeadless(). + info().set("headless", "true"); + } + else info().set(k, v.toString()); + } + + private Double priority(final Object p) { + final Double pDouble = as(p, Double.class); + if (pDouble != null) return pDouble; + + final String pString = as(p, String.class); + if (pString == null) return null; + + final String lString = pString.toLowerCase(); + if (lString.matches("first")) return Priority.FIRST; + if (lString.matches("extremely[ _-]?high")) return Priority.EXTREMELY_HIGH; + if (lString.matches("very[ _-]?high")) return Priority.VERY_HIGH; + if (lString.matches("high")) return Priority.HIGH; + if (lString.matches("normal")) return Priority.NORMAL; + if (lString.matches("low")) return Priority.LOW; + if (lString.matches("very[ _-]?low")) return Priority.VERY_LOW; + if (lString.matches("extremely[ _-]?low")) return Priority.EXTREMELY_LOW; + if (lString.matches("last")) return Priority.LAST; + return null; + } + + private ScriptLanguage parseScriptLanguage(final Object v) { + final String langHint = v.toString(); + ScriptLanguage lang = scriptService.getLanguageByName(langHint); + if (lang == null) lang = scriptService.getLanguageByExtension(langHint); + if (lang == null) log.warn("Unknown script language: " + langHint); + return lang; + } +} diff --git a/src/main/java/org/scijava/script/process/ScriptProcessor.java b/src/main/java/org/scijava/script/process/ScriptProcessor.java new file mode 100644 index 000000000..7e5347932 --- /dev/null +++ b/src/main/java/org/scijava/script/process/ScriptProcessor.java @@ -0,0 +1,52 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.script.process; + +import org.scijava.plugin.SciJavaPlugin; +import org.scijava.script.ScriptInfo; + +/** + * A script processor defines some sort of processing that primes a particular + * script for execution. + *

+ * Typically, these plugins look for special directives in the script itself + * beginning with distinctive character sequences like {@code #@}, and then + * perform some action in response. + *

+ * + * @author Curtis Rueden + */ +public interface ScriptProcessor extends SciJavaPlugin { + + void begin(ScriptInfo info); + String process(String line); + default void end() {} + +} diff --git a/src/main/java/org/scijava/script/process/ScriptProcessorService.java b/src/main/java/org/scijava/script/process/ScriptProcessorService.java new file mode 100644 index 000000000..e78250b4d --- /dev/null +++ b/src/main/java/org/scijava/script/process/ScriptProcessorService.java @@ -0,0 +1,96 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.script.process; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.List; + +import org.scijava.plugin.PTService; +import org.scijava.script.ScriptInfo; +import org.scijava.service.SciJavaService; + +/** + * Interface for service that processes scripts. This service discovers + * available {@link ScriptProcessor} plugins, and provides convenience methods + * to interact with them. + * + * @author Curtis Rueden + */ +public interface ScriptProcessorService extends + PTService, SciJavaService +{ + + /** + * Invokes all {@link ScriptProcessor} plugins on the given script, line by + * line in sequence. + */ + default String process(final ScriptInfo info) throws IOException { + final List processors = // + pluginService().createInstances(getPlugins()); + + BufferedReader reader = info.getReader(); + if (reader == null) { + reader = new BufferedReader(new FileReader(info.getPath())); + } + + for (final ScriptProcessor p : processors) { + p.begin(info); + } + + final StringBuilder sb = new StringBuilder(); + + try (final BufferedReader in = reader) { + while (true) { + String line = in.readLine(); + if (line == null) break; + for (final ScriptProcessor p : processors) { + line = p.process(line); + } + sb.append(line); + sb.append("\n"); + } + } + + for (final ScriptProcessor p : processors) { + p.end(); + } + + return sb.toString(); + } + + // -- PTService methods -- + + @Override + default Class getPluginType() { + return ScriptProcessor.class; + } +} diff --git a/src/main/java/org/scijava/script/process/ShebangScriptProcessor.java b/src/main/java/org/scijava/script/process/ShebangScriptProcessor.java new file mode 100644 index 000000000..188f50906 --- /dev/null +++ b/src/main/java/org/scijava/script/process/ShebangScriptProcessor.java @@ -0,0 +1,78 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.script.process; + +import org.scijava.log.LogService; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.script.ScriptInfo; +import org.scijava.script.ScriptLanguage; +import org.scijava.script.ScriptService; + +/** + * A {@link ScriptProcessor} which looks for a {@code #!} at the beginning of a + * script, and set the language accordingly. + * + * @author Curtis Rueden + */ +@Plugin(type = ScriptProcessor.class) +public class ShebangScriptProcessor implements ScriptProcessor { + + @Parameter + private ScriptService scriptService; + + @Parameter + private LogService log; + + private ScriptInfo info; + private boolean first = true; + + // -- ScriptProcessor methods -- + + @Override + public void begin(final ScriptInfo scriptInfo) { + info = scriptInfo; + } + + @Override + public String process(final String line) { + if (!first) return line; + first = false; + if (line.startsWith("#!")) { + // shebang! + final String langName = line.substring(2); + final ScriptLanguage lang = scriptService.getLanguageByName(langName); + if (lang != null) info.setLanguage(lang); + else log.warn("Unknown script language: " + langName); + return ""; + } + return line; + } +} diff --git a/src/main/java/org/scijava/script/run/ScriptCodeRunner.java b/src/main/java/org/scijava/script/run/ScriptCodeRunner.java index b56d24831..86c73042b 100644 --- a/src/main/java/org/scijava/script/run/ScriptCodeRunner.java +++ b/src/main/java/org/scijava/script/run/ScriptCodeRunner.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/service/AbstractService.java b/src/main/java/org/scijava/service/AbstractService.java index dab8524d7..d8f3ef1da 100644 --- a/src/main/java/org/scijava/service/AbstractService.java +++ b/src/main/java/org/scijava/service/AbstractService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/service/SciJavaService.java b/src/main/java/org/scijava/service/SciJavaService.java index eaa461ba0..d252e1b52 100644 --- a/src/main/java/org/scijava/service/SciJavaService.java +++ b/src/main/java/org/scijava/service/SciJavaService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/service/Service.java b/src/main/java/org/scijava/service/Service.java index a8e2d6827..a7119ba21 100644 --- a/src/main/java/org/scijava/service/Service.java +++ b/src/main/java/org/scijava/service/Service.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/service/ServiceHelper.java b/src/main/java/org/scijava/service/ServiceHelper.java index 1a684c034..0cf2a21ed 100644 --- a/src/main/java/org/scijava/service/ServiceHelper.java +++ b/src/main/java/org/scijava/service/ServiceHelper.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -64,7 +62,7 @@ public class ServiceHelper extends AbstractContextual { * Classes to scan when searching for dependencies. Data structure is a map * with keys being relevant classes, and values being associated priorities. */ - private final Map, Double> classPoolMap; + private final Map, PluginInfo> classPoolMap; /** Classes to scan when searching for dependencies, sorted by priority. */ private final List> classPoolList; @@ -302,9 +300,10 @@ private S createServiceRecursively(final Class c) final S service = c.newInstance(); service.setContext(getContext()); - // propagate priority if known - final Double priority = classPoolMap.get(c); - if (priority != null) service.setPriority(priority); + // propagate plugin metadata and priority if known + final PluginInfo info = classPoolMap.get(c); + service.setInfo(info); + if (info != null) service.setPriority(info.getPriority()); // NB: If there are any @EventHandler annotated methods, we treat the // EventService as a required dependency, _unless_ there is also an @@ -357,7 +356,7 @@ private S createServiceRecursively(final Class c) /** Asks the plugin index for all available service implementations. */ private void findServiceClasses( - final Map, Double> serviceMap, + final Map, PluginInfo> serviceMap, final List> serviceList) { // ask the plugin index for the (sorted) list of available services @@ -367,8 +366,7 @@ private void findServiceClasses( for (final PluginInfo info : services) { try { final Class c = info.loadClass(); - final double priority = info.getPriority(); - serviceMap.put(c, priority); + serviceMap.put(c, info); serviceList.add(c); } catch (final Throwable e) { diff --git a/src/main/java/org/scijava/service/ServiceIndex.java b/src/main/java/org/scijava/service/ServiceIndex.java index 23f48642e..910c6273d 100644 --- a/src/main/java/org/scijava/service/ServiceIndex.java +++ b/src/main/java/org/scijava/service/ServiceIndex.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/service/event/ServicesLoadedEvent.java b/src/main/java/org/scijava/service/event/ServicesLoadedEvent.java index 93f8588fd..e2fc6e3fe 100644 --- a/src/main/java/org/scijava/service/event/ServicesLoadedEvent.java +++ b/src/main/java/org/scijava/service/event/ServicesLoadedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/startup/DefaultStartupService.java b/src/main/java/org/scijava/startup/DefaultStartupService.java new file mode 100644 index 000000000..d3b253a58 --- /dev/null +++ b/src/main/java/org/scijava/startup/DefaultStartupService.java @@ -0,0 +1,73 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.startup; + +import java.util.ArrayDeque; +import java.util.Deque; + +import org.scijava.log.LogService; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.service.AbstractService; +import org.scijava.service.Service; + +/** + * Default implementation of {@link StartupService}. + * + * @author Curtis Rueden + */ +@Plugin(type = Service.class) +public class DefaultStartupService extends AbstractService implements + StartupService +{ + + private final Deque operations = new ArrayDeque<>(); + + @Parameter(required = false) + private LogService log; + + @Override + public void addOperation(final Runnable operation) { + operations.add(operation); + } + + @Override + public void executeOperations() { + while (!operations.isEmpty()) { + final Runnable operation = operations.pop(); + try { + operation.run(); + } + catch (final RuntimeException exc) { + if (log != null) log.error(exc); + } + } + } +} diff --git a/src/main/java/org/scijava/io/DataHandleService.java b/src/main/java/org/scijava/startup/StartupService.java similarity index 65% rename from src/main/java/org/scijava/io/DataHandleService.java rename to src/main/java/org/scijava/startup/StartupService.java index 723461870..6446b3447 100644 --- a/src/main/java/org/scijava/io/DataHandleService.java +++ b/src/main/java/org/scijava/startup/StartupService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -29,35 +27,23 @@ * #L% */ -package org.scijava.io; +package org.scijava.startup; -import org.scijava.plugin.WrapperService; import org.scijava.service.SciJavaService; /** - * Interface for low-level data I/O: reading and writing bytes using - * {@link DataHandle}s. + * Interface for service managing startup operations. * * @author Curtis Rueden - * @see IOService - * @see Location */ -public interface DataHandleService extends - WrapperService>, SciJavaService -{ +public interface StartupService extends SciJavaService { - // -- PTService methods -- + /** Adds an operation that will run as soon as the application starts up. */ + void addOperation(Runnable r); - @Override - @SuppressWarnings({ "rawtypes", "unchecked" }) - default Class> getPluginType() { - return (Class) DataHandle.class; - } - - // -- Typed methods -- - - @Override - default Class getType() { - return Location.class; - } + /** + * Execute all registered startup operations, in the order they were + * registered, blocking until complete. + */ + void executeOperations(); } diff --git a/src/main/java/org/scijava/task/DefaultTask.java b/src/main/java/org/scijava/task/DefaultTask.java new file mode 100644 index 000000000..310dd8f15 --- /dev/null +++ b/src/main/java/org/scijava/task/DefaultTask.java @@ -0,0 +1,249 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.scijava.task; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import org.scijava.event.EventService; +import org.scijava.task.event.TaskEvent; +import org.scijava.thread.ThreadService; + +/** + * Default implementation of {@link Task}. Throughout the task (or job), + * {@link Task#setProgressValue(long)} can be called to inform + * how the job is progressing. + *

+ * Asynchronous case: + * A job (runnable) is sent for execution to the linked {@link ThreadService}. + * It reports status updates via the linked {@link EventService}. + * A {@link org.scijava.task.event.TaskEvent} is sent before the job + * is started and when finished. + * In the asynchronous case, upon task cancellation ({@link Task#cancel(String)} call), + * the runnable associated to the ThreadService is attempted to be stopped + * by calling {@link Future#cancel(boolean)}. + * This default behaviour can be supplemented by an additional + * custom callback which can be set in {@link Task#setCancelCallBack(Runnable)}. + *

+ *

+ * Synchronous case: + * A job that reports its status in between calls of {@link Task#start()}, + * and {@link Task#finish()}. It also reports its status via + * the linked {@link EventService}. + * Start and finish calls allow publishing proper {@link org.scijava.task.event.TaskEvent} + * to subscribers (with the EventService). + * Upon cancellation of a synchronous task, it is the responsibility + * of the synchronous task to handle its own cancellation through + * a custom callback which can be set via {@link Task#setCancelCallBack(Runnable)}. + *

+ * + * @author Curtis Rueden + * @author Nicolas Chiaruttini + */ +public class DefaultTask implements Task { + + private final ThreadService threadService; + private final EventService eventService; + + private Future future; + + private boolean canceled; + private String cancelReason; + volatile boolean isDone = false; + + private String status; + private long step; + private long max; + + private String name; + + private Runnable cancelCallBack; + + /** + * Creates a new task. + * + * @param threadService Service to use for launching the task in its own + * thread. Required. + * @param eventService Service to use for reporting status updates as + * {@link TaskEvent}s. May be null, in which case no events are + * reported. + */ + public DefaultTask(final ThreadService threadService, + final EventService eventService) + { + this.threadService = threadService; + this.eventService = eventService; + cancelCallBack = this::defaultCancelCallback; + } + + // -- Task methods -- + + // - Asynchronous + @Override + public void run(final Runnable r) { + if (r == null) throw new NullPointerException(); + future(r); + } + + // - Asynchronous + @Override + public void waitFor() throws InterruptedException, ExecutionException { + future().get(); + } + + // - Synchronous + @Override + public void start() { + fireTaskEvent(); + } + + // - Synchronous + @Override + public void finish() { + isDone = true; + fireTaskEvent(); + } + + @Override + public boolean isDone() { + return (isDone) || (future != null && future.isDone()); + } + + @Override + public String getStatusMessage() { + return status; + } + + @Override + public long getProgressValue() { + return step; + } + + @Override + public long getProgressMaximum() { + return max; + } + + @Override + public void setStatusMessage(final String status) { + this.status = status; + fireTaskEvent(); + } + + @Override + public void setProgressValue(final long step) { + this.step = step; + fireTaskEvent(); + } + + @Override + public void setProgressMaximum(final long max) { + this.max = max; + fireTaskEvent(); + } + + // -- Cancelable methods -- + + @Override + public boolean isCanceled() { + return canceled; + } + + @Override + public void cancel(final String reason) { + canceled = true; + cancelReason = reason; + if (cancelCallBack != null) cancelCallBack.run(); + fireTaskEvent(); + } + + void defaultCancelCallback() { + if (future != null) { + isDone = future.cancel(true); + } + } + + @Override + public void setCancelCallBack(Runnable r) { + this.cancelCallBack = r; + } + + @Override + public Runnable getCancelCallBack() { + return this.cancelCallBack; + } + + @Override + public String getCancelReason() { + return cancelReason; + } + + // -- Named methods -- + + @Override + public String getName() { + return name; + } + + @Override + public void setName(final String name) { + this.name = name; + } + + // -- Helper methods -- + + private Future future() { + return future(null); + } + + private Future future(final Runnable r) { + if (future == null) initFuture(r); + return future; + } + + private synchronized void initFuture(final Runnable r) { + if (future != null) return; + if (r == null) throw new IllegalArgumentException("Must call run first"); + future = threadService.run(() -> { + try { + fireTaskEvent(); // Triggers an event just before the task is executed + r.run(); + } + finally { + isDone = true; + fireTaskEvent(); // Triggers an event just after the task has + // successfully completed or failed + } + }); + } + + private void fireTaskEvent() { + if (eventService != null) eventService.publish(new TaskEvent(this)); + } +} diff --git a/src/main/java/org/scijava/task/DefaultTaskService.java b/src/main/java/org/scijava/task/DefaultTaskService.java new file mode 100644 index 000000000..7a2016f51 --- /dev/null +++ b/src/main/java/org/scijava/task/DefaultTaskService.java @@ -0,0 +1,61 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.task; + +import org.scijava.event.EventService; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.service.AbstractService; +import org.scijava.service.Service; +import org.scijava.thread.ThreadService; + +/** + * Default implementation of {@link TaskService}. + * + * @author Curtis Rueden + */ +@Plugin(type = Service.class) +public class DefaultTaskService extends AbstractService implements + TaskService +{ + + @Parameter + private ThreadService threadService; + + @Parameter(required = false) + private EventService eventService; + + @Override + public Task createTask(String name) { + final DefaultTask task = new DefaultTask(threadService, eventService); + task.setName(name); + return task; + } +} diff --git a/src/main/java/org/scijava/task/Task.java b/src/main/java/org/scijava/task/Task.java new file mode 100644 index 000000000..1dd539ab5 --- /dev/null +++ b/src/main/java/org/scijava/task/Task.java @@ -0,0 +1,154 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.task; + +import java.util.concurrent.ExecutionException; + +import org.scijava.Cancelable; +import org.scijava.Named; + +/** + * A self-aware job which reports its status and progress as it runs. + * + * There are two ways to use a Task object: + * - A job can be run asynchronously by using {@link Task#run(Runnable)}, and + * can report its progression from within the Runnable. + * + * - A {@link Task} object can simply be used to report in a synchronous manner + * the progression of a piece of code. In the case of synchronous reporting, + * the job is considered started when {@link Task#start()} is called and + * finished when {@link Task#finish()} is called. A finished job can be finished + * either because it is done or because it has been cancelled. + * + * A cancel callback can be set with {@link Task#setCancelCallBack(Runnable)}. + * The runnable argument will be executed in the case of an external event + * requesting a cancellation of the task - typically, if a user clicks + * a cancel button on the GUI, task.cancel("User cancellation requested") will + * be called. As a result, the task implementors should run the callback. + * This callback can be used to make the task aware that a cancellation + * has been requested, and should proceed to stop its execution. + * + * See also {@link TaskService}, {@link DefaultTask} + * + * @author Curtis Rueden, Nicolas Chiaruttini + */ +public interface Task extends Cancelable, Named { + + /** + * Starts running the task - asynchronous job + * + * @throws IllegalStateException if the task was already started. + */ + void run(Runnable r); + + /** + * Waits for the task to complete - asynchronous job + * + * @throws IllegalStateException if {@link #run} has not been called yet. + * @throws InterruptedException if the task is interrupted. + * @throws ExecutionException if the task throws an exception while running. + */ + void waitFor() throws InterruptedException, ExecutionException; + + /** + * reports that the task is started - synchronous job + */ + default void start() {} + + /** + * reports that the task is finished - synchronous job + */ + default void finish() {} + + /** Checks whether the task has completed. */ + boolean isDone(); + + /** Gets a status message describing what the task is currently doing. */ + String getStatusMessage(); + + /** + * Gets the step the task is currently performing. + * + * @return A value between 0 and {@link #getProgressMaximum()} inclusive. + * @see #getProgressMaximum() + */ + long getProgressValue(); + + /** + * Gets the number of steps the task performs in total. + * + * @return Total number of steps the task will perform, or 0 if unknown. + * @see #getProgressValue() + */ + long getProgressMaximum(); + + /** + * Sets the status message. Called by task implementors. + * + * @param status The message to set. + * @see #getStatusMessage() + */ + void setStatusMessage(String status); + + /** + * Sets the current step. Called by task implementors. + * + * @param step The step vaule to set. + * @see #getProgressValue() + */ + void setProgressValue(long step); + + /** + * Sets the total number of steps. Called by task implementors. + * + * @param max The step count to set. + * @see #getProgressMaximum() + */ + void setProgressMaximum(long max); + + /** + * If the task is cancelled (external call to {@link Task#cancel(String)}), + * the input runnable argument should be executed by task implementors. + * + * @param runnable : should be executed if this task is cancelled through + * {@link Task#cancel(String)} + */ + default void setCancelCallBack(Runnable runnable) {} + + /** + * Returns the current cancel callback runnable, This can be used to + * concatenate callbacks in order, for instance, to ask for a user + * confirmation before cancelling the task + */ + default Runnable getCancelCallBack() { + return () -> {}; + } + +} diff --git a/src/main/java/org/scijava/task/TaskService.java b/src/main/java/org/scijava/task/TaskService.java new file mode 100644 index 000000000..d3a46b7f9 --- /dev/null +++ b/src/main/java/org/scijava/task/TaskService.java @@ -0,0 +1,50 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.task; + +import org.scijava.service.SciJavaService; + +/** + * Service for working with {@link Task}s. + * + * @author Curtis Rueden + * @see Task + */ +public interface TaskService extends SciJavaService { + + /** + * Creates a new, empty {@link Task}. It is the responsibility of the caller + * to then launch the task via the {@link Task#run(Runnable)} method. + * + * @param name The task's name, to differentiate it from others. + * @return A newly created task which awaits execution. + */ + Task createTask(String name); +} diff --git a/src/main/java/org/scijava/task/event/TaskEvent.java b/src/main/java/org/scijava/task/event/TaskEvent.java new file mode 100644 index 000000000..bb4cfb4dd --- /dev/null +++ b/src/main/java/org/scijava/task/event/TaskEvent.java @@ -0,0 +1,59 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.scijava.task.event; + +import org.scijava.event.SciJavaEvent; +import org.scijava.task.Task; + +/** + * An event indicating a {@link Task} has been updated. + * + * @author Curtis Rueden + */ +public class TaskEvent extends SciJavaEvent { + + private final Task task; + + public TaskEvent(final Task task) { + this.task = task; + } + + // -- TaskEvent methods -- + + public Task getTask() { + return task; + } + + // -- Object methods -- + + @Override + public String toString() { + return super.toString() + "\n\ttask = " + task; + } +} diff --git a/src/main/java/org/scijava/test/TestUtils.java b/src/main/java/org/scijava/test/TestUtils.java index fd7e48eee..fd4ed17c7 100644 --- a/src/main/java/org/scijava/test/TestUtils.java +++ b/src/main/java/org/scijava/test/TestUtils.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -37,8 +35,8 @@ import java.util.AbstractMap; import java.util.Map; -import org.scijava.util.ClassUtils; import org.scijava.util.FileUtils; +import org.scijava.util.Types; /** * A bunch of helpful functions for unit tests. @@ -127,7 +125,7 @@ public static File createTemporaryDirectory(final String prefix, public static File createTemporaryDirectory(final String prefix, final Class forClass, final String suffix) throws IOException { - final URL directory = ClassUtils.getLocation(forClass); + final URL directory = Types.location(forClass); if (directory == null) { throw new IllegalArgumentException("No location for class " + forClass); } diff --git a/src/main/java/org/scijava/text/AbstractTextFormat.java b/src/main/java/org/scijava/text/AbstractTextFormat.java index 032dae0af..f07498edd 100644 --- a/src/main/java/org/scijava/text/AbstractTextFormat.java +++ b/src/main/java/org/scijava/text/AbstractTextFormat.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -43,5 +41,14 @@ public abstract class AbstractTextFormat extends AbstractHandlerPlugin implements TextFormat { - // NB: No implementation needed. + // -- Typed methods -- + + @Override + public boolean supports(final File data) { + // NB: This override is necessary, because the default super is + // AbstractHandlerPlugin->AbstractTypedPlugin->TypedPlugin->Typed, + // which fails to invoke the needed TextFormat.super. + // See fiji/fiji#303 and fiji/HDF5_Vibez#18. + return TextFormat.super.supports(data); + } } diff --git a/src/main/java/org/scijava/text/DefaultTextService.java b/src/main/java/org/scijava/text/DefaultTextService.java index 80e23acbc..9a562a334 100644 --- a/src/main/java/org/scijava/text/DefaultTextService.java +++ b/src/main/java/org/scijava/text/DefaultTextService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -61,17 +59,13 @@ public final class DefaultTextService extends @Override public String open(final File file) throws IOException { - // This routine is from: http://stackoverflow.com/a/326440 - final FileInputStream stream = new FileInputStream(file); - try { + // This routine is from: https://stackoverflow.com/a/326440 + try (final FileInputStream stream = new FileInputStream(file)) { final FileChannel fc = stream.getChannel(); final MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); return Charset.defaultCharset().decode(bb).toString(); } - finally { - stream.close(); - } } @Override diff --git a/src/main/java/org/scijava/text/TextFormat.java b/src/main/java/org/scijava/text/TextFormat.java index 3aa6122c8..66f5fc405 100644 --- a/src/main/java/org/scijava/text/TextFormat.java +++ b/src/main/java/org/scijava/text/TextFormat.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -65,6 +63,7 @@ public interface TextFormat extends HandlerPlugin { @Override default boolean supports(final File file) { + if (!HandlerPlugin.super.supports(file)) return false; for (final String ext : getExtensions()) { if (FileUtils.getExtension(file).equalsIgnoreCase(ext)) return true; } diff --git a/src/main/java/org/scijava/text/TextService.java b/src/main/java/org/scijava/text/TextService.java index 3d16e46e3..6884d700f 100644 --- a/src/main/java/org/scijava/text/TextService.java +++ b/src/main/java/org/scijava/text/TextService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/text/io/DefaultTextIOService.java b/src/main/java/org/scijava/text/io/DefaultTextIOService.java new file mode 100644 index 000000000..a16fc1933 --- /dev/null +++ b/src/main/java/org/scijava/text/io/DefaultTextIOService.java @@ -0,0 +1,43 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.text.io; + +import org.scijava.io.AbstractTypedIOService; +import org.scijava.plugin.Plugin; +import org.scijava.service.Service; + +/** + * Default {@link TextIOService} implementation for opening and saving text data + * + * @author Deborah Schmidt + */ +@Plugin(type = Service.class) +public class DefaultTextIOService extends AbstractTypedIOService implements TextIOService { +} diff --git a/src/main/java/org/scijava/text/io/TextIOPlugin.java b/src/main/java/org/scijava/text/io/TextIOPlugin.java index 5311ba524..cd4d4e5c4 100644 --- a/src/main/java/org/scijava/text/io/TextIOPlugin.java +++ b/src/main/java/org/scijava/text/io/TextIOPlugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -31,12 +29,13 @@ package org.scijava.text.io; -import java.io.File; import java.io.IOException; import org.scijava.Priority; import org.scijava.io.AbstractIOPlugin; import org.scijava.io.IOPlugin; +import org.scijava.io.location.FileLocation; +import org.scijava.io.location.Location; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; import org.scijava.text.TextService; @@ -47,7 +46,7 @@ * @author Curtis Rueden * @see TextService */ -@Plugin(type = IOPlugin.class, priority = Priority.LOW_PRIORITY - 1) +@Plugin(type = IOPlugin.class, priority = Priority.LOW - 1) public class TextIOPlugin extends AbstractIOPlugin { @Parameter(required = false) @@ -61,15 +60,19 @@ public Class getDataType() { } @Override - public boolean supportsOpen(final String source) { + public boolean supportsOpen(final Location source) { if (textService == null) return false; // no service for opening text files - return textService.supports(new File(source)); + if (!(source instanceof FileLocation)) return false; + final FileLocation loc = (FileLocation) source; + return textService.supports(loc.getFile()); } @Override - public String open(final String source) throws IOException { + public String open(final Location source) throws IOException { if (textService == null) return null; // no service for opening text files - return textService.asHTML(new File(source)); + if (!(source instanceof FileLocation)) throw new IllegalArgumentException(); + final FileLocation loc = (FileLocation) source; + return textService.asHTML(loc.getFile()); } } diff --git a/src/main/java/org/scijava/text/io/TextIOService.java b/src/main/java/org/scijava/text/io/TextIOService.java new file mode 100644 index 000000000..d21d11c87 --- /dev/null +++ b/src/main/java/org/scijava/text/io/TextIOService.java @@ -0,0 +1,40 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.text.io; + +import org.scijava.io.TypedIOService; + +/** + * {@link TypedIOService} for opening and saving text data + * + * @author Deborah Schmidt + */ +public interface TextIOService extends TypedIOService { +} diff --git a/src/main/java/org/scijava/thread/DefaultThreadService.java b/src/main/java/org/scijava/thread/DefaultThreadService.java index 945a6fe43..121dc74e1 100644 --- a/src/main/java/org/scijava/thread/DefaultThreadService.java +++ b/src/main/java/org/scijava/thread/DefaultThreadService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -33,11 +31,15 @@ import java.awt.EventQueue; import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; import org.scijava.log.LogService; import org.scijava.plugin.Parameter; @@ -57,6 +59,8 @@ public final class DefaultThreadService extends AbstractService implements private static final String SCIJAVA_THREAD_PREFIX = "SciJava-"; + private static final long SHUTDOWN_TIMEOUT = 5000; + private static WeakHashMap parents = new WeakHashMap<>(); @@ -65,6 +69,9 @@ public final class DefaultThreadService extends AbstractService implements private ExecutorService executor; + /** Mapping from ID to single-thread {@link ExecutorService} queue. */ + private Map queues; + private int nextThread = 0; private boolean disposed; @@ -117,6 +124,16 @@ public void queue(final Runnable code) { EventQueue.invokeLater(wrap(code)); } + @Override + public Future queue(final String id, final Runnable code) { + return executor(id).submit(wrap(code)); + } + + @Override + public Future queue(final String id, final Callable code) { + return executor(id).submit(wrap(code)); + } + @Override public Thread getParent(final Thread thread) { return parents.get(thread != null ? thread : Thread.currentThread()); @@ -141,9 +158,29 @@ public ThreadContext getThreadContext(final Thread thread) { // -- Disposable methods -- @Override - public void dispose() { + public synchronized void dispose() { disposed = true; - if (executor != null) executor.shutdown(); + if (executor != null) { + executor.shutdown(); + try { + executor.awaitTermination(SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS); + } + catch (final InterruptedException exc) { + log.debug(exc); + } + executor = null; + } + if (queues != null) { + for (final ExecutorService queue : queues.values()) { + queue.shutdown(); + try { + queue.awaitTermination(SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS); + } + catch (final InterruptedException exc) { + log.debug(exc); + } + } + } } // -- ThreadFactory methods -- @@ -151,48 +188,66 @@ public void dispose() { @Override public Thread newThread(final Runnable r) { final String threadName = contextThreadPrefix() + nextThread++; - return new Thread(r, threadName); + final Thread thread = new Thread(r, threadName); + // NB: Use daemon threads for the thread pool, so that idling threads do + // not prevent the JVM shutdown sequence from starting. The application + // context, and therefore the thread service, will try to dispose itself + // upon JVM shutdown, which will invoke executor.awaitTermination(), so + // there will be a chance for these threads to complete any pending work. + thread.setDaemon(true); + return thread; } // -- Helper methods -- private ExecutorService executor() { - if (executor == null) { - executor = Executors.newCachedThreadPool(this); - } + if (executor == null) initExecutor(); return executor; } + private synchronized ExecutorService executor(final String id) { + if (disposed) return null; + if (queues == null) queues = new HashMap<>(); + if (!queues.containsKey(id)) { + final ThreadFactory factory = r -> { + final String threadName = contextThreadPrefix() + id; + return new Thread(r, threadName); + }; + final ExecutorService queue = Executors.newSingleThreadExecutor(factory); + queues.put(id, queue); + } + return queues.get(id); + } + + private synchronized void initExecutor() { + if (executor != null) return; + executor = Executors.newCachedThreadPool(this); + } + private Runnable wrap(final Runnable r) { final Thread parent = Thread.currentThread(); - return new Runnable() { - @Override - public void run() { - final Thread thread = Thread.currentThread(); - try { - if (parent != thread) parents.put(thread, parent); - r.run(); - } - finally { - if (parent != thread) parents.remove(thread); - } + return () -> { + final Thread thread = Thread.currentThread(); + try { + if (parent != thread) parents.put(thread, parent); + r.run(); + } + finally { + if (parent != thread) parents.remove(thread); } }; } private Callable wrap(final Callable c) { final Thread parent = Thread.currentThread(); - return new Callable() { - @Override - public V call() throws Exception { - final Thread thread = Thread.currentThread(); - try { - if (parent != thread) parents.put(thread, parent); - return c.call(); - } - finally { - if (parent != thread) parents.remove(thread); - } + return () -> { + final Thread thread = Thread.currentThread(); + try { + if (parent != thread) parents.put(thread, parent); + return c.call(); + } + finally { + if (parent != thread) parents.remove(thread); } }; } diff --git a/src/main/java/org/scijava/thread/ThreadService.java b/src/main/java/org/scijava/thread/ThreadService.java index 7be845d97..3531fc2a3 100644 --- a/src/main/java/org/scijava/thread/ThreadService.java +++ b/src/main/java/org/scijava/thread/ThreadService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -112,7 +110,7 @@ public enum ThreadContext { /** * Gets whether the current thread is a dispatch thread for use with - * {@link #invoke} and {@link #queue}. + * {@link #invoke(Runnable)} and {@link #queue(Runnable)}. *

* In the case of AWT-based applications (e.g., Java on the desktop), this is * typically the AWT Event Dispatch Thread (EDT). However, ultimately the @@ -141,7 +139,8 @@ void invoke(Runnable code) throws InterruptedException, InvocationTargetException; /** - * Queues the given code for later execution in a special dispatch thread. + * Queues the given code for later execution in a special dispatch thread, + * returning immediately. *

* In the case of AWT-based applications (e.g., Java on the desktop), this is * typically the AWT Event Dispatch Thread (EDT). However, ultimately the @@ -152,6 +151,31 @@ void invoke(Runnable code) throws InterruptedException, */ void queue(Runnable code); + /** + * Queues the given code for later execution in a dispatch thread associated + * with the specified ID, returning immediately. + * + * @param id The ID designating which dispatch thread will execute the code. + * @param code The code to execute. + * @return A {@link Future} whose {@link Future#get()} method blocks until the + * queued code has completed executing and returns {@code null}. + * @see ExecutorService#submit(Runnable) + */ + Future queue(String id, Runnable code); + + /** + * Queues the given code for later execution in a dispatch thread associated + * with the specified ID, returning immediately. + * + * @param id The ID designating which dispatch thread will execute the code. + * @param code The code to execute. + * @return A {@link Future} whose {@link Future#get()} method blocks until the + * queued code has completed executing and returns the result of the + * execution. + * @see ExecutorService#submit(Callable) + */ + Future queue(String id, Callable code); + /** * Returns the thread that called the specified thread. *

diff --git a/src/main/java/org/scijava/tool/AbstractTool.java b/src/main/java/org/scijava/tool/AbstractTool.java index b28ddd785..35f3d0d0d 100644 --- a/src/main/java/org/scijava/tool/AbstractTool.java +++ b/src/main/java/org/scijava/tool/AbstractTool.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/tool/CustomDrawnTool.java b/src/main/java/org/scijava/tool/CustomDrawnTool.java index 1bd46df11..b7e766542 100644 --- a/src/main/java/org/scijava/tool/CustomDrawnTool.java +++ b/src/main/java/org/scijava/tool/CustomDrawnTool.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/tool/DefaultToolService.java b/src/main/java/org/scijava/tool/DefaultToolService.java index 47e1c89a7..a8ae89fe7 100644 --- a/src/main/java/org/scijava/tool/DefaultToolService.java +++ b/src/main/java/org/scijava/tool/DefaultToolService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/tool/DummyTool.java b/src/main/java/org/scijava/tool/DummyTool.java index 208f8b497..f8b90807e 100644 --- a/src/main/java/org/scijava/tool/DummyTool.java +++ b/src/main/java/org/scijava/tool/DummyTool.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/tool/IconDrawer.java b/src/main/java/org/scijava/tool/IconDrawer.java index ab95d0f8d..1c4778eb1 100644 --- a/src/main/java/org/scijava/tool/IconDrawer.java +++ b/src/main/java/org/scijava/tool/IconDrawer.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/tool/IconService.java b/src/main/java/org/scijava/tool/IconService.java index ecb572f53..8c7473dcb 100644 --- a/src/main/java/org/scijava/tool/IconService.java +++ b/src/main/java/org/scijava/tool/IconService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/tool/Tool.java b/src/main/java/org/scijava/tool/Tool.java index 43267f76a..b42af857e 100644 --- a/src/main/java/org/scijava/tool/Tool.java +++ b/src/main/java/org/scijava/tool/Tool.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/tool/ToolService.java b/src/main/java/org/scijava/tool/ToolService.java index 4dca68db9..061f3fed4 100644 --- a/src/main/java/org/scijava/tool/ToolService.java +++ b/src/main/java/org/scijava/tool/ToolService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/tool/event/ToolActivatedEvent.java b/src/main/java/org/scijava/tool/event/ToolActivatedEvent.java index 55fb9e038..3ab52fca2 100644 --- a/src/main/java/org/scijava/tool/event/ToolActivatedEvent.java +++ b/src/main/java/org/scijava/tool/event/ToolActivatedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/tool/event/ToolDeactivatedEvent.java b/src/main/java/org/scijava/tool/event/ToolDeactivatedEvent.java index 62a895a5c..fc61e8332 100644 --- a/src/main/java/org/scijava/tool/event/ToolDeactivatedEvent.java +++ b/src/main/java/org/scijava/tool/event/ToolDeactivatedEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/tool/event/ToolEvent.java b/src/main/java/org/scijava/tool/event/ToolEvent.java index 0062595c6..f55392dc8 100644 --- a/src/main/java/org/scijava/tool/event/ToolEvent.java +++ b/src/main/java/org/scijava/tool/event/ToolEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/ARGBPlane.java b/src/main/java/org/scijava/ui/ARGBPlane.java index 370c4668e..7d158e366 100644 --- a/src/main/java/org/scijava/ui/ARGBPlane.java +++ b/src/main/java/org/scijava/ui/ARGBPlane.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/AbstractInputHarvesterPlugin.java b/src/main/java/org/scijava/ui/AbstractInputHarvesterPlugin.java index f7c566aed..71afdf0cc 100644 --- a/src/main/java/org/scijava/ui/AbstractInputHarvesterPlugin.java +++ b/src/main/java/org/scijava/ui/AbstractInputHarvesterPlugin.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/AbstractUIInputWidget.java b/src/main/java/org/scijava/ui/AbstractUIInputWidget.java index c76edb136..04561a123 100644 --- a/src/main/java/org/scijava/ui/AbstractUIInputWidget.java +++ b/src/main/java/org/scijava/ui/AbstractUIInputWidget.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -79,12 +77,7 @@ public void refreshWidget() { // on the EDT. if (ui().requiresEDT()) { try { - threadService.invoke(new Runnable() { - @Override - public void run() { - doRefresh(); - } - }); + threadService.invoke(() -> doRefresh()); } catch (InterruptedException e) { log.error("Interrupted while refresh widget: " + getClass(), e); diff --git a/src/main/java/org/scijava/ui/AbstractUserInterface.java b/src/main/java/org/scijava/ui/AbstractUserInterface.java index b625f5d0f..8b5e426a5 100644 --- a/src/main/java/org/scijava/ui/AbstractUserInterface.java +++ b/src/main/java/org/scijava/ui/AbstractUserInterface.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -43,7 +41,6 @@ import org.scijava.prefs.PrefService; import org.scijava.thread.ThreadService; import org.scijava.ui.viewer.DisplayViewer; -import org.scijava.ui.viewer.DisplayWindow; /** * Abstract superclass for {@link UserInterface} implementations. @@ -82,6 +79,7 @@ public abstract class AbstractUserInterface extends AbstractRichPlugin @Override public void show() { + if (visible) return; createUI(); visible = true; } @@ -137,12 +135,8 @@ public void show(final Display display) { threadService.queue(new Runnable() { @Override public void run() { - final DisplayWindow displayWindow = createDisplayWindow(display); - finalViewer.view(displayWindow, display); - displayWindow.setTitle(display.getName()); uiService.addDisplayViewer(finalViewer); - displayWindow.showDisplay(true); - display.update(); + finalViewer.view(AbstractUserInterface.this, display); } }); } diff --git a/src/main/java/org/scijava/ui/ApplicationFrame.java b/src/main/java/org/scijava/ui/ApplicationFrame.java index 761bd52ef..571ab621e 100644 --- a/src/main/java/org/scijava/ui/ApplicationFrame.java +++ b/src/main/java/org/scijava/ui/ApplicationFrame.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/Arrangeable.java b/src/main/java/org/scijava/ui/Arrangeable.java index 9a3649fab..1b92b5768 100644 --- a/src/main/java/org/scijava/ui/Arrangeable.java +++ b/src/main/java/org/scijava/ui/Arrangeable.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/CloseConfirmable.java b/src/main/java/org/scijava/ui/CloseConfirmable.java index c1e7f905e..73e657b63 100644 --- a/src/main/java/org/scijava/ui/CloseConfirmable.java +++ b/src/main/java/org/scijava/ui/CloseConfirmable.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/DefaultUIService.java b/src/main/java/org/scijava/ui/DefaultUIService.java index 8b88490ca..7c8103a4c 100644 --- a/src/main/java/org/scijava/ui/DefaultUIService.java +++ b/src/main/java/org/scijava/ui/DefaultUIService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -31,7 +29,10 @@ package org.scijava.ui; +import java.awt.GraphicsEnvironment; import java.io.File; +import java.io.FileFilter; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -117,6 +118,17 @@ public final class DefaultUIService extends AbstractService implements /** The default user interface to use, if one is not explicitly specified. */ private UserInterface defaultUI; + /** The last UI used when performing UI operations via the service. */ + private UserInterface activeUI; + + /** + * When true, {@link #isHeadless()} will return true regardless of the value + * of the {@code java.awt.headless} system property. When false, {@link + * #isHeadless()} matches the global JVM headless state defined by {@code + * java.awt.headless}. + */ + private boolean forceHeadless; + private boolean activationInvocationPending = false; // -- UIService methods -- @@ -135,14 +147,12 @@ public void addUI(final String name, final UserInterface ui) { @Override public void showUI() { if (disposed) return; - final UserInterface ui = getDefaultUI(); - if (ui == null) throw noUIsAvailableException(); - showUI(ui); + showUI(activeUI()); } @Override public void showUI(final String name) { - final UserInterface ui = uiMap().get(name); + final UserInterface ui = getUI(name); if (ui == null) { throw new IllegalArgumentException("No such user interface: " + name); } @@ -152,43 +162,56 @@ public void showUI(final String name) { @Override public void showUI(final UserInterface ui) { log.debug("Launching user interface: " + ui.getClass().getName()); - ui.show(); - // NB: Also show all the current displays. - for (final Display display : displayService.getDisplays()) { - ui.show(display); + Runnable showUI = () -> { + ui.show(); + // NB: Also show all the current displays. + for (final Display display : displayService.getDisplays()) { + ui.show(display); + } + }; + + // Dispatch on EDT if necessary + if (ui.requiresEDT()) { + try { + threadService.invoke(showUI); + } + catch (InterruptedException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + else { + showUI.run(); } eventService.publish(new UIShownEvent(ui)); } @Override public boolean isVisible() { - final UserInterface ui = getDefaultUI(); - if (ui == null) throw noUIsAvailableException(); - return ui.isVisible(); + return activeUI().isVisible(); } @Override public boolean isVisible(final String name) { - final UserInterface ui = uiMap().get(name); - if (ui == null) { - throw new IllegalArgumentException("No such user interface: " + name); - } - return ui.isVisible(); + final UserInterface ui = getUI(name); + return ui != null && ui.isVisible(); } @Override public void setHeadless(final boolean headless) { - System.setProperty("java.awt.headless", String.valueOf(headless)); + forceHeadless = headless; } @Override public boolean isHeadless() { - return Boolean.getBoolean("java.awt.headless"); + return forceHeadless || + Boolean.getBoolean("java.awt.headless") || + GraphicsEnvironment.isHeadless(); } @Override public UserInterface getDefaultUI() { - if (isHeadless()) return uiMap().get(HeadlessUI.NAME); + if (!initialized) discoverUIs(); + if (isHeadless()) return getUI(HeadlessUI.NAME); if (defaultUI != null) return defaultUI; return uiList().isEmpty() ? null : uiList().get(0); } @@ -232,17 +255,17 @@ public List>> getViewerPlugins() { @Override public void show(final Object o) { - getDefaultUI().show(o); + activeUI().show(o); } @Override public void show(final String name, final Object o) { - getDefaultUI().show(name, o); + activeUI().show(name, o); } @Override public void show(final Display display) { - getDefaultUI().show(display); + activeUI().show(display); } @Override @@ -297,33 +320,38 @@ public DialogPrompt.Result showDialog(final String message, final String title, final DialogPrompt.MessageType messageType, final DialogPrompt.OptionType optionType) { - final UserInterface ui = getDefaultUI(); - if (ui == null) return null; - final DialogPrompt dialogPrompt = - ui.dialogPrompt(message, title, messageType, optionType); + final DialogPrompt dialogPrompt = // + activeUI().dialogPrompt(message, title, messageType, optionType); return dialogPrompt == null ? null : dialogPrompt.prompt(); } @Override public File chooseFile(final File file, final String style) { - final UserInterface ui = getDefaultUI(); - return ui == null ? null : ui.chooseFile(file, style); + return activeUI().chooseFile(file, style); } @Override public File chooseFile(final String title, final File file, final String style) { - final UserInterface ui = getDefaultUI(); - return ui == null ? null : ui.chooseFile(title, file, style); + return activeUI().chooseFile(title, file, style); + } + + @Override + public File[] chooseFiles(File parent, File[] files, FileFilter filter, String style) { + return activeUI().chooseFiles(parent, files, filter, style); + } + + @Override + public List chooseFiles(File parent, List fileList, FileFilter filter, String style) { + return activeUI().chooseFiles(parent, fileList, filter, style); } @Override public void showContextMenu(final String menuRoot, final Display display, final int x, final int y) { - final UserInterface ui = getDefaultUI(); - if (ui != null) ui.showContextMenu(menuRoot, display, x, y); + activeUI().showContextMenu(menuRoot, display, x, y); } @Override @@ -421,7 +449,10 @@ public void run() { } @EventHandler - protected void onEvent(@SuppressWarnings("unused") final AppQuitEvent event) { + protected synchronized void onEvent( + @SuppressWarnings("unused") final AppQuitEvent event) + { + if (!initialized) return; for (final UserInterface ui : getVisibleUIs()) { ui.saveLocation(); } @@ -516,9 +547,16 @@ private String getTitle() { return appService.getApp().getTitle(); } - private IllegalStateException noUIsAvailableException() { - return new IllegalStateException("No UIs available. " + - "Please add a component containing a UIPlugin " + - "(e.g., scijava-ui-swing) to your class-path."); + /** Gets the UI to use when performing UI operations via the service. */ + private UserInterface activeUI() { + // If a particular UI is already active and still visible, use that one. + if (activeUI != null && activeUI.isVisible()) return activeUI; + + // If a UI is visible, use it. + final List visibleUIs = getVisibleUIs(); + if (visibleUIs.size() > 0) return activeUI = visibleUIs.get(0); + + // No UI is visible, so use the default one. + return activeUI = getDefaultUI(); } } diff --git a/src/main/java/org/scijava/ui/Desktop.java b/src/main/java/org/scijava/ui/Desktop.java index e2fd8df81..4e39f09dd 100644 --- a/src/main/java/org/scijava/ui/Desktop.java +++ b/src/main/java/org/scijava/ui/Desktop.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/DialogPrompt.java b/src/main/java/org/scijava/ui/DialogPrompt.java index 97b921a15..0f598875a 100644 --- a/src/main/java/org/scijava/ui/DialogPrompt.java +++ b/src/main/java/org/scijava/ui/DialogPrompt.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/FileListPreprocessor.java b/src/main/java/org/scijava/ui/FileListPreprocessor.java new file mode 100644 index 000000000..9d8da8f0b --- /dev/null +++ b/src/main/java/org/scijava/ui/FileListPreprocessor.java @@ -0,0 +1,94 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.scijava.ui; + +import java.io.File; + +import org.scijava.module.Module; +import org.scijava.module.ModuleItem; +import org.scijava.module.process.AbstractPreprocessorPlugin; +import org.scijava.module.process.PreprocessorPlugin; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.widget.InputHarvester; + +@Plugin(type = PreprocessorPlugin.class, priority = InputHarvester.PRIORITY + 1.0) +public class FileListPreprocessor extends AbstractPreprocessorPlugin { + + @Parameter(required = false) + private UIService uiService; + + @Override + public void process(final Module module) { + if (uiService == null) return; + final ModuleItem fileInput = getFilesInput(module); + if (fileInput == null) return; + + final File[] files = fileInput.getValue(module); + + // show file chooser dialog box + // TODO decide how to create filter from style attributes + // TODO retrieve parent folder?? + final File[] result = uiService.chooseFiles(null, files, null, fileInput.getWidgetStyle()); + if (result == null) { + cancel(""); + return; + } + + fileInput.setValue(module, result); + module.resolveInput(fileInput.getName()); + } + + // -- Helper methods -- + + /** + * Gets the single unresolved {@link File} input parameter. If there is not + * exactly one unresolved {@link File} input parameter, or if there are other + * types of unresolved parameters, this method returns null. + */ + private ModuleItem getFilesInput(final Module module) { + ModuleItem result = null; + for (final ModuleItem input : module.getInfo().inputs()) { + if (module.isInputResolved(input.getName())) continue; + final Class type = input.getType(); + if (!File[].class.isAssignableFrom(type)) { + // not a File[] parameter; abort + return null; + } + if (result != null) { + // second File parameter; abort + return null; + } + @SuppressWarnings("unchecked") + final ModuleItem fileInput = (ModuleItem) input; + result = fileInput; + } + return result; + } +} diff --git a/src/main/java/org/scijava/ui/FilePreprocessor.java b/src/main/java/org/scijava/ui/FilePreprocessor.java index bed279e2b..0e4a52d97 100644 --- a/src/main/java/org/scijava/ui/FilePreprocessor.java +++ b/src/main/java/org/scijava/ui/FilePreprocessor.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/StatusBar.java b/src/main/java/org/scijava/ui/StatusBar.java index 4d4a20f11..009a5a7e0 100644 --- a/src/main/java/org/scijava/ui/StatusBar.java +++ b/src/main/java/org/scijava/ui/StatusBar.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/SystemClipboard.java b/src/main/java/org/scijava/ui/SystemClipboard.java index ca9f8d925..a2f5b2c59 100644 --- a/src/main/java/org/scijava/ui/SystemClipboard.java +++ b/src/main/java/org/scijava/ui/SystemClipboard.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/ToolBar.java b/src/main/java/org/scijava/ui/ToolBar.java index d63910601..289dbc8b4 100644 --- a/src/main/java/org/scijava/ui/ToolBar.java +++ b/src/main/java/org/scijava/ui/ToolBar.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/UIPreprocessor.java b/src/main/java/org/scijava/ui/UIPreprocessor.java index 2449bb517..d9af6ebca 100644 --- a/src/main/java/org/scijava/ui/UIPreprocessor.java +++ b/src/main/java/org/scijava/ui/UIPreprocessor.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -45,7 +43,7 @@ * * @author Curtis Rueden */ -@Plugin(type = PreprocessorPlugin.class, priority = Priority.VERY_HIGH_PRIORITY) +@Plugin(type = PreprocessorPlugin.class, priority = Priority.VERY_HIGH) public class UIPreprocessor extends AbstractPreprocessorPlugin { @Parameter(required = false) @@ -61,9 +59,12 @@ public void process(final Module module) { if (ui == null) return; // no default UI for (final ModuleItem input : module.getInfo().inputs()) { - if (!input.isAutoFill()) continue; // cannot auto-fill this input + if (!input.isAutoFill()) continue; // skip unfillable inputs + if (module.isInputResolved(input.getName())) continue; // skip resolved inputs final Class type = input.getType(); - if (type.isAssignableFrom(ui.getClass())) { + if (UserInterface.class.isAssignableFrom(type) && // + type.isAssignableFrom(ui.getClass())) + { // input is a compatible UI final String name = input.getName(); module.setInput(name, ui); diff --git a/src/main/java/org/scijava/ui/UIService.java b/src/main/java/org/scijava/ui/UIService.java index 1c6a2d15e..a86652b8e 100644 --- a/src/main/java/org/scijava/ui/UIService.java +++ b/src/main/java/org/scijava/ui/UIService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -32,6 +30,7 @@ package org.scijava.ui; import java.io.File; +import java.io.FileFilter; import java.util.List; import org.scijava.app.StatusService; @@ -102,10 +101,33 @@ public interface UIService extends SciJavaService { /** Gets whether the UI with the given name or class name is visible. */ boolean isVisible(String name); - /** Sets whether the application is running in headless mode (no UI). */ + /** + * Sets whether the application should run in headless mode (no UI). + *

+ * Note that if the system itself is headless—which can be detected via + * the {@code java.awt.headless} system property or by calling + * {@link java.awt.GraphicsEnvironment#isHeadless()}—then calling + * {@code setHeadless(false)} will have no effect; the system will still be + * headless, and {@link #isHeadless()} will still return true. + *

+ *

+ * But if the system itself is not headless, calling + * {@code setHeadless(true)} will force {@link #isHeadless()} to return true, + * instructing the application to behave in a headless manner insofar as it + * can. + *

+ */ void setHeadless(boolean isHeadless); - /** Gets whether the UI is running in headless mode (no UI). */ + /** + * Gets whether the UI is running in headless mode (no UI). + *

+ * More precisely: returns true when {@code java.awt.headless} system + * property is set, and/or {@link java.awt.GraphicsEnvironment#isHeadless()} + * returns true, and/or {@link #setHeadless(boolean)} was called with {@code + * true} to force headless behavior in an otherwise headful environment. + *

+ */ boolean isHeadless(); /** @@ -293,6 +315,28 @@ DialogPrompt.Result showDialog(String message, String title, */ File chooseFile(String title, File file, String style); + /** + * Prompts the user to select one or multiple files. + *

+ * The prompt is displayed in the default user interface. + *

+ * + * @param files The initial value displayed in the file chooser prompt. + * @param filter A filter allowing to restrict the choice of files + */ + File[] chooseFiles(File parent, File[] files, FileFilter filter, String style); + + /** + * Prompts the user to select one or multiple files. + *

+ * The prompt is displayed in the default user interface. + *

+ * + * @param fileList The initial value displayed in the file chooser prompt. + * @param filter A filter allowing to restrict the choice of files + */ + List chooseFiles(File parent, List fileList, FileFilter filter, String style); + /** * Displays a popup context menu for the given display at the specified * position. @@ -308,5 +352,4 @@ DialogPrompt.Result showDialog(String message, String title, * @see StatusService#getStatusMessage(String, StatusEvent) */ String getStatusMessage(StatusEvent statusEvent); - } diff --git a/src/main/java/org/scijava/ui/UserInterface.java b/src/main/java/org/scijava/ui/UserInterface.java index d041fabc9..c2808bdc1 100644 --- a/src/main/java/org/scijava/ui/UserInterface.java +++ b/src/main/java/org/scijava/ui/UserInterface.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -32,6 +30,9 @@ package org.scijava.ui; import java.io.File; +import java.io.FileFilter; +import java.util.Arrays; +import java.util.List; import org.scijava.Disposable; import org.scijava.display.Display; @@ -40,6 +41,7 @@ import org.scijava.ui.console.ConsolePane; import org.scijava.ui.viewer.DisplayWindow; import org.scijava.widget.FileWidget; +import org.scijava.widget.WidgetStyle; /** * An end-user SciJava application user interface. @@ -158,9 +160,12 @@ DialogPrompt dialogPrompt(String message, String title, */ default File chooseFile(final File file, final String style) { final String title; - if (style.equals(FileWidget.DIRECTORY_STYLE)) title = "Choose a directory"; - else if (style.equals(FileWidget.OPEN_STYLE)) title = "Open"; - else if (style.equals(FileWidget.SAVE_STYLE)) title = "Save"; + // style can be a string with multiple comma-separated keywords + if (style == null) title = "Choose a file"; + else if (WidgetStyle.isStyle(style, FileWidget.DIRECTORY_STYLE)) title = "Choose a directory"; + else if (WidgetStyle.isStyle(style, FileWidget.FILE_AND_DIRECTORY_STYLE)) title = "Choose a file or directory"; + else if (WidgetStyle.isStyle(style, FileWidget.OPEN_STYLE)) title = "Open"; + else if (WidgetStyle.isStyle(style, FileWidget.SAVE_STYLE)) title = "Save"; else title = "Choose a file"; return chooseFile(title, file, style); @@ -176,6 +181,7 @@ default File chooseFile(final File file, final String style) { *
  • {@link FileWidget#OPEN_STYLE}
  • *
  • {@link FileWidget#SAVE_STYLE}
  • *
  • {@link FileWidget#DIRECTORY_STYLE}
  • + *
  • {@link FileWidget#FILE_AND_DIRECTORY_STYLE}
  • * * @return The {@link File} chosen by the user, or null if prompt is not * available @@ -184,6 +190,36 @@ default File chooseFile(String title, File file, String style) { throw new UnsupportedOperationException(); } + /** + * Prompts the user to choose a list of files. + * + * @param parent Parent folder for file selection + * @param files The initial value displayed in the file chooser prompt. + * @param filter A filter allowing to restrict file choice. + * @param style File selection style (files, directories, or both) and optional filters + * @return The selected {@link File}s chosen by the user, or null if the + * user cancels the prompt. + */ + default File[] chooseFiles(File parent, File[] files, FileFilter filter, String style) { + throw new UnsupportedOperationException(); + } + + /** + * Prompts the user to choose a list of files. + * + * @param parent Parent folder for file selection + * @param fileList The initial value displayed in the file chooser prompt. + * @param filter A filter allowing to restrict file choice. + * @param style File selection style (files, directories, or both) and optional filters + * @return The selected {@link File}s chosen by the user, or null if the + * user cancels the prompt. + */ + default List chooseFiles(File parent, List fileList, FileFilter filter, String style) { + final File[] initialFiles = fileList.toArray(new File[fileList.size()]); + final File[] chosenFiles = chooseFiles(parent, initialFiles, filter, style); + return chosenFiles == null ? null : Arrays.asList(chosenFiles); + } + /** * Displays a popup context menu for the given display at the specified * position. @@ -198,5 +234,4 @@ default File chooseFile(String title, File file, String style) { /** Returns true if this UI requires the EDT. */ boolean requiresEDT(); - } diff --git a/src/main/java/org/scijava/ui/console/AbstractConsolePane.java b/src/main/java/org/scijava/ui/console/AbstractConsolePane.java index da45ed875..1657a42e4 100644 --- a/src/main/java/org/scijava/ui/console/AbstractConsolePane.java +++ b/src/main/java/org/scijava/ui/console/AbstractConsolePane.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/console/ConsolePane.java b/src/main/java/org/scijava/ui/console/ConsolePane.java index ab6866f4b..de3c876b0 100644 --- a/src/main/java/org/scijava/ui/console/ConsolePane.java +++ b/src/main/java/org/scijava/ui/console/ConsolePane.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/console/HeadlessArgument.java b/src/main/java/org/scijava/ui/console/HeadlessArgument.java index 2272eb14b..821dd516e 100644 --- a/src/main/java/org/scijava/ui/console/HeadlessArgument.java +++ b/src/main/java/org/scijava/ui/console/HeadlessArgument.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -69,11 +67,11 @@ public void handle(final LinkedList args) { uiService.setHeadless(true); } + // -- Typed methods -- @Override public boolean supports(final LinkedList args) { return uiService != null && super.supports(args); } - } diff --git a/src/main/java/org/scijava/ui/console/ShowUIArgument.java b/src/main/java/org/scijava/ui/console/ShowUIArgument.java index 9294b94f7..00b7a192f 100644 --- a/src/main/java/org/scijava/ui/console/ShowUIArgument.java +++ b/src/main/java/org/scijava/ui/console/ShowUIArgument.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/console/UIArgument.java b/src/main/java/org/scijava/ui/console/UIArgument.java index 29345595d..8f962fbe7 100644 --- a/src/main/java/org/scijava/ui/console/UIArgument.java +++ b/src/main/java/org/scijava/ui/console/UIArgument.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/dnd/AbstractDragAndDropData.java b/src/main/java/org/scijava/ui/dnd/AbstractDragAndDropData.java index 9a1939671..0e9d59514 100644 --- a/src/main/java/org/scijava/ui/dnd/AbstractDragAndDropData.java +++ b/src/main/java/org/scijava/ui/dnd/AbstractDragAndDropData.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/dnd/AbstractDragAndDropHandler.java b/src/main/java/org/scijava/ui/dnd/AbstractDragAndDropHandler.java index da0213cca..77bf71ca0 100644 --- a/src/main/java/org/scijava/ui/dnd/AbstractDragAndDropHandler.java +++ b/src/main/java/org/scijava/ui/dnd/AbstractDragAndDropHandler.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/dnd/DefaultDragAndDropData.java b/src/main/java/org/scijava/ui/dnd/DefaultDragAndDropData.java index 1886ddcdf..e6095d0ea 100644 --- a/src/main/java/org/scijava/ui/dnd/DefaultDragAndDropData.java +++ b/src/main/java/org/scijava/ui/dnd/DefaultDragAndDropData.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/dnd/DefaultDragAndDropService.java b/src/main/java/org/scijava/ui/dnd/DefaultDragAndDropService.java index 19d875485..4b36ad97d 100644 --- a/src/main/java/org/scijava/ui/dnd/DefaultDragAndDropService.java +++ b/src/main/java/org/scijava/ui/dnd/DefaultDragAndDropService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/dnd/DragAndDropData.java b/src/main/java/org/scijava/ui/dnd/DragAndDropData.java index 9195070f0..2d3c6f856 100644 --- a/src/main/java/org/scijava/ui/dnd/DragAndDropData.java +++ b/src/main/java/org/scijava/ui/dnd/DragAndDropData.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/dnd/DragAndDropHandler.java b/src/main/java/org/scijava/ui/dnd/DragAndDropHandler.java index 3ca4e88bb..419ef09e6 100644 --- a/src/main/java/org/scijava/ui/dnd/DragAndDropHandler.java +++ b/src/main/java/org/scijava/ui/dnd/DragAndDropHandler.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/dnd/DragAndDropService.java b/src/main/java/org/scijava/ui/dnd/DragAndDropService.java index 15522d4cb..d63c06e53 100644 --- a/src/main/java/org/scijava/ui/dnd/DragAndDropService.java +++ b/src/main/java/org/scijava/ui/dnd/DragAndDropService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/dnd/FileDragAndDropHandler.java b/src/main/java/org/scijava/ui/dnd/FileDragAndDropHandler.java index 9b989496d..4e16054f7 100644 --- a/src/main/java/org/scijava/ui/dnd/FileDragAndDropHandler.java +++ b/src/main/java/org/scijava/ui/dnd/FileDragAndDropHandler.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -38,6 +36,7 @@ import org.scijava.display.Display; import org.scijava.display.DisplayService; import org.scijava.io.IOService; +import org.scijava.io.location.FileLocation; import org.scijava.log.LogService; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; @@ -48,7 +47,7 @@ * @author Curtis Rueden * @author Barry DeZonia */ -@Plugin(type = DragAndDropHandler.class, priority = Priority.LOW_PRIORITY) +@Plugin(type = DragAndDropHandler.class, priority = Priority.LOW) public class FileDragAndDropHandler extends AbstractDragAndDropHandler { @@ -70,7 +69,8 @@ public boolean supports(final File file) { if (!super.supports(file)) return false; // verify that the file can be opened somehow - return ioService.getOpener(file.getAbsolutePath()) != null; + final FileLocation loc = new FileLocation(file); + return ioService.getOpener(loc) != null; } @Override @@ -80,13 +80,12 @@ public boolean drop(final File file, final Display display) { if (file == null) return true; // trivial case // load the data - final String filename = file.getAbsolutePath(); final Object data; try { - data = ioService.open(filename); + data = ioService.open(new FileLocation(file)); } catch (final IOException exc) { - if (log != null) log.error("Error opening file: " + filename, exc); + if (log != null) log.error("Error opening file: " + file, exc); return false; } diff --git a/src/main/java/org/scijava/ui/dnd/ListDragAndDropHandler.java b/src/main/java/org/scijava/ui/dnd/ListDragAndDropHandler.java index dcd1e7581..0057d863e 100644 --- a/src/main/java/org/scijava/ui/dnd/ListDragAndDropHandler.java +++ b/src/main/java/org/scijava/ui/dnd/ListDragAndDropHandler.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/dnd/MIMEType.java b/src/main/java/org/scijava/ui/dnd/MIMEType.java index 3a084bb38..71e1f13f1 100644 --- a/src/main/java/org/scijava/ui/dnd/MIMEType.java +++ b/src/main/java/org/scijava/ui/dnd/MIMEType.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/dnd/ScriptFileDragAndDropHandler.java b/src/main/java/org/scijava/ui/dnd/ScriptFileDragAndDropHandler.java index 5546dea09..f456671ae 100644 --- a/src/main/java/org/scijava/ui/dnd/ScriptFileDragAndDropHandler.java +++ b/src/main/java/org/scijava/ui/dnd/ScriptFileDragAndDropHandler.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/dnd/event/DragAndDropEvent.java b/src/main/java/org/scijava/ui/dnd/event/DragAndDropEvent.java index df48275e2..4aecd6b8b 100644 --- a/src/main/java/org/scijava/ui/dnd/event/DragAndDropEvent.java +++ b/src/main/java/org/scijava/ui/dnd/event/DragAndDropEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/dnd/event/DragEnterEvent.java b/src/main/java/org/scijava/ui/dnd/event/DragEnterEvent.java index d3effdcf3..7b09815ba 100644 --- a/src/main/java/org/scijava/ui/dnd/event/DragEnterEvent.java +++ b/src/main/java/org/scijava/ui/dnd/event/DragEnterEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/dnd/event/DragExitEvent.java b/src/main/java/org/scijava/ui/dnd/event/DragExitEvent.java index 47b241366..5f07f0b9d 100644 --- a/src/main/java/org/scijava/ui/dnd/event/DragExitEvent.java +++ b/src/main/java/org/scijava/ui/dnd/event/DragExitEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/dnd/event/DragOverEvent.java b/src/main/java/org/scijava/ui/dnd/event/DragOverEvent.java index a0fbff395..7baeaa77e 100644 --- a/src/main/java/org/scijava/ui/dnd/event/DragOverEvent.java +++ b/src/main/java/org/scijava/ui/dnd/event/DragOverEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/dnd/event/DropEvent.java b/src/main/java/org/scijava/ui/dnd/event/DropEvent.java index d68710044..2775e56f0 100644 --- a/src/main/java/org/scijava/ui/dnd/event/DropEvent.java +++ b/src/main/java/org/scijava/ui/dnd/event/DropEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/event/UIEvent.java b/src/main/java/org/scijava/ui/event/UIEvent.java index da808fa0e..84a32ed63 100644 --- a/src/main/java/org/scijava/ui/event/UIEvent.java +++ b/src/main/java/org/scijava/ui/event/UIEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/event/UIShownEvent.java b/src/main/java/org/scijava/ui/event/UIShownEvent.java index e176d238e..2de7f2685 100644 --- a/src/main/java/org/scijava/ui/event/UIShownEvent.java +++ b/src/main/java/org/scijava/ui/event/UIShownEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/headless/HeadlessDisplayViewer.java b/src/main/java/org/scijava/ui/headless/HeadlessDisplayViewer.java index c3f11f2c9..dc65aae76 100644 --- a/src/main/java/org/scijava/ui/headless/HeadlessDisplayViewer.java +++ b/src/main/java/org/scijava/ui/headless/HeadlessDisplayViewer.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/headless/HeadlessUI.java b/src/main/java/org/scijava/ui/headless/HeadlessUI.java index 188a6b952..04536a6c0 100644 --- a/src/main/java/org/scijava/ui/headless/HeadlessUI.java +++ b/src/main/java/org/scijava/ui/headless/HeadlessUI.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -54,7 +52,7 @@ * @author Curtis Rueden */ @Plugin(type = UserInterface.class, name = HeadlessUI.NAME, - priority = Priority.VERY_LOW_PRIORITY) + priority = Priority.VERY_LOW) public class HeadlessUI extends AbstractRichPlugin implements UserInterface { public static final String NAME = "headless"; diff --git a/src/main/java/org/scijava/ui/headlessUI/HeadlessUI.java b/src/main/java/org/scijava/ui/headlessUI/HeadlessUI.java index 25b21c871..d473685b3 100644 --- a/src/main/java/org/scijava/ui/headlessUI/HeadlessUI.java +++ b/src/main/java/org/scijava/ui/headlessUI/HeadlessUI.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/viewer/AbstractDisplayViewer.java b/src/main/java/org/scijava/ui/viewer/AbstractDisplayViewer.java index bbbfb511e..418f43830 100644 --- a/src/main/java/org/scijava/ui/viewer/AbstractDisplayViewer.java +++ b/src/main/java/org/scijava/ui/viewer/AbstractDisplayViewer.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/viewer/DisplayPanel.java b/src/main/java/org/scijava/ui/viewer/DisplayPanel.java index 1b7e1d628..3f88949e4 100644 --- a/src/main/java/org/scijava/ui/viewer/DisplayPanel.java +++ b/src/main/java/org/scijava/ui/viewer/DisplayPanel.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/viewer/DisplayViewer.java b/src/main/java/org/scijava/ui/viewer/DisplayViewer.java index f8fd52a74..9698cb56e 100644 --- a/src/main/java/org/scijava/ui/viewer/DisplayViewer.java +++ b/src/main/java/org/scijava/ui/viewer/DisplayViewer.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -66,6 +64,27 @@ public interface DisplayViewer extends RichPlugin, Disposable { */ boolean canView(Display d); + /** + * Begins viewing the given display. + *

    + * The default behavior of this method is to ask the given + * {@link UserInterface} to create a {@link DisplayWindow} via + * {@link UserInterface#createDisplayWindow(Display)} and then pass it to + * {@link #view(DisplayWindow, Display)}. Viewers needing to customize details + * of the {@link DisplayWindow} creation can do so via this method. + *

    + * + * @param ui The user interface with which the viewer will be associated. + * @param d the model for the display to show. + */ + default void view(final UserInterface ui, final Display d) { + final DisplayWindow w = ui.createDisplayWindow(d); + w.setTitle(d.getName()); + view(w, d); + w.showDisplay(true); + d.update(); + } + /** * Begins viewing the given display. * @@ -92,6 +111,7 @@ public interface DisplayViewer extends RichPlugin, Disposable { /** Synchronizes the user interface appearance with the display model. */ default void onDisplayUpdatedEvent(final DisplayUpdatedEvent e) { + if (getPanel() == null) return; if (e.getLevel() == DisplayUpdateLevel.REBUILD) { getPanel().redoLayout(); } @@ -101,6 +121,7 @@ default void onDisplayUpdatedEvent(final DisplayUpdatedEvent e) { /** Removes the user interface when the display is deleted. */ @SuppressWarnings("unused") default void onDisplayDeletedEvent(final DisplayDeletedEvent e) { + if (getPanel() == null || getPanel().getWindow() == null) return; getPanel().getWindow().close(); } @@ -111,6 +132,7 @@ default void onDisplayDeletedEvent(final DisplayDeletedEvent e) { */ @SuppressWarnings("unused") default void onDisplayActivatedEvent(final DisplayActivatedEvent e) { + if (getPanel() == null || getPanel().getWindow() == null) return; getPanel().getWindow().requestFocus(); } diff --git a/src/main/java/org/scijava/ui/viewer/DisplayWindow.java b/src/main/java/org/scijava/ui/viewer/DisplayWindow.java index a7ae16b15..c6397ed6e 100644 --- a/src/main/java/org/scijava/ui/viewer/DisplayWindow.java +++ b/src/main/java/org/scijava/ui/viewer/DisplayWindow.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/viewer/text/AbstractTextDisplayViewer.java b/src/main/java/org/scijava/ui/viewer/text/AbstractTextDisplayViewer.java index 3abc8f587..e19a33e18 100644 --- a/src/main/java/org/scijava/ui/viewer/text/AbstractTextDisplayViewer.java +++ b/src/main/java/org/scijava/ui/viewer/text/AbstractTextDisplayViewer.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/viewer/text/TextDisplayPanel.java b/src/main/java/org/scijava/ui/viewer/text/TextDisplayPanel.java index eff753250..107d46aa8 100644 --- a/src/main/java/org/scijava/ui/viewer/text/TextDisplayPanel.java +++ b/src/main/java/org/scijava/ui/viewer/text/TextDisplayPanel.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/ui/viewer/text/TextDisplayViewer.java b/src/main/java/org/scijava/ui/viewer/text/TextDisplayViewer.java index 5c64f9c6a..4efb2adb1 100644 --- a/src/main/java/org/scijava/ui/viewer/text/TextDisplayViewer.java +++ b/src/main/java/org/scijava/ui/viewer/text/TextDisplayViewer.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/AbstractPrimitiveArray.java b/src/main/java/org/scijava/util/AbstractPrimitiveArray.java index 492ced3c7..6b284c0c3 100644 --- a/src/main/java/org/scijava/util/AbstractPrimitiveArray.java +++ b/src/main/java/org/scijava/util/AbstractPrimitiveArray.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/AppUtils.java b/src/main/java/org/scijava/util/AppUtils.java index c8ed71cf8..134aa2ac4 100644 --- a/src/main/java/org/scijava/util/AppUtils.java +++ b/src/main/java/org/scijava/util/AppUtils.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -36,6 +34,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.scijava.util.Types; + /** * Useful methods for obtaining details of the SciJava application environment. * @@ -50,7 +50,7 @@ public final class AppUtils { // Get the class whose main method launched the application. The heuristic // will fail if the main thread has terminated before this class loads. final String className = DebugUtils.getMainClassName(); - mainClass = className == null ? null : ClassUtils.loadClass(className); + mainClass = className == null ? null : Types.load(className); } private AppUtils() { @@ -101,7 +101,8 @@ public static File getBaseDirectory(final String sysProp, final Class c, // repository cache (~/.m2/repository), so the corePath will be null. // However, the classes of the launching project will be located in // target/classes, so we search up the tree from one of those. - final File appPath = AppUtils.getBaseDirectory(AppUtils.getMainClass()); + final Class mc = AppUtils.getMainClass(); + final File appPath = mc == null ? null : AppUtils.getBaseDirectory(mc); if (appPath != null) return appPath; // last resort: use current working directory @@ -129,10 +130,10 @@ public static File getBaseDirectory(final Class c) { public static File getBaseDirectory(final Class c, final String baseSubdirectory) { - // see: http://stackoverflow.com/a/12733172/1207769 + // see: https://stackoverflow.com/a/12733172/1207769 // step 1: convert Class to URL - final URL location = ClassUtils.getLocation(c); + final URL location = Types.location(c); // step 2: convert URL to File File baseFile; @@ -170,7 +171,7 @@ public static File getBaseDirectory(final Class c, * this cache is located in {@code ~/.m2/repository}. The location will be * {@code groupId/artifactId/version/artifactId-version.jar} where * {@code groupId}, {@code artifactId} and {@code version} are the Maven GAV + * href="https://maven.apache.org/pom.html#Maven_Coordinates">Maven GAV * coordinates. Note that in this case, no base directory with respect to * the given class can be found, and this method will return null. *
  • Within a JAR file beneath the base directory. Common cases @@ -233,10 +234,13 @@ public static File getBaseDirectory(final File classLocation, if (baseSubdirectory != null) basePrefix += baseSubdirectory + "/"; final String targetClassesSuffix = basePrefix + "target/classes"; - if (path.endsWith(targetClassesSuffix)) { - // NB: The class is a file beneath the Maven build directory - // ("target/classes"). - path = path.substring(0, path.length() - targetClassesSuffix.length()); + final String targetTestClassesSuffix = basePrefix + "target/test-classes"; + final String[] suffixes = {targetClassesSuffix, targetTestClassesSuffix}; + for (final String suffix : suffixes) { + if (!path.endsWith(suffix)) continue; + + // NB: The class is a file beneath a Maven build directory. + path = path.substring(0, path.length() - suffix.length()); File dir = new File(path); if (baseSubdirectory == null) { diff --git a/src/main/java/org/scijava/util/ArrayUtils.java b/src/main/java/org/scijava/util/ArrayUtils.java index 06a67b80a..42afdfbcb 100644 --- a/src/main/java/org/scijava/util/ArrayUtils.java +++ b/src/main/java/org/scijava/util/ArrayUtils.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -64,7 +62,7 @@ import java.util.List; /** - * Utility class for creating and manipulating {@link PrimitiveArray} instances. + * Utility class for working with arrays, particularly arrays of primitives. * * @author Mark Hiner * @author Curtis Rueden @@ -90,7 +88,7 @@ public static T[] array(final T... values) { * object is an array type, a {@link PrimitiveArray} wrapper will be created. */ public static Collection toCollection(final Object value) { - // If the value is null or we we have a collection, just return it + // If the value is null or we have a collection, just return it if (value == null || Collection.class.isAssignableFrom(value.getClass())) { return (Collection) value; } diff --git a/src/main/java/org/scijava/util/BoolArray.java b/src/main/java/org/scijava/util/BoolArray.java index 80ce58772..d0d6589b7 100644 --- a/src/main/java/org/scijava/util/BoolArray.java +++ b/src/main/java/org/scijava/util/BoolArray.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/ByteArray.java b/src/main/java/org/scijava/util/ByteArray.java index 85f59844e..ad092abb5 100644 --- a/src/main/java/org/scijava/util/ByteArray.java +++ b/src/main/java/org/scijava/util/ByteArray.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/Bytes.java b/src/main/java/org/scijava/util/Bytes.java index 7be781de3..5b56f8d03 100644 --- a/src/main/java/org/scijava/util/Bytes.java +++ b/src/main/java/org/scijava/util/Bytes.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/CharArray.java b/src/main/java/org/scijava/util/CharArray.java index 94e8f10c1..8459597da 100644 --- a/src/main/java/org/scijava/util/CharArray.java +++ b/src/main/java/org/scijava/util/CharArray.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/CheckSezpoz.java b/src/main/java/org/scijava/util/CheckSezpoz.java index 1a207272c..d5f384f9e 100644 --- a/src/main/java/org/scijava/util/CheckSezpoz.java +++ b/src/main/java/org/scijava/util/CheckSezpoz.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/ClassUtils.java b/src/main/java/org/scijava/util/ClassUtils.java index cd18e2500..e9e7e4dc0 100644 --- a/src/main/java/org/scijava/util/ClassUtils.java +++ b/src/main/java/org/scijava/util/ClassUtils.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -31,14 +29,11 @@ package org.scijava.util; -import java.io.File; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Type; -import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; @@ -89,271 +84,6 @@ private ClassUtils() { // -- Class loading, querying and reflection -- - /** - * Loads the class with the given name, using the current thread's context - * class loader, or null if it cannot be loaded. - * - * @param name The name of the class to load. - * @return The loaded class, or null if the class could not be loaded. - * @see #loadClass(String, ClassLoader, boolean) - */ - public static Class loadClass(final String name) { - return loadClass(name, null, true); - } - - /** - * Loads the class with the given name, using the specified - * {@link ClassLoader}, or null if it cannot be loaded. - * - * @param name The name of the class to load. - * @param classLoader The class loader with which to load the class; if null, - * the current thread's context class loader will be used. - * @return The loaded class, or null if the class could not be loaded. - * @see #loadClass(String, ClassLoader, boolean) - */ - public static Class loadClass(final String name, - final ClassLoader classLoader) - { - return loadClass(name, classLoader, true); - } - - /** - * Loads the class with the given name, using the current thread's context - * class loader. - * - * @param className the name of the class to load - * @param quietly Whether to return {@code null} (rather than throwing - * {@link IllegalArgumentException}) if something goes wrong loading - * the class - * @return The loaded class, or {@code null} if the class could not be loaded - * and the {@code quietly} flag is set. - * @see #loadClass(String, ClassLoader, boolean) - * @throws IllegalArgumentException If the class cannot be loaded and the - * {@code quietly} flag is not set. - */ - public static Class loadClass(final String className, - final boolean quietly) - { - return loadClass(className, null, quietly); - } - - /** - * Loads the class with the given name, using the specified - * {@link ClassLoader}, or null if it cannot be loaded. - *

    - * This method is capable of parsing several different class name syntaxes. In - * particular, array classes (including primitives) represented using either - * square brackets or internal Java array name syntax are supported. Examples: - *

    - *
      - *
    • {@code boolean} is loaded as {@code boolean.class}
    • - *
    • {@code Z} is loaded as {@code boolean.class}
    • - *
    • {@code double[]} is loaded as {@code double[].class}
    • - *
    • {@code string[]} is loaded as {@code java.lang.String.class}
    • - *
    • {@code [F} is loaded as {@code float[].class}
    • - *
    - * - * @param name The name of the class to load. - * @param classLoader The class loader with which to load the class; if null, - * the current thread's context class loader will be used. - * @param quietly Whether to return {@code null} (rather than throwing - * {@link IllegalArgumentException}) if something goes wrong loading - * the class - * @return The loaded class, or {@code null} if the class could not be loaded - * and the {@code quietly} flag is set. - * @throws IllegalArgumentException If the class cannot be loaded and the - * {@code quietly} flag is not set. - */ - public static Class loadClass(final String name, - final ClassLoader classLoader, final boolean quietly) - { - // handle primitive types - if (name.equals("Z") || name.equals("boolean")) return boolean.class; - if (name.equals("B") || name.equals("byte")) return byte.class; - if (name.equals("C") || name.equals("char")) return char.class; - if (name.equals("D") || name.equals("double")) return double.class; - if (name.equals("F") || name.equals("float")) return float.class; - if (name.equals("I") || name.equals("int")) return int.class; - if (name.equals("J") || name.equals("long")) return long.class; - if (name.equals("S") || name.equals("short")) return short.class; - if (name.equals("V") || name.equals("void")) return void.class; - - // handle built-in class shortcuts - final String className; - if (name.equals("string")) className = "java.lang.String"; - else className = name; - - // handle source style arrays (e.g.: "java.lang.String[]") - if (name.endsWith("[]")) { - final String elementClassName = name.substring(0, name.length() - 2); - return getArrayClass(loadClass(elementClassName, classLoader)); - } - - // handle non-primitive internal arrays (e.g.: "[Ljava.lang.String;") - if (name.startsWith("[L") && name.endsWith(";")) { - final String elementClassName = name.substring(2, name.length() - 1); - return getArrayClass(loadClass(elementClassName, classLoader)); - } - - // handle other internal arrays (e.g.: "[I", "[[I", "[[Ljava.lang.String;") - if (name.startsWith("[")) { - final String elementClassName = name.substring(1); - return getArrayClass(loadClass(elementClassName, classLoader)); - } - - // load the class! - try { - final ClassLoader cl = - classLoader == null ? Thread.currentThread().getContextClassLoader() - : classLoader; - return cl.loadClass(className); - } - catch (final Throwable t) { - // NB: Do not allow any failure to load the class to crash us. - // Not ClassNotFoundException. - // Not NoClassDefFoundError. - // Not UnsupportedClassVersionError! - if (quietly) return null; - throw new IllegalArgumentException("Cannot load class: " + className, t); - } - } - - /** - * Gets the array class corresponding to the given element type. - *

    - * For example, {@code getArrayClass(double.class)} returns - * {@code double[].class}. - *

    - */ - public static Class getArrayClass(final Class elementClass) { - if (elementClass == null) return null; - // NB: It appears the reflection API has no built-in way to do this. - // So unfortunately, we must allocate a new object and then inspect it. - try { - return Array.newInstance(elementClass, 0).getClass(); - } - catch (final IllegalArgumentException exc) { - return null; - } - } - - /** Checks whether a class with the given name exists. */ - public static boolean hasClass(final String className) { - return hasClass(className, null); - } - - /** Checks whether a class with the given name exists. */ - public static boolean hasClass(final String className, - final ClassLoader classLoader) - { - return loadClass(className, classLoader) != null; - } - - /** - * Gets the base location of the given class. - *

    - * If the class is directly on the file system (e.g., - * "/path/to/my/package/MyClass.class") then it will return the base directory - * (e.g., "/path/to"). - *

    - *

    - * If the class is within a JAR file (e.g., - * "/path/to/my-jar.jar!/my/package/MyClass.class") then it will return the - * path to the JAR (e.g., "/path/to/my-jar.jar"). - *

    - * - * @param className The name of the class whose location is desired. - * @see FileUtils#urlToFile(URL) to convert the result to a {@link File}. - */ - public static URL getLocation(final String className) { - return getLocation(className, null); - } - - /** - * Gets the base location of the given class. - *

    - * If the class is directly on the file system (e.g., - * "/path/to/my/package/MyClass.class") then it will return the base directory - * (e.g., "/path/to"). - *

    - *

    - * If the class is within a JAR file (e.g., - * "/path/to/my-jar.jar!/my/package/MyClass.class") then it will return the - * path to the JAR (e.g., "/path/to/my-jar.jar"). - *

    - * - * @param className The name of the class whose location is desired. - * @param classLoader The class loader to use when loading the class. - * @see FileUtils#urlToFile(URL) to convert the result to a {@link File}. - */ - public static URL getLocation(final String className, - final ClassLoader classLoader) - { - final Class c = loadClass(className, classLoader); - return getLocation(c); - } - - /** - * Gets the base location of the given class. - *

    - * If the class is directly on the file system (e.g., - * "/path/to/my/package/MyClass.class") then it will return the base directory - * (e.g., "file:/path/to"). - *

    - *

    - * If the class is within a JAR file (e.g., - * "/path/to/my-jar.jar!/my/package/MyClass.class") then it will return the - * path to the JAR (e.g., "file:/path/to/my-jar.jar"). - *

    - * - * @param c The class whose location is desired. - * @see FileUtils#urlToFile(URL) to convert the result to a {@link File}. - */ - public static URL getLocation(final Class c) { - if (c == null) return null; // could not load the class - - // try the easy way first - try { - final URL codeSourceLocation = - c.getProtectionDomain().getCodeSource().getLocation(); - if (codeSourceLocation != null) return codeSourceLocation; - } - catch (final SecurityException e) { - // NB: Cannot access protection domain. - } - catch (final NullPointerException e) { - // NB: Protection domain or code source is null. - } - - // NB: The easy way failed, so we try the hard way. We ask for the class - // itself as a resource, then strip the class's path from the URL string, - // leaving the base path. - - // get the class's raw resource path - final URL classResource = c.getResource(c.getSimpleName() + ".class"); - if (classResource == null) return null; // cannot find class resource - - final String url = classResource.toString(); - final String suffix = c.getCanonicalName().replace('.', '/') + ".class"; - if (!url.endsWith(suffix)) return null; // weird URL - - // strip the class's path from the URL string - final String base = url.substring(0, url.length() - suffix.length()); - - String path = base; - - // remove the "jar:" prefix and "!/" suffix, if present - if (path.startsWith("jar:")) path = path.substring(4, path.length() - 2); - - try { - return new URL(path); - } - catch (final MalformedURLException e) { - e.printStackTrace(); - return null; - } - } - /** * Gets the given class's {@link Method}s marked with the annotation of the * specified class. @@ -460,7 +190,7 @@ public static void getAnnotatedFields( cachedFields = fieldCache.getList(c, annotationClass); } - fields.addAll(cachedFields); + if (cachedFields != null) fields.addAll(cachedFields); } /** @@ -570,26 +300,6 @@ else if (Field.class.isAssignableFrom(objectClass)) { } } - /** - * Gets the specified field of the given class, or null if it does not exist. - */ - public static Field getField(final String className, final String fieldName) { - return getField(loadClass(className), fieldName); - } - - /** - * Gets the specified field of the given class, or null if it does not exist. - */ - public static Field getField(final Class c, final String fieldName) { - if (c == null) return null; - try { - return c.getDeclaredField(fieldName); - } - catch (final NoSuchFieldException e) { - return null; - } - } - /** * Gets the given field's value of the specified object instance, or null if * the value cannot be obtained. @@ -622,8 +332,7 @@ public static void setValue(final Field field, final Object instance, } else { // the given value needs to be converted to a compatible type - final Type fieldType = - GenericUtils.getFieldType(field, instance.getClass()); + final Type fieldType = Types.fieldType(field, instance.getClass()); @SuppressWarnings("deprecation") final Object convertedValue = ConversionUtils.convert(value, fieldType); compatibleValue = convertedValue; @@ -638,50 +347,9 @@ public static void setValue(final Field field, final Object instance, // -- Type querying -- - public static boolean isBoolean(final Class type) { - return type == boolean.class || Boolean.class.isAssignableFrom(type); - } - - public static boolean isByte(final Class type) { - return type == byte.class || Byte.class.isAssignableFrom(type); - } - - public static boolean isCharacter(final Class type) { - return type == char.class || Character.class.isAssignableFrom(type); - } - - public static boolean isDouble(final Class type) { - return type == double.class || Double.class.isAssignableFrom(type); - } - - public static boolean isFloat(final Class type) { - return type == float.class || Float.class.isAssignableFrom(type); - } - - public static boolean isInteger(final Class type) { - return type == int.class || Integer.class.isAssignableFrom(type); - } - - public static boolean isLong(final Class type) { - return type == long.class || Long.class.isAssignableFrom(type); - } - - public static boolean isShort(final Class type) { - return type == short.class || Short.class.isAssignableFrom(type); - } - - public static boolean isNumber(final Class type) { - return Number.class.isAssignableFrom(type) || type == byte.class || - type == double.class || type == float.class || type == int.class || - type == long.class || type == short.class; - } - - public static boolean isText(final Class type) { - return String.class.isAssignableFrom(type) || isCharacter(type); - } - // -- Comparison -- + // START HERE: Migrate remaining methods to Types, then deprecate this class. /** * Compares two {@link Class} objects using their fully qualified names. *

    @@ -707,6 +375,15 @@ public static int compare(final Class c1, final Class c2) { // -- Helper methods -- + private static Class arrayOrNull(final Class componentType) { + try { + return Types.array(componentType); + } + catch (final IllegalArgumentException exc) { + return null; + } + } + /** * Populates the cache of annotated elements for a particular class by looking * for all inherited and declared instances annotated with the specified @@ -755,6 +432,132 @@ private static void populateCache( // -- Deprecated methods -- + /** @deprecated Use {@link Types#load(String)} instead. */ + @Deprecated + public static Class loadClass(final String name) { + return Types.load(name); + } + + /** @deprecated Use {@link Types#load(String, ClassLoader)} instead. */ + @Deprecated + public static Class loadClass(final String name, + final ClassLoader classLoader) + { + return Types.load(name, classLoader); + } + + /** @deprecated Use {@link Types#load(String, boolean)} instead. */ + @Deprecated + public static Class loadClass(final String className, + final boolean quietly) + { + return Types.load(className, quietly); + } + + /** + * @deprecated Use {@link Types#load(String, ClassLoader, boolean)} instead. + */ + @Deprecated + public static Class loadClass(final String name, + final ClassLoader classLoader, final boolean quietly) + { + return Types.load(name, classLoader, quietly); + } + + /** @deprecated Use {@link Types#load(String)} instead. */ + @Deprecated + public static boolean hasClass(final String className) { + return Types.load(className) != null; + } + + /** @deprecated Use {@link Types#load(String, ClassLoader)} instead. */ + @Deprecated + public static boolean hasClass(final String className, + final ClassLoader classLoader) + { + return Types.load(className, classLoader) != null; + } + + /** @deprecated Use {@link Types#location} and {@link Types#load} instead. */ + @Deprecated + public static URL getLocation(final String className) { + return Types.location(Types.load(className)); + } + + /** @deprecated Use {@link Types#location} and {@link Types#load} instead. */ + @Deprecated + public static URL getLocation(final String className, + final ClassLoader classLoader) + { + return Types.location(Types.load(className, classLoader)); + } + + /** @deprecated Use {@link Types#location} and {@link Types#load} instead. */ + @Deprecated + public static URL getLocation(final Class c) { + return Types.location(c); + } + + /** @deprecated Use {@link Types#isBoolean} instead. */ + @Deprecated + public static boolean isBoolean(final Class type) { + return Types.isBoolean(type); + } + + /** @deprecated Use {@link Types#isByte} instead. */ + @Deprecated + public static boolean isByte(final Class type) { + return Types.isByte(type); + } + + /** @deprecated Use {@link Types#isCharacter} instead. */ + @Deprecated + public static boolean isCharacter(final Class type) { + return Types.isCharacter(type); + } + + /** @deprecated Use {@link Types#isDouble} instead. */ + @Deprecated + public static boolean isDouble(final Class type) { + return Types.isDouble(type); + } + + /** @deprecated Use {@link Types#isFloat} instead. */ + @Deprecated + public static boolean isFloat(final Class type) { + return Types.isFloat(type); + } + + /** @deprecated Use {@link Types#isInteger} instead. */ + @Deprecated + public static boolean isInteger(final Class type) { + return Types.isInteger(type); + } + + /** @deprecated Use {@link Types#isLong} instead. */ + @Deprecated + public static boolean isLong(final Class type) { + return Types.isLong(type); + } + + /** @deprecated Use {@link Types#isShort} instead. */ + @Deprecated + public static boolean isShort(final Class type) { + return Types.isShort(type); + } + + /** @deprecated Use {@link Types#isNumber} instead. */ + @Deprecated + public static boolean isNumber(final Class type) { + return Types.isNumber(type); + } + + /** @deprecated Use {@link Types#isText} instead. */ + @Deprecated + public static boolean isText(final Class type) { + return Types.isText(type); + } + /** @deprecated use {@link ConversionUtils#convert(Object, Class)} */ @Deprecated public static T convert(final Object value, final Class type) { @@ -773,47 +576,76 @@ public static boolean canConvert(final Object value, final Class type) { return ConversionUtils.canConvert(value, type); } - /** @deprecated use {@link ConversionUtils#cast(Object, Class)} */ + /** @deprecated use {@link Types#cast(Object, Class)} */ @Deprecated public static T cast(final Object obj, final Class type) { - return ConversionUtils.cast(obj, type); + return Types.cast(obj, type); } - /** @deprecated use {@link ConversionUtils#canCast(Class, Class)} */ + /** @deprecated use {@link Types#isAssignable(Type, Type)} */ @Deprecated public static boolean canCast(final Class c, final Class type) { - return ConversionUtils.canCast(c, type); + return Types.isAssignable(c, type); } - /** @deprecated use {@link ConversionUtils#canCast(Object, Class)} */ + /** @deprecated use {@link Types#isInstance(Object, Class)} */ @Deprecated public static boolean canCast(final Object obj, final Class type) { - return ConversionUtils.canCast(obj, type); + return Types.isInstance(obj, type); } - /** @deprecated use {@link ConversionUtils#getNonprimitiveType(Class)} */ + /** @deprecated use {@link Types#box(Class)} */ @Deprecated public static Class getNonprimitiveType(final Class type) { - return ConversionUtils.getNonprimitiveType(type); + return Types.box(type); } - /** @deprecated use {@link ConversionUtils#getNullValue(Class)} */ + /** @deprecated use {@link Types#nullValue(Class)} */ @Deprecated public static T getNullValue(final Class type) { - return ConversionUtils.getNullValue(type); + return Types.nullValue(type); } - /** @deprecated use {@link GenericUtils#getFieldClasses(Field, Class)} */ + /** + * @deprecated Use {@link Types#fieldType(Field, Class)} and {@link Types#raws} + * instead. + */ @Deprecated public static List> getTypes(final Field field, final Class type) { - return GenericUtils.getFieldClasses(field, type); + return Types.raws(Types.fieldType(field, type)); } - /** @deprecated use {@link GenericUtils#getFieldType(Field, Class)} */ + /** @deprecated Use {@link Types#fieldType(Field, Class)} instead. */ @Deprecated public static Type getGenericType(final Field field, final Class type) { - return GenericUtils.getFieldType(field, type); + return Types.fieldType(field, type); + } + + /** @deprecated Use {@link Types#field} instead. */ + @Deprecated + public static Field getField(final String className, final String fieldName) { + try { + return Types.field(Types.load(className), fieldName); + } catch (final IllegalArgumentException e) { + return null; + } + } + + /** @deprecated Use {@link Types#field} instead. */ + @Deprecated + public static Field getField(final Class c, final String fieldName) { + try { + return Types.field(c, fieldName); + } catch (final IllegalArgumentException e) { + return null; + } + } + + /** @deprecated Use {@link Types#array(Class)} instead. */ + @Deprecated + public static Class getArrayClass(final Class elementClass) { + return Types.raw(arrayOrNull(elementClass)); } // -- Helper classes -- @@ -864,12 +696,8 @@ private static class CacheMap extends public List getList(final Class c, final Class annotationClass) { - List annotatedFields = null; final Map, List> annotationTypes = get(c); - if (annotationTypes != null) { - annotatedFields = annotationTypes.get(annotationClass); - } - return annotatedFields; + return annotationTypes == null ? null : annotationTypes.get(annotationClass); } /** diff --git a/src/main/java/org/scijava/util/ColorRGB.java b/src/main/java/org/scijava/util/ColorRGB.java index 9895caf95..5cbcfb181 100644 --- a/src/main/java/org/scijava/util/ColorRGB.java +++ b/src/main/java/org/scijava/util/ColorRGB.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/ColorRGBA.java b/src/main/java/org/scijava/util/ColorRGBA.java index cb124a382..34f2dac25 100644 --- a/src/main/java/org/scijava/util/ColorRGBA.java +++ b/src/main/java/org/scijava/util/ColorRGBA.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/Colors.java b/src/main/java/org/scijava/util/Colors.java index ce96ba8b1..c8d70d55f 100644 --- a/src/main/java/org/scijava/util/Colors.java +++ b/src/main/java/org/scijava/util/Colors.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -117,8 +115,7 @@ public final class Colors { public static final ColorRGB LIGHTBLUE = new ColorRGB(173, 216, 230); public static final ColorRGB LIGHTCORAL = new ColorRGB(240, 128, 128); public static final ColorRGB LIGHTCYAN = new ColorRGB(224, 255, 255); - public static final ColorRGB LIGHTGOLDENRODYELLOW = new ColorRGB(250, 250, - 210); + public static final ColorRGB LIGHTGOLDENRODYELLOW = new ColorRGB(250, 250, 210); public static final ColorRGB LIGHTGRAY = new ColorRGB(211, 211, 211); public static final ColorRGB LIGHTGREEN = new ColorRGB(144, 238, 144); public static final ColorRGB LIGHTGREY = LIGHTGRAY; @@ -196,19 +193,15 @@ public final class Colors { public static final ColorRGB YELLOW = new ColorRGB(255, 255, 0); public static final ColorRGB YELLOWGREEN = new ColorRGB(154, 205, 50); - private static final Map COLORS = - new HashMap<>(); + private static final Map COLORS = new HashMap<>(); static { - for (final Field f : Colors.class.getDeclaredFields()) { + for (final Field f : Colors.class.getFields()) { final Object value; try { value = f.get(null); } - catch (final IllegalArgumentException e) { - continue; - } - catch (final IllegalAccessException e) { + catch (final IllegalAccessException | IllegalArgumentException e) { continue; } if (!(value instanceof ColorRGB)) continue; // not a color diff --git a/src/main/java/org/scijava/util/CombineAnnotations.java b/src/main/java/org/scijava/util/CombineAnnotations.java index fc7e6deee..b248d64b3 100644 --- a/src/main/java/org/scijava/util/CombineAnnotations.java +++ b/src/main/java/org/scijava/util/CombineAnnotations.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/Combiner.java b/src/main/java/org/scijava/util/Combiner.java index c3397a45c..209c57c08 100644 --- a/src/main/java/org/scijava/util/Combiner.java +++ b/src/main/java/org/scijava/util/Combiner.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/ConversionUtils.java b/src/main/java/org/scijava/util/ConversionUtils.java index 00439911c..e1dd17570 100644 --- a/src/main/java/org/scijava/util/ConversionUtils.java +++ b/src/main/java/org/scijava/util/ConversionUtils.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -32,225 +30,80 @@ package org.scijava.util; import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.List; import org.scijava.convert.ConversionRequest; import org.scijava.convert.ConvertService; import org.scijava.convert.Converter; -import org.scijava.convert.DefaultConverter; -/** - * Useful methods for converting and casting between classes and types. - *

    - * For extensible type conversion, use {@link ConvertService}. - *

    - * - * @author Curtis Rueden - * @author Mark Hiner - */ +/** @deprecated use {@link ConvertService} and {@link Types} */ +@Deprecated public class ConversionUtils { - private static ConvertService convertService; - - private static Converter converterNoContext; - - private static double servicePriority = 0.0; + private static List> converters = Arrays.asList( + new org.scijava.convert.NullConverter(), + new org.scijava.convert.CastingConverter(), + new org.scijava.convert.ArrayConverters.BoolArrayUnwrapper(), + new org.scijava.convert.ArrayConverters.BoolArrayWrapper(), + new org.scijava.convert.ArrayConverters.ByteArrayUnwrapper(), + new org.scijava.convert.ArrayConverters.ByteArrayWrapper(), + new org.scijava.convert.ArrayConverters.CharArrayUnwrapper(), + new org.scijava.convert.ArrayConverters.CharArrayWrapper(), + new org.scijava.convert.ArrayConverters.DoubleArrayUnwrapper(), + new org.scijava.convert.ArrayConverters.DoubleArrayWrapper(), + new org.scijava.convert.ArrayConverters.FloatArrayUnwrapper(), + new org.scijava.convert.ArrayConverters.FloatArrayWrapper(), + new org.scijava.convert.ArrayConverters.IntArrayUnwrapper(), + new org.scijava.convert.ArrayConverters.IntArrayWrapper(), + new org.scijava.convert.ArrayConverters.LongArrayUnwrapper(), + new org.scijava.convert.ArrayConverters.LongArrayWrapper(), + new org.scijava.convert.ArrayConverters.ShortArrayUnwrapper(), + new org.scijava.convert.ArrayConverters.ShortArrayWrapper(), + new org.scijava.convert.FileListConverters.FileArrayToStringConverter(), + new org.scijava.convert.FileListConverters.FileToStringConverter(), + new org.scijava.convert.FileListConverters.StringToFileArrayConverter(), + new org.scijava.convert.FileListConverters.StringToFileConverter(), + new org.scijava.convert.NumberConverters.BigIntegerToBigDecimalConverter(), + new org.scijava.convert.NumberConverters.ByteToBigDecimalConverter(), + new org.scijava.convert.NumberConverters.ByteToBigIntegerConverter(), + new org.scijava.convert.NumberConverters.ByteToDoubleConverter(), + new org.scijava.convert.NumberConverters.ByteToFloatConverter(), + new org.scijava.convert.NumberConverters.ByteToIntegerConverter(), + new org.scijava.convert.NumberConverters.ByteToLongConverter(), + new org.scijava.convert.NumberConverters.ByteToShortConverter(), + new org.scijava.convert.NumberConverters.DoubleToBigDecimalConverter(), + new org.scijava.convert.NumberConverters.FloatToBigDecimalConverter(), + new org.scijava.convert.NumberConverters.FloatToDoubleConverter(), + new org.scijava.convert.NumberConverters.IntegerToBigDecimalConverter(), + new org.scijava.convert.NumberConverters.IntegerToBigIntegerConverter(), + new org.scijava.convert.NumberConverters.IntegerToDoubleConverter(), + new org.scijava.convert.NumberConverters.IntegerToLongConverter(), + new org.scijava.convert.NumberConverters.LongToBigDecimalConverter(), + new org.scijava.convert.NumberConverters.LongToBigIntegerConverter(), + new org.scijava.convert.NumberConverters.ShortToBigDecimalConverter(), + new org.scijava.convert.NumberConverters.ShortToBigIntegerConverter(), + new org.scijava.convert.NumberConverters.ShortToDoubleConverter(), + new org.scijava.convert.NumberConverters.ShortToFloatConverter(), + new org.scijava.convert.NumberConverters.ShortToIntegerConverter(), + new org.scijava.convert.NumberConverters.ShortToLongConverter(), + new org.scijava.convert.StringToNumberConverter(), + new org.scijava.convert.DefaultConverter() + ); private ConversionUtils() { // prevent instantiation of utility class } - // -- Type casting -- - - /** - * Converts the given string value to an enumeration constant of the specified - * type. - * - * @param src The value to convert. - * @param dest The type of the enumeration constant. - * @return The converted enumeration constant, or null if the type is not an - * enumeration type or has no such constant. - */ - public static T convertToEnum(final String src, final Class dest) { - if (src == null || !dest.isEnum()) return null; - try { - @SuppressWarnings({ "rawtypes", "unchecked" }) - final Enum result = Enum.valueOf((Class) dest, src); - @SuppressWarnings("unchecked") - final T typedResult = (T) result; - return typedResult; - } - catch (final IllegalArgumentException exc) { - // no such enum constant - return null; - } - } - - /** - * Casts the given object to the specified type, or null if the types are - * incompatible. - */ - public static T cast(final Object src, final Class dest) { - if (!canCast(src, dest)) return null; - @SuppressWarnings("unchecked") - final T result = (T) src; - return result; - } - - /** - * Checks whether objects of the given class can be assigned to the specified - * type. Unlike {@link Class#isAssignableFrom(Class)}, this method considers - * auto-unboxing. - * - * @return true If the destination class is assignable from the source one, or - * if the source class is null and destination class is non-null. - */ - public static boolean canAssign(final Class src, final Class dest) { - return canCast(src, dest); - } - - /** - * Checks whether the given object can be assigned to the specified type. - * Unlike {@link Class#isAssignableFrom(Class)}, this method considers - * auto-unboxing. - * - * @return true If the destination class is assignable from the source - * object's class, or if the source object is null and destionation - * class is non-null. - */ - public static boolean canAssign(final Object src, final Class dest) { - return canCast(src, dest); - } - - /** - * @deprecated use {@link #canAssign(Class, Class)} instead - */ - @Deprecated - public static boolean canCast(final Class src, final Class dest) { - if (dest == null) return false; - if (src == null) return true; - final Class saneSrc = getNonprimitiveType(src); - final Class saneDest = getNonprimitiveType(dest); - return saneDest.isAssignableFrom(saneSrc); - } - - /** - * @deprecated use {@link #canAssign(Object, Class)} instead - */ - @Deprecated - public static boolean canCast(final Object src, final Class dest) { - if (dest == null) return false; - return src == null || canCast(src.getClass(), dest); - } - - /** - * Returns the primitive {@link Class} closest to the given type. - *

    - * Specifically, the following type conversions are done: - *

    - *
      - *
    • Boolean.class becomes boolean.class
    • - *
    • Byte.class becomes byte.class
    • - *
    • Character.class becomes char.class
    • - *
    • Double.class becomes double.class
    • - *
    • Float.class becomes float.class
    • - *
    • Integer.class becomes int.class
    • - *
    • Long.class becomes long.class
    • - *
    • Short.class becomes short.class
    • - *
    • Void.class becomes void.class
    • - *
    - *

    - * All other types are unchanged. - *

    - */ - public static Class getPrimitiveType(final Class type) { - final Class destType; - if (type == Boolean.class) destType = boolean.class; - else if (type == Byte.class) destType = byte.class; - else if (type == Character.class) destType = char.class; - else if (type == Double.class) destType = double.class; - else if (type == Float.class) destType = float.class; - else if (type == Integer.class) destType = int.class; - else if (type == Long.class) destType = long.class; - else if (type == Short.class) destType = short.class; - else if (type == Void.class) destType = void.class; - else destType = type; - @SuppressWarnings("unchecked") - final Class result = (Class) destType; - return result; - } - - /** - * Returns the non-primitive {@link Class} closest to the given type. - *

    - * Specifically, the following type conversions are done: - *

    - *
      - *
    • boolean.class becomes Boolean.class
    • - *
    • byte.class becomes Byte.class
    • - *
    • char.class becomes Character.class
    • - *
    • double.class becomes Double.class
    • - *
    • float.class becomes Float.class
    • - *
    • int.class becomes Integer.class
    • - *
    • long.class becomes Long.class
    • - *
    • short.class becomes Short.class
    • - *
    • void.class becomes Void.class
    • - *
    - *

    - * All other types are unchanged. - *

    - */ - public static Class getNonprimitiveType(final Class type) { - final Class destType; - if (type == boolean.class) destType = Boolean.class; - else if (type == byte.class) destType = Byte.class; - else if (type == char.class) destType = Character.class; - else if (type == double.class) destType = Double.class; - else if (type == float.class) destType = Float.class; - else if (type == int.class) destType = Integer.class; - else if (type == long.class) destType = Long.class; - else if (type == short.class) destType = Short.class; - else if (type == void.class) destType = Void.class; - else destType = type; - @SuppressWarnings("unchecked") - final Class result = (Class) destType; - return result; - } - - /** - * Gets the "null" value for the given type. For non-primitives, this will - * actually be null. For primitives, it will be zero for numeric types, false - * for boolean, and the null character for char. - */ - public static T getNullValue(final Class type) { - final Object defaultValue; - if (type == boolean.class) defaultValue = false; - else if (type == byte.class) defaultValue = (byte) 0; - else if (type == char.class) defaultValue = '\0'; - else if (type == double.class) defaultValue = 0d; - else if (type == float.class) defaultValue = 0f; - else if (type == int.class) defaultValue = 0; - else if (type == long.class) defaultValue = 0L; - else if (type == short.class) defaultValue = (short) 0; - else defaultValue = null; - @SuppressWarnings("unchecked") - final T result = (T) defaultValue; - return result; - } - // -- ConvertService setter -- - /** - * Sets the {@link ConvertService} to use for handling conversion requests. - */ + /** @deprecated This method should not be used anymore. */ + @Deprecated + @SuppressWarnings("unused") public static void setDelegateService(final ConvertService convertService, final double priority) { - if (ConversionUtils.convertService == null || - Double.compare(priority, servicePriority) > 0) - { - ConversionUtils.convertService = convertService; - servicePriority = priority; - } + // NB: This method is now a no-op. } // -- Deprecated methods -- @@ -315,32 +168,91 @@ public static boolean canConvert(final Object src, final Class dest) { return (handler == null ? false : handler.canConvert(src, dest)); } - /** @deprecated use {@link GenericUtils#getClass(Type)} */ + /** @deprecated use {@link Types#enumValue} */ + @Deprecated + public static T convertToEnum(final String src, final Class dest) { + try { + return Types.enumValue(src, dest); + } + catch (final IllegalArgumentException exc) { + return null; + } + } + + /** @deprecated use {@link Types#raw} */ @Deprecated public static Class getClass(final Type type) { - return GenericUtils.getClass(type); + return Types.raw(type); + } + + /** @deprecated use {@link Types#cast} */ + @Deprecated + public static T cast(final Object src, final Class dest) { + if (!canCast(src, dest)) return null; + @SuppressWarnings("unchecked") + final T result = (T) src; + return result; } - /** @deprecated use {@link GenericUtils#getComponentClass(Type)} */ + /** @deprecated use {@link Types#isAssignable} */ + @Deprecated + public static boolean canAssign(final Class src, final Class dest) { + return canCast(src, dest); + } + + /** @deprecated use {@link Types#isInstance} */ + @Deprecated + public static boolean canAssign(final Object src, final Class dest) { + return canCast(src, dest); + } + + /** @deprecated use {@link Types#isAssignable} */ + @Deprecated + public static boolean canCast(final Class src, final Class dest) { + if (dest == null) return false; + if (src == null) return true; + return Types.isAssignable(Types.box(src), Types.box(dest)); + } + + /** @deprecated use {@link Types#isInstance} */ + @Deprecated + public static boolean canCast(final Object src, final Class dest) { + if (dest == null) return false; + return src == null || canCast(src.getClass(), dest); + } + + /** @deprecated use {@link Types#raws} and {@link Types#component} */ @Deprecated public static Class getComponentClass(final Type type) { - return GenericUtils.getComponentClass(type); + return Types.raw(Types.component(type)); + } + + /** @deprecated use {@link Types#unbox} */ + @Deprecated + public static Class getPrimitiveType(final Class type) { + return Types.unbox(type); + } + + /** @deprecated use {@link Types#box} */ + @Deprecated + public static Class getNonprimitiveType(final Class type) { + return Types.box(type); + } + + /** @deprecated Use {@link Types#nullValue} instead. */ + @Deprecated + public static T getNullValue(final Class type) { + return Types.nullValue(type); } -//-- Helper methods -- + // -- Helper methods -- /** - * Gets the {@link Converter} to use for the given conversion request. If the - * delegate {@link ConvertService} has not been explicitly set, then a - * {@link DefaultConverter} will be used. + * Gets the {@link Converter} to use for the given conversion request. * * @return The {@link Converter} to use for handling the given request. */ private static Converter handler(final ConversionRequest data) { - if (convertService != null) return convertService.getHandler(data); - - if (converterNoContext == null) converterNoContext = new DefaultConverter(); - - return converterNoContext; + return converters.stream().filter(c -> c.supports(data)).findFirst().orElse(null); } } diff --git a/src/main/java/org/scijava/util/DebugUtils.java b/src/main/java/org/scijava/util/DebugUtils.java index e0c6e34a7..44e28f780 100644 --- a/src/main/java/org/scijava/util/DebugUtils.java +++ b/src/main/java/org/scijava/util/DebugUtils.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -38,7 +36,6 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.Map; /** @@ -66,6 +63,32 @@ public static String getStackTrace(final Throwable t) { } } + /** + * Provides a stack dump of the given thread. + *

    + * The output is similar to a subset of that given when Ctrl+\ (or Ctrl+Pause + * on Windows) is pressed from the console. + *

    + */ + public static String getStackDump(final Thread thread) { + return getStackDump(thread, thread.getStackTrace()); + } + + /** + * Provides a stack dump of the given thread + call stack. + *

    + * The output is similar to a subset of that given when Ctrl+\ (or Ctrl+Pause + * on Windows) is pressed from the console. + *

    + */ + public static String getStackDump(final Thread thread, + final StackTraceElement[] stackTrace) + { + final StringBuilder sb = new StringBuilder(); + dumpThread(thread, stackTrace, sb); + return sb.toString(); + } + /** * Provides a complete stack dump of all threads. *

    @@ -81,13 +104,7 @@ public static String getStackDump() { // sort list of threads by name final ArrayList threads = new ArrayList<>(stackTraces.keySet()); - Collections.sort(threads, new Comparator() { - - @Override - public int compare(final Thread t1, final Thread t2) { - return t1.getName().compareTo(t2.getName()); - } - }); + Collections.sort(threads, (t1, t2) -> t1.getName().compareTo(t2.getName())); for (final Thread t : threads) { dumpThread(t, stackTraces.get(t), sb); diff --git a/src/main/java/org/scijava/util/DefaultTreeNode.java b/src/main/java/org/scijava/util/DefaultTreeNode.java new file mode 100644 index 000000000..e2c97985d --- /dev/null +++ b/src/main/java/org/scijava/util/DefaultTreeNode.java @@ -0,0 +1,79 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.util; + +import java.util.ArrayList; +import java.util.List; + +/** + * Default implementation of {@link TreeNode}. + * + * @author Alison Walter + * @param type of data associated with the node + */ +public class DefaultTreeNode implements TreeNode { + + private TreeNode parent; + private final List> children; + private final T data; + + /** + * Creates a new tree node wrapping the given data, located in the tree + * beneath the specified parent. + * + * @param data The data to wrap. + * @param parent The parent node of the tree. + */ + public DefaultTreeNode(final T data, final TreeNode parent) { + this.data = data; + this.parent = parent; + children = new ArrayList<>(); + } + + @Override + public T data() { + return data; + } + + @Override + public TreeNode parent() { + return parent; + } + + @Override + public void setParent(final TreeNode parent) { + this.parent = parent; + } + + @Override + public List> children() { + return children; + } +} diff --git a/src/main/java/org/scijava/util/DigestUtils.java b/src/main/java/org/scijava/util/DigestUtils.java index 0b568e776..84a79eb66 100644 --- a/src/main/java/org/scijava/util/DigestUtils.java +++ b/src/main/java/org/scijava/util/DigestUtils.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -34,8 +32,7 @@ import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; - -import javax.xml.bind.DatatypeConverter; +import java.util.Base64; /** * Utility class for computing cryptographic hashes. @@ -100,7 +97,7 @@ public static String hex(final byte[] bytes) { /** Converts the given byte array to a base64 string. */ public static String base64(final byte[] bytes) { - return DatatypeConverter.printBase64Binary(bytes); + return new String(Base64.getEncoder().encode(bytes)); } /** diff --git a/src/main/java/org/scijava/util/DoubleArray.java b/src/main/java/org/scijava/util/DoubleArray.java index 244abd2ae..de007478c 100644 --- a/src/main/java/org/scijava/util/DoubleArray.java +++ b/src/main/java/org/scijava/util/DoubleArray.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/FileUtils.java b/src/main/java/org/scijava/util/FileUtils.java index f916dd5db..2761edf73 100644 --- a/src/main/java/org/scijava/util/FileUtils.java +++ b/src/main/java/org/scijava/util/FileUtils.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -30,7 +28,7 @@ */ // File path shortening code adapted from: -// from: http://www.rgagnon.com/javadetails/java-0661.html +// from: https://www.rgagnon.com/javadetails/java-0661.html package org.scijava.util; @@ -57,6 +55,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.scijava.Context; + /** * Useful methods for working with file paths. * @@ -151,10 +151,12 @@ public static byte[] readFile(final File file) throws IOException { if (length > Integer.MAX_VALUE) { throw new IllegalArgumentException("File too large"); } - final DataInputStream dis = new DataInputStream(new FileInputStream(file)); final byte[] bytes = new byte[(int) length]; - dis.readFully(bytes); - dis.close(); + try (final DataInputStream dis = new DataInputStream(new FileInputStream( + file))) + { + dis.readFully(bytes); + } return bytes; } @@ -167,13 +169,9 @@ public static byte[] readFile(final File file) throws IOException { public static void writeFile(final File file, final byte[] bytes) throws IOException { - final FileOutputStream out = new FileOutputStream(file); - try { + try (final FileOutputStream out = new FileOutputStream(file)) { out.write(bytes); } - finally { - out.close(); - } } public static String stripFilenameVersion(final String filename) { @@ -398,6 +396,30 @@ public static String limitPath(final String path, final int limit) { return new String(shortPathArray); } + /** + * Creates a temporary directory. + *

    + * Since there is no atomic operation to do that, we create a temporary file, + * delete it and create a directory in its place. To avoid race conditions, we + * use the optimistic approach: if the directory cannot be created, we try to + * obtain a new temporary file rather than erroring out. + *

    + *

    + * It is the caller's responsibility to make sure that the directory is + * deleted; see {@link #deleteRecursively(File)}. + *

    + * + * @param prefix The prefix string to be used in generating the file's name; + * see {@link File#createTempFile(String, String, File)} + * @return An abstract pathname denoting a newly-created empty directory + * @throws IOException + */ + public static File createTemporaryDirectory(final String prefix) + throws IOException + { + return createTemporaryDirectory(prefix, null, null); + } + /** * Creates a temporary directory. *

    @@ -576,29 +598,29 @@ else if (protocol.equals("jar")) { final JarURLConnection connection = (JarURLConnection) new URL(baseURL).openConnection(); - final JarFile jar = connection.getJarFile(); - for (final JarEntry entry : new IteratorPlus<>(jar.entries())) { - final String urlEncoded = - new URI(null, null, entry.getName(), null).toString(); - if (urlEncoded.length() > prefix.length() && // omit directory itself - urlEncoded.startsWith(prefix)) - { - if (filesOnly && urlEncoded.endsWith("/")) { - // URL is directory; exclude it - continue; - } - if (!recurse) { - // check whether this URL is a *direct* child of the directory - final int slash = urlEncoded.indexOf("/", prefix.length()); - if (slash >= 0 && slash != urlEncoded.length() - 1) { - // not a direct child + try (final JarFile jar = connection.getJarFile()) { + for (final JarEntry entry : new IteratorPlus<>(jar.entries())) { + final String urlEncoded = + new URI(null, null, entry.getName(), null).toString(); + if (urlEncoded.length() > prefix.length() && // omit directory itself + urlEncoded.startsWith(prefix)) + { + if (filesOnly && urlEncoded.endsWith("/")) { + // URL is directory; exclude it continue; } + if (!recurse) { + // check whether this URL is a *direct* child of the directory + final int slash = urlEncoded.indexOf("/", prefix.length()); + if (slash >= 0 && slash != urlEncoded.length() - 1) { + // not a direct child + continue; + } + } + result.add(new URL(baseURL + urlEncoded)); } - result.add(new URL(baseURL + urlEncoded)); } } - jar.close(); } catch (final IOException e) { e.printStackTrace(); @@ -641,7 +663,7 @@ public static Map findResources(final String regex, final String pathPrefix, final File baseDirectory) { // scan URL resource paths first - final ClassLoader loader = Thread.currentThread().getContextClassLoader(); + final ClassLoader loader = Context.getClassLoader(); final ArrayList urls = new ArrayList<>(); try { urls.addAll(Collections.list(loader.getResources(pathPrefix + "/"))); @@ -706,9 +728,10 @@ private static String classifiers() { "shaded", "sources", "javadoc", - "native", - "(natives-)?(android|linux|macosx|solaris|windows)-" + - "(aarch64|amd64|arm|armv6|armv6hf|i586|universal|x86|x86_64)", + "natives?-?\\w*", + "(natives-)?(android|linux|macosx|macos|solaris|windows)-" + + "(aarch64|amd64|arm64|armv6hf|armv6|arm|" + + "i386|i486|i586|i686|universal|x86[_-]32|x86[_-]64|x86)", }; final StringBuilder sb = new StringBuilder("("); for (final String classifier : classifiers) { diff --git a/src/main/java/org/scijava/util/FloatArray.java b/src/main/java/org/scijava/util/FloatArray.java index 22244a61f..49046497a 100644 --- a/src/main/java/org/scijava/util/FloatArray.java +++ b/src/main/java/org/scijava/util/FloatArray.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/GenericUtils.java b/src/main/java/org/scijava/util/GenericUtils.java index e0a6ae873..9cdb71614 100644 --- a/src/main/java/org/scijava/util/GenericUtils.java +++ b/src/main/java/org/scijava/util/GenericUtils.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -31,198 +29,89 @@ package org.scijava.util; -import com.googlecode.gentyref.GenericTypeReflector; - import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.List; -/** - * Useful methods for working with {@link Type} objects, particularly generic - * types. - *

    - * This class leans heavily on the excellent gentyref library, and exists - * mainly to keep the gentyref dependency encapsulated within SciJava Common. - *

    - * - * @author Curtis Rueden - * @see ClassUtils For utility methods specific to {@link Class} objects. - * @see ConversionUtils For utility methods that convert between {@link Type}s. - */ +import org.scijava.util.Types; + +/** @deprecated Use {@link Types} instead. */ +@Deprecated public final class GenericUtils { private GenericUtils() { // prevent instantiation of utility class } - /** - * Gets the sole raw class corresponding to the given type, or null if none. - */ + /** @deprecated Use {@link Types#raw} instead. */ + @Deprecated public static Class getClass(final Type type) { - if (type == null) return null; - if (type instanceof Class) return (Class) type; - final List> c = getClasses(type); - if (c == null || c.size() != 1) return null; - return c.get(0); + final List> bounds = Types.raws(type); + return bounds != null && bounds.size() == 1 ? bounds.get(0) : null; } - /** - * Gets all raw classes corresponding to the given type. - *

    - * For example, a type parameter {@code A extends Number & Iterable} will - * return both {@link Number} and {@link Iterable} as its raw classes. - *

    - */ + /** @deprecated Use {@link Types#raws} instead. */ + @Deprecated public static List> getClasses(final Type type) { - if (type == null) return null; - return GenericTypeReflector.getUpperBoundClassAndInterfaces(type); + return Types.raws(type); } - /** - * Gets the component type of the given array type, or null if not an array. - */ + /** @deprecated Use {@link Types#component} instead. */ + @Deprecated public static Type getComponentType(final Type type) { - return GenericTypeReflector.getArrayComponentType(type); + return Types.component(type); } /** - * Gets the sole component class of the given array type, or null if none. + * @deprecated Use {@link Types#component} and {@link Types#raw} instead. */ + @Deprecated public static Class getComponentClass(final Type type) { - return getClass(getComponentType(type)); + return Types.raw(Types.component(type)); } - /** - * Returns the "safe" generic type of the given field, as viewed from the - * given type. This may be narrower than what {@link Field#getGenericType()} - * returns, if the field is declared in a superclass, or {@code type} has a - * type parameter that is used in the type of the field. - *

    - * For example, suppose we have the following three classes: - *

    - * - *
    -	 * public class Thing<T> {
    -	 * 	public T thing;
    -	 * }
    -	 * 
    -	 * public class NumberThing<N extends Number> extends Thing<N> { }
    -	 * 
    -	 * public class IntegerThing extends NumberThing<Integer> { }
    -	 * 
    - * - * Then this method operates as follows: - * - *
    -	 * field = ClassUtils.getField(Thing.class, "thing");
    -	 * 
    -	 * field.getType(); // Object
    -	 * field.getGenericType(); // T
    -	 * 
    -	 * GenericUtils.getFieldType(field, Thing.class); // T
    -	 * GenericUtils.getFieldType(field, NumberThing.class); // N extends Number
    -	 * GenericUtils.getFieldType(field, IntegerThing.class); // Integer
    -	 * 
    - */ + /** @deprecated Use {@link Types#fieldType(Field, Class)} instead. */ + @Deprecated public static Type getFieldType(final Field field, final Class type) { - final Type wildType = GenericTypeReflector.addWildcardParameters(type); - return GenericTypeReflector.getExactFieldType(field, wildType); + return Types.fieldType(field, type); } /** - * Returns the "safe" class(es) of the given field, as viewed from the - * specified type. This may be narrower than what {@link Field#getType()} - * returns, if the field is declared in a superclass, or {@code type} has a - * type parameter that is used in the type of the field. - *

    - * For example, suppose we have the following three classes: - *

    - * - *
    -	 * 
    -	 * public class Thing<T> {
    -	 * 
    -	 * 	public T thing;
    -	 * }
    -	 * 
    -	 * public class NumberThing<N extends Number> extends Thing<N> {}
    -	 * 
    -	 * public class IntegerThing extends NumberThing<Integer> {}
    -	 * 
    - * - * Then this method operates as follows: - * - *
    -	 * field = ClassUtils.getField(Thing.class, "thing");
    -	 * 
    -	 * field.getType(); // Object
    -	 * 
    -	 * ClassUtils.getTypes(field, Thing.class).get(0); // Object
    -	 * ClassUtils.getTypes(field, NumberThing.class).get(0); // Number
    -	 * ClassUtils.getTypes(field, IntegerThing.class).get(0); // Integer
    -	 * 
    - *

    - * In cases of complex generics which take the intersection of multiple types - * using the {@code &} operator, there may be multiple types returned by this - * method. For example: - *

    - * - *
    -	 * public class ComplexThing<T extends Serializable & Cloneable> extends Thing<T> {}
    -	 * 
    -	 * ClassUtils.getTypes(field, ComplexThing.class); // Serializable, Cloneable
    -	 * 
    - * - * @see #getFieldType(Field, Class) - * @see #getClasses(Type) + * @deprecated Use {@link Types#fieldType(Field, Class)} and {@link Types#raws} + * instead. */ + @Deprecated public static List> getFieldClasses(final Field field, final Class type) { - final Type genericType = getFieldType(field, type); - return getClasses(genericType); + return Types.raws(Types.fieldType(field, type)); } - /** - * As {@link #getFieldType(Field, Class)}, but with respect to the return - * type of the given {@link Method} rather than a {@link Field}. - */ + /** @deprecated Use {@link Types#methodReturnType} instead. */ + @Deprecated public static Type getMethodReturnType(final Method method, final Class type) { - final Type wildType = GenericTypeReflector.addWildcardParameters(type); - return GenericTypeReflector.getExactReturnType(method, wildType); + return Types.methodReturnType(method, type); } /** - * As {@link #getFieldClasses(Field, Class)}, but with respect to the return - * type of the given {@link Method} rather than a {@link Field}. - * - * @see #getMethodReturnType(Method, Class) - * @see #getClasses(Type) + * @deprecated Use {@link Types#methodReturnType} and {@link Types#raws} instead. */ - public static List> - getMethodReturnClasses(final Method method, final Class type) + @Deprecated + public static List> getMethodReturnClasses(final Method method, + final Class type) { - final Type genericType = getMethodReturnType(method, type); - return getClasses(genericType); + return Types.raws(Types.methodReturnType(method, type)); } - /** - * Gets the given type's {@code n}th type parameter of the specified class. - *

    - * For example, with class {@code StringList implements List}, - * {@code getTypeParameter(StringList.class, Collection.class, 0)} returns - * {@code String}. - *

    - */ + /** @deprecated Use {@link Types#param} instead. */ + @Deprecated public static Type getTypeParameter(final Type type, final Class c, final int paramNo) { - return GenericTypeReflector.getTypeParameter(type, - c.getTypeParameters()[paramNo]); + return Types.param(type, c, paramNo); } } diff --git a/src/main/java/org/scijava/util/IntArray.java b/src/main/java/org/scijava/util/IntArray.java index 6800f7929..f3c6a8e71 100644 --- a/src/main/java/org/scijava/util/IntArray.java +++ b/src/main/java/org/scijava/util/IntArray.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/IntCoords.java b/src/main/java/org/scijava/util/IntCoords.java index eb4bce513..d0498a7f3 100644 --- a/src/main/java/org/scijava/util/IntCoords.java +++ b/src/main/java/org/scijava/util/IntCoords.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/IntRect.java b/src/main/java/org/scijava/util/IntRect.java index d6a674e13..33670744f 100644 --- a/src/main/java/org/scijava/util/IntRect.java +++ b/src/main/java/org/scijava/util/IntRect.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/IteratorPlus.java b/src/main/java/org/scijava/util/IteratorPlus.java index 6abf6562d..35c9cd3b6 100644 --- a/src/main/java/org/scijava/util/IteratorPlus.java +++ b/src/main/java/org/scijava/util/IteratorPlus.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/LastRecentlyUsed.java b/src/main/java/org/scijava/util/LastRecentlyUsed.java index 64f5679ce..50d292856 100644 --- a/src/main/java/org/scijava/util/LastRecentlyUsed.java +++ b/src/main/java/org/scijava/util/LastRecentlyUsed.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -145,7 +143,7 @@ public boolean replace(final int index, T newValue) { throw new IllegalArgumentException("No current entry at position " + index); } - if (newValue.equals(previous)) return false; + if (newValue.equals(previousValue)) return false; map.remove(previousValue); map.put(newValue, index); entries[index] = newValue; diff --git a/src/main/java/org/scijava/util/LineOutputStream.java b/src/main/java/org/scijava/util/LineOutputStream.java index 21cab3510..b0cd47e87 100644 --- a/src/main/java/org/scijava/util/LineOutputStream.java +++ b/src/main/java/org/scijava/util/LineOutputStream.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/ListUtils.java b/src/main/java/org/scijava/util/ListUtils.java index aef0f63b6..8f07d06a3 100644 --- a/src/main/java/org/scijava/util/ListUtils.java +++ b/src/main/java/org/scijava/util/ListUtils.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -30,7 +28,7 @@ */ // File path shortening code adapted from: -// from: http://www.rgagnon.com/javadetails/java-0661.html +// from: https://www.rgagnon.com/javadetails/java-0661.html package org.scijava.util; diff --git a/src/main/java/org/scijava/util/LongArray.java b/src/main/java/org/scijava/util/LongArray.java index 3c3a6ad56..12ae4a737 100644 --- a/src/main/java/org/scijava/util/LongArray.java +++ b/src/main/java/org/scijava/util/LongArray.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/Manifest.java b/src/main/java/org/scijava/util/Manifest.java index cbf701e9f..58100535e 100644 --- a/src/main/java/org/scijava/util/Manifest.java +++ b/src/main/java/org/scijava/util/Manifest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -138,7 +136,7 @@ public Map getAll() { /** Gets the JAR manifest associated with the given class. */ public static Manifest getManifest(final Class c) { try { - return getManifest(new URL("jar:" + ClassUtils.getLocation(c) + "!/")); + return getManifest(new URL("jar:" + Types.location(c) + "!/")); } catch (final IOException e) { return null; diff --git a/src/main/java/org/scijava/util/MersenneTwisterFast.java b/src/main/java/org/scijava/util/MersenneTwisterFast.java index 03b5dca30..e1cdb4a48 100644 --- a/src/main/java/org/scijava/util/MersenneTwisterFast.java +++ b/src/main/java/org/scijava/util/MersenneTwisterFast.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/MetaInfCombiner.java b/src/main/java/org/scijava/util/MetaInfCombiner.java index 75418f949..3d03195f6 100644 --- a/src/main/java/org/scijava/util/MetaInfCombiner.java +++ b/src/main/java/org/scijava/util/MetaInfCombiner.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/MirrorWebsite.java b/src/main/java/org/scijava/util/MirrorWebsite.java index 2f21226eb..2b5bdf4f9 100644 --- a/src/main/java/org/scijava/util/MirrorWebsite.java +++ b/src/main/java/org/scijava/util/MirrorWebsite.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -60,7 +58,7 @@ * This program mirrors a given website. *

    * Its primary purpose is to provide the code necessary to keep ImageJ Mirror up-to-date. + * href="https://mirror.imagej.net/">ImageJ Mirror up-to-date. *

    * * @author Johannes Schindelin diff --git a/src/main/java/org/scijava/util/MiscUtils.java b/src/main/java/org/scijava/util/MiscUtils.java index 9502f7894..eb9fad0c1 100644 --- a/src/main/java/org/scijava/util/MiscUtils.java +++ b/src/main/java/org/scijava/util/MiscUtils.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/NumberUtils.java b/src/main/java/org/scijava/util/NumberUtils.java index 95f7207a2..ba3fdc6d9 100644 --- a/src/main/java/org/scijava/util/NumberUtils.java +++ b/src/main/java/org/scijava/util/NumberUtils.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -34,6 +32,8 @@ import java.math.BigDecimal; import java.math.BigInteger; +import org.scijava.util.Types; + /** * Useful methods for working with {@link Number} objects. @@ -53,7 +53,7 @@ private NumberUtils() { */ public static Number toNumber(final Object value, final Class type) { final Object num = ConversionUtils.convert(value, type); - return num == null ? null : ConversionUtils.cast(num, Number.class); + return num == null ? null : Types.cast(num, Number.class); } public static BigDecimal asBigDecimal(final Number n) { @@ -72,22 +72,26 @@ public static BigInteger asBigInteger(final Number n) { } public static Number getMinimumNumber(final Class type) { - if (ClassUtils.isByte(type)) return Byte.MIN_VALUE; - if (ClassUtils.isShort(type)) return Short.MIN_VALUE; - if (ClassUtils.isInteger(type)) return Integer.MIN_VALUE; - if (ClassUtils.isLong(type)) return Long.MIN_VALUE; - if (ClassUtils.isFloat(type)) return -Float.MAX_VALUE; - if (ClassUtils.isDouble(type)) return -Double.MAX_VALUE; + if (Types.isByte(type)) return Byte.MIN_VALUE; + if (Types.isShort(type)) return Short.MIN_VALUE; + if (Types.isInteger(type)) return Integer.MIN_VALUE; + if (Types.isLong(type)) return Long.MIN_VALUE; + if (Types.isFloat(type)) return -Float.MAX_VALUE; + if (Types.isDouble(type)) return -Double.MAX_VALUE; + // Fallback for Number.class + if (Types.isNumber(type)) return -Double.MAX_VALUE; return null; } public static Number getMaximumNumber(final Class type) { - if (ClassUtils.isByte(type)) return Byte.MAX_VALUE; - if (ClassUtils.isShort(type)) return Short.MAX_VALUE; - if (ClassUtils.isInteger(type)) return Integer.MAX_VALUE; - if (ClassUtils.isLong(type)) return Long.MAX_VALUE; - if (ClassUtils.isFloat(type)) return Float.MAX_VALUE; - if (ClassUtils.isDouble(type)) return Double.MAX_VALUE; + if (Types.isByte(type)) return Byte.MAX_VALUE; + if (Types.isShort(type)) return Short.MAX_VALUE; + if (Types.isInteger(type)) return Integer.MAX_VALUE; + if (Types.isLong(type)) return Long.MAX_VALUE; + if (Types.isFloat(type)) return Float.MAX_VALUE; + if (Types.isDouble(type)) return Double.MAX_VALUE; + // Fallback for Number.class + if (Types.isNumber(type)) return Double.MAX_VALUE; return null; } diff --git a/src/main/java/org/scijava/util/ObjectArray.java b/src/main/java/org/scijava/util/ObjectArray.java index febf6bdaa..daaa6e7ed 100644 --- a/src/main/java/org/scijava/util/ObjectArray.java +++ b/src/main/java/org/scijava/util/ObjectArray.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -183,7 +181,7 @@ public boolean contains(final Object o) { @Override public boolean remove(final Object o) { if (!compatibleClass(o)) return false; - final E value = ConversionUtils.cast(o, objectClass); + final E value = Types.cast(o, objectClass); return removeValue(value); } @@ -215,7 +213,7 @@ public boolean removeAll(final Collection c) { boolean changed = false; for (final Object o : c) { if (!compatibleClass(o)) continue; - final E value = ConversionUtils.cast(o, objectClass); + final E value = Types.cast(o, objectClass); final boolean result = removeValue(value); if (result) changed = true; } diff --git a/src/main/java/org/scijava/util/POM.java b/src/main/java/org/scijava/util/POM.java index 85b403b3c..ed7dc8009 100644 --- a/src/main/java/org/scijava/util/POM.java +++ b/src/main/java/org/scijava/util/POM.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -42,6 +40,7 @@ import javax.xml.parsers.ParserConfigurationException; +import org.scijava.Context; import org.scijava.Versioned; import org.xml.sax.SAXException; @@ -183,18 +182,19 @@ public String getCIManagementURL() { // -- Comparable methods -- - @Override - public int compareTo(final POM pom) { + private static final Comparator STRING_COMPARATOR = // + Comparator.nullsFirst(String::compareTo); + private static final Comparator POM_COMPARATOR = Comparator// // sort by groupId first - final int gid = getGroupId().compareTo(pom.getGroupId()); - if (gid != 0) return gid; - + .comparing(POM::getGroupId, STRING_COMPARATOR) // sort by artifactId second - final int aid = getArtifactId().compareTo(pom.getArtifactId()); - if (aid != 0) return aid; - + .thenComparing(POM::getArtifactId, STRING_COMPARATOR)// // finally, sort by version - return compareVersions(getVersion(), pom.getVersion()); + .thenComparing(POM::getVersion, POM::compareVersions); + + @Override + public int compareTo(final POM pom) { + return POM_COMPARATOR.compare(this, pom); } // -- Versioned methods -- @@ -215,27 +215,53 @@ public String getVersion() { // -- Utility methods -- + /** + * Gets the Maven POM associated with the given class. + * + * @param c The class to use as a base when searching for a pom.xml. + * @return {@link POM} object representing the discovered POM, or null if no + * POM could be found. + */ + public static POM getPOM(final Class c) { + return getPOM(c, null, null); + } + /** * Gets the Maven POM associated with the given class. * * @param c The class to use as a base when searching for a pom.xml. * @param groupId The Maven groupId of the desired POM. * @param artifactId The Maven artifactId of the desired POM. + * @return {@link POM} object representing the discovered POM, or null if no + * POM could be found. */ public static POM getPOM(final Class c, final String groupId, final String artifactId) { try { - final URL location = ClassUtils.getLocation(c); + final URL location = Types.location(c); if (!location.getProtocol().equals("file") || location.toString().endsWith(".jar")) { // look for pom.xml in JAR's META-INF/maven subdirectory - final String pomPath = - "META-INF/maven/" + groupId + "/" + artifactId + "/pom.xml"; - final URL pomURL = - new URL("jar:" + location.toString() + "!/" + pomPath); - return new POM(pomURL); + if (groupId == null || artifactId == null) { + // groupId and/or artifactId is unknown; scan for the POM + final URL pomBase = new URL("jar:" + // + location.toString() + "!/META-INF/maven"); + for (final URL url : FileUtils.listContents(pomBase, true, true)) { + if (url.toExternalForm().endsWith("/pom.xml")) { + return new POM(url); + } + } + } + else { + // known groupId and artifactId; grab it directly + final String pomPath = + "META-INF/maven/" + groupId + "/" + artifactId + "/pom.xml"; + final URL pomURL = + new URL("jar:" + location.toString() + "!/" + pomPath); + return new POM(pomURL); + } } // look for the POM in the class's base directory final File file = FileUtils.urlToFile(location); @@ -243,13 +269,7 @@ public static POM getPOM(final Class c, final String groupId, final File pomFile = new File(baseDir, "pom.xml"); return new POM(pomFile); } - catch (final IOException e) { - return null; - } - catch (final ParserConfigurationException e) { - return null; - } - catch (final SAXException e) { + catch (final IOException | ParserConfigurationException | SAXException e) { return null; } } @@ -258,8 +278,7 @@ public static POM getPOM(final Class c, final String groupId, public static List getAllPOMs() { // find all META-INF/maven/ folders on the classpath final String pomPrefix = "META-INF/maven/"; - final ClassLoader classLoader = - Thread.currentThread().getContextClassLoader(); + final ClassLoader classLoader = Context.getClassLoader(); final Enumeration resources; try { resources = classLoader.getResources(pomPrefix); @@ -316,7 +335,7 @@ public static List getAllPOMs() { * that one has a suffix beginning with a dash ({@code -}), the version with * suffix will be considered less than the one without a suffix. The * reason for this is to accommodate the SemVer versioning scheme's usage of + * href="https://semver.org/">SemVer versioning scheme's usage of * "prerelease" version suffixes. For example, {@code 2.0.0} will compare * greater than {@code 2.0.0-beta-1}, whereas {@code 2.0.0} will compare less * than {@code 2.0.0.1}.
  • diff --git a/src/main/java/org/scijava/util/PlatformUtils.java b/src/main/java/org/scijava/util/PlatformUtils.java index 3ffcf9c59..a8b8cf909 100644 --- a/src/main/java/org/scijava/util/PlatformUtils.java +++ b/src/main/java/org/scijava/util/PlatformUtils.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/Prefs.java b/src/main/java/org/scijava/util/Prefs.java index c7919fb9c..f70bd70d1 100644 --- a/src/main/java/org/scijava/util/Prefs.java +++ b/src/main/java/org/scijava/util/Prefs.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/PrimitiveArray.java b/src/main/java/org/scijava/util/PrimitiveArray.java index c1c9d47dd..6efb5e463 100644 --- a/src/main/java/org/scijava/util/PrimitiveArray.java +++ b/src/main/java/org/scijava/util/PrimitiveArray.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/ProcessUtils.java b/src/main/java/org/scijava/util/ProcessUtils.java index 6e09e202a..2e5cb62e5 100644 --- a/src/main/java/org/scijava/util/ProcessUtils.java +++ b/src/main/java/org/scijava/util/ProcessUtils.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/PropertiesHelper.java b/src/main/java/org/scijava/util/PropertiesHelper.java new file mode 100644 index 000000000..c2abeb78b --- /dev/null +++ b/src/main/java/org/scijava/util/PropertiesHelper.java @@ -0,0 +1,72 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.util; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; + +/** + * Simple utility for reading and writing a property map to/from plain text. + */ +public final class PropertiesHelper { + + public static Map get(File filename) { + Map map = new HashMap<>(); + try (BufferedReader reader = new BufferedReader(new FileReader(filename))) { + String line; + while ((line = reader.readLine()) != null) { + String[] parts = line.split("=", 2); + if (parts.length == 2) { + map.put(parts[0], parts[1]); + } + } + } + catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + catch (IOException e) { + throw new RuntimeException(e); + } + return map; + } + + public static void put(Map properties, File filename) { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) { + for (Map.Entry entry : properties.entrySet()) { + writer.write(entry.getKey() + "=" + entry.getValue()); + writer.newLine(); + } + } + catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/org/scijava/util/Query.java b/src/main/java/org/scijava/util/Query.java index a2cabad05..7217b290e 100644 --- a/src/main/java/org/scijava/util/Query.java +++ b/src/main/java/org/scijava/util/Query.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/ReadInto.java b/src/main/java/org/scijava/util/ReadInto.java index 8f43b9070..cefca34fd 100644 --- a/src/main/java/org/scijava/util/ReadInto.java +++ b/src/main/java/org/scijava/util/ReadInto.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/RealCoords.java b/src/main/java/org/scijava/util/RealCoords.java index a75303756..fd949dbbf 100644 --- a/src/main/java/org/scijava/util/RealCoords.java +++ b/src/main/java/org/scijava/util/RealCoords.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/RealRect.java b/src/main/java/org/scijava/util/RealRect.java index 0c90c0e4b..507406235 100644 --- a/src/main/java/org/scijava/util/RealRect.java +++ b/src/main/java/org/scijava/util/RealRect.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/ReflectException.java b/src/main/java/org/scijava/util/ReflectException.java index 9c9d621ca..bc4e0f246 100644 --- a/src/main/java/org/scijava/util/ReflectException.java +++ b/src/main/java/org/scijava/util/ReflectException.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/ReflectedUniverse.java b/src/main/java/org/scijava/util/ReflectedUniverse.java index 2f7f1d6fa..c43a05dab 100644 --- a/src/main/java/org/scijava/util/ReflectedUniverse.java +++ b/src/main/java/org/scijava/util/ReflectedUniverse.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/ServiceCombiner.java b/src/main/java/org/scijava/util/ServiceCombiner.java index 8422b4b5f..64053aa7c 100644 --- a/src/main/java/org/scijava/util/ServiceCombiner.java +++ b/src/main/java/org/scijava/util/ServiceCombiner.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -42,10 +40,10 @@ import java.util.Map; import java.util.Map.Entry; -import javax.xml.ws.Service; +import org.scijava.Context; /** - * Combines {@link Service} information from all JAR files on the classpath. + * Combines {@code Service} information from all JAR files on the classpath. * * @author Johannes Schindelin * @author Mark Hiner @@ -60,8 +58,7 @@ public void combine(final File outputDirectory) throws IOException { final Map files = new HashMap<>(); final Enumeration directories = - Thread.currentThread().getContextClassLoader().getResources( - SERVICES_PREFIX); + Context.getClassLoader().getResources(SERVICES_PREFIX); // Iterate over all the service files while (directories.hasMoreElements()) { diff --git a/src/main/java/org/scijava/util/ShortArray.java b/src/main/java/org/scijava/util/ShortArray.java index 4f6fea077..935bf6b96 100644 --- a/src/main/java/org/scijava/util/ShortArray.java +++ b/src/main/java/org/scijava/util/ShortArray.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/Sizable.java b/src/main/java/org/scijava/util/Sizable.java index 936e4b0ed..21a1466d7 100644 --- a/src/main/java/org/scijava/util/Sizable.java +++ b/src/main/java/org/scijava/util/Sizable.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/SizableArrayList.java b/src/main/java/org/scijava/util/SizableArrayList.java index db572380d..21ecdf0ac 100644 --- a/src/main/java/org/scijava/util/SizableArrayList.java +++ b/src/main/java/org/scijava/util/SizableArrayList.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -31,9 +29,9 @@ package org.scijava.util; -import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; /** * An {@link ArrayList} whose size can be adjusted more efficiently. @@ -86,42 +84,8 @@ public void setSize(final int size) { else { // need to add some elements ensureCapacity(size); - final boolean hackSuccessful = hackSize(size); - if (!hackSuccessful) { - // explicitly increase the size by adding nulls - while (size() < size) add(null); - } + addAll(Collections.nCopies(size - oldSize, null)); } } - // -- Helper methods -- - - private boolean hackSize(final int size) { - // HACK: Override the size field directly. - final int oldSize; - try { - final Field sizeField = ArrayList.class.getDeclaredField("size"); - sizeField.setAccessible(true); - oldSize = (Integer) sizeField.get(this); - sizeField.set(this, size); - - // NB: Check that it worked. In the case of Java 1.7.0_45, it is possible - // for the capacity to not *actually* be ensured, in which case - // subsequently attempting to get the (size - 1)th value results in - // ArrayIndexOutOfBoundsException. So let's be safe and verify it here. - try { - get(size - 1); - } - catch (final Exception exc) { - // NB: Restore the previous size, then fail. - sizeField.set(this, oldSize); - return false; - } - } - catch (final Exception exc) { - return false; - } - return true; - } - } diff --git a/src/main/java/org/scijava/util/StringMaker.java b/src/main/java/org/scijava/util/StringMaker.java index 3e3bf8385..3df9b8786 100644 --- a/src/main/java/org/scijava/util/StringMaker.java +++ b/src/main/java/org/scijava/util/StringMaker.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/StringUtils.java b/src/main/java/org/scijava/util/StringUtils.java index 0d77ce1bc..a38afb144 100644 --- a/src/main/java/org/scijava/util/StringUtils.java +++ b/src/main/java/org/scijava/util/StringUtils.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -62,6 +60,7 @@ import java.io.File; import java.text.DecimalFormatSymbols; +import java.util.regex.Pattern; /** * Useful methods for working with {@link String}s. @@ -79,6 +78,16 @@ private StringUtils() { // NB: prevent instantiation of utility class. } + /** + * Splits a string only at separators outside of quotation marks ({@code "}). + * Does not handle escaped quotes. + */ + public static String[] splitUnquoted(final String s, final String separator) { + // See https://stackoverflow.com/a/1757107/1919049 + return s.split(Pattern.quote(separator) + + "(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)", -1); + } + /** Normalizes the decimal separator for the user's locale. */ public static String sanitizeDouble(String value) { value = value.replaceAll("[^0-9,\\.]", ""); diff --git a/src/main/java/org/scijava/util/Timing.java b/src/main/java/org/scijava/util/Timing.java index 15641e7dd..9cd4a1863 100644 --- a/src/main/java/org/scijava/util/Timing.java +++ b/src/main/java/org/scijava/util/Timing.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/TreeNode.java b/src/main/java/org/scijava/util/TreeNode.java new file mode 100644 index 000000000..f302f9e59 --- /dev/null +++ b/src/main/java/org/scijava/util/TreeNode.java @@ -0,0 +1,64 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.util; + +import java.util.List; + +/** + * A wrapper around a data object, for storing it in a tree structure. + * + * @author Alison Walter + * @param type of data associated with the node + */ +public interface TreeNode { + + /** Gets the data associated with the node. */ + T data(); + + /** Gets the parent of this node. */ + TreeNode parent(); + + void setParent(TreeNode parent); + + /** + * Gets the node's children. If this list is mutated, the children will be + * affected accordingly. It is the responsibility of the caller to ensure + * continued integrity, particularly of parent linkages. + */ + List> children(); + + /** Adds the given list of children to this node. */ + default void addChildren(final List> nodes) { + for (final TreeNode child : nodes) { + child.setParent(parent()); + } + children().addAll(nodes); + } +} diff --git a/src/main/java/org/scijava/util/TunePlayer.java b/src/main/java/org/scijava/util/TunePlayer.java index f5acd838b..8ade8a695 100644 --- a/src/main/java/org/scijava/util/TunePlayer.java +++ b/src/main/java/org/scijava/util/TunePlayer.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/Types.java b/src/main/java/org/scijava/util/Types.java new file mode 100644 index 000000000..f9c9068a9 --- /dev/null +++ b/src/main/java/org/scijava/util/Types.java @@ -0,0 +1,3868 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +// Portions of this class were adapted from the +// org.apache.commons.lang3.reflect.TypeUtils and +// org.apache.commons.lang3.Validate classes of +// Apache Commons Lang 3.4, which is distributed +// under the Apache 2 license. +// See lines below starting with "BEGIN FORK OF APACHE COMMONS LANG". +// +// Portions of this class were adapted from the GenTyRef project +// by Wouter Coekaerts, which is distributed under the Apache 2 license. +// See lines below starting with "BEGIN FORK OF GENTYREF". +// +// See NOTICE.txt for further details on third-party licenses. + +package org.scijava.util; + +import java.io.File; +import java.io.Serializable; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.CodeSource; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.scijava.Context; + +/** + * Utility class for working with generic types, fields and methods. + *

    + * Logic and inspiration were drawn from the following excellent libraries: + *

    + *
      + *
    • Google Guava's {@code com.google.common.reflect} package.
    • + *
    • Apache Commons Lang 3's {@code org.apache.commons.lang3.reflect} package. + *
    • + *
    • GenTyRef (Generic Type + * Reflector), a library for runtime generic type introspection.
    • + *
    + *

    + * All three of these libraries contain fantastic generics-related logic, but + * none of the three contained everything that SciJava needed for all its use + * cases. Hence, we have drawn from sources as needed to create a unified + * generics API for use from SciJava applications. See in particular the + * SciJava Ops project, + * which utilizes these functions heavily. + *

    + *

    + * NB: The + * org.apache.commons.reflect.TypeUtils class of + * Apache Commons + * Lang version 3.4 is forked internally within this class. We did this for + * two reasons: 1) to avoid bringing in the whole of Apache Commons Lang as a + * dependency; and 2) to fix an infinite recursion bug in the + * {@code TypeUtils.toString(Type)} method. + *

    + * + * @author Curtis Rueden + * @author Gabe Selzer + */ +public final class Types { + + private Types() { + // NB: Prevent instantiation of utility class. + } + + /** + * Loads the class with the given name, using the current thread's context + * class loader, or null if it cannot be loaded. + * + * @param name The name of the class to load. + * @return The loaded class, or null if the class could not be loaded. + * @see #load(String, ClassLoader, boolean) + */ + public static Class load(final String name) { + return load(name, null, true); + } + + /** + * Loads the class with the given name, using the specified + * {@link ClassLoader}, or null if it cannot be loaded. + * + * @param name The name of the class to load. + * @param classLoader The class loader with which to load the class; if null, + * the current thread's context class loader will be used. + * @return The loaded class, or null if the class could not be loaded. + * @see #load(String, ClassLoader, boolean) + */ + public static Class load(final String name, + final ClassLoader classLoader) + { + return load(name, classLoader, true); + } + + /** + * Loads the class with the given name, using the current thread's context + * class loader. + * + * @param className the name of the class to load. + * @param quietly Whether to return {@code null} (rather than throwing + * {@link IllegalArgumentException}) if something goes wrong loading + * the class. + * @return The loaded class, or {@code null} if the class could not be loaded + * and the {@code quietly} flag is set. + * @see #load(String, ClassLoader, boolean) + * @throws IllegalArgumentException If the class cannot be loaded and the + * {@code quietly} flag is not set. + */ + public static Class load(final String className, final boolean quietly) { + return load(className, null, quietly); + } + + /** + * Loads the class with the given name, using the specified + * {@link ClassLoader}, or null if it cannot be loaded. + *

    + * This method is capable of parsing several different class name syntaxes. In + * particular, array classes (including primitives) represented using either + * square brackets or internal Java array name syntax are supported. Examples: + *

    + *
      + *
    • {@code boolean} is loaded as {@code boolean.class}
    • + *
    • {@code Z} is loaded as {@code boolean.class}
    • + *
    • {@code double[]} is loaded as {@code double[].class}
    • + *
    • {@code string[]} is loaded as {@code java.lang.String.class}
    • + *
    • {@code [F} is loaded as {@code float[].class}
    • + *
    + * + * @param name The name of the class to load. + * @param classLoader The class loader with which to load the class; if null, + * the current thread's context class loader will be used. + * @param quietly Whether to return {@code null} (rather than throwing + * {@link IllegalArgumentException}) if something goes wrong loading + * the class + * @return The loaded class, or {@code null} if the class could not be loaded + * and the {@code quietly} flag is set. + * @throws IllegalArgumentException If the class cannot be loaded and the + * {@code quietly} flag is not set. + */ + public static Class load(final String name, final ClassLoader classLoader, + final boolean quietly) + { + // handle primitive types + if (name.equals("Z") || name.equals("boolean")) return boolean.class; + if (name.equals("B") || name.equals("byte")) return byte.class; + if (name.equals("C") || name.equals("char")) return char.class; + if (name.equals("D") || name.equals("double")) return double.class; + if (name.equals("F") || name.equals("float")) return float.class; + if (name.equals("I") || name.equals("int")) return int.class; + if (name.equals("J") || name.equals("long")) return long.class; + if (name.equals("S") || name.equals("short")) return short.class; + if (name.equals("V") || name.equals("void")) return void.class; + + // handle built-in class shortcuts + final String className; + if (name.equals("string")) className = "java.lang.String"; + else className = name; + + // handle source style arrays (e.g.: "java.lang.String[]") + if (name.endsWith("[]")) { + final String elementClassName = name.substring(0, name.length() - 2); + return arrayOrNull(load(elementClassName, classLoader)); + } + + // handle non-primitive internal arrays (e.g.: "[Ljava.lang.String;") + if (name.startsWith("[L") && name.endsWith(";")) { + final String elementClassName = name.substring(2, name.length() - 1); + return arrayOrNull(load(elementClassName, classLoader)); + } + + // handle other internal arrays (e.g.: "[I", "[[I", "[[Ljava.lang.String;") + if (name.startsWith("[")) { + final String elementClassName = name.substring(1); + return arrayOrNull(load(elementClassName, classLoader)); + } + + // load the class! + try { + final ClassLoader cl = // + classLoader != null ? classLoader : Context.getClassLoader(); + return cl.loadClass(className); + } + catch (final Throwable t) { + // NB: Do not allow any failure to load the class to crash us. + // Not ClassNotFoundException. + // Not NoClassDefFoundError. + // Not UnsupportedClassVersionError! + if (quietly) return null; + throw iae(t, "Cannot load class: " + className); + } + } + + /** + * Gets the base location of the given class. + * + * @param c The class whose location is desired. + * @return URL pointing to the class, or null if the location could not be + * determined. + * @see #location(Class, boolean) + */ + public static URL location(final Class c) { + return location(c, true); + } + + /** + * Gets the base location of the given class. + *

    + * If the class is directly on the file system (e.g., + * "/path/to/my/package/MyClass.class") then it will return the base directory + * (e.g., "file:/path/to"). + *

    + *

    + * If the class is within a JAR file (e.g., + * "/path/to/my-jar.jar!/my/package/MyClass.class") then it will return the + * path to the JAR (e.g., "file:/path/to/my-jar.jar"). + *

    + * + * @param c The class whose location is desired. + * @param quietly Whether to return {@code null} (rather than throwing + * {@link IllegalArgumentException}) if something goes wrong + * determining the location. + * @return URL pointing to the class, or null if the location could not be + * determined and the {@code quietly} flag is set. + * @throws IllegalArgumentException If the location cannot be determined and + * the {@code quietly} flag is not set. + * @see FileUtils#urlToFile(URL) to convert the result to a {@link File}. + */ + public static URL location(final Class c, final boolean quietly) { + Exception cause = null; + String why = null; + + // try the easy way first + try { + final CodeSource codeSource = c.getProtectionDomain().getCodeSource(); + if (codeSource != null) { + final URL location = codeSource.getLocation(); + if (location != null) return location; + why = "null code source location"; + } + else why = "null code source"; + } + catch (final SecurityException exc) { + // NB: Cannot access protection domain. + cause = exc; + why = "cannot access protection domain"; + } + + // NB: The easy way failed, so we try the hard way. We ask for the class + // itself as a resource, then strip the class's path from the URL string, + // leaving the base path. + + // get the class's raw resource path + final URL classResource = c.getResource(c.getSimpleName() + ".class"); + if (classResource == null) { + // cannot find class resource + if (quietly) return null; + throw iae(cause, "No class resource for class: " + name(c), why); + } + + final String url = classResource.toString(); + final String suffix = c.getCanonicalName().replace('.', '/') + ".class"; + if (!url.endsWith(suffix)) { + // weird URL + if (quietly) return null; + throw iae(cause, "Unsupported URL format: " + url, why); + } + + // strip the class's path from the URL string + final String base = url.substring(0, url.length() - suffix.length()); + + String path = base; + + // remove the "jar:" prefix and "!/" suffix, if present + if (path.startsWith("jar:")) path = path.substring(4, path.length() - 2); + + try { + return new URL(path); + } + catch (final MalformedURLException e) { + if (quietly) return null; + throw iae(e, "Malformed URL", why); + } + } + + /** + * Gets a string representation of the given type. + * + * @param t Type whose name is desired. + * @return The name of the given type. + */ + public static String name(final Type t) { + if (t instanceof Class) { + final Class c = (Class) t; + return c.isArray() ? (name(component(c)) + "[]") : c.getName(); + } + return t.toString(); + } + + /** + * Gets the (first) raw class of the given type. + *
      + *
    • If the type is a {@code Class} itself, the type itself is returned. + *
    • + *
    • If the type is a {@link ParameterizedType}, the raw type of the + * parameterized type is returned.
    • + *
    • If the type is a {@link GenericArrayType}, the returned type is the + * corresponding array class. For example: {@code List[] => List[]}. + *
    • + *
    • If the type is a type variable or wildcard type, the raw type of the + * first upper bound is returned. For example: + * {@code => Foo}.
    • + *
    + *

    + * If you want all raw classes of the given type, use {@link #raws}. + *

    + * + * @param type The type from which to discern the (first) raw class. + * @return The type's first raw class. + */ + public static Class raw(final Type type) { + if (type == null) return null; + if (type instanceof Class) return (Class) type; + if (type instanceof GenericArrayType) + return array(raw(((GenericArrayType) type).getGenericComponentType())); + final List> c = raws(type); + if (c == null || c.size() == 0) return null; + return c.get(0); + } + + /** + * Gets all raw classes corresponding to the given type. + *

    + * For example, a type parameter {@code A extends Number & Iterable} will + * return both {@link Number} and {@link Iterable} as its raw classes. + *

    + * + * @param type The type from which to discern the raw classes. + * @return List of the type's raw classes. + * @see #raw + */ + public static List> raws(final Type type) { + if (type == null) return null; + return GenericTypeReflector.getUpperBoundClassAndInterfaces(type); + } + + public static boolean isBoolean(final Class type) { + return type == boolean.class || Boolean.class.isAssignableFrom(type); + } + + public static boolean isByte(final Class type) { + return type == byte.class || Byte.class.isAssignableFrom(type); + } + + public static boolean isCharacter(final Class type) { + return type == char.class || Character.class.isAssignableFrom(type); + } + + public static boolean isDouble(final Class type) { + return type == double.class || Double.class.isAssignableFrom(type); + } + + public static boolean isFloat(final Class type) { + return type == float.class || Float.class.isAssignableFrom(type); + } + + public static boolean isInteger(final Class type) { + return type == int.class || Integer.class.isAssignableFrom(type); + } + + public static boolean isLong(final Class type) { + return type == long.class || Long.class.isAssignableFrom(type); + } + + public static boolean isShort(final Class type) { + return type == short.class || Short.class.isAssignableFrom(type); + } + + public static boolean isNumber(final Class type) { + return Number.class.isAssignableFrom(type) || type == byte.class || + type == double.class || type == float.class || type == int.class || + type == long.class || type == short.class; + } + + public static boolean isText(final Class type) { + return String.class.isAssignableFrom(type) || isCharacter(type); + } + + /** + * Returns the non-primitive {@link Class} closest to the given type. + *

    + * Specifically, the following type conversions are done: + *

    + *
      + *
    • boolean.class becomes Boolean.class
    • + *
    • byte.class becomes Byte.class
    • + *
    • char.class becomes Character.class
    • + *
    • double.class becomes Double.class
    • + *
    • float.class becomes Float.class
    • + *
    • int.class becomes Integer.class
    • + *
    • long.class becomes Long.class
    • + *
    • short.class becomes Short.class
    • + *
    • void.class becomes Void.class
    • + *
    + *

    + * All other types are unchanged. + *

    + */ + public static Class box(final Class type) { + final Class destType; + if (type == boolean.class) destType = Boolean.class; + else if (type == byte.class) destType = Byte.class; + else if (type == char.class) destType = Character.class; + else if (type == double.class) destType = Double.class; + else if (type == float.class) destType = Float.class; + else if (type == int.class) destType = Integer.class; + else if (type == long.class) destType = Long.class; + else if (type == short.class) destType = Short.class; + else if (type == void.class) destType = Void.class; + else destType = type; + @SuppressWarnings("unchecked") + final Class result = (Class) destType; + return result; + } + + /** + * Returns the primitive {@link Class} closest to the given type. + *

    + * Specifically, the following type conversions are done: + *

    + *
      + *
    • Boolean.class becomes boolean.class
    • + *
    • Byte.class becomes byte.class
    • + *
    • Character.class becomes char.class
    • + *
    • Double.class becomes double.class
    • + *
    • Float.class becomes float.class
    • + *
    • Integer.class becomes int.class
    • + *
    • Long.class becomes long.class
    • + *
    • Short.class becomes short.class
    • + *
    • Void.class becomes void.class
    • + *
    + *

    + * All other types are unchanged. + *

    + */ + public static Class unbox(final Class type) { + final Class destType; + if (type == Boolean.class) destType = boolean.class; + else if (type == Byte.class) destType = byte.class; + else if (type == Character.class) destType = char.class; + else if (type == Double.class) destType = double.class; + else if (type == Float.class) destType = float.class; + else if (type == Integer.class) destType = int.class; + else if (type == Long.class) destType = long.class; + else if (type == Short.class) destType = short.class; + else if (type == Void.class) destType = void.class; + else destType = type; + @SuppressWarnings("unchecked") + final Class result = (Class) destType; + return result; + } + + /** + * Gets the "null" value for the given type. For non-primitives, this will + * actually be null. For primitives, it will be zero for numeric types, false + * for boolean, and the null character for char. + */ + public static T nullValue(final Class type) { + final Object defaultValue; + if (type == boolean.class) defaultValue = false; + else if (type == byte.class) defaultValue = (byte) 0; + else if (type == char.class) defaultValue = '\0'; + else if (type == double.class) defaultValue = 0d; + else if (type == float.class) defaultValue = 0f; + else if (type == int.class) defaultValue = 0; + else if (type == long.class) defaultValue = 0L; + else if (type == short.class) defaultValue = (short) 0; + else defaultValue = null; + @SuppressWarnings("unchecked") + final T result = (T) defaultValue; + return result; + } + + /** + * Gets the field with the specified name, of the given class, or superclass + * thereof. + *

    + * Unlike {@link Class#getField(String)}, this method will return fields of + * any visibility, not just {@code public}. And unlike + * {@link Class#getDeclaredField(String)}, it will do so recursively, + * returning the first field of the given name from the class's superclass + * hierarchy. + *

    + *

    + * Note that this method does not guarantee that the returned field is + * accessible; if the field is not {@code public}, calling code will need to + * use {@link Field#setAccessible(boolean)} in order to manipulate the field's + * contents. + *

    + * + * @param c The class (or subclass thereof) containing the desired field. + * @param name + * @return The first field with the given name in the class's superclass + * hierarchy. + * @throws IllegalArgumentException if the specified class does not contain a + * method with the given name + */ + public static Field field(final Class c, final String name) { + if (c == null) throw iae("No such field: " + name); + try { + return c.getDeclaredField(name); + } + catch (final NoSuchFieldException e) {} + return field(c.getSuperclass(), name); + } + + /** + * Gets the method with the specified name and argument types, of the given + * class, or superclass thereof. + *

    + * Unlike {@link Class#getMethod(String, Class[])}, this method will return + * methods of any visibility, not just {@code public}. And unlike + * {@link Class#getDeclaredMethod(String, Class[])}, it will do so + * recursively, returning the first method of the given name and argument + * types from the class's superclass hierarchy. + *

    + *

    + * Note that this method does not guarantee that the returned method is + * accessible; if the method is not {@code public}, calling code will need to + * use {@link Method#setAccessible(boolean)} in order to invoke the method. + *

    + * + * @param c The class (or subclass thereof) containing the desired method. + * @param name Name of the method. + * @param parameterTypes Types of the method parameters. + * @return The first method with the given name and argument types in the + * class's superclass hierarchy. + * @throws IllegalArgumentException If the specified class does not contain a + * method with the given name and argument types. + */ + public static Method method(final Class c, final String name, + final Class... parameterTypes) + { + if (c == null) throw iae("No such field: " + name); + try { + return c.getDeclaredMethod(name, parameterTypes); + } + catch (final NoSuchMethodException exc) {} + return method(c.getSuperclass(), name, parameterTypes); + } + + /** + * Gets the array class corresponding to the given element type. + *

    + * For example, {@code arrayType(double.class)} returns {@code double[].class} + * . + *

    + * + * @param componentType The type of elements which the array possesses + * @throws IllegalArgumentException if the type cannot be the component type + * of an array (this is the case e.g. for {@code void.class}). + */ + public static Class array(final Class componentType) { + if (componentType == null) return null; + // NB: It appears the reflection API has no built-in way to do this. + // So unfortunately, we must allocate a new object and then inspect it. + return Array.newInstance(componentType, 0).getClass(); + } + + /** + * Gets the array class corresponding to the given element type and + * dimensionality. + *

    + * For example, {@code arrayType(double.class, 2)} returns + * {@code double[][].class} . + *

    + * + * @param componentType The type of elements which the array possesses + * @param dim The dimensionality of the array + */ + public static Class array(final Class componentType, final int dim) { + if (dim < 0) throw iae("Negative dimension"); + if (dim == 0) return componentType; + return array(array(componentType), dim - 1); + } + + /** + * Gets the array type—which might be a {@link Class} or a + * {@link GenericArrayType} depending on the argument—corresponding to + * the given element type. + *

    + * For example, {@code arrayType(double.class)} returns {@code double[].class} + * . + *

    + * + * @param componentType The type of elements which the array possesses + * @see #component + */ + public static Type array(final Type componentType) { + if (componentType == null) return null; + if (componentType instanceof Class) { + return array((Class) componentType); + } + return new TypeUtils.GenericArrayTypeImpl(componentType); + } + + /** + * Gets the component type of the given array type, or null if not an array. + *

    + * If you have a {@link Class}, you can call {@link Class#getComponentType()} + * for a narrower return type. + *

    + *

    + * This is the opposite of {@link #array(Type)}. + *

    + */ + public static Type component(final Type type) { + if (type instanceof Class) { + return ((Class) type).getComponentType(); + } + if (type instanceof GenericArrayType) { + return ((GenericArrayType) type).getGenericComponentType(); + } + return null; + } + + /** + * Returns the "safe" generic type of the given field, as viewed from the + * given type. This may be narrower than what {@link Field#getGenericType()} + * returns, if the field is declared in a superclass, or {@code type} has a + * type parameter that is used in the type of the field. + *

    + * For example, suppose we have the following three classes: + *

    + * + *
    +	 * public class Thing<T> {
    +	 *
    +	 * 	public T thing;
    +	 * }
    +	 *
    +	 * public class NumberThing<N extends Number> extends Thing<N> {}
    +	 *
    +	 * public class IntegerThing extends NumberThing<Integer> {}
    +	 * 
    + * + * Then this method operates as follows: + * + *
    +	 * field = Types.field(Thing.class, "thing");
    +	 *
    +	 * field.getType(); // Object
    +	 * field.getGenericType(); // T
    +	 *
    +	 * Types.fieldType(field, Thing.class); // T
    +	 * Types.fieldType(field, NumberThing.class); // N extends Number
    +	 * Types.fieldType(field, IntegerThing.class); // Integer
    +	 * 
    + */ + public static Type fieldType(final Field field, final Class type) { + final Type wildType = GenericTypeReflector.addWildcardParameters(type); + return GenericTypeReflector.getExactFieldType(field, wildType); + } + + /** + * As {@link #fieldType(Field, Class)}, but with respect to the return type of + * the given {@link Method} rather than a {@link Field}. + */ + public static Type methodReturnType(final Method method, + final Class type) + { + final Type wildType = GenericTypeReflector.addWildcardParameters(type); + return GenericTypeReflector.getExactReturnType(method, wildType); + } + + /** + * As {@link #fieldType(Field, Class)}, but with respect to the parameter + * types of the given {@link Method} rather than a {@link Field}. + */ + public static Type[] methodParamTypes(final Method method, + final Class type) + { + final Type wildType = GenericTypeReflector.addWildcardParameters(type); + return GenericTypeReflector.getExactParameterTypes(method, wildType); + } + + /** + * Gets the given type's {@code n}th type parameter of the specified class. + *

    + * For example, with class {@code StringList implements List}, + * {@code Types.param(StringList.class, Collection.class, 0)} returns + * {@code String}. + *

    + */ + public static Type param(final Type type, final Class c, final int no) { + return GenericTypeReflector.getTypeParameter(type, // + c.getTypeParameters()[no]); + } + + /** + * Discerns whether it would be legal to assign a reference of type + * {@code source} to a reference of type {@code target}. + * + * @param source The type from which assignment is desired. + * @param target The type to which assignment is desired. + * @return True if the source is assignable to the target. + * @throws NullPointerException if {@code target} is null. + * @see Class#isAssignableFrom(Class) + */ + public static boolean isAssignable(final Type source, final Type target) { + return TypeUtils.isAssignable(source, target); + } + + /** + * Checks whether the given object can be cast to the specified type. + * + * @return true If the destination class is assignable from the source + * object's class, or if the source object is null and destination + * class is non-null. + * @see #cast(Object, Class) + */ + public static boolean isInstance(final Object obj, final Class dest) { + if (dest == null) return false; + return obj == null || dest.isInstance(obj); + } + + /** + * Casts the given object to the specified type, or null if the types are + * incompatible. + */ + public static T cast(final Object src, final Class dest) { + if (!isInstance(src, dest)) return null; + @SuppressWarnings("unchecked") + final T result = (T) src; + return result; + } + + /** + * Converts the given string value to an enumeration constant of the + * specified type. For example, {@code enumValue("APPLE", Fruit.class)} + * returns {@code Fruit.APPLE} if such a value is among those of the + * requested enum class. + * + * @param name The value to convert. + * @param dest The type of the enumeration constant. + * @return The converted enumeration constant. + * @throws IllegalArgumentException if the type is not an enumeration type, or + * has no such constant. + */ + public static T enumValue(final String name, final Class dest) { + if (!dest.isEnum()) throw iae("Not an enum type: " + name(dest)); + @SuppressWarnings({ "rawtypes", "unchecked" }) + final Enum result = Enum.valueOf((Class) dest, name); + @SuppressWarnings("unchecked") + final T typedResult = (T) result; + return typedResult; + } + + /** + * Converts the given string label to an enumeration constant of the + * specified type. An enum label is the string returned by the enum constant's + * {@link Object#toString()} method. For example, + * {@code enumFromLabel("Apple", Fruit.class)} returns {@code Fruit.APPLE} if + * {@code Fruit.APPLE.toString()} is implemented to return {@code "Apple"}. + * + * @param label The {@code toString()} result of the desired enum value. + * @param dest The type of the enumeration constant. + * @return The matching enumeration constant. + * @throws IllegalArgumentException if the type is not an enumeration type, or + * has no constant with the given label. + */ + public static T enumFromLabel(final String label, final Class dest) { + final T[] values = dest.getEnumConstants(); + if (values == null) throw iae("Not an enum type: " + name(dest)); + for (T value : values) { + if (Objects.equals(label, value.toString())) return value; + } + throw iae("Enum class " + dest.getName() + " has no such label: " + label); + } + + /** + * Converts the given string value or label to an enumeration constant of the + * specified type. + *

    + * If the string matches one of the enum values directly, that value will be + * returned via {@link #enumValue(String, Class)}. Otherwise, the result of + * {@link #enumFromLabel} is returned. + *

    + * + * @param s The name or label of the desired enum value. + * @param dest The type of the enumeration constant. + * @return The matching enumeration constant. + * @throws IllegalArgumentException if the type is not an enumeration type, + * or has no such constant with the given name nor label. + */ + public static T enumFromString(final String s, final Class dest) { + if (!dest.isEnum()) throw iae("Not an enum type: " + name(dest)); + try { + return enumValue(s, dest); + } + catch (final IllegalArgumentException exc) { + // NB: No action needed. + } + try { + return enumFromLabel(s, dest); + } + catch (final IllegalArgumentException exc) { + // NB: No action needed. + } + throw iae("Enum class " + dest.getName() + " has no such value nor label: " + s); + } + + /** + * Creates a new {@link ParameterizedType} of the given class together with + * the specified type arguments. + * + * @param rawType The class of the {@link ParameterizedType}. + * @param typeArgs The type arguments to use in parameterizing it. + * @return The newly created {@link ParameterizedType}. + */ + public static ParameterizedType parameterize(final Class rawType, + final Type... typeArgs) + { + return parameterizeWithOwner(null, rawType, typeArgs); + } + + /** + * Creates a new {@link ParameterizedType} of the given class together with + * the specified type arguments. + * + * @param ownerType The owner type of the parameterized class. + * @param rawType The class of the {@link ParameterizedType}. + * @param typeArgs The type arguments to use in parameterizing it. + * @return The newly created {@link ParameterizedType}. + */ + public static ParameterizedType parameterizeWithOwner(final Type ownerType, + final Class rawType, final Type... typeArgs) + { + return TypeUtils.parameterizeWithOwner(ownerType, rawType, typeArgs); + } + + /** + * Creates a new {@link WildcardType} with no upper or lower bounds (i.e., + * {@code ?}). + * + * @return The newly created {@link WildcardType}. + */ + public static WildcardType wildcard() { + return wildcard((Type) null, (Type) null); + } + + /** + * Creates a new {@link WildcardType} with the given upper and/or lower bound. + * + * @param upperBound Upper bound of the wildcard, or null for none. + * @param lowerBound Lower bound of the wildcard, or null for none. + * @return The newly created {@link WildcardType}. + */ + public static WildcardType wildcard(final Type upperBound, + final Type lowerBound) + { + return new TypeUtils.WildcardTypeImpl(upperBound, lowerBound); + } + + /** + * Creates a new {@link WildcardType} with the given upper and/or lower + * bounds. + * + * @param upperBounds Upper bounds of the wildcard, or null for none. + * @param lowerBounds Lower bounds of the wildcard, or null for none. + * @return The newly created {@link WildcardType}. + */ + public static WildcardType wildcard(final Type[] upperBounds, + final Type[] lowerBounds) + { + return new TypeUtils.WildcardTypeImpl(upperBounds, lowerBounds); + } + + /** + * Learn, recursively, whether any of the type parameters associated with + * {@code type} are bound to variables. + * + * @param type the type to check for type variables + * @return boolean + */ + public static boolean containsTypeVars(final Type type) { + return TypeUtils.containsTypeVariables(type); + } + + /** + * Gets the type arguments of a class/interface based on a subtype. For + * instance, this method will determine that both of the parameters for the + * interface {@link Map} are {@link Object} for the subtype + * {@link java.util.Properties Properties} even though the subtype does not + * directly implement the {@code Map} interface. + *

    + * This method returns {@code null} if {@code type} is not assignable to + * {@code toClass}. It returns an empty map if none of the classes or + * interfaces in its inheritance hierarchy specify any type arguments. + *

    + *

    + * A side effect of this method is that it also retrieves the type arguments + * for the classes and interfaces that are part of the hierarchy between + * {@code type} and {@code toClass}. So with the above example, this method + * will also determine that the type arguments for {@link java.util.Hashtable + * Hashtable} are also both {@code Object}. In cases where the interface + * specified by {@code toClass} is (indirectly) implemented more than once + * (e.g. where {@code toClass} specifies the interface + * {@link java.lang.Iterable Iterable} and {@code type} specifies a + * parameterized type that implements both {@link java.util.Set Set} and + * {@link java.util.Collection Collection}), this method will look at the + * inheritance hierarchy of only one of the implementations/subclasses; the + * first interface encountered that isn't a subinterface to one of the others + * in the {@code type} to {@code toClass} hierarchy. + *

    + * + * @param type the type from which to determine the type parameters of + * {@code toClass} + * @param toClass the class whose type parameters are to be determined based + * on the subtype {@code type} + * @return a {@code Map} of the type assignments for the type variables in + * each type in the inheritance hierarchy from {@code type} to + * {@code toClass} inclusive. + */ + public static Map, Type> args(final Type type, + final Class toClass) + { + return TypeUtils.getTypeArguments(type, toClass); + } + + /** + * Tries to determine the type arguments of a class/interface based on a super + * parameterized type's type arguments. This method is the inverse of + * {@link #args(Type, Class)} which gets a class/interface's type arguments + * based on a subtype. It is far more limited in determining the type + * arguments for the subject class's type variables in that it can only + * determine those parameters that map from the subject {@link Class} object + * to the supertype. + *

    + * Example: {@link java.util.TreeSet TreeSet} sets its parameter as the + * parameter for {@link java.util.NavigableSet NavigableSet}, which in turn + * sets the parameter of {@link java.util.SortedSet}, which in turn sets the + * parameter of {@link Set}, which in turn sets the parameter of + * {@link java.util.Collection}, which in turn sets the parameter of + * {@link java.lang.Iterable}. Since {@code TreeSet}'s parameter maps + * (indirectly) to {@code Iterable}'s parameter, it will be able to determine + * that based on the super type {@code Iterable>>}, the parameter of {@code TreeSet} + * is {@code ? extends Map>}. + *

    + * + * @param c the class whose type parameters are to be determined, not + * {@code null} + * @param superType the super type from which {@code c}'s type arguments are + * to be determined, not {@code null} + * @return a {@code Map} of the type assignments that could be determined for + * the type variables in each type in the inheritance hierarchy from + * {@code type} to {@code c} inclusive. + */ + public static Map, Type> args(final Class c, + final ParameterizedType superType) + { + return TypeUtils.determineTypeArguments(c, superType); + } + + /** + * Create a parameterized type instance. + * + * @param raw the raw class to create a parameterized type instance for + * @param typeArgMappings the mapping used for parameterization + * @return {@link ParameterizedType} + */ + public static final ParameterizedType parameterize(final Class raw, + final Map, Type> typeArgMappings) + { + return TypeUtils.parameterize(raw, typeArgMappings); + } + + // -- Helper methods -- + + private static IllegalArgumentException iae(final String... s) { + return iae(null, s); + } + + private static IllegalArgumentException iae(final Throwable cause, + final String... notes) + { + final String s = String.join(", ", notes); + final IllegalArgumentException exc = new IllegalArgumentException(s); + if (cause != null) exc.initCause(cause); + throw exc; + } + + private static Class arrayOrNull(final Class componentType) { + try { + return Types.array(componentType); + } + catch (final IllegalArgumentException exc) { + return null; + } + } + + // -- BEGIN FORK OF APACHE COMMONS LANG 3.4 CODE -- + + /* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + /** + *

    + * Utility methods focusing on type inspection, particularly with regard to + * generics. + *

    + * + * @since 3.0 + * @version $Id: TypeUtils.java 1606051 2014-06-27 12:22:17Z ggregory $ + */ + @SuppressWarnings("unused") + private static class TypeUtils { + + /** + * {@link WildcardType} builder. + * + * @since 3.2 + */ + public static class WildcardTypeBuilder { + + /** + * Constructor + */ + private WildcardTypeBuilder() {} + + private Type[] upperBounds; + private Type[] lowerBounds; + + /** + * Specify upper bounds of the wildcard type to build. + * + * @param bounds to set + * @return {@code this} + */ + public WildcardTypeBuilder withUpperBounds(final Type... bounds) { + this.upperBounds = bounds; + return this; + } + + /** + * Specify lower bounds of the wildcard type to build. + * + * @param bounds to set + * @return {@code this} + */ + public WildcardTypeBuilder withLowerBounds(final Type... bounds) { + this.lowerBounds = bounds; + return this; + } + + public WildcardType build() { + return new WildcardTypeImpl(upperBounds, lowerBounds); + } + } + + /** + * GenericArrayType implementation class. + * + * @since 3.2 + */ + private static final class GenericArrayTypeImpl implements + GenericArrayType + { + + private final Type componentType; + + /** + * Constructor + * + * @param componentType of this array type + */ + private GenericArrayTypeImpl(final Type componentType) { + this.componentType = componentType; + } + + @Override + public Type getGenericComponentType() { + return componentType; + } + + @Override + public String toString() { + return TypeUtils.toString(this); + } + + @Override + public boolean equals(final Object obj) { + return obj == this || obj instanceof GenericArrayType && TypeUtils + .equals(this, (GenericArrayType) obj); + } + + @Override + public int hashCode() { + int result = 67 << 4; + result |= componentType.hashCode(); + return result; + } + } + + /** + * ParameterizedType implementation class. + * + * @since 3.2 + */ + private static final class ParameterizedTypeImpl implements + ParameterizedType + { + + private final Class raw; + private final Type useOwner; + private final Type[] typeArguments; + + /** + * Constructor + * + * @param raw type + * @param useOwner owner type to use, if any + * @param typeArguments formal type arguments + */ + private ParameterizedTypeImpl(final Class raw, final Type useOwner, + final Type[] typeArguments) + { + this.raw = raw; + this.useOwner = useOwner; + this.typeArguments = typeArguments; + } + + @Override + public Type getRawType() { + return raw; + } + + @Override + public Type getOwnerType() { + return useOwner; + } + + @Override + public Type[] getActualTypeArguments() { + return typeArguments.clone(); + } + + @Override + public String toString() { + return TypeUtils.toString(this); + } + + @Override + public boolean equals(final Object obj) { + return obj == this || obj instanceof ParameterizedType && TypeUtils + .equals(this, ((ParameterizedType) obj)); + } + + @Override + public int hashCode() { + int result = 71 << 4; + result |= raw.hashCode(); + result <<= 4; + result |= Objects.hashCode(useOwner); + result <<= 8; + result |= Arrays.hashCode(typeArguments); + return result; + } + } + + /** + * WildcardType implementation class. + * + * @since 3.2 + */ + private static final class WildcardTypeImpl implements WildcardType { + + private static final Type[] EMPTY_BOUNDS = new Type[0]; + + private final Type[] upperBounds; + private final Type[] lowerBounds; + + /** + * Constructor + * + * @param upperBound of this type + * @param lowerBound of this type + */ + private WildcardTypeImpl(final Type upperBound, final Type lowerBound) { + this(upperBound == null ? null : new Type[] { upperBound }, + lowerBound == null ? null : new Type[] { lowerBound }); + } + + /** + * Constructor + * + * @param upperBounds of this type + * @param lowerBounds of this type + */ + private WildcardTypeImpl(final Type[] upperBounds, + final Type[] lowerBounds) + { + this.upperBounds = upperBounds == null ? EMPTY_BOUNDS : upperBounds; + this.lowerBounds = lowerBounds == null ? EMPTY_BOUNDS : lowerBounds; + } + + @Override + public Type[] getUpperBounds() { + return upperBounds.clone(); + } + + @Override + public Type[] getLowerBounds() { + return lowerBounds.clone(); + } + + @Override + public String toString() { + return TypeUtils.toString(this); + } + + @Override + public boolean equals(final Object obj) { + return obj == this || obj instanceof WildcardType && TypeUtils.equals( + this, (WildcardType) obj); + } + + @Override + public int hashCode() { + int result = 73 << 8; + result |= Arrays.hashCode(upperBounds); + result <<= 8; + result |= Arrays.hashCode(lowerBounds); + return result; + } + } + + /** + * A wildcard instance matching {@code ?}. + * + * @since 3.2 + */ + public static final WildcardType WILDCARD_ALL = // + wildcardType().withUpperBounds(Object.class).build(); + + /** + *

    + * Checks if the subject type may be implicitly cast to the target type + * following the Java generics rules. If both types are {@link Class} + * objects, the method returns the result of + * {@link Class#isAssignableFrom(Class)}. + *

    + * + * @param type the subject type to be assigned to the target type + * @param toType the target type + * @return {@code true} if {@code type} is assignable to {@code toType}. + * @throws NullPointerException if {@code toType} is null. + */ + public static boolean isAssignable(final Type type, final Type toType) { + if (toType == null) { + throw new NullPointerException("Destination type is null"); + } + return isAssignable(type, toType, null); + } + + /** + *

    + * Checks if the subject type may be implicitly cast to the target type + * following the Java generics rules. + *

    + * + * @param type the subject type to be assigned to the target type + * @param toType the target type + * @param typeVarAssigns optional map of type variable assignments + * @return {@code true} if {@code type} is assignable to {@code toType}. + */ + private static boolean isAssignable(final Type type, final Type toType, + final Map, Type> typeVarAssigns) + { + if (toType == null || toType instanceof Class) { + return isAssignable(type, (Class) toType); + } + + if (toType instanceof ParameterizedType) { + return isAssignable(type, (ParameterizedType) toType, typeVarAssigns); + } + + if (toType instanceof GenericArrayType) { + return isAssignable(type, (GenericArrayType) toType, typeVarAssigns); + } + + if (toType instanceof WildcardType) { + return isAssignable(type, (WildcardType) toType, typeVarAssigns); + } + + if (toType instanceof TypeVariable) { + return isAssignable(type, (TypeVariable) toType, typeVarAssigns); + } + + throw new IllegalStateException("found an unhandled type: " + toType); + } + + /** + *

    + * Checks if the subject type may be implicitly cast to the target class + * following the Java generics rules. + *

    + * + * @param type the subject type to be assigned to the target type + * @param toClass the target class + * @return {@code true} if {@code type} is assignable to {@code toClass}. + */ + private static boolean isAssignable(final Type type, + final Class toClass) + { + if (type == null) { + // consistency with ClassUtils.isAssignable() behavior + return toClass == null || !toClass.isPrimitive(); + } + + // only a null type can be assigned to null type which + // would have cause the previous to return true + if (toClass == null) { + return false; + } + + // all types are assignable to themselves + if (toClass.equals(type)) { + return true; + } + + if (type instanceof Class) { + // just comparing two classes + return toClass.isAssignableFrom((Class) type); + } + + if (type instanceof ParameterizedType) { + // only have to compare the raw type to the class + return isAssignable(getRawType((ParameterizedType) type), toClass); + } + + // * + if (type instanceof TypeVariable) { + // if any of the bounds are assignable to the class, then the + // type is assignable to the class. + for (final Type bound : ((TypeVariable) type).getBounds()) { + if (isAssignable(bound, toClass)) { + return true; + } + } + + return false; + } + + // the only classes to which a generic array type can be assigned + // are class Object and array classes + if (type instanceof GenericArrayType) { + return toClass.equals(Object.class) || toClass.isArray() && + isAssignable(((GenericArrayType) type).getGenericComponentType(), + toClass.getComponentType()); + } + + // wildcard types are not assignable to a class (though one would think + // "? super Object" would be assignable to Object) + if (type instanceof WildcardType) { + return false; + } + + throw new IllegalStateException("found an unhandled type: " + type); + } + + /** + *

    + * Checks if the subject type may be implicitly cast to the target + * parameterized type following the Java generics rules. + *

    + * + * @param type the subject type to be assigned to the target type + * @param toParameterizedType the target parameterized type + * @param typeVarAssigns a map with type variables + * @return {@code true} if {@code type} is assignable to {@code toType}. + */ + private static boolean isAssignable(final Type type, + final ParameterizedType toParameterizedType, + final Map, Type> typeVarAssigns) + { + if (type == null) { + return true; + } + + // only a null type can be assigned to null type which + // would have cause the previous to return true + if (toParameterizedType == null) { + return false; + } + + // all types are assignable to themselves + if (toParameterizedType.equals(type)) { + return true; + } + + // get the target type's raw type + final Class toClass = getRawType(toParameterizedType); + // get the subject type's type arguments including owner type arguments + // and supertype arguments up to and including the target class. + final Map, Type> fromTypeVarAssigns = getTypeArguments( + type, toClass, null); + + // null means the two types are not compatible + if (fromTypeVarAssigns == null) { + return false; + } + + // compatible types, but there's no type arguments. this is equivalent + // to comparing Map< ?, ? > to Map, and raw types are always assignable + // to parameterized types. + if (fromTypeVarAssigns.isEmpty()) { + return true; + } + + // get the target type's type arguments including owner type arguments + final Map, Type> toTypeVarAssigns = getTypeArguments( + toParameterizedType, toClass, typeVarAssigns); + + // now to check each type argument + for (final TypeVariable var : toTypeVarAssigns.keySet()) { + final Type toTypeArg = unrollVariableAssignments(var, toTypeVarAssigns); + final Type fromTypeArg = unrollVariableAssignments(var, + fromTypeVarAssigns); + + // parameters must either be absent from the subject type, within + // the bounds of the wildcard type, or be an exact match to the + // parameters of the target type. + if (fromTypeArg != null && !fromTypeArg.equals(toTypeArg) && + !(toTypeArg instanceof WildcardType && isAssignable(fromTypeArg, + toTypeArg, typeVarAssigns))) + { + return false; + } + } + return true; + } + + /** + * Look up {@code var} in {@code typeVarAssigns} transitively, i.e. + * keep looking until the value found is not a type variable. + * + * @param var the type variable to look up + * @param typeVarAssigns the map used for the look up + * @return Type or {@code null} if some variable was not in the map + * @since 3.2 + */ + private static Type unrollVariableAssignments(TypeVariable var, + final Map, Type> typeVarAssigns) + { + Type result; + do { + result = typeVarAssigns.get(var); + if (result instanceof TypeVariable && !result.equals(var)) { + var = (TypeVariable) result; + continue; + } + break; + } + while (true); + return result; + } + + /** + *

    + * Checks if the subject type may be implicitly cast to the target generic + * array type following the Java generics rules. + *

    + * + * @param type the subject type to be assigned to the target type + * @param toGenericArrayType the target generic array type + * @param typeVarAssigns a map with type variables + * @return {@code true} if {@code type} is assignable to + * {@code toGenericArrayType}. + */ + private static boolean isAssignable(final Type type, + final GenericArrayType toGenericArrayType, + final Map, Type> typeVarAssigns) + { + if (type == null) { + return true; + } + + // only a null type can be assigned to null type which + // would have cause the previous to return true + if (toGenericArrayType == null) { + return false; + } + + // all types are assignable to themselves + if (toGenericArrayType.equals(type)) { + return true; + } + + final Type toComponentType = toGenericArrayType.getGenericComponentType(); + + if (type instanceof Class) { + final Class cls = (Class) type; + + // compare the component types + return cls.isArray() && isAssignable(cls.getComponentType(), + toComponentType, typeVarAssigns); + } + + if (type instanceof GenericArrayType) { + // compare the component types + return isAssignable(((GenericArrayType) type).getGenericComponentType(), + toComponentType, typeVarAssigns); + } + + if (type instanceof WildcardType) { + // so long as one of the upper bounds is assignable, it's good + for (final Type bound : getImplicitUpperBounds((WildcardType) type)) { + if (isAssignable(bound, toGenericArrayType)) { + return true; + } + } + + return false; + } + + if (type instanceof TypeVariable) { + // probably should remove the following logic and just return false. + // type variables cannot specify arrays as bounds. + for (final Type bound : getImplicitBounds((TypeVariable) type)) { + if (isAssignable(bound, toGenericArrayType)) { + return true; + } + } + + return false; + } + + if (type instanceof ParameterizedType) { + // the raw type of a parameterized type is never an array or + // generic array, otherwise the declaration would look like this: + // Collection[]< ? extends String > collection; + return false; + } + + throw new IllegalStateException("found an unhandled type: " + type); + } + + /** + *

    + * Checks if the subject type may be implicitly cast to the target wildcard + * type following the Java generics rules. + *

    + * + * @param type the subject type to be assigned to the target type + * @param toWildcardType the target wildcard type + * @param typeVarAssigns a map with type variables + * @return {@code true} if {@code type} is assignable to + * {@code toWildcardType}. + */ + private static boolean isAssignable(final Type type, + final WildcardType toWildcardType, + final Map, Type> typeVarAssigns) + { + if (type == null) { + return true; + } + + // only a null type can be assigned to null type which + // would have cause the previous to return true + if (toWildcardType == null) { + return false; + } + + // all types are assignable to themselves + if (toWildcardType.equals(type)) { + return true; + } + + final Type[] toUpperBounds = getImplicitUpperBounds(toWildcardType); + final Type[] toLowerBounds = getImplicitLowerBounds(toWildcardType); + + if (type instanceof WildcardType) { + final WildcardType wildcardType = (WildcardType) type; + final Type[] upperBounds = getImplicitUpperBounds(wildcardType); + final Type[] lowerBounds = getImplicitLowerBounds(wildcardType); + + for (Type toBound : toUpperBounds) { + // if there are assignments for unresolved type variables, + // now's the time to substitute them. + toBound = substituteTypeVariables(toBound, typeVarAssigns); + + // each upper bound of the subject type has to be assignable to + // each + // upper bound of the target type + for (final Type bound : upperBounds) { + if (!isAssignable(bound, toBound, typeVarAssigns)) { + return false; + } + } + } + + for (Type toBound : toLowerBounds) { + // if there are assignments for unresolved type variables, + // now's the time to substitute them. + toBound = substituteTypeVariables(toBound, typeVarAssigns); + + // each lower bound of the target type has to be assignable to + // each + // lower bound of the subject type + for (final Type bound : lowerBounds) { + if (!isAssignable(toBound, bound, typeVarAssigns)) { + return false; + } + } + } + return true; + } + + for (final Type toBound : toUpperBounds) { + // if there are assignments for unresolved type variables, + // now's the time to substitute them. + if (!isAssignable(type, substituteTypeVariables(toBound, + typeVarAssigns), typeVarAssigns)) + { + return false; + } + } + + for (final Type toBound : toLowerBounds) { + // if there are assignments for unresolved type variables, + // now's the time to substitute them. + if (!isAssignable(substituteTypeVariables(toBound, typeVarAssigns), + type, typeVarAssigns)) + { + return false; + } + } + return true; + } + + /** + *

    + * Checks if the subject type may be implicitly cast to the target type + * variable following the Java generics rules. + *

    + * + * @param type the subject type to be assigned to the target type + * @param toTypeVariable the target type variable + * @param typeVarAssigns a map with type variables + * @return {@code true} if {@code type} is assignable to + * {@code toTypeVariable}. + */ + private static boolean isAssignable(final Type type, + final TypeVariable toTypeVariable, + final Map, Type> typeVarAssigns) + { + if (type == null) { + return true; + } + + // only a null type can be assigned to null type which + // would have cause the previous to return true + if (toTypeVariable == null) { + return false; + } + + // all types are assignable to themselves + if (toTypeVariable.equals(type)) { + return true; + } + + if (type instanceof TypeVariable) { + // a type variable is assignable to another type variable, if + // and only if the former is the latter, extends the latter, or + // is otherwise a descendant of the latter. + final Type[] bounds = getImplicitBounds((TypeVariable) type); + + for (final Type bound : bounds) { + if (isAssignable(bound, toTypeVariable, typeVarAssigns)) { + return true; + } + } + } + + if (type instanceof Class || type instanceof ParameterizedType || + type instanceof GenericArrayType || type instanceof WildcardType) + { + return false; + } + + throw new IllegalStateException("found an unhandled type: " + type); + } + + /** + *

    + * Find the mapping for {@code type} in {@code typeVarAssigns}. + *

    + * + * @param type the type to be replaced + * @param typeVarAssigns the map with type variables + * @return the replaced type + * @throws IllegalArgumentException if the type cannot be substituted + */ + private static Type substituteTypeVariables(final Type type, + final Map, Type> typeVarAssigns) + { + if (type instanceof TypeVariable && typeVarAssigns != null) { + final Type replacementType = typeVarAssigns.get(type); + + if (replacementType == null) { + throw new IllegalArgumentException( + "missing assignment type for type variable " + type); + } + return replacementType; + } + return type; + } + + /** + *

    + * Retrieves all the type arguments for this parameterized type including + * owner hierarchy arguments such as {@code Outer.Inner.DeepInner + * } . The arguments are returned in a {@link Map} specifying the + * argument type for each {@link TypeVariable}. + *

    + * + * @param type specifies the subject parameterized type from which to + * harvest the parameters. + * @return a {@code Map} of the type arguments to their respective type + * variables. + */ + public static Map, Type> getTypeArguments( + final ParameterizedType type) + { + return getTypeArguments(type, getRawType(type), null); + } + + /** + *

    + * Gets the type arguments of a class/interface based on a subtype. For + * instance, this method will determine that both of the parameters for the + * interface {@link Map} are {@link Object} for the subtype + * {@link java.util.Properties Properties} even though the subtype does not + * directly implement the {@code Map} interface. + *

    + *

    + * This method returns {@code null} if {@code type} is not assignable to + * {@code toClass}. It returns an empty map if none of the classes or + * interfaces in its inheritance hierarchy specify any type arguments. + *

    + *

    + * A side effect of this method is that it also retrieves the type arguments + * for the classes and interfaces that are part of the hierarchy between + * {@code type} and {@code toClass}. So with the above example, this method + * will also determine that the type arguments for + * {@link java.util.Hashtable Hashtable} are also both {@code Object}. In + * cases where the interface specified by {@code toClass} is (indirectly) + * implemented more than once (e.g. where {@code toClass} specifies the + * interface {@link java.lang.Iterable Iterable} and {@code type} specifies + * a parameterized type that implements both {@link java.util.Set Set} and + * {@link java.util.Collection Collection}), this method will look at the + * inheritance hierarchy of only one of the implementations/subclasses; the + * first interface encountered that isn't a subinterface to one of the + * others in the {@code type} to {@code toClass} hierarchy. + *

    + * + * @param type the type from which to determine the type parameters of + * {@code toClass} + * @param toClass the class whose type parameters are to be determined based + * on the subtype {@code type} + * @return a {@code Map} of the type assignments for the type variables in + * each type in the inheritance hierarchy from {@code type} to + * {@code toClass} inclusive. + */ + public static Map, Type> getTypeArguments(final Type type, + final Class toClass) + { + return getTypeArguments(type, toClass, null); + } + + /** + *

    + * Return a map of the type arguments of @{code type} in the context of + * {@code toClass}. + *

    + * + * @param type the type in question + * @param toClass the class + * @param subtypeVarAssigns a map with type variables + * @return the {@code Map} with type arguments + */ + private static Map, Type> getTypeArguments(final Type type, + final Class toClass, + final Map, Type> subtypeVarAssigns) + { + if (type instanceof Class) { + return getTypeArguments((Class) type, toClass, subtypeVarAssigns); + } + + if (type instanceof ParameterizedType) { + return getTypeArguments((ParameterizedType) type, toClass, + subtypeVarAssigns); + } + + if (type instanceof GenericArrayType) { + return getTypeArguments(((GenericArrayType) type) + .getGenericComponentType(), toClass.isArray() ? toClass + .getComponentType() : toClass, subtypeVarAssigns); + } + + // since wildcard types are not assignable to classes, should this just + // return null? + if (type instanceof WildcardType) { + for (final Type bound : getImplicitUpperBounds((WildcardType) type)) { + // find the first bound that is assignable to the target class + if (isAssignable(bound, toClass)) { + return getTypeArguments(bound, toClass, subtypeVarAssigns); + } + } + + return null; + } + + if (type instanceof TypeVariable) { + for (final Type bound : getImplicitBounds((TypeVariable) type)) { + // find the first bound that is assignable to the target class + if (isAssignable(bound, toClass)) { + return getTypeArguments(bound, toClass, subtypeVarAssigns); + } + } + + return null; + } + throw new IllegalStateException("found an unhandled type: " + type); + } + + /** + *

    + * Return a map of the type arguments of a parameterized type in the context + * of {@code toClass}. + *

    + * + * @param parameterizedType the parameterized type + * @param toClass the class + * @param subtypeVarAssigns a map with type variables + * @return the {@code Map} with type arguments + */ + private static Map, Type> getTypeArguments( + final ParameterizedType parameterizedType, final Class toClass, + final Map, Type> subtypeVarAssigns) + { + final Class cls = getRawType(parameterizedType); + + // make sure they're assignable + if (!isAssignable(cls, toClass)) { + return null; + } + + final Type ownerType = parameterizedType.getOwnerType(); + Map, Type> typeVarAssigns; + + if (ownerType instanceof ParameterizedType) { + // get the owner type arguments first + final ParameterizedType parameterizedOwnerType = + (ParameterizedType) ownerType; + typeVarAssigns = getTypeArguments(parameterizedOwnerType, getRawType( + parameterizedOwnerType), subtypeVarAssigns); + } + else { + // no owner, prep the type variable assignments map + typeVarAssigns = subtypeVarAssigns == null ? new HashMap<>() + : new HashMap<>(subtypeVarAssigns); + } + + // get the subject parameterized type's arguments + final Type[] typeArgs = parameterizedType.getActualTypeArguments(); + // and get the corresponding type variables from the raw class + final TypeVariable[] typeParams = cls.getTypeParameters(); + + // map the arguments to their respective type variables + for (int i = 0; i < typeParams.length; i++) { + final Type typeArg = typeArgs[i]; + typeVarAssigns.put(typeParams[i], typeVarAssigns.containsKey(typeArg) + ? typeVarAssigns.get(typeArg) : typeArg); + } + + if (toClass.equals(cls)) { + // target class has been reached. Done. + return typeVarAssigns; + } + + // walk the inheritance hierarchy until the target class is reached + return getTypeArguments(getClosestParentType(cls, toClass), toClass, + typeVarAssigns); + } + + /** + *

    + * Return a map of the type arguments of a class in the context of @{code + * toClass}. + *

    + * + * @param cls the class in question + * @param toClass the context class + * @param subtypeVarAssigns a map with type variables + * @return the {@code Map} with type arguments + */ + private static Map, Type> getTypeArguments(Class cls, + final Class toClass, + final Map, Type> subtypeVarAssigns) + { + // make sure they're assignable + if (!isAssignable(cls, toClass)) { + return null; + } + + // can't work with primitives + if (cls.isPrimitive()) { + // both classes are primitives? + if (toClass.isPrimitive()) { + // dealing with widening here. No type arguments to be + // harvested with these two types. + return new HashMap<>(); + } + + // work with wrapper the wrapper class instead of the primitive + cls = Types.box(cls); + } + + // create a copy of the incoming map, or an empty one if it's null + final HashMap, Type> typeVarAssigns = + subtypeVarAssigns == null ? new HashMap<>() : new HashMap<>( + subtypeVarAssigns); + + // has target class been reached? + if (toClass.equals(cls)) { + return typeVarAssigns; + } + + // walk the inheritance hierarchy until the target class is reached + return getTypeArguments(getClosestParentType(cls, toClass), toClass, + typeVarAssigns); + } + + /** + *

    + * Tries to determine the type arguments of a class/interface based on a + * super parameterized type's type arguments. This method is the inverse of + * {@link #getTypeArguments(Type, Class)} which gets a class/interface's + * type arguments based on a subtype. It is far more limited in determining + * the type arguments for the subject class's type variables in that it can + * only determine those parameters that map from the subject {@link Class} + * object to the supertype. + *

    + *

    + * Example: {@link java.util.TreeSet TreeSet} sets its parameter as the + * parameter for {@link java.util.NavigableSet NavigableSet}, which in turn + * sets the parameter of {@link java.util.SortedSet}, which in turn sets the + * parameter of {@link Set}, which in turn sets the parameter of + * {@link java.util.Collection}, which in turn sets the parameter of + * {@link java.lang.Iterable}. Since {@code TreeSet}'s parameter maps + * (indirectly) to {@code Iterable}'s parameter, it will be able to + * determine that based on the super type {@code Iterable>>}, the parameter of {@code TreeSet} + * is {@code ? extends Map>}. + *

    + * + * @param cls the class whose type parameters are to be determined, not + * {@code null} + * @param superType the super type from which {@code cls}'s type arguments + * are to be determined, not {@code null} + * @return a {@code Map} of the type assignments that could be determined + * for the type variables in each type in the inheritance hierarchy + * from {@code type} to {@code toClass} inclusive. + */ + public static Map, Type> determineTypeArguments( + final Class cls, final ParameterizedType superType) + { + validateNotNull(cls, "cls is null"); + validateNotNull(superType, "superType is null"); + + final Class superClass = getRawType(superType); + + // compatibility check + if (!isAssignable(cls, superClass)) { + return null; + } + + if (cls.equals(superClass)) { + return getTypeArguments(superType, superClass, null); + } + + // get the next class in the inheritance hierarchy + final Type midType = getClosestParentType(cls, superClass); + + // can only be a class or a parameterized type + if (midType instanceof Class) { + return determineTypeArguments((Class) midType, superType); + } + + final ParameterizedType midParameterizedType = + (ParameterizedType) midType; + final Class midClass = getRawType(midParameterizedType); + // get the type variables of the mid class that map to the type + // arguments of the super class + final Map, Type> typeVarAssigns = determineTypeArguments( + midClass, superType); + // map the arguments of the mid type to the class type variables + mapTypeVariablesToArguments(cls, midParameterizedType, typeVarAssigns); + + return typeVarAssigns; + } + + /** + *

    + * Performs a mapping of type variables. + *

    + * + * @param the generic type of the class in question + * @param cls the class in question + * @param parameterizedType the parameterized type + * @param typeVarAssigns the map to be filled + */ + private static void mapTypeVariablesToArguments(final Class cls, + final ParameterizedType parameterizedType, + final Map, Type> typeVarAssigns) + { + // capture the type variables from the owner type that have assignments + final Type ownerType = parameterizedType.getOwnerType(); + + if (ownerType instanceof ParameterizedType) { + // recursion to make sure the owner's owner type gets processed + mapTypeVariablesToArguments(cls, (ParameterizedType) ownerType, + typeVarAssigns); + } + + // parameterizedType is a generic interface/class (or it's in the owner + // hierarchy of said interface/class) implemented/extended by the class + // cls. Find out which type variables of cls are type arguments of + // parameterizedType: + final Type[] typeArgs = parameterizedType.getActualTypeArguments(); + + // of the cls's type variables that are arguments of parameterizedType, + // find out which ones can be determined from the super type's arguments + final TypeVariable[] typeVars = getRawType(parameterizedType) + .getTypeParameters(); + + // use List view of type parameters of cls so the contains() method can be + // used: + final List>> typeVarList = Arrays.asList(cls + .getTypeParameters()); + + for (int i = 0; i < typeArgs.length; i++) { + final TypeVariable typeVar = typeVars[i]; + final Type typeArg = typeArgs[i]; + + // argument of parameterizedType is a type variable of cls + if (typeVarList.contains(typeArg) + // type variable of parameterizedType has an assignment in + // the super type. + && typeVarAssigns.containsKey(typeVar)) { + // map the assignment to the cls's type variable + typeVarAssigns.put((TypeVariable) typeArg, typeVarAssigns.get( + typeVar)); + } + } + } + + /** + *

    + * Get the closest parent type to the super class specified by + * {@code superClass}. + *

    + * + * @param cls the class in question + * @param superClass the super class + * @return the closes parent type + */ + private static Type getClosestParentType(final Class cls, + final Class superClass) + { + // only look at the interfaces if the super class is also an interface + if (superClass.isInterface()) { + // get the generic interfaces of the subject class + final Type[] interfaceTypes = cls.getGenericInterfaces(); + // will hold the best generic interface match found + Type genericInterface = null; + + // find the interface closest to the super class + for (final Type midType : interfaceTypes) { + Class midClass = null; + + if (midType instanceof ParameterizedType) { + midClass = getRawType((ParameterizedType) midType); + } + else if (midType instanceof Class) { + midClass = (Class) midType; + } + else { + throw new IllegalStateException("Unexpected generic" + + " interface type found: " + midType); + } + + // check if this interface is further up the inheritance chain + // than the previously found match + if (isAssignable(midClass, superClass) && isAssignable( + genericInterface, (Type) midClass)) + { + genericInterface = midType; + } + } + + // found a match? + if (genericInterface != null) { + return genericInterface; + } + } + + // none of the interfaces were descendants of the target class, so the + // super class has to be one, instead + return cls.getGenericSuperclass(); + } + + /** + *

    + * Checks if the given value can be assigned to the target type following + * the Java generics rules. + *

    + * + * @param value the value to be checked + * @param type the target type + * @return {@code true} if {@code value} is an instance of {@code type}. + */ + public static boolean isInstance(final Object value, final Type type) { + if (type == null) { + return false; + } + + return value == null ? !(type instanceof Class) || !((Class) type) + .isPrimitive() : isAssignable(value.getClass(), type, null); + } + + /** + *

    + * This method strips out the redundant upper bound types in type variable + * types and wildcard types (or it would with wildcard types if multiple + * upper bounds were allowed). + *

    + *

    + * Example, with the variable type declaration: + * + *

    +		 * <K extends java.util.Collection<String> &
    +		 * java.util.List<String>>
    +		 * 
    + *

    + * since {@code List} is a subinterface of {@code Collection}, this method + * will return the bounds as if the declaration had been: + *

    + * + *
    +		 * <K extends java.util.List<String>>
    +		 * 
    + * + * @param bounds an array of types representing the upper bounds of either + * {@link WildcardType} or {@link TypeVariable}, not {@code null}. + * @return an array containing the values from {@code bounds} minus the + * redundant types. + */ + public static Type[] normalizeUpperBounds(final Type[] bounds) { + validateNotNull(bounds, "null value specified for bounds array"); + // don't bother if there's only one (or none) type + if (bounds.length < 2) { + return bounds; + } + + final Set types = new HashSet<>(bounds.length); + + for (final Type type1 : bounds) { + boolean subtypeFound = false; + + for (final Type type2 : bounds) { + if (type1 != type2 && isAssignable(type2, type1, null)) { + subtypeFound = true; + break; + } + } + + if (!subtypeFound) { + types.add(type1); + } + } + + return types.toArray(new Type[types.size()]); + } + + /** + *

    + * Returns an array containing the sole type of {@link Object} if + * {@link TypeVariable#getBounds()} returns an empty array. Otherwise, it + * returns the result of {@link TypeVariable#getBounds()} passed into + * {@link #normalizeUpperBounds}. + *

    + * + * @param typeVariable the subject type variable, not {@code null} + * @return a non-empty array containing the bounds of the type variable. + */ + public static Type[] getImplicitBounds(final TypeVariable typeVariable) { + validateNotNull(typeVariable, "typeVariable is null"); + final Type[] bounds = typeVariable.getBounds(); + + return bounds.length == 0 ? new Type[] { Object.class } + : normalizeUpperBounds(bounds); + } + + /** + *

    + * Returns an array containing the sole value of {@link Object} if + * {@link WildcardType#getUpperBounds()} returns an empty array. Otherwise, + * it returns the result of {@link WildcardType#getUpperBounds()} passed + * into {@link #normalizeUpperBounds}. + *

    + * + * @param wildcardType the subject wildcard type, not {@code null} + * @return a non-empty array containing the upper bounds of the wildcard + * type. + */ + public static Type[] getImplicitUpperBounds( + final WildcardType wildcardType) + { + validateNotNull(wildcardType, "wildcardType is null"); + final Type[] bounds = wildcardType.getUpperBounds(); + + return bounds.length == 0 ? new Type[] { Object.class } + : normalizeUpperBounds(bounds); + } + + /** + *

    + * Returns an array containing a single value of {@code null} if + * {@link WildcardType#getLowerBounds()} returns an empty array. Otherwise, + * it returns the result of {@link WildcardType#getLowerBounds()}. + *

    + * + * @param wildcardType the subject wildcard type, not {@code null} + * @return a non-empty array containing the lower bounds of the wildcard + * type. + */ + public static Type[] getImplicitLowerBounds( + final WildcardType wildcardType) + { + validateNotNull(wildcardType, "wildcardType is null"); + final Type[] bounds = wildcardType.getLowerBounds(); + + return bounds.length == 0 ? new Type[] { null } : bounds; + } + + /** + *

    + * Determines whether or not specified types satisfy the bounds of their + * mapped type variables. When a type parameter extends another (such as + * {@code }), uses another as a type parameter (such as + * {@code >}), or otherwise depends on another type + * variable to be specified, the dependencies must be included in + * {@code typeVarAssigns}. + *

    + * + * @param typeVarAssigns specifies the potential types to be assigned to the + * type variables, not {@code null}. + * @return whether or not the types can be assigned to their respective type + * variables. + */ + public static boolean typesSatisfyVariables( + final Map, Type> typeVarAssigns) + { + validateNotNull(typeVarAssigns, "typeVarAssigns is null"); + // all types must be assignable to all the bounds of the their mapped + // type variable. + for (final Map.Entry, Type> entry : typeVarAssigns + .entrySet()) + { + final TypeVariable typeVar = entry.getKey(); + final Type type = entry.getValue(); + + for (final Type bound : getImplicitBounds(typeVar)) { + if (!isAssignable(type, substituteTypeVariables(bound, + typeVarAssigns), typeVarAssigns)) + { + return false; + } + } + } + return true; + } + + /** + *

    + * Transforms the passed in type to a {@link Class} object. Type-checking + * method of convenience. + *

    + * + * @param parameterizedType the type to be converted + * @return the corresponding {@code Class} object + * @throws IllegalStateException if the conversion fails + */ + private static Class getRawType( + final ParameterizedType parameterizedType) + { + final Type rawType = parameterizedType.getRawType(); + + // check if raw type is a Class object + // not currently necessary, but since the return type is Type instead of + // Class, there's enough reason to believe that future versions of Java + // may return other Type implementations. And type-safety checking is + // rarely a bad idea. + if (!(rawType instanceof Class)) { + throw new IllegalStateException("Wait... What!? Type of rawType: " + + rawType); + } + + return (Class) rawType; + } + + /** + *

    + * Get the raw type of a Java type, given its context. Primarily for use + * with {@link TypeVariable}s and {@link GenericArrayType}s, or when you do + * not know the runtime type of {@code type}: if you know you have a + * {@link Class} instance, it is already raw; if you know you have a + * {@link ParameterizedType}, its raw type is only a method call away. + *

    + * + * @param type to resolve + * @param assigningType type to be resolved against + * @return the resolved {@link Class} object or {@code null} if the type + * could not be resolved + */ + public static Class getRawType(final Type type, + final Type assigningType) + { + if (type instanceof Class) { + // it is raw, no problem + return (Class) type; + } + + if (type instanceof ParameterizedType) { + // simple enough to get the raw type of a ParameterizedType + return getRawType((ParameterizedType) type); + } + + if (type instanceof TypeVariable) { + if (assigningType == null) { + return null; + } + + // get the entity declaring this type variable + final Object genericDeclaration = ((TypeVariable) type) + .getGenericDeclaration(); + + // can't get the raw type of a method- or constructor-declared type + // variable + if (!(genericDeclaration instanceof Class)) { + return null; + } + + // get the type arguments for the declaring class/interface based + // on the enclosing type + final Map, Type> typeVarAssigns = getTypeArguments( + assigningType, (Class) genericDeclaration); + + // enclosingType has to be a subclass (or subinterface) of the + // declaring type + if (typeVarAssigns == null) { + return null; + } + + // get the argument assigned to this type variable + final Type typeArgument = typeVarAssigns.get(type); + + if (typeArgument == null) { + return null; + } + + // get the argument for this type variable + return getRawType(typeArgument, assigningType); + } + + if (type instanceof GenericArrayType) { + // get raw component type + final Class rawComponentType = getRawType(((GenericArrayType) type) + .getGenericComponentType(), assigningType); + + // return the corresponding array type + return array(rawComponentType); + } + + // (hand-waving) this is not the method you're looking for + if (type instanceof WildcardType) { + return null; + } + + throw new IllegalArgumentException("unknown type: " + type); + } + + /** + * Learn whether the specified type denotes an array type. + * + * @param type the type to be checked + * @return {@code true} if {@code type} is an array class or a + * {@link GenericArrayType}. + */ + public static boolean isArrayType(final Type type) { + return type instanceof GenericArrayType || type instanceof Class && + ((Class) type).isArray(); + } + + /** + * Get the array component type of {@code type}. + * + * @param type the type to be checked + * @return component type or null if type is not an array type + */ + public static Type getArrayComponentType(final Type type) { + if (type instanceof Class) { + final Class clazz = (Class) type; + return clazz.isArray() ? clazz.getComponentType() : null; + } + if (type instanceof GenericArrayType) { + return ((GenericArrayType) type).getGenericComponentType(); + } + return null; + } + + /** + * Get a type representing {@code type} with variable assignments + * "unrolled." + * + * @param typeArguments as from + * {@link TypeUtils#getTypeArguments(Type, Class)} + * @param type the type to unroll variable assignments for + * @return Type + * @since 3.2 + */ + public static Type unrollVariables(Map, Type> typeArguments, + final Type type) + { + if (typeArguments == null) { + typeArguments = Collections., Type> emptyMap(); + } + if (containsTypeVariables(type)) { + if (type instanceof TypeVariable) { + return unrollVariables(typeArguments, typeArguments.get(type)); + } + if (type instanceof ParameterizedType) { + final ParameterizedType p = (ParameterizedType) type; + final Map, Type> parameterizedTypeArguments; + if (p.getOwnerType() == null) { + parameterizedTypeArguments = typeArguments; + } + else { + parameterizedTypeArguments = new HashMap<>(typeArguments); + parameterizedTypeArguments.putAll(TypeUtils.getTypeArguments(p)); + } + final Type[] args = p.getActualTypeArguments(); + for (int i = 0; i < args.length; i++) { + final Type unrolled = unrollVariables(parameterizedTypeArguments, + args[i]); + if (unrolled != null) { + args[i] = unrolled; + } + } + return parameterizeWithOwner(p.getOwnerType(), (Class) p + .getRawType(), args); + } + if (type instanceof WildcardType) { + final WildcardType wild = (WildcardType) type; + return wildcardType().withUpperBounds(unrollBounds(typeArguments, wild + .getUpperBounds())).withLowerBounds(unrollBounds(typeArguments, wild + .getLowerBounds())).build(); + } + } + return type; + } + + /** + * Local helper method to unroll variables in a type bounds array. + * + * @param typeArguments assignments {@link Map} + * @param bounds in which to expand variables + * @return {@code bounds} with any variables reassigned + * @since 3.2 + */ + private static Type[] unrollBounds( + final Map, Type> typeArguments, final Type[] bounds) + { + final ArrayList result = new ArrayList<>(); + for (final Type bound : bounds) { + final Type unrolled = unrollVariables(typeArguments, bound); + if (unrolled != null) result.add(unrolled); + } + return result.toArray(new Type[result.size()]); + } + + /** + * Learn, recursively, whether any of the type parameters associated with + * {@code type} are bound to variables. + * + * @param type the type to check for type variables + * @return boolean + * @since 3.2 + */ + public static boolean containsTypeVariables(final Type type) { + if (type instanceof TypeVariable) { + return true; + } + if (type instanceof Class) { + return ((Class) type).getTypeParameters().length > 0; + } + if (type instanceof ParameterizedType) { + for (final Type arg : ((ParameterizedType) type) + .getActualTypeArguments()) + { + if (containsTypeVariables(arg)) { + return true; + } + } + return false; + } + if (type instanceof WildcardType) { + final WildcardType wild = (WildcardType) type; + return containsTypeVariables(TypeUtils.getImplicitLowerBounds( + wild)[0]) || containsTypeVariables(TypeUtils.getImplicitUpperBounds( + wild)[0]); + } + return false; + } + + /** + * Create a parameterized type instance. + * + * @param raw the raw class to create a parameterized type instance for + * @param typeArguments the types used for parameterization + * @return {@link ParameterizedType} + * @since 3.2 + */ + public static final ParameterizedType parameterize(final Class raw, + final Type... typeArguments) + { + return parameterizeWithOwner(null, raw, typeArguments); + } + + /** + * Create a parameterized type instance. + * + * @param raw the raw class to create a parameterized type instance for + * @param typeArgMappings the mapping used for parameterization + * @return {@link ParameterizedType} + * @since 3.2 + */ + public static final ParameterizedType parameterize(final Class raw, + final Map, Type> typeArgMappings) + { + validateNotNull(raw, "raw class is null"); + validateNotNull(typeArgMappings, "typeArgMappings is null"); + return parameterizeWithOwner(null, raw, extractTypeArgumentsFrom( + typeArgMappings, raw.getTypeParameters())); + } + + /** + * Create a parameterized type instance. + * + * @param owner the owning type + * @param raw the raw class to create a parameterized type instance for + * @param typeArguments the types used for parameterization + * @return {@link ParameterizedType} + * @since 3.2 + */ + public static final ParameterizedType parameterizeWithOwner( + final Type owner, final Class raw, final Type... typeArguments) + { + validateNotNull(raw, "raw class is null"); + final Type useOwner; + if (raw.getEnclosingClass() == null) { + validateIsTrue(owner == null, "no owner allowed for top-level %s", raw); + useOwner = null; + } + else if (owner == null) { + useOwner = raw.getEnclosingClass(); + } + else { + validateIsTrue(TypeUtils.isAssignable(owner, raw.getEnclosingClass()), + "%s is invalid owner type for parameterized %s", owner, raw); + useOwner = owner; + } + validateNoNullElements(typeArguments, "null type argument at index %s"); + validateIsTrue(raw.getTypeParameters().length == typeArguments.length, + "invalid number of type parameters specified: expected %s, got %s", raw + .getTypeParameters().length, typeArguments.length); + + return new ParameterizedTypeImpl(raw, useOwner, typeArguments); + } + + /** + * Create a parameterized type instance. + * + * @param owner the owning type + * @param raw the raw class to create a parameterized type instance for + * @param typeArgMappings the mapping used for parameterization + * @return {@link ParameterizedType} + * @since 3.2 + */ + public static final ParameterizedType parameterizeWithOwner( + final Type owner, final Class raw, + final Map, Type> typeArgMappings) + { + validateNotNull(raw, "raw class is null"); + validateNotNull(typeArgMappings, "typeArgMappings is null"); + return parameterizeWithOwner(owner, raw, extractTypeArgumentsFrom( + typeArgMappings, raw.getTypeParameters())); + } + + /** + * Helper method to establish the formal parameters for a parameterized + * type. + * + * @param mappings map containing the assignements + * @param variables expected map keys + * @return array of map values corresponding to specified keys + */ + private static Type[] extractTypeArgumentsFrom( + final Map, Type> mappings, + final TypeVariable[] variables) + { + final Type[] result = new Type[variables.length]; + int index = 0; + for (final TypeVariable var : variables) { + validateIsTrue(mappings.containsKey(var), + "missing argument mapping for %s", toString(var)); + result[index++] = mappings.get(var); + } + return result; + } + + /** + * Get a {@link WildcardTypeBuilder}. + * + * @return {@link WildcardTypeBuilder} + * @since 3.2 + */ + public static WildcardTypeBuilder wildcardType() { + return new WildcardTypeBuilder(); + } + + /** + * Create a generic array type instance. + * + * @param componentType the type of the elements of the array. For example + * the component type of {@code boolean[]} is {@code boolean} + * @return {@link GenericArrayType} + * @since 3.2 + */ + public static GenericArrayType genericArrayType(final Type componentType) { + return new GenericArrayTypeImpl(validateNotNull(componentType, + "componentType is null")); + } + + /** + * Check equality of types. + * + * @param t1 the first type + * @param t2 the second type + * @return boolean + * @since 3.2 + */ + public static boolean equals(final Type t1, final Type t2) { + if (Objects.equals(t1, t2)) { + return true; + } + if (t1 instanceof ParameterizedType) { + return equals((ParameterizedType) t1, t2); + } + if (t1 instanceof GenericArrayType) { + return equals((GenericArrayType) t1, t2); + } + if (t1 instanceof WildcardType) { + return equals((WildcardType) t1, t2); + } + return false; + } + + /** + * Learn whether {@code t} equals {@code p}. + * + * @param p LHS + * @param t RHS + * @return boolean + * @since 3.2 + */ + private static boolean equals(final ParameterizedType p, final Type t) { + if (t instanceof ParameterizedType) { + final ParameterizedType other = (ParameterizedType) t; + if (equals(p.getRawType(), other.getRawType()) && equals(p + .getOwnerType(), other.getOwnerType())) + { + return equals(p.getActualTypeArguments(), other + .getActualTypeArguments()); + } + } + return false; + } + + /** + * Learn whether {@code t} equals {@code a}. + * + * @param a LHS + * @param t RHS + * @return boolean + * @since 3.2 + */ + private static boolean equals(final GenericArrayType a, final Type t) { + return t instanceof GenericArrayType && equals(a + .getGenericComponentType(), ((GenericArrayType) t) + .getGenericComponentType()); + } + + /** + * Learn whether {@code t} equals {@code w}. + * + * @param w LHS + * @param t RHS + * @return boolean + * @since 3.2 + */ + private static boolean equals(final WildcardType w, final Type t) { + if (t instanceof WildcardType) { + final WildcardType other = (WildcardType) t; + return equals(getImplicitLowerBounds(w), getImplicitLowerBounds( + other)) && equals(getImplicitUpperBounds(w), getImplicitUpperBounds( + other)); + } + return true; + } + + /** + * Learn whether {@code t1} equals {@code t2}. + * + * @param t1 LHS + * @param t2 RHS + * @return boolean + * @since 3.2 + */ + private static boolean equals(final Type[] t1, final Type[] t2) { + if (t1.length == t2.length) { + for (int i = 0; i < t1.length; i++) { + if (!equals(t1[i], t2[i])) { + return false; + } + } + return true; + } + return false; + } + + /** + * Present a given type as a Java-esque String. + * + * @param type the type to create a String representation for, not + * {@code null} + * @return String + * @since 3.2 + */ + public static String toString(final Type type) { + return toString(type, new HashSet<>()); + } + + private static String toString(final Type type, final Set done) { + validateNotNull(type); + if (type instanceof Class) { + return classToString((Class) type, done); + } + if (type instanceof ParameterizedType) { + return parameterizedTypeToString((ParameterizedType) type, done); + } + if (type instanceof WildcardType) { + return wildcardTypeToString((WildcardType) type, done); + } + if (type instanceof CaptureType) { + return captureTypeToString((CaptureType) type, done); + } + if (type instanceof TypeVariable) { + return typeVariableToString((TypeVariable) type, done); + } + if (type instanceof GenericArrayType) { + return genericArrayTypeToString((GenericArrayType) type); + } + throw new IllegalArgumentException("Unknown generic type: " + // + type.getClass().getName()); + } + + /** + * Format a {@link TypeVariable} including its {@link GenericDeclaration}. + * + * @param var the type variable to create a String representation for, not + * {@code null} + * @return String + * @since 3.2 + */ + public static String toLongString(final TypeVariable var) { + validateNotNull(var, "var is null"); + final StringBuilder buf = new StringBuilder(); + final GenericDeclaration d = ((TypeVariable) var) + .getGenericDeclaration(); + if (d instanceof Class) { + Class c = (Class) d; + while (true) { + if (c.getEnclosingClass() == null) { + buf.insert(0, c.getName()); + break; + } + buf.insert(0, c.getSimpleName()).insert(0, '.'); + c = c.getEnclosingClass(); + } + } + else if (d instanceof Type) {// not possible as of now + buf.append(toString((Type) d)); + } + else { + buf.append(d); + } + return buf.append(':').append(typeVariableToString(var, new HashSet<>())) + .toString(); + } + +// /** +// * Wrap the specified {@link Type} in a {@link Typed} wrapper. +// * +// * @param inferred generic type +// * @param type to wrap +// * @return Typed<T> +// * @since 3.2 +// */ +// public static Typed wrap(final Type type) { +// return new Typed() { +// +// @Override +// public Type getType() { +// return type; +// } +// }; +// } +// +// /** +// * Wrap the specified {@link Class} in a {@link Typed} wrapper. +// * +// * @param generic type +// * @param type to wrap +// * @return Typed<T> +// * @since 3.2 +// */ +// public static Typed wrap(final Class type) { +// return TypeUtils. wrap((Type) type); +// } + + /** + * Format a {@link Class} as a {@link String}. + * + * @param c {@code Class} to format + * @param done list of already-encountered types + * @return String + * @since 3.2 + */ + private static String classToString(final Class c, + final Set done) + { + final StringBuilder buf = new StringBuilder(); + + if (c.getEnclosingClass() != null) { + buf.append(classToString(c.getEnclosingClass(), done)).append('.') + .append(c.getSimpleName()); + } + else { + buf.append(c.getName()); + } + if (c.getTypeParameters().length > 0) { + buf.append('<'); + appendAllTo(buf, ", ", done, c.getTypeParameters()); + buf.append('>'); + } + return buf.toString(); + } + + /** + * Format a {@link TypeVariable} as a {@link String}. + * + * @param v {@code TypeVariable} to format + * @param done list of already-encountered types + * @return String + * @since 3.2 + */ + private static String typeVariableToString(final TypeVariable v, + final Set done) + { + final StringBuilder buf = new StringBuilder(v.getName()); + if (done.contains(v)) return buf.toString(); + done.add(v); + final Type[] bounds = v.getBounds(); + if (bounds.length > 0 && !(bounds.length == 1 && Object.class.equals( + bounds[0]))) + { + buf.append(" extends "); + appendAllTo(buf, " & ", done, v.getBounds()); + } + return buf.toString(); + } + + /** + * Format a {@link ParameterizedType} as a {@link String}. + * + * @param p {@code ParameterizedType} to format + * @param done list of already-encountered types + * @return String + * @since 3.2 + */ + private static String parameterizedTypeToString(final ParameterizedType p, + final Set done) + { + final StringBuilder buf = new StringBuilder(); + + final Type useOwner = p.getOwnerType(); + final Class raw = (Class) p.getRawType(); + final Type[] typeArguments = p.getActualTypeArguments(); + if (useOwner == null) { + buf.append(raw.getName()); + } + else { + if (useOwner instanceof Class) { + buf.append(((Class) useOwner).getName()); + } + else { + buf.append(useOwner.toString()); + } + buf.append('.').append(raw.getSimpleName()); + } + + appendAllTo(buf.append('<'), ", ", done, typeArguments).append('>'); + return buf.toString(); + } + + /** + * Format a {@link WildcardType} as a {@link String}. + * + * @param w {@code WildcardType} to format + * @param done list of already-encountered types + * @return String + * @since 3.2 + */ + private static String wildcardTypeToString(final WildcardType w, + final Set done) + { + final StringBuilder buf = new StringBuilder().append('?'); + if (done.contains(w)) return buf.toString(); + done.add(w); + appendTypeBounds(buf, w.getLowerBounds(), w.getUpperBounds(), done); + return buf.toString(); + } + + /** + * Format a {@link CaptureType} as a {@link String}. + * + * @param t {@code CaptureType} to format + * @param done list of already-encountered types + * @return String + * @since 3.2 + */ + private static String captureTypeToString(final CaptureType t, + final Set done) + { + final StringBuilder buf = new StringBuilder().append("capture of ?"); + if (done.contains(t)) return buf.toString(); + done.add(t); + appendTypeBounds(buf, t.getLowerBounds(), t.getUpperBounds(), done); + return buf.toString(); + } + + /** + * Format a {@link GenericArrayType} as a {@link String}. + * + * @param g {@code GenericArrayType} to format + * @return String + * @since 3.2 + */ + private static String genericArrayTypeToString(final GenericArrayType g) { + return String.format("%s[]", toString(g.getGenericComponentType())); + } + + private static void appendTypeBounds(final StringBuilder buf, + final Type[] lowerBounds, final Type[] upperBounds, final Set done) + { + if (lowerBounds.length > 1 || lowerBounds.length == 1 && + lowerBounds[0] != null) + { + appendAllTo(buf.append(" super "), " & ", done, lowerBounds); + } + else if (upperBounds.length > 1 || upperBounds.length == 1 && + !Object.class.equals(upperBounds[0])) + { + appendAllTo(buf.append(" extends "), " & ", done, upperBounds); + } + } + + /** + * Append {@code types} to {@code buf} with separator {@code sep}. + * + * @param buf destination + * @param sep separator + * @param done list of already-encountered types + * @param types to append + * @return {@code buf} + * @since 3.2 + */ + private static StringBuilder appendAllTo(final StringBuilder buf, + final String sep, final Set done, final Type... types) + { + validateNotEmpty(validateNoNullElements(types)); + if (types.length > 0) { + buf.append(toString(types[0], done)); + for (int i = 1; i < types.length; i++) { + buf.append(sep).append(toString(types[i], done)); + } + } + return buf; + } + + private static final String DEFAULT_IS_NULL_EX_MESSAGE = + "The validated object is null"; + + /** Forked from {@code org.apache.commons.lang3.Validate#notNull}. */ + private static T validateNotNull(final T object) { + return validateNotNull(object, DEFAULT_IS_NULL_EX_MESSAGE); + } + + /** Forked from {@code org.apache.commons.lang3.Validate#notNull}. */ + private static T validateNotNull(final T object, final String message, + final Object... values) + { + if (object == null) { + throw new NullPointerException(String.format(message, values)); + } + return object; + } + + /** Forked from {@code org.apache.commons.lang3.Validate#isTrue}. */ + private static void validateIsTrue(final boolean expression, + final String message, final Object... values) + { + if (expression == false) { + throw new IllegalArgumentException(String.format(message, values)); + } + } + + private static final String DEFAULT_NO_NULL_ELEMENTS_ARRAY_EX_MESSAGE = + "The validated array contains null element at index: %d"; + + /** Forked from {@code org.apache.commons.lang3.Validate#noNullElements}. */ + private static T[] validateNoNullElements(final T[] array) { + return validateNoNullElements(array, + DEFAULT_NO_NULL_ELEMENTS_ARRAY_EX_MESSAGE); + } + + /** Forked from {@code org.apache.commons.lang3.Validate#noNullElements}. */ + private static T[] validateNoNullElements(final T[] array, + final String message, final Object... values) + { + validateNotNull(array); + for (int i = 0; i < array.length; i++) { + if (array[i] == null) { + final Object[] values2 = new Object[values.length + 1]; + System.arraycopy(values, 0, values2, 0, values.length); + values2[values.length] = Integer.valueOf(i); + throw new IllegalArgumentException(String.format(message, values2)); + } + } + return array; + } + + private static final String DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE = + "The validated array is empty"; + + /** Forked from {@code org.apache.commons.lang3.Validate#notEmpty}. */ + private static T[] validateNotEmpty(final T[] array) { + return validateNotEmpty(array, DEFAULT_NOT_EMPTY_ARRAY_EX_MESSAGE); + } + + /** Forked from {@code org.apache.commons.lang3.Validate#notEmpty}. */ + private static T[] validateNotEmpty(final T[] array, + final String message, final Object... values) + { + if (array == null) { + throw new NullPointerException(String.format(message, values)); + } + if (array.length == 0) { + throw new IllegalArgumentException(String.format(message, values)); + } + return array; + } + } + + // -- END FORK OF APACHE COMMONS LANG 3.4 CODE -- + + // -- BEGIN FORK OF GENTYREF 1.1.0 CODE -- + + /** + * Utility class for doing reflection on types. + * + * @author Wouter Coekaerts + */ + private static class GenericTypeReflector { + + private static final Type UNBOUND_WILDCARD = new TypeUtils.WildcardTypeImpl( + new Type[] { Object.class }, new Type[] {}); + + /** + * Returns the erasure of the given type. + */ + public static Class erase(final Type type) { + if (type instanceof Class) { + return (Class) type; + } + else if (type instanceof ParameterizedType) { + return (Class) ((ParameterizedType) type).getRawType(); + } + else if (type instanceof TypeVariable) { + final TypeVariable tv = (TypeVariable) type; + if (tv.getBounds().length == 0) return Object.class; + return erase(tv.getBounds()[0]); + } + else if (type instanceof GenericArrayType) { + final GenericArrayType aType = (GenericArrayType) type; + return array(erase(aType.getGenericComponentType())); + } + else { + // TODO at least support CaptureType here + throw new RuntimeException("not supported: " + type.getClass()); + } + } + + /** + * Maps type parameters in a type to their values. + * + * @param toMapType Type possibly containing type arguments + * @param typeAndParams must be either ParameterizedType, or (in case there + * are no type arguments, or it's a raw type) Class + * @return toMapType, but with type parameters from typeAndParams replaced. + */ + private static Type mapTypeParameters(final Type toMapType, + final Type typeAndParams) + { + if (isMissingTypeParameters(typeAndParams)) { + return erase(toMapType); + } + final VarMap varMap = new VarMap(); + Type handlingTypeAndParams = typeAndParams; + while (handlingTypeAndParams instanceof ParameterizedType) { + final ParameterizedType pType = + (ParameterizedType) handlingTypeAndParams; + // getRawType should always be Class + final Class clazz = (Class) pType.getRawType(); + varMap.addAll(clazz.getTypeParameters(), pType + .getActualTypeArguments()); + handlingTypeAndParams = pType.getOwnerType(); + } + return varMap.map(toMapType); + } + + /** + * Checks if the given type is a class that is supposed to have type + * parameters, but doesn't. In other words, if it's a really raw type. + */ + private static boolean isMissingTypeParameters(final Type type) { + if (type instanceof Class) { + for (Class clazz = (Class) type; clazz != null; clazz = clazz + .getEnclosingClass()) + { + if (clazz.getTypeParameters().length != 0) return true; + } + return false; + } + else if (type instanceof ParameterizedType) { + return false; + } + else { + throw new AssertionError("Unexpected type " + type.getClass()); + } + } + + /** + * Returns a type representing the class, with all type parameters the + * unbound wildcard ("?"). For example, + * addWildcardParameters(Map.class) returns a type representing + * Map<?,?>. + * + * @return + *
      + *
    • If clazz is a class or interface without type parameters, + * clazz itself is returned.
    • + *
    • If clazz is a class or interface with type parameters, an + * instance of ParameterizedType is returned.
    • + *
    • if clazz is an array type, an array type is returned with + * unbound wildcard parameters added in the the component type. + *
    + */ + public static Type addWildcardParameters(final Class clazz) { + if (clazz.isArray()) { + return array(addWildcardParameters(clazz.getComponentType())); + } + else if (isMissingTypeParameters(clazz)) { + final TypeVariable[] vars = clazz.getTypeParameters(); + final Type[] arguments = new Type[vars.length]; + Arrays.fill(arguments, UNBOUND_WILDCARD); + final Type owner = clazz.getDeclaringClass() == null ? null + : addWildcardParameters(clazz.getDeclaringClass()); + return parameterizeWithOwner(owner, clazz, arguments); + } + else { + return clazz; + } + } + + /** + * Finds the most specific supertype of type whose erasure is + * searchClass. In other words, returns a type representing the + * class searchClass plus its exact type parameters in + * type. + *
      + *
    • Returns an instance of {@link ParameterizedType} if + * searchClass is a real class or interface and type has + * parameters for it
    • + *
    • Returns an instance of {@link GenericArrayType} if + * searchClass is an array type, and type has type + * parameters for it
    • + *
    • Returns an instance of {@link Class} if type is a raw type, + * or has no type parameters for searchClass
    • + *
    • Returns null if searchClass is not a superclass of type. + *
    • + *
    + *

    + * For example, with + * class StringList implements List<String>, + * getExactSuperType(StringList.class, Collection.class) returns a + * {@link ParameterizedType} representing Collection<String>. + *

    + */ + public static Type getExactSuperType(final Type type, + final Class searchClass) + { + if (type instanceof ParameterizedType || type instanceof Class || + type instanceof GenericArrayType) + { + final Class clazz = erase(type); + + if (searchClass == clazz) { + return type; + } + + if (!searchClass.isAssignableFrom(clazz)) return null; + } + + for (final Type superType : getExactDirectSuperTypes(type)) { + final Type result = getExactSuperType(superType, searchClass); + if (result != null) return result; + } + + return null; + } + + /** + * Gets the type parameter for a given type that is the value for a given + * type variable. For example, with + * class StringList implements List<String>, + * getTypeParameter(StringList.class, Collection.class.getTypeParameters()[0]) + * returns String. + * + * @param type The type to inspect. + * @param variable The type variable to find the value for. + * @return The type parameter for the given variable. Or null if type is not + * a subtype of the type that declares the variable, or if the + * variable isn't known (because of raw types). + */ + public static Type getTypeParameter(final Type type, + final TypeVariable> variable) + { + final Class clazz = variable.getGenericDeclaration(); + final Type superType = getExactSuperType(type, clazz); + if (superType instanceof ParameterizedType) { + final int index = Arrays.asList(clazz.getTypeParameters()).indexOf( + variable); + return ((ParameterizedType) superType).getActualTypeArguments()[index]; + } + return null; + } + + /** + * Checks if the capture of subType is a subtype of superType + */ + public static boolean isSuperType(final Type superType, + final Type subType) + { + if (superType instanceof ParameterizedType || + superType instanceof Class || superType instanceof GenericArrayType) + { + final Class superClass = erase(superType); + final Type mappedSubType = getExactSuperType(capture(subType), + superClass); + if (mappedSubType == null) { + return false; + } + else if (superType instanceof Class) { + return true; + } + else if (mappedSubType instanceof Class) { + // TODO treat supertype by being raw type differently ("supertype, but + // with warnings") + return true; // class has no parameters, or it's a raw type + } + else if (mappedSubType instanceof GenericArrayType) { + final Type superComponentType = getArrayComponentType(superType); + assert superComponentType != null; + final Type mappedSubComponentType = getArrayComponentType( + mappedSubType); + assert mappedSubComponentType != null; + return isSuperType(superComponentType, mappedSubComponentType); + } + else { + assert mappedSubType instanceof ParameterizedType; + final ParameterizedType pMappedSubType = + (ParameterizedType) mappedSubType; + assert pMappedSubType.getRawType() == superClass; + final ParameterizedType pSuperType = (ParameterizedType) superType; + + final Type[] superTypeArgs = pSuperType.getActualTypeArguments(); + final Type[] subTypeArgs = pMappedSubType.getActualTypeArguments(); + assert superTypeArgs.length == subTypeArgs.length; + for (int i = 0; i < superTypeArgs.length; i++) { + if (!contains(superTypeArgs[i], subTypeArgs[i])) { + return false; + } + } + // params of the class itself match, so if the owner types are + // supertypes too, it's a supertype. + return pSuperType.getOwnerType() == null || isSuperType(pSuperType + .getOwnerType(), pMappedSubType.getOwnerType()); + } + } + else if (superType instanceof CaptureType) { + if (superType.equals(subType)) return true; + for (final Type lowerBound : ((CaptureType) superType) + .getLowerBounds()) + { + if (isSuperType(lowerBound, subType)) { + return true; + } + } + return false; + } + else if (superType instanceof GenericArrayType) { + return isArraySupertype(superType, subType); + } + else { + throw new RuntimeException("not implemented: " + superType.getClass()); + } + } + + private static boolean isArraySupertype(final Type arraySuperType, + final Type subType) + { + final Type superTypeComponent = getArrayComponentType(arraySuperType); + assert superTypeComponent != null; + final Type subTypeComponent = getArrayComponentType(subType); + if (subTypeComponent == null) { // subType is not an array type + return false; + } + return isSuperType(superTypeComponent, subTypeComponent); + } + + /** + * If type is an array type, returns the type of the component of the array. + * Otherwise, returns null. + */ + public static Type getArrayComponentType(final Type type) { + if (type instanceof Class) { + final Class clazz = (Class) type; + return clazz.getComponentType(); + } + else if (type instanceof GenericArrayType) { + final GenericArrayType aType = (GenericArrayType) type; + return aType.getGenericComponentType(); + } + else { + return null; + } + } + + private static boolean contains(final Type containingType, + final Type containedType) + { + if (containingType instanceof WildcardType) { + final WildcardType wContainingType = (WildcardType) containingType; + for (final Type upperBound : wContainingType.getUpperBounds()) { + if (!isSuperType(upperBound, containedType)) { + return false; + } + } + for (final Type lowerBound : wContainingType.getLowerBounds()) { + if (!isSuperType(containedType, lowerBound)) { + return false; + } + } + return true; + } + return containingType.equals(containedType); + } + + /** + * Returns the direct supertypes of the given type. Resolves type + * parameters. + */ + private static Type[] getExactDirectSuperTypes(final Type type) { + if (type instanceof ParameterizedType || type instanceof Class) { + Class clazz; + if (type instanceof ParameterizedType) { + clazz = (Class) ((ParameterizedType) type).getRawType(); + } + else { + // TODO primitive types? + clazz = (Class) type; + if (clazz.isArray()) return getArrayExactDirectSuperTypes(clazz); + } + + final Type[] superInterfaces = clazz.getGenericInterfaces(); + final Type superClass = clazz.getGenericSuperclass(); + Type[] result; + int resultIndex; + if (superClass == null) { + result = new Type[superInterfaces.length]; + resultIndex = 0; + } + else { + result = new Type[superInterfaces.length + 1]; + resultIndex = 1; + result[0] = mapTypeParameters(superClass, type); + } + for (final Type superInterface : superInterfaces) { + result[resultIndex++] = mapTypeParameters(superInterface, type); + } + + return result; + } + else if (type instanceof TypeVariable) { + final TypeVariable tv = (TypeVariable) type; + return tv.getBounds(); + } + else if (type instanceof WildcardType) { + // This should be a rare case: normally this wildcard is already + // captured. + // But it does happen if the upper bound of a type variable contains a + // wildcard + // TODO shouldn't upper bound of type variable have been captured too? + // (making this case impossible?) + return ((WildcardType) type).getUpperBounds(); + } + else if (type instanceof CaptureType) { + return ((CaptureType) type).getUpperBounds(); + } + else if (type instanceof GenericArrayType) { + return getArrayExactDirectSuperTypes(type); + } + else { + throw new RuntimeException("not implemented type: " + type); + } + } + + private static Type[] getArrayExactDirectSuperTypes(final Type arrayType) { + final Type typeComponent = getArrayComponentType(arrayType); + + Type[] result; + int resultIndex; + if (typeComponent instanceof Class && ((Class) typeComponent) + .isPrimitive()) + { + resultIndex = 0; + result = new Type[3]; + } + else { + final Type[] componentSupertypes = getExactDirectSuperTypes( + typeComponent); + result = new Type[componentSupertypes.length + 3]; + for (resultIndex = + 0; resultIndex < componentSupertypes.length; resultIndex++) + { + result[resultIndex] = array(componentSupertypes[resultIndex]); + } + } + result[resultIndex++] = Object.class; + result[resultIndex++] = Cloneable.class; + result[resultIndex++] = Serializable.class; + return result; + } + + /** + * Returns the exact return type of the given method in the given type. This + * may be different from m.getGenericReturnType() when the method + * was declared in a superclass, or type has a type parameter that + * is used in the return type, or type is a raw type. + */ + public static Type getExactReturnType(final Method m, final Type type) { + final Type returnType = m.getGenericReturnType(); + final Type exactDeclaringType = getExactSuperType(capture(type), m + .getDeclaringClass()); + if (exactDeclaringType == null) { + // capture(type) is not a subtype of m.getDeclaringClass() + throw new IllegalArgumentException("The method " + m + + " is not a member of type " + type); + } + return mapTypeParameters(returnType, exactDeclaringType); + } + + /** + * Returns the exact type of the given field in the given type. This may be + * different from f.getGenericType() when the field was declared in + * a superclass, or type has a type parameter that is used in the + * type of the field, or type is a raw type. + */ + public static Type getExactFieldType(final Field f, final Type type) { + final Type returnType = f.getGenericType(); + final Type exactDeclaringType = getExactSuperType(capture(type), f + .getDeclaringClass()); + if (exactDeclaringType == null) { + // capture(type) is not a subtype of f.getDeclaringClass() + throw new IllegalArgumentException("The field " + f + + " is not a member of type " + type); + } + return mapTypeParameters(returnType, exactDeclaringType); + } + + /** + * Returns the exact parameter types of the given method in the given type. + * This may be different from m.getGenericParameterTypes() when the + * method was declared in a superclass, or type has a type + * parameter that is used in one of the parameters, or type is a + * raw type. + */ + public static Type[] getExactParameterTypes(final Method m, + final Type type) + { + final Type[] parameterTypes = m.getGenericParameterTypes(); + final Type exactDeclaringType = getExactSuperType(capture(type), m + .getDeclaringClass()); + if (exactDeclaringType == null) { + // capture(type) is not a subtype of m.getDeclaringClass() + throw new IllegalArgumentException("The method " + m + + " is not a member of type " + type); + } + + final Type[] result = new Type[parameterTypes.length]; + for (int i = 0; i < parameterTypes.length; i++) { + result[i] = mapTypeParameters(parameterTypes[i], exactDeclaringType); + } + return result; + } + + /** + * Applies capture conversion to the given type. + */ + public static Type capture(final Type type) { + final VarMap varMap = new VarMap(); + final List toInit = new ArrayList<>(); + if (type instanceof ParameterizedType) { + final ParameterizedType pType = (ParameterizedType) type; + final Class clazz = (Class) pType.getRawType(); + final Type[] arguments = pType.getActualTypeArguments(); + final TypeVariable[] vars = clazz.getTypeParameters(); + final Type[] capturedArguments = new Type[arguments.length]; + assert arguments.length == vars.length; + for (int i = 0; i < arguments.length; i++) { + Type argument = arguments[i]; + if (argument instanceof WildcardType) { + final CaptureTypeImpl captured = new CaptureTypeImpl( + (WildcardType) argument, vars[i]); + argument = captured; + toInit.add(captured); + } + capturedArguments[i] = argument; + varMap.add(vars[i], argument); + } + for (final CaptureTypeImpl captured : toInit) { + captured.init(varMap); + } + final Type ownerType = pType.getOwnerType() == null ? null : capture( + pType.getOwnerType()); + return parameterizeWithOwner(ownerType, clazz, capturedArguments); + } + return type; + } + + /** + * Returns list of classes and interfaces that are supertypes of the given + * type. For example given this class: + * class Foo<A extends Number & Iterable<A>, B extends A> + *
    + * calling this method on type parameters B + * (Foo.class.getTypeParameters()[1]) returns a list containing + * Number and Iterable. + *

    + * This is mostly useful if you get a type from one of the other methods in + * GenericTypeReflector, but you don't want to deal with all the + * different sorts of types, and you are only really interested in concrete + * classes and interfaces. + *

    + * + * @return A List of classes, each of them a supertype of the given type. If + * the given type is a class or interface itself, returns a List + * with just the given type. The list contains no duplicates, and is + * ordered in the order the upper bounds are defined on the type. + */ + public static List> getUpperBoundClassAndInterfaces( + final Type type) + { + final LinkedHashSet> result = new LinkedHashSet<>(); + buildUpperBoundClassAndInterfaces(type, result); + return new ArrayList<>(result); + } + + /** + * Helper method for getUpperBoundClassAndInterfaces, adding the result to + * the given set. + */ + private static void buildUpperBoundClassAndInterfaces(final Type type, + final Set> result) + { + if (type instanceof ParameterizedType || type instanceof Class) { + result.add(erase(type)); + return; + } + + for (final Type superType : getExactDirectSuperTypes(type)) { + buildUpperBoundClassAndInterfaces(superType, result); + } + } + } + + /** + * CaptureType represents a wildcard that has gone through capture conversion. + * It is a custom subinterface of Type, not part of the java builtin Type + * hierarchy. + * + * @author Wouter Coekaerts + */ + private interface CaptureType extends Type { + + /** + * Returns an array of Type objects representing the upper bound(s) + * of this capture. This includes both the upper bound of a + * ? extends wildcard, and the bounds declared with the type + * variable. References to other (or the same) type variables in bounds + * coming from the type variable are replaced by their matching capture. + */ + Type[] getUpperBounds(); + + /** + * Returns an array of Type objects representing the lower bound(s) + * of this type variable. This is the bound of a ? super wildcard. + * This normally contains only one or no types; it is an array for + * consistency with {@link WildcardType#getLowerBounds()}. + */ + Type[] getLowerBounds(); + } + + private static class CaptureTypeImpl implements CaptureType { + + private final WildcardType wildcard; + private final TypeVariable variable; + private final Type[] lowerBounds; + private Type[] upperBounds; + + /** + * Creates an uninitialized CaptureTypeImpl. Before using this type, + * {@link #init(VarMap)} must be called. + * + * @param wildcard The wildcard this is a capture of + * @param variable The type variable where the wildcard is a parameter for. + */ + public CaptureTypeImpl(final WildcardType wildcard, + final TypeVariable variable) + { + this.wildcard = wildcard; + this.variable = variable; + this.lowerBounds = wildcard.getLowerBounds(); + } + + /** + * Initialize this CaptureTypeImpl. This is needed for type variable bounds + * referring to each other: we need the capture of the argument. + */ + void init(final VarMap varMap) { + final ArrayList upperBoundsList = new ArrayList<>(); + upperBoundsList.addAll(Arrays.asList(varMap.map(variable.getBounds()))); + + final List wildcardUpperBounds = Arrays.asList(wildcard + .getUpperBounds()); + if (wildcardUpperBounds.size() > 0 && wildcardUpperBounds.get( + 0) == Object.class) + { + // skip the Object bound, we already have a first upper bound from + // 'variable' + upperBoundsList.addAll(wildcardUpperBounds.subList(1, + wildcardUpperBounds.size())); + } + else { + upperBoundsList.addAll(wildcardUpperBounds); + } + upperBounds = new Type[upperBoundsList.size()]; + upperBoundsList.toArray(upperBounds); + } + + @Override + public Type[] getLowerBounds() { + return lowerBounds.clone(); + } + + @Override + public Type[] getUpperBounds() { + assert upperBounds != null; + return upperBounds.clone(); + } + + @Override + public String toString() { + return "capture of " + wildcard; + } + } + + /** + * Mapping between type variables and actual parameters. + * + * @author Wouter Coekaerts + */ + private static class VarMap { + + private final Map, Type> map = + new HashMap<>(); + + /** + * Creates an empty VarMap + */ + VarMap() {} + + void add(final TypeVariable variable, final Type value) { + map.put(variable, value); + } + + void addAll(final TypeVariable[] variables, final Type[] values) { + assert variables.length == values.length; + for (int i = 0; i < variables.length; i++) { + map.put(variables[i], values[i]); + } + } + + Type map(final Type type) { + if (type instanceof Class) { + return type; + } + else if (type instanceof TypeVariable) { + assert map.containsKey(type); + return map.get(type); + } + else if (type instanceof ParameterizedType) { + final ParameterizedType pType = (ParameterizedType) type; + final Type ownerType = pType.getOwnerType() == null ? // + pType.getOwnerType() : map(pType.getOwnerType()); + return parameterizeWithOwner(ownerType, (Class) pType.getRawType(), + map(pType.getActualTypeArguments())); + } + else if (type instanceof WildcardType) { + final WildcardType wType = (WildcardType) type; + return new TypeUtils.WildcardTypeImpl(map(wType.getUpperBounds()), map( + wType.getLowerBounds())); + } + else if (type instanceof GenericArrayType) { + return array(map(((GenericArrayType) type).getGenericComponentType())); + } + else { + throw new RuntimeException("not implemented: mapping " + type + .getClass() + " (" + type + ")"); + } + } + + Type[] map(final Type[] types) { + final Type[] result = new Type[types.length]; + for (int i = 0; i < types.length; i++) { + result[i] = map(types[i]); + } + return result; + } + } + + // -- END FORK OF GENTYREF 1.1.0 CODE -- +} diff --git a/src/main/java/org/scijava/util/UnitUtils.java b/src/main/java/org/scijava/util/UnitUtils.java index 8446802c6..43c13f1cb 100644 --- a/src/main/java/org/scijava/util/UnitUtils.java +++ b/src/main/java/org/scijava/util/UnitUtils.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/util/VersionUtils.java b/src/main/java/org/scijava/util/VersionUtils.java index c1ae21e45..2d7e97a32 100644 --- a/src/main/java/org/scijava/util/VersionUtils.java +++ b/src/main/java/org/scijava/util/VersionUtils.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -113,4 +111,67 @@ public static String getBuildNumber(final Class c) { return m == null ? null : m.getImplementationBuild(); } + /** + * Compares two version strings. + * @param v1 The first version string. + * @param v2 The second version string. + * @return a negative integer, zero, or a positive integer as the + * first argument is less than, equal to, or greater than the + * second. + */ + public static int compare(final String v1, final String v2) { + final String[] t1 = splitDots(v1), t2 = splitDots(v2); + final int count = Math.min(t1.length, t2.length); + for (int t=0; t 0 && i2 > 0) { + // Versions start with digits; compare them numerically. + final long d1 = Long.parseLong(t1.substring(0, i1)); + final long d2 = Long.parseLong(t2.substring(0, i2)); + if (d1 < d2) return -1; + if (d1 > d2) return 1; + suffix1 = t1.substring(i1); + suffix2 = t2.substring(i2); + } + + // Final version (empty string) is larger than non-final (non-empty). + // For example: 2.0.0 > 2.0.0-beta-1. + if (suffix1.isEmpty() && suffix2.isEmpty()) return 0; + if (suffix1.isEmpty()) return 1; + if (suffix2.isEmpty()) return -1; + + // Compare lexicographically. + return suffix1.compareTo(suffix2); + } + + /** Gets the subsequent index to all the given string's leading digits. */ + private static int digitIndex(final String s) { + int index = 0; + for (int i=0; i '9') break; + index++; + } + return index; + } } diff --git a/src/main/java/org/scijava/util/XML.java b/src/main/java/org/scijava/util/XML.java index 3fce35ced..0dcbb1126 100644 --- a/src/main/java/org/scijava/util/XML.java +++ b/src/main/java/org/scijava/util/XML.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -128,7 +126,7 @@ public XML(final String path, final Document doc) { // configured services, including the // com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl). if (debug) { - System.err.println(ClassUtils.getLocation(XPathFactory.class)); + System.err.println(Types.location(XPathFactory.class)); } XPath xp = null; @@ -146,7 +144,7 @@ public XML(final String path, final Document doc) { } catch (Throwable t) { if (debug) { System.err.println("There was a problem with " + xp.getClass() + - " in " + ClassUtils.getLocation(xp.getClass()) + ":"); + " in " + Types.location(xp.getClass()) + ":"); t.printStackTrace(); } throw new Error(t); diff --git a/src/main/java/org/scijava/welcome/DefaultWelcomeService.java b/src/main/java/org/scijava/welcome/DefaultWelcomeService.java index 2be5c30ea..8977b0001 100644 --- a/src/main/java/org/scijava/welcome/DefaultWelcomeService.java +++ b/src/main/java/org/scijava/welcome/DefaultWelcomeService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -46,6 +44,7 @@ import org.scijava.service.AbstractService; import org.scijava.service.Service; import org.scijava.text.TextService; +import org.scijava.ui.UIService; import org.scijava.ui.event.UIShownEvent; import org.scijava.util.DigestUtils; import org.scijava.welcome.event.WelcomeEvent; @@ -82,6 +81,9 @@ public class DefaultWelcomeService extends AbstractService implements @Parameter private EventService eventService; + @Parameter + private UIService uiService; + @Parameter private PrefService prefService; @@ -126,7 +128,7 @@ public void setFirstRun(final boolean firstRun) { /** Displays the welcome text when a UI is shown for the first time. */ @EventHandler protected void onEvent(@SuppressWarnings("unused") final UIShownEvent evt) { - if (!isFirstRun()) return; + if (uiService.isHeadless() || !isFirstRun()) return; eventService.publish(new WelcomeEvent()); setFirstRun(false); displayWelcome(false); diff --git a/src/main/java/org/scijava/welcome/WelcomeService.java b/src/main/java/org/scijava/welcome/WelcomeService.java index f4fa9470d..8e2f77aec 100644 --- a/src/main/java/org/scijava/welcome/WelcomeService.java +++ b/src/main/java/org/scijava/welcome/WelcomeService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/welcome/event/WelcomeEvent.java b/src/main/java/org/scijava/welcome/event/WelcomeEvent.java index 513895055..6f9c5946c 100644 --- a/src/main/java/org/scijava/welcome/event/WelcomeEvent.java +++ b/src/main/java/org/scijava/welcome/event/WelcomeEvent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/widget/AbstractInputHarvester.java b/src/main/java/org/scijava/widget/AbstractInputHarvester.java index a9bf541de..0785fa157 100644 --- a/src/main/java/org/scijava/widget/AbstractInputHarvester.java +++ b/src/main/java/org/scijava/widget/AbstractInputHarvester.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -32,7 +30,11 @@ package org.scijava.widget; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import org.scijava.AbstractContextual; import org.scijava.convert.ConvertService; @@ -119,8 +121,15 @@ private WidgetModel addInput(final InputPanel inputPanel, } if (item.isRequired()) { - throw new ModuleException("A " + type.getSimpleName() + - " is required but none exist."); + final List vowelSoundPrefixes = Arrays.asList( + "a", "e", "i", "o", "u", "honor", "honour", "hour", "xml" + ); + final String typeName = type.getSimpleName(); + final String article = vowelSoundPrefixes.stream().anyMatch( + prefix -> typeName.toLowerCase().startsWith(prefix) + ) ? "An" : "A"; + throw new ModuleException(article + " " + typeName + + " is required but none is available."); } // item is not required; we can skip it @@ -128,13 +137,25 @@ private WidgetModel addInput(final InputPanel inputPanel, } /** Asks the object service and convert service for valid choices */ - @SuppressWarnings("unchecked") - private List getObjects(final Class type) { - @SuppressWarnings("rawtypes") - List compatibleInputs = - new ArrayList(convertService.getCompatibleInputs(type)); - compatibleInputs.addAll(objectService.getObjects(type)); - return compatibleInputs; + private List getObjects(final Class type) { + // Start with the known, unconverted objects of the desired type + List objects = new ArrayList<>(objectService.getObjects(type)); + + // Get all the known objects that can be converted to the destination type + Collection compatibleInputs = convertService.getCompatibleInputs(type); + + // HACK: Add each convertible object that doesn't share a name with any other object + // Our goal here is to de-duplicate by avoiding similar inputs that could be converted + // to the same effective output (e.g. an ImageDisplay and a Dataset that map to the same + // ImgPlus) + Set knownNames = objects.stream().map(Object::toString).collect(Collectors.toSet()); + for (Object o : compatibleInputs) { + final String s = o.toString(); + if (!knownNames.contains(s)) { + objects.add(o); + knownNames.add(s); + } + } + return objects; } - } diff --git a/src/main/java/org/scijava/widget/AbstractInputPanel.java b/src/main/java/org/scijava/widget/AbstractInputPanel.java index ce65e8c2c..756f5ed15 100644 --- a/src/main/java/org/scijava/widget/AbstractInputPanel.java +++ b/src/main/java/org/scijava/widget/AbstractInputPanel.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/widget/AbstractInputWidget.java b/src/main/java/org/scijava/widget/AbstractInputWidget.java index 0a95225ac..b6304cfd3 100644 --- a/src/main/java/org/scijava/widget/AbstractInputWidget.java +++ b/src/main/java/org/scijava/widget/AbstractInputWidget.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -67,4 +65,11 @@ public void set(final WidgetModel model) { public WidgetModel get() { return widgetModel; } + + // -- Typed methods -- + + @Override + public boolean supports(final WidgetModel data) { + return InputWidget.super.supports(data); + } } diff --git a/src/main/java/org/scijava/widget/Button.java b/src/main/java/org/scijava/widget/Button.java index 6b9340c08..3ddfa0217 100644 --- a/src/main/java/org/scijava/widget/Button.java +++ b/src/main/java/org/scijava/widget/Button.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/widget/ButtonWidget.java b/src/main/java/org/scijava/widget/ButtonWidget.java index 53181eb71..c836c522d 100644 --- a/src/main/java/org/scijava/widget/ButtonWidget.java +++ b/src/main/java/org/scijava/widget/ButtonWidget.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/widget/ChoiceWidget.java b/src/main/java/org/scijava/widget/ChoiceWidget.java index bfd15d7c8..275b39607 100644 --- a/src/main/java/org/scijava/widget/ChoiceWidget.java +++ b/src/main/java/org/scijava/widget/ChoiceWidget.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/widget/ColorWidget.java b/src/main/java/org/scijava/widget/ColorWidget.java index 6e539f14f..93573a5ca 100644 --- a/src/main/java/org/scijava/widget/ColorWidget.java +++ b/src/main/java/org/scijava/widget/ColorWidget.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/widget/DateWidget.java b/src/main/java/org/scijava/widget/DateWidget.java index 477a86aa4..ecd9953cb 100644 --- a/src/main/java/org/scijava/widget/DateWidget.java +++ b/src/main/java/org/scijava/widget/DateWidget.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/widget/DefaultWidgetModel.java b/src/main/java/org/scijava/widget/DefaultWidgetModel.java index 395def82f..e7d6b9352 100644 --- a/src/main/java/org/scijava/widget/DefaultWidgetModel.java +++ b/src/main/java/org/scijava/widget/DefaultWidgetModel.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -31,7 +29,6 @@ package org.scijava.widget; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; @@ -46,11 +43,11 @@ import org.scijava.module.Module; import org.scijava.module.ModuleItem; import org.scijava.module.ModuleService; +import org.scijava.object.ObjectService; import org.scijava.plugin.Parameter; import org.scijava.thread.ThreadService; -import org.scijava.util.ClassUtils; -import org.scijava.util.ConversionUtils; import org.scijava.util.NumberUtils; +import org.scijava.util.Types; /** * The backing data model for a particular {@link InputWidget}. @@ -74,11 +71,16 @@ public class DefaultWidgetModel extends AbstractContextual implements WidgetMode @Parameter private ModuleService moduleService; + @Parameter + private ObjectService objectService; + @Parameter(required = false) private LogService log; private boolean initialized; + private String validationMessage; + public DefaultWidgetModel(final Context context, final InputPanel inputPanel, final Module module, final ModuleItem item, final List objectPool) { @@ -128,12 +130,7 @@ public String getWidgetLabel() { @Override public boolean isStyle(final String style) { - final String widgetStyle = getItem().getWidgetStyle(); - if (widgetStyle == null) return style == null; - for (final String s : widgetStyle.split(",")) { - if (s.equals(style)) return true; - } - return false; + return WidgetStyle.isStyle(getItem(), style); } @Override @@ -160,6 +157,7 @@ public void setValue(final Object value) { // Pass the value through the convertService convertedInput = convertService.convert(value, item.getType()); + if (convertedInput == null) convertedInput = value; // If we get a different (converted) value back, cache it weakly. if (convertedInput != value) { @@ -169,14 +167,15 @@ public void setValue(final Object value) { module.setInput(name, convertedInput); if (initialized) { - threadService.run(new Runnable() { - - @Override - public void run() { - callback(); - inputPanel.refresh(); // must be on AWT thread? - module.preview(); + threadService.queue(() -> { + callback(); + // Revalidate all inputs: changing one value may affect others' validity. + for (final ModuleItem anyItem : module.getInfo().inputs()) { + final InputWidget w = inputPanel.getWidget(anyItem.getName()); + if (w != null) w.get().updateValidation(); } + inputPanel.refresh(); // must be on AWT thread? + module.preview(); }); } } @@ -228,10 +227,11 @@ public Number getStepSize() { @Override public String[] getChoices() { - final List choicesList = item.getChoices(); + final List choicesList = getItem().getChoices(); + if (choicesList == null) return null; final String[] choices = new String[choicesList.size()]; for (int i = 0; i < choices.length; i++) { - choices[i] = choicesList.get(i).toString(); + choices[i] = objectService.getName(choicesList.get(i)); } return choices; } @@ -252,22 +252,22 @@ public boolean isMessage() { @Override public boolean isText() { - return ClassUtils.isText(getItem().getType()); + return Types.isText(getItem().getType()); } @Override public boolean isCharacter() { - return ClassUtils.isCharacter(getItem().getType()); + return Types.isCharacter(getItem().getType()); } @Override public boolean isNumber() { - return ClassUtils.isNumber(getItem().getType()); + return Types.isNumber(getItem().getType()); } @Override public boolean isBoolean() { - return ClassUtils.isBoolean(getItem().getType()); + return Types.isBoolean(getItem().getType()); } @Override @@ -291,16 +291,25 @@ public boolean isInitialized() { return initialized; } + @Override + public String getValidationMessage() { + return validationMessage; + } + + @Override + public void updateValidation() { + validationMessage = item.validateMessage(module); + } + // -- Helper methods -- /** * For multiple choice widgets, ensures the value is a valid choice. * - * @see #getChoices() * @see ChoiceWidget */ private Object ensureValidChoice(final Object value) { - return ensureValid(value, Arrays.asList(getChoices())); + return ensureValid(value, getItem().getChoices()); } /** @@ -334,7 +343,7 @@ private Object ensureValid(final Object value, final List list) { /** Converts the given object to a number matching the input type. */ private Number toNumber(final Object value) { final Class type = item.getType(); - final Class saneType = ConversionUtils.getNonprimitiveType(type); + final Class saneType = Types.box(type); return NumberUtils.toNumber(value, saneType); } diff --git a/src/main/java/org/scijava/widget/DefaultWidgetService.java b/src/main/java/org/scijava/widget/DefaultWidgetService.java index 78d0ae3f6..7ceaf2c45 100644 --- a/src/main/java/org/scijava/widget/DefaultWidgetService.java +++ b/src/main/java/org/scijava/widget/DefaultWidgetService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/widget/FileListWidget.java b/src/main/java/org/scijava/widget/FileListWidget.java new file mode 100644 index 000000000..466f51d2f --- /dev/null +++ b/src/main/java/org/scijava/widget/FileListWidget.java @@ -0,0 +1,54 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.scijava.widget; + +import java.io.File; + +public interface FileListWidget extends InputWidget { + /** + * Widget style to allow file selection only + * + * @see org.scijava.plugin.Parameter#style() + */ + String FILES_ONLY = "files"; + + /** + * Widget style to allow directory selection only + * + * @see org.scijava.plugin.Parameter#style() + */ + String DIRECTORIES_ONLY = "directories"; + + /** + * Widget style to allow selection of both files and directories + * + * @see org.scijava.plugin.Parameter#style() + */ + String FILES_AND_DIRECTORIES = "both"; +} diff --git a/src/main/java/org/scijava/widget/FileWidget.java b/src/main/java/org/scijava/widget/FileWidget.java index 5ba0c5dd5..dcf38039d 100644 --- a/src/main/java/org/scijava/widget/FileWidget.java +++ b/src/main/java/org/scijava/widget/FileWidget.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -61,4 +59,11 @@ public interface FileWidget extends InputWidget { */ String DIRECTORY_STYLE = "directory"; + /** + * Widget style for directory chooser dialogs. + * + * @see org.scijava.plugin.Parameter#style() + */ + String FILE_AND_DIRECTORY_STYLE = "both"; + } diff --git a/src/main/java/org/scijava/widget/InputHarvester.java b/src/main/java/org/scijava/widget/InputHarvester.java index 4370cbd8a..c0638cd96 100644 --- a/src/main/java/org/scijava/widget/InputHarvester.java +++ b/src/main/java/org/scijava/widget/InputHarvester.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -56,7 +54,7 @@ */ public interface InputHarvester { - double PRIORITY = Priority.VERY_LOW_PRIORITY; + double PRIORITY = Priority.VERY_LOW; /** * Performs the harvesting process. diff --git a/src/main/java/org/scijava/widget/InputPanel.java b/src/main/java/org/scijava/widget/InputPanel.java index 81c4d508f..27869b777 100644 --- a/src/main/java/org/scijava/widget/InputPanel.java +++ b/src/main/java/org/scijava/widget/InputPanel.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/widget/InputWidget.java b/src/main/java/org/scijava/widget/InputWidget.java index 19cd7030a..fe12174a0 100644 --- a/src/main/java/org/scijava/widget/InputWidget.java +++ b/src/main/java/org/scijava/widget/InputWidget.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/widget/MessageWidget.java b/src/main/java/org/scijava/widget/MessageWidget.java index 42492607b..fe4877975 100644 --- a/src/main/java/org/scijava/widget/MessageWidget.java +++ b/src/main/java/org/scijava/widget/MessageWidget.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/widget/NumberWidget.java b/src/main/java/org/scijava/widget/NumberWidget.java index 423de683e..f0aaefe28 100644 --- a/src/main/java/org/scijava/widget/NumberWidget.java +++ b/src/main/java/org/scijava/widget/NumberWidget.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/widget/ObjectWidget.java b/src/main/java/org/scijava/widget/ObjectWidget.java index f6da02732..925826ab9 100644 --- a/src/main/java/org/scijava/widget/ObjectWidget.java +++ b/src/main/java/org/scijava/widget/ObjectWidget.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/widget/TextWidget.java b/src/main/java/org/scijava/widget/TextWidget.java index f8d76bb4d..401d22076 100644 --- a/src/main/java/org/scijava/widget/TextWidget.java +++ b/src/main/java/org/scijava/widget/TextWidget.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/widget/ToggleWidget.java b/src/main/java/org/scijava/widget/ToggleWidget.java index bf13ebdff..11c028862 100644 --- a/src/main/java/org/scijava/widget/ToggleWidget.java +++ b/src/main/java/org/scijava/widget/ToggleWidget.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/widget/UIComponent.java b/src/main/java/org/scijava/widget/UIComponent.java index 1647d4db7..e36fcd919 100644 --- a/src/main/java/org/scijava/widget/UIComponent.java +++ b/src/main/java/org/scijava/widget/UIComponent.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/widget/WidgetModel.java b/src/main/java/org/scijava/widget/WidgetModel.java index 52cbcc1bd..4eb8e2703 100644 --- a/src/main/java/org/scijava/widget/WidgetModel.java +++ b/src/main/java/org/scijava/widget/WidgetModel.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -189,6 +187,27 @@ public interface WidgetModel extends Contextual { /** Gets whether the input is compatible with the given type. */ boolean isType(Class type); + /** + * Gets the current validation error message for this widget. + * + * @return the error message from the most recent validation run, or + * {@code null} if the last validation passed or no validation has + * been run yet. + * @see org.scijava.module.ModuleItem#validateMessage(org.scijava.module.Module) + */ + String getValidationMessage(); + + /** + * Re-runs this item's validation and updates the stored validation message. + *

    + * This should be called on all widgets whenever any parameter value changes, + * since a change to one parameter may affect the validity of others. + *

    + * + * @see #getValidationMessage() + */ + void updateValidation(); + /** * Toggles the widget's initialization state. An initialized widget can be * assumed to be an active part of a container {@link InputPanel}. diff --git a/src/main/java/org/scijava/widget/WidgetService.java b/src/main/java/org/scijava/widget/WidgetService.java index b1fbedc5a..88bb00e93 100644 --- a/src/main/java/org/scijava/widget/WidgetService.java +++ b/src/main/java/org/scijava/widget/WidgetService.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/main/java/org/scijava/widget/WidgetStyle.java b/src/main/java/org/scijava/widget/WidgetStyle.java new file mode 100644 index 000000000..ddd56824d --- /dev/null +++ b/src/main/java/org/scijava/widget/WidgetStyle.java @@ -0,0 +1,115 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.scijava.widget; + +import org.scijava.module.ModuleItem; + +public class WidgetStyle { + private WidgetStyle() { + // prevent instantiation of utility class + } + + /** + * Check whether a given widget style contains the target style. + * + * @param widgetStyle + * The style declaration to test, usually a comma-separated + * {@code String}; trailing spaces are ignored. + * @param target + * The style being checked, case-insensitive. + * @return {@code true} if the target style matches. + */ + public static boolean isStyle(String widgetStyle, String target) { + if (widgetStyle == null || target == null) + return widgetStyle == target; + for (final String s : widgetStyle.split(",")) { + if (s.trim().toLowerCase().equals(target.toLowerCase())) return true; + } + return false; + } + + /** + * Check whether a given {@link ModuleItem} has the target style. + * + * @param item + * The module item to test. + * @param target + * The style being checked, case-insensitive. + * @return {@code true} if the module item has the target style. + */ + public static boolean isStyle(ModuleItem item, String target) { + return isStyle(item.getWidgetStyle(), target); + } + + /** + * Get the modifying value for a given style attribute in a style declaration. + * + *

    + * For example, for {@code style="format:#0.00"}, this will return + * {@code "#0.00"}. + *

    + * + * @param widgetStyle + * The style declaration string, e.g. "format:#0.00". + * @param target + * The target style attribute, e.g. "format". + * @return The modifier for the given target, e.g. "#0.00". + */ + public static String getStyleModifier(String widgetStyle, String target) { + if (widgetStyle == null || target == null) + return null; + String[] styles = widgetStyle.split(","); + for (String s : styles) { + if (s.trim().toLowerCase().startsWith(target.toLowerCase())) { + return s.split(":")[1]; + } + } + return null; + } + + /** + * Get an array of all modifying values for a given style attribute. + * + *

    + * For example, for {@code style="extensions:png/gif/bmp"}, this will return {@code ["png", "gif", "bmp"]}. + *

    + * + * @param widgetStyle + * The style declaration string, e.g. "extensions:png/gif/bmp". + * @param target + * The target style attribute, e.g. "extensions". + * @return An array of modifiers for the given target, e.g. ["png", "gif", "bmp"]. + */ + public static String[] getStyleModifiers(String widgetStyle, String target) { + String suffix = getStyleModifier(widgetStyle, target); + if (suffix == null) return null; + return suffix.split("/"); + } + +} diff --git a/src/test/java/org/scijava/ContextCreationTest.java b/src/test/java/org/scijava/ContextCreationTest.java index 8de76276e..4760fa340 100644 --- a/src/test/java/org/scijava/ContextCreationTest.java +++ b/src/test/java/org/scijava/ContextCreationTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -44,6 +42,8 @@ import java.util.List; import org.junit.Test; +import org.scijava.event.ContextCreatedEvent; +import org.scijava.event.EventHandler; import org.scijava.plugin.Parameter; import org.scijava.plugin.PluginIndex; import org.scijava.plugin.PluginInfo; @@ -86,18 +86,20 @@ public void testNoPlugins() { public void testFull() { final Class[] expected = { org.scijava.event.DefaultEventService.class, - org.scijava.script.DefaultScriptService.class, org.scijava.app.DefaultAppService.class, org.scijava.app.DefaultStatusService.class, org.scijava.command.DefaultCommandService.class, org.scijava.console.DefaultConsoleService.class, org.scijava.convert.DefaultConvertService.class, org.scijava.display.DefaultDisplayService.class, + org.scijava.download.DefaultDownloadService.class, org.scijava.event.DefaultEventHistory.class, org.scijava.input.DefaultInputService.class, - org.scijava.io.DefaultDataHandleService.class, org.scijava.io.DefaultIOService.class, org.scijava.io.DefaultRecentFileService.class, + org.scijava.io.handle.DefaultDataHandleService.class, + org.scijava.io.location.DefaultLocationService.class, + org.scijava.io.nio.DefaultNIOService.class, org.scijava.main.DefaultMainService.class, org.scijava.menu.DefaultMenuService.class, org.scijava.module.DefaultModuleService.class, @@ -109,7 +111,12 @@ public void testFull() { org.scijava.prefs.DefaultPrefService.class, org.scijava.run.DefaultRunService.class, org.scijava.script.DefaultScriptHeaderService.class, + org.scijava.script.DefaultScriptService.class, + org.scijava.script.process.DefaultScriptProcessorService.class, + org.scijava.startup.DefaultStartupService.class, + org.scijava.task.DefaultTaskService.class, org.scijava.text.DefaultTextService.class, + org.scijava.text.io.DefaultTextIOService.class, org.scijava.thread.DefaultThreadService.class, org.scijava.tool.DefaultToolService.class, org.scijava.ui.DefaultUIService.class, @@ -144,6 +151,14 @@ public void testSciJavaServices() { } } + /** Tests that {@link ContextCreatedEvent} is published as expected. */ + @Test + public void testContextCreatedEvent() { + assertEquals(0, ServiceNoticingContextCreated.created); + final Context context = new Context(ServiceNoticingContextCreated.class); + assertEquals(1, ServiceNoticingContextCreated.created); + } + /** * Tests that dependent {@link Service}s are automatically created and * populated in downstream {@link Service} classes. @@ -436,6 +451,18 @@ private PluginIndex pluginIndex(final Class... plugins) { // -- Helper classes -- + /** A service that notices when {@link ContextCreatedEvent} is published. */ + public static class ServiceNoticingContextCreated extends AbstractService { + + public static int created = 0; + + @EventHandler + public void onEvent(final ContextCreatedEvent evt) { + created++; + } + + } + /** A service which requires a {@link BarService}. */ public static class FooService extends AbstractService { diff --git a/src/test/java/org/scijava/ContextDisposalTest.java b/src/test/java/org/scijava/ContextDisposalTest.java new file mode 100644 index 000000000..50f4a1369 --- /dev/null +++ b/src/test/java/org/scijava/ContextDisposalTest.java @@ -0,0 +1,51 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava; + +import org.junit.Test; + +/** + * Tests disposal of {@link Context}s. + * + * @author Curtis Rueden + */ +public class ContextDisposalTest { + + /** + * Tests that a {@link Context} can be disposed more than once without + * throwing an exception. + */ + @Test + public void testDoubleDisposal() { + final Context context = new Context(); + context.dispose(); + context.dispose(); + } +} diff --git a/src/test/java/org/scijava/ContextInjectionTest.java b/src/test/java/org/scijava/ContextInjectionTest.java index d7369f2dd..7393056fc 100644 --- a/src/test/java/org/scijava/ContextInjectionTest.java +++ b/src/test/java/org/scijava/ContextInjectionTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/SciJavaTest.java b/src/test/java/org/scijava/SciJavaTest.java new file mode 100644 index 000000000..2a34e6607 --- /dev/null +++ b/src/test/java/org/scijava/SciJavaTest.java @@ -0,0 +1,65 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; + +import org.junit.Test; +import org.scijava.plugin.PluginInfo; + +/** + * Tests {@link SciJava}. + * + * @author Curtis Rueden + */ +public class SciJavaTest { + + @Test + public void testInfo() { + final SciJava sj = new SciJava(); + + final PluginInfo infoFromObject = sj.getInfo(); + final PluginInfo infoFromServiceNoType = // + sj.plugin().getPlugin(SciJava.class); + final PluginInfo infoFromServiceWithType = // + sj.plugin().getPlugin(SciJava.class, Gateway.class); + assertSame(infoFromServiceNoType, infoFromObject); + assertSame(infoFromServiceWithType, infoFromObject); + + assertNotNull(infoFromObject); + assertSame(Gateway.class, infoFromObject.getPluginType()); + + assertEquals(Priority.NORMAL, sj.getPriority(), 0); + + sj.dispose(); + } +} diff --git a/src/test/java/org/scijava/annotations/AnnotatedA.java b/src/test/java/org/scijava/annotations/AnnotatedA.java index 92836407d..6bf9c3864 100644 --- a/src/test/java/org/scijava/annotations/AnnotatedA.java +++ b/src/test/java/org/scijava/annotations/AnnotatedA.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/annotations/AnnotatedB.java b/src/test/java/org/scijava/annotations/AnnotatedB.java index 7a7dae9d7..d723d0e76 100644 --- a/src/test/java/org/scijava/annotations/AnnotatedB.java +++ b/src/test/java/org/scijava/annotations/AnnotatedB.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/annotations/AnnotatedC.java b/src/test/java/org/scijava/annotations/AnnotatedC.java index fe4b360a8..a5e4977b4 100644 --- a/src/test/java/org/scijava/annotations/AnnotatedC.java +++ b/src/test/java/org/scijava/annotations/AnnotatedC.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/annotations/AnnotatedD.java b/src/test/java/org/scijava/annotations/AnnotatedD.java index d63e3cae7..d1a42b98e 100644 --- a/src/test/java/org/scijava/annotations/AnnotatedD.java +++ b/src/test/java/org/scijava/annotations/AnnotatedD.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/annotations/AnnotatedInnerClass.java b/src/test/java/org/scijava/annotations/AnnotatedInnerClass.java index 0387fdfe8..9f2304ff5 100644 --- a/src/test/java/org/scijava/annotations/AnnotatedInnerClass.java +++ b/src/test/java/org/scijava/annotations/AnnotatedInnerClass.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/annotations/Complex.java b/src/test/java/org/scijava/annotations/Complex.java index d9b960dd3..554afabee 100644 --- a/src/test/java/org/scijava/annotations/Complex.java +++ b/src/test/java/org/scijava/annotations/Complex.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/annotations/DirectoryIndexerTest.java b/src/test/java/org/scijava/annotations/DirectoryIndexerTest.java index a9c84b302..0046a6c7a 100644 --- a/src/test/java/org/scijava/annotations/DirectoryIndexerTest.java +++ b/src/test/java/org/scijava/annotations/DirectoryIndexerTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/annotations/EclipseHelperTest.java b/src/test/java/org/scijava/annotations/EclipseHelperTest.java index c56a4f1ad..9acf9e1d9 100644 --- a/src/test/java/org/scijava/annotations/EclipseHelperTest.java +++ b/src/test/java/org/scijava/annotations/EclipseHelperTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/annotations/Fruit.java b/src/test/java/org/scijava/annotations/Fruit.java index 9abcc4fb3..59fd00b47 100644 --- a/src/test/java/org/scijava/annotations/Fruit.java +++ b/src/test/java/org/scijava/annotations/Fruit.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/annotations/LegacyTest.java b/src/test/java/org/scijava/annotations/LegacyTest.java index 8ee5cc012..4734a4763 100644 --- a/src/test/java/org/scijava/annotations/LegacyTest.java +++ b/src/test/java/org/scijava/annotations/LegacyTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/annotations/Simple.java b/src/test/java/org/scijava/annotations/Simple.java index aadb9ab4d..87a63ddf7 100644 --- a/src/test/java/org/scijava/annotations/Simple.java +++ b/src/test/java/org/scijava/annotations/Simple.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/app/StatusServiceTest.java b/src/test/java/org/scijava/app/StatusServiceTest.java index d3fe9e66e..dbca89909 100644 --- a/src/test/java/org/scijava/app/StatusServiceTest.java +++ b/src/test/java/org/scijava/app/StatusServiceTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/command/CommandArrayConverterTest.java b/src/test/java/org/scijava/command/CommandArrayConverterTest.java new file mode 100644 index 000000000..7a9915a47 --- /dev/null +++ b/src/test/java/org/scijava/command/CommandArrayConverterTest.java @@ -0,0 +1,184 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.scijava.command; + +import org.junit.Test; +import org.scijava.Context; +import org.scijava.ItemIO; +import org.scijava.Priority; +import org.scijava.convert.AbstractConverter; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; + +import java.util.concurrent.ExecutionException; + +import static org.junit.Assert.*; + +public class CommandArrayConverterTest { + + @Test + public void testArrayCommandRaw() throws InterruptedException, + ExecutionException + { + final Context context = new Context(CommandService.class); + final CommandService commandService = context.service(CommandService.class); + + UserClass[] userObjects = new UserClass[2]; + userObjects[0] = new UserClass("User Object 0", new Object()); + userObjects[1] = new UserClass("User Object 1", new Object()); + + final CommandModule module = // + commandService.run(CommandRawArrayInput.class, true, "userObjects", userObjects ).get(); // + assertEquals("User Object 0;User Object 1;", module.getOutput("result")); + } + + @Test + public void testArrayConvertFromStringCommandRaw() throws InterruptedException, + ExecutionException + { + final Context context = new Context(CommandService.class); + final CommandService commandService = context.service(CommandService.class); + + final CommandModule module = // + commandService.run(CommandRawArrayInput.class, true, "userObjects", "User Object 0,User Object 1" ).get(); // + + assertEquals("User Object 0;User Object 1;", module.getOutput("result")); + } + + @Test + public void testArrayCommandWildcardGenerics() throws InterruptedException, + ExecutionException + { + final Context context = new Context(CommandService.class); + final CommandService commandService = context.service(CommandService.class); + + UserClass[] userObjects = new UserClass[2]; + userObjects[0] = new UserClass("User Object 0", new Object()); + userObjects[1] = new UserClass("User Object 1", new Object()); + + final CommandModule module = // + commandService.run(CommandGenericsWildcardArrayInput.class, true, "userObjects", userObjects ).get(); // + assertEquals("User Object 0;User Object 1;", module.getOutput("result")); + } + + @Test + public void testArrayConvertFromStringCommandWildcardGenerics() throws InterruptedException, + ExecutionException + { + final Context context = new Context(CommandService.class); + final CommandService commandService = context.service(CommandService.class); + + final CommandModule module = // + commandService.run(CommandGenericsWildcardArrayInput.class, true, "userObjects", "User Object 0,User Object 1" ).get(); // + + assertEquals("User Object 0;User Object 1;", module.getOutput("result")); + } + + /** A command which uses a UserClass Raw Array parameter. */ + @Plugin(type = Command.class) + public static class CommandRawArrayInput implements Command { + + @Parameter + private UserClass[] userObjects; + + @Parameter(type = ItemIO.OUTPUT) + private String result = "default"; + + @Override + public void run() { + final StringBuilder sb = new StringBuilder(); + for (UserClass obj : userObjects) { + sb.append(obj.toString()+";"); + } + result = sb.toString(); + } + } + + /** A command which uses a UserClass Array with generics wildcard */ + @Plugin(type = Command.class) + public static class CommandGenericsWildcardArrayInput implements Command { + + @Parameter + private UserClass[] userObjects; + + @Parameter(type = ItemIO.OUTPUT) + private String result = "default"; + + @Override + public void run() { + final StringBuilder sb = new StringBuilder(); + for (UserClass obj : userObjects) { + sb.append(obj.toString()+";"); + } + result = sb.toString(); + } + } + + @Plugin(type = org.scijava.convert.Converter.class, priority = Priority.LOW) + public static class StringToUserClassConverterNoGenerics extends AbstractConverter { + + @Override + public T convert(Object src, Class dest) { + String str = (String) src; + String[] names = str.split(","); + UserClass[] userObjects = new UserClass[names.length]; + for (int index = 0; index < names.length ; index++) { + userObjects[index] = new UserClass(names[index], new Object()); + } + return (T) userObjects; + } + + @Override + public Class getOutputType() { + return UserClass[].class; + } + + @Override + public Class getInputType() { + return String.class; + } + } + + /** + * Simple class to test input arrays in command + */ + public static class UserClass { + + String name; + + public UserClass(String objectName, T generic_object) { + name = objectName; + } + + public String toString() { + return name; + } + } + +} diff --git a/src/test/java/org/scijava/command/CommandInfoTest.java b/src/test/java/org/scijava/command/CommandInfoTest.java index 35579a804..0811f0f44 100644 --- a/src/test/java/org/scijava/command/CommandInfoTest.java +++ b/src/test/java/org/scijava/command/CommandInfoTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -41,10 +39,12 @@ import java.util.Arrays; import java.util.Iterator; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.scijava.Context; import org.scijava.command.CommandInfoTest.CommandWithEnumParam.Choice; +import org.scijava.log.LogService; import org.scijava.module.ModuleItem; import org.scijava.plugin.Parameter; @@ -55,14 +55,20 @@ */ public class CommandInfoTest { + private Context ctx; private CommandService commandService; @Before public void setUp() { - final Context ctx = new Context(CommandService.class); + ctx = new Context(CommandService.class); commandService = ctx.getService(CommandService.class); } + @After + public void tearDown() { + ctx.dispose(); + } + @Test public void testEnumParam() { final CommandInfo info = commandService.getCommand( @@ -90,6 +96,12 @@ public void testEnumParam() { choice.getChoices()); } + @Test + public void testDuplicateServiceParameters() { + CommandInfo commandInfo = new CommandInfo(ExtendedServiceCommand.class); + assertTrue(commandInfo.isValid()); + } + // -- Helper classes -- /** A command with an enum parameter. */ @@ -114,4 +126,26 @@ public void run() { // NB: No implementation needed. } } + + private static class ServiceCommand implements Command { + + @Parameter + private LogService logService; + + @Override + public void run() { + // do nothing + } + } + + private static class ExtendedServiceCommand extends ServiceCommand { + + @Parameter + private LogService logService; + + @Override + public void run() { + // do nothing + } + } } diff --git a/src/test/java/org/scijava/command/CommandModuleTest.java b/src/test/java/org/scijava/command/CommandModuleTest.java index 76ee6bba7..50080055d 100644 --- a/src/test/java/org/scijava/command/CommandModuleTest.java +++ b/src/test/java/org/scijava/command/CommandModuleTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -249,8 +247,7 @@ public void run() { * not as early as {@link Service} and {@link Context} parameters get * populated. */ - @Plugin(type = PreprocessorPlugin.class, - priority = Priority.VERY_HIGH_PRIORITY) + @Plugin(type = PreprocessorPlugin.class, priority = Priority.VERY_HIGH) public static class StuffPreprocessor extends AbstractPreprocessorPlugin { @Override diff --git a/src/test/java/org/scijava/command/CommandServiceTest.java b/src/test/java/org/scijava/command/CommandServiceTest.java index 7b06c0451..9465b37ff 100644 --- a/src/test/java/org/scijava/command/CommandServiceTest.java +++ b/src/test/java/org/scijava/command/CommandServiceTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/command/InputsTest.java b/src/test/java/org/scijava/command/InputsTest.java new file mode 100644 index 000000000..9ca5e46b2 --- /dev/null +++ b/src/test/java/org/scijava/command/InputsTest.java @@ -0,0 +1,179 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.command; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.scijava.Context; +import org.scijava.InstantiableException; +import org.scijava.module.Module; +import org.scijava.module.ModuleItem; +import org.scijava.module.MutableModuleItem; +import org.scijava.module.process.AbstractPreprocessorPlugin; +import org.scijava.module.process.PreprocessorPlugin; +import org.scijava.plugin.PluginInfo; +import org.scijava.plugin.PluginService; +import org.scijava.widget.InputHarvester; +import org.scijava.widget.NumberWidget; + +/** + * Tests {@link Inputs}. + * + * @author Curtis Rueden + * @author Deborah Schmidt + */ +public class InputsTest { + + private Context context; + + @Before + public void setUp() { + context = new Context(); + context.service(PluginService.class); + } + + @After + public void tearDown() { + context.dispose(); + } + + /** Tests single input, no configuration. */ + @Test + public void testSingleInput() { + setExpected(new HashMap() {{ + put("sigma", 3.9f); + }}); + Inputs inputs = new Inputs(context); + inputs.getInfo().setName("testSingleInput");//TEMP + addTempInput(inputs, "sigma", Float.class); + float sigma = (Float) inputs.harvest().get("sigma"); + assertEquals(3.9f, sigma, 0); + } + + /** Tests two inputs, no configuration. */ + @Test + public void testTwoInputs() { + setExpected(new HashMap() {{ + put("name", "Chuckles"); + put("age", 37); + }}); + Inputs inputs = new Inputs(context); + inputs.getInfo().setName("testTwoInputs");//TEMP + addTempInput(inputs, "name", String.class); + addTempInput(inputs, "age", Integer.class); + Map values = inputs.harvest(); + String name = (String) values.get("name"); + int age = (Integer) values.get("age"); + assertEquals("Chuckles", name); + assertEquals(37, age); + } + + /** Tests inputs with configuration. */ + @Test + public void testWithConfiguration() { + setExpected(new HashMap() {{ + put("word", "brown"); + put("opacity", 0.8); + }}); + Inputs inputs = new Inputs(context); + inputs.getInfo().setName("testWithConfiguration");//TEMP + MutableModuleItem wordInput = addTempInput(inputs, "word", + String.class); + wordInput.setLabel("Favorite word"); + wordInput.setChoices(Arrays.asList("quick", "brown", "fox")); + wordInput.setDefaultValue("fox"); + MutableModuleItem opacityInput = addTempInput(inputs, "opacity", + Double.class); + opacityInput.setMinimumValue(0.0); + opacityInput.setMaximumValue(1.0); + opacityInput.setDefaultValue(0.5); + opacityInput.setWidgetStyle(NumberWidget.SCROLL_BAR_STYLE); + inputs.harvest(); + String word = wordInput.getValue(inputs); + double opacity = opacityInput.getValue(inputs); + assertEquals("brown", word); + assertEquals(0.8, opacity, 0); + } + + public void setExpected(final Map expected) { + final PluginInfo info = + new PluginInfo(MockInputHarvester.class, + PreprocessorPlugin.class) + { + @Override + public PreprocessorPlugin createInstance() throws InstantiableException { + final PreprocessorPlugin pp = super.createInstance(); + ((MockInputHarvester) pp).setExpected(expected); + return pp; + } + }; + info.setPriority(InputHarvester.PRIORITY); + context.service(PluginService.class).addPlugin(info); + } + + /** + * Add a non-persisted input to ensure we are testing with the mock input + * harvester. + */ + private static MutableModuleItem addTempInput(Inputs inputs, + String inputName, Class inputType) + { + MutableModuleItem input = inputs.addInput(inputName, inputType); + input.setPersisted(false); + return input; + } + + public static class MockInputHarvester extends AbstractPreprocessorPlugin { + private Map expected; + public void setExpected(final Map expected) { + this.expected = expected; + } + + @Override + public void process(final Module module) { + for (final ModuleItem input : module.getInfo().inputs()) { + if (module.isInputResolved(input.getName())) continue; + final String name = input.getName(); + if (!expected.containsKey(name)) { + throw new AssertionError("No value for input: " + input.getName()); + } + final Object value = expected.get(name); + module.setInput(name, value); + } + } + } +} diff --git a/src/test/java/org/scijava/command/InvalidCommandTest.java b/src/test/java/org/scijava/command/InvalidCommandTest.java index 94e25e694..84a60851d 100644 --- a/src/test/java/org/scijava/command/InvalidCommandTest.java +++ b/src/test/java/org/scijava/command/InvalidCommandTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/command/run/CommandCodeRunnerTest.java b/src/test/java/org/scijava/command/run/CommandCodeRunnerTest.java index c2284b95f..d45954aa7 100644 --- a/src/test/java/org/scijava/command/run/CommandCodeRunnerTest.java +++ b/src/test/java/org/scijava/command/run/CommandCodeRunnerTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/console/ConsoleServiceTest.java b/src/test/java/org/scijava/console/ConsoleServiceTest.java index 84d552f6a..f7e36062a 100644 --- a/src/test/java/org/scijava/console/ConsoleServiceTest.java +++ b/src/test/java/org/scijava/console/ConsoleServiceTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -85,9 +83,12 @@ public void testProcessArgs() { */ @Test public void testInfiniteLoopAvoidance() { - assertFalse(consoleService.getInstance(BrokenArgument.class).argsHandled); + final BrokenArgument broken = // + consoleService.getInstance(BrokenArgument.class); + assertNotNull(broken); + assertFalse(broken.argsHandled); consoleService.processArgs("--broken"); - assertTrue(consoleService.getInstance(BrokenArgument.class).argsHandled); + assertTrue(broken.argsHandled); } /** @@ -219,7 +220,7 @@ private void assertOutputEvent(final Source source, final String output, // -- Helper classes -- - @Plugin(type = ConsoleArgument.class, priority = Priority.HIGH_PRIORITY) + @Plugin(type = ConsoleArgument.class, priority = Priority.HIGH) public static class FooArgument extends AbstractConsoleArgument { public FooArgument() { diff --git a/src/test/java/org/scijava/console/SystemPropertyArgumentTest.java b/src/test/java/org/scijava/console/SystemPropertyArgumentTest.java index 22ad053e1..63aae7e48 100644 --- a/src/test/java/org/scijava/console/SystemPropertyArgumentTest.java +++ b/src/test/java/org/scijava/console/SystemPropertyArgumentTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/AbstractNumberConverterTests.java b/src/test/java/org/scijava/convert/AbstractNumberConverterTests.java index 3c15f173e..d6427e3fc 100644 --- a/src/test/java/org/scijava/convert/AbstractNumberConverterTests.java +++ b/src/test/java/org/scijava/convert/AbstractNumberConverterTests.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -37,7 +35,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.scijava.util.ConversionUtils; +import org.scijava.util.Types; /** * Tests converter plugins that convert from primitive numeric types to other @@ -79,8 +77,7 @@ public void testWrapper() { public void testPrimitive() { final Number src = getSrc(); final Number expect = getExpectedValue(); - assertEquals(expect, converter.convert(src, ConversionUtils - .getPrimitiveType(destType))); + assertEquals(expect, converter.convert(src, Types.unbox(destType))); } /** diff --git a/src/test/java/org/scijava/convert/ArrayToStringConverterTest.java b/src/test/java/org/scijava/convert/ArrayToStringConverterTest.java new file mode 100644 index 000000000..7393d0261 --- /dev/null +++ b/src/test/java/org/scijava/convert/ArrayToStringConverterTest.java @@ -0,0 +1,177 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.convert; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.scijava.Context; +import org.scijava.parse.ParseService; + +/** + * Tests {@link ArrayToStringConverter}. + * + * @author Gabriel Selzer + */ +public class ArrayToStringConverterTest { + + private final ArrayToStringConverter converter = new ArrayToStringConverter(); + private Context context; + + @Before + public void setUp() { + context = new Context(ConvertService.class, ParseService.class); + context.inject(converter); + } + + @After + public void tearDown() { + context.dispose(); + } + + /** + * Tests the ability of {@link StringToArrayConverter} in converting to arrays + * of various types + */ + @Test + public void testArrayConversion() { + // Component types for array conversions + List arrays = Arrays.asList( // + new byte[] { 1, 2, 3 }, // + new Byte[] { 1, 2, 3 }, // + new short[] { 1, 2, 3 }, // + new Short[] { 1, 2, 3 }, // + new int[] { 1, 2, 3 }, // + new Integer[] { 1, 2, 3 }, // + new long[] { 1, 2, 3 }, // + new Long[] { 1L, 2L, 3L }, // + new float[] { 1, 2, 3 }, // + new Float[] { 1F, 2F, 3F }, // + new double[] { 1, 2, 3 }, // + new Double[] { 1., 2., 3. } // + ); + // String expectation + String sInt = "{1, 2, 3}"; + String sFloat = "{1.0, 2.0, 3.0}"; + for (Object array : arrays) { + // Ensure our Converter can do the conversion + assertTrue(converter.canConvert(array, String.class)); + // Do the conversion + String converted = converter.convert(array, String.class); + // Ensure correctness + assertTrue(converted.equals(sInt) || converted.equals(sFloat)); + } + } + + /** + * Tests the ability of {@link ArrayToStringConverter} in converting + * 2-dimensional arrays + */ + @Test + public void test2DArrayConversion() { + byte[][] arr = new byte[][] { new byte[] { 0, 1 }, new byte[] { 2, 3 } }; + assertTrue(converter.canConvert(arr, String.class)); + String actual = converter.convert(arr, String.class); + String expected = "{{0, 1}, {2, 3}}"; + assertEquals(expected, actual); + } + + /** + * Tests the ability of {@link ArrayToStringConverter} in converting + * 3-dimensional arrays + */ + @Test + public void test3DArrayConversion() { + byte[][][] arr = new byte[2][2][2]; + for (int i = 0; i < 2; i++) + for (int j = 0; j < 2; j++) + for (int k = 0; k < 2; k++) + arr[i][j][k] = (byte) (i + j + k); + + assertTrue(converter.canConvert(arr, String.class)); + String actual = converter.convert(arr, String.class); + String expected = "{{{0, 1}, {1, 2}}, {{1, 2}, {2, 3}}}"; + assertEquals(expected, actual); + } + + /** + * Tests the ability of {@link ArrayToStringConverter} in converting empty + * arrays + */ + @Test + public void testEmptyArrayConversion() { + byte[] arr = new byte[0]; + assertTrue(converter.canConvert(arr, String.class)); + String actual = converter.convert(arr, String.class); + String expected = "{}"; + assertEquals(expected, actual); + } + + /** + * Tests the ability of {@link ArrayToStringConverter} and + * {@link StringToArrayConverter} to perform a cyclic conversion. + */ + @Test + public void testCyclicConversion() { + byte[] expected = new byte[] {1, 2, 3}; + // Do the first conversion + ArrayToStringConverter c1 = new ArrayToStringConverter(); + context.inject(c1); + String converted = c1.convert(expected, String.class); + // Convert back + StringToArrayConverter c2 = new StringToArrayConverter(); + context.inject(c2); + byte[] actual = c2.convert(converted, byte[].class); + assertArrayEquals(expected, actual); + } + + @Test + public void testNullConversion() { + // Do the first conversion + ArrayToStringConverter c1 = new ArrayToStringConverter(); + context.inject(c1); + String converted = c1.convert(new String[] {null}, String.class); + // Try to convert back + StringToArrayConverter c2 = new StringToArrayConverter(); + context.inject(c2); + String[] actual = c2.convert(converted, String[].class); + assertNotNull(actual); + assertEquals(1, actual.length); + assertEquals("null", actual[0]); + } +} diff --git a/src/test/java/org/scijava/convert/BigIntegerToBigDecimalConverterTest.java b/src/test/java/org/scijava/convert/BigIntegerToBigDecimalConverterTest.java index fdac8b40f..9b166ee23 100644 --- a/src/test/java/org/scijava/convert/BigIntegerToBigDecimalConverterTest.java +++ b/src/test/java/org/scijava/convert/BigIntegerToBigDecimalConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/ByteToBigDecimalConverterTest.java b/src/test/java/org/scijava/convert/ByteToBigDecimalConverterTest.java index 9e96ff0b0..50e44776d 100644 --- a/src/test/java/org/scijava/convert/ByteToBigDecimalConverterTest.java +++ b/src/test/java/org/scijava/convert/ByteToBigDecimalConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/ByteToBigIntegerConverterTest.java b/src/test/java/org/scijava/convert/ByteToBigIntegerConverterTest.java index ca9cf8e5a..11ac2238b 100644 --- a/src/test/java/org/scijava/convert/ByteToBigIntegerConverterTest.java +++ b/src/test/java/org/scijava/convert/ByteToBigIntegerConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/ByteToDoubleConverterTest.java b/src/test/java/org/scijava/convert/ByteToDoubleConverterTest.java index 05f3ab842..f871cfeff 100644 --- a/src/test/java/org/scijava/convert/ByteToDoubleConverterTest.java +++ b/src/test/java/org/scijava/convert/ByteToDoubleConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/ByteToFloatConverterTest.java b/src/test/java/org/scijava/convert/ByteToFloatConverterTest.java index 2cafe9404..ff08de5b4 100644 --- a/src/test/java/org/scijava/convert/ByteToFloatConverterTest.java +++ b/src/test/java/org/scijava/convert/ByteToFloatConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/ByteToIntegerConverterTest.java b/src/test/java/org/scijava/convert/ByteToIntegerConverterTest.java index 562a282e9..c19a70311 100644 --- a/src/test/java/org/scijava/convert/ByteToIntegerConverterTest.java +++ b/src/test/java/org/scijava/convert/ByteToIntegerConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/ByteToLongConverterTest.java b/src/test/java/org/scijava/convert/ByteToLongConverterTest.java index 4cd8dc2ae..58a68e7bc 100644 --- a/src/test/java/org/scijava/convert/ByteToLongConverterTest.java +++ b/src/test/java/org/scijava/convert/ByteToLongConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/ByteToShortConverterTest.java b/src/test/java/org/scijava/convert/ByteToShortConverterTest.java index e1c58e705..598e9857f 100644 --- a/src/test/java/org/scijava/convert/ByteToShortConverterTest.java +++ b/src/test/java/org/scijava/convert/ByteToShortConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/ConvertServiceTest.java b/src/test/java/org/scijava/convert/ConvertServiceTest.java index 8985e0d58..ddb50acab 100644 --- a/src/test/java/org/scijava/convert/ConvertServiceTest.java +++ b/src/test/java/org/scijava/convert/ConvertServiceTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -37,10 +35,11 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import java.lang.reflect.Type; +import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -53,8 +52,17 @@ import org.junit.Test; import org.scijava.Context; import org.scijava.Priority; +import org.scijava.convert.ArrayConverters.ByteArrayWrapper; +import org.scijava.convert.ArrayConverters.DoubleArrayUnwrapper; +import org.scijava.convert.ArrayConverters.FloatArrayWrapper; +import org.scijava.convert.ArrayConverters.LongArrayWrapper; +import org.scijava.convert.ArrayConverters.ShortArrayUnwrapper; +import org.scijava.convert.NumberConverters.ByteToLongConverter; +import org.scijava.convert.NumberConverters.DoubleToBigDecimalConverter; +import org.scijava.convert.NumberConverters.ShortToFloatConverter; import org.scijava.plugin.Plugin; import org.scijava.util.BoolArray; +import org.scijava.util.ByteArray; import org.scijava.util.CharArray; import org.scijava.util.ClassUtils; import org.scijava.util.DoubleArray; @@ -63,6 +71,7 @@ import org.scijava.util.LongArray; import org.scijava.util.PrimitiveArray; import org.scijava.util.ShortArray; +import org.scijava.util.Types; /** * Tests {@link ConvertService}. @@ -76,6 +85,7 @@ public class ConvertServiceTest { @Before public void setUp() { + @SuppressWarnings("resource") final Context context = new Context(ConvertService.class); convertService = context.getService(ConvertService.class); } @@ -134,7 +144,7 @@ public void testPrimitives() { */ @Test public void testArrays() { - // Test that each primitive [] is compatible in either direciton with its + // Test that each primitive [] is compatible in either direction with its // paired PrimitiveArray testIntechangeable(int[].class, IntArray.class); testIntechangeable(long[].class, LongArray.class); @@ -144,8 +154,14 @@ public void testArrays() { testIntechangeable(char[].class, CharArray.class); testIntechangeable(boolean[].class, BoolArray.class); - // Test that primitive [] can not be convertied to mismatched PrimitiveArray - assertFalse(convertService.supports(int[].class, LongArray.class)); + // Test that primitive [] can be cross-converted to mismatched PrimitiveArray + assertTrue(convertService.supports(int[].class, LongArray.class)); + final LongArray crossConverted = // + convertService.convert(new int[] {2, 3, 5}, LongArray.class); + assertEquals(3, crossConverted.size()); + assertEquals(2L, (long) crossConverted.get(0)); + assertEquals(3L, (long) crossConverted.get(1)); + assertEquals(5L, (long) crossConverted.get(2)); // Test that lists can be converted to any primitive [] final List list = new ArrayList<>(); @@ -209,8 +225,10 @@ public void testCanConvert() { assertTrue(convertService.supports(HashSet.class, ArrayList.class)); assertTrue(convertService.supports(long.class, Date.class)); + // check conversion to collection type + assertTrue(convertService.supports(Collection.class, List.class)); + // check lack of conversion of various types w/o appropriate constructor - assertFalse(convertService.supports(Collection.class, List.class)); assertFalse(convertService.supports(int.class, Date.class)); } @@ -324,19 +342,11 @@ public void testConvertSubclass() { assertEquals("Bar", objectToHisList.get(1)); // ArrayList subclass to ArrayList subclass - // This surprisingly works due to type erasure... dangerous stuff. final NumberList hisToNumberList = convertService.convert(hisList, NumberList.class); assertEquals(2, hisToNumberList.size()); - assertEquals("Foo", hisToNumberList.get(0)); - assertEquals("Bar", hisToNumberList.get(1)); - try { - final Number n0 = hisToNumberList.get(0); - fail("expected ClassCastException but got: " + n0); - } - catch (final ClassCastException exc) { - // NB: Exception expected. - } + assertNull(hisToNumberList.get(0)); + assertNull(hisToNumberList.get(1)); } /** @@ -387,11 +397,15 @@ class Struct { assertEquals(123456789012.0, struct.myDoubles.get(0), 0.0); assertEquals(987654321098.0, struct.myDoubles.get(1), 0.0); - // Conversion to a list of strings (with no generic parameter) fails. + // Conversion to a list of strings (with no generic parameter) succeeds. setFieldValue(struct, "myStrings", longArray); - assertNull(struct.myStrings); + assertNotNull(struct.myStrings); + System.out.println(struct.myStrings); + assertEquals(2, struct.myStrings.size()); + assertEquals("123456789012", struct.myStrings.get(0)); + assertEquals("987654321098", struct.myStrings.get(1)); } /** @@ -446,16 +460,18 @@ class Struct { /** * Tests setting an incompatible element value for a primitive array. */ - @Test(expected = IllegalArgumentException.class) + @Test public void testBadPrimitiveArray() { class Struct { - @SuppressWarnings("unused") private int[] intArray; } final Struct struct = new Struct(); setFieldValue(struct, "intArray", "not an int array"); + assertNotNull(struct.intArray); + assertEquals(1, struct.intArray.length); + assertSame(0, struct.intArray[0]); } /** @@ -463,23 +479,39 @@ class Struct { * and a collection. */ @Test - public void testBadObjectElements() { + public void testIncompatibleCollections() { class Struct { private Double[] doubleArray; - private List stringList; - @SuppressWarnings("unused") - private Set nestedArray; + private List numberList; + private Set setOfIntegerArrays; } final Struct struct = new Struct(); - // Test abnormal behavior for an object array - setFieldValue(struct, "doubleArray", "not a double array"); - assertEquals(null, struct.doubleArray[0]); + // NB: DefaultConverter converts non-collection/array objects to + // collection/array objects, even if some or all of the constituent elements + // cannot be converted to the array/collection component/element type. - // Test abnormal behavior for a list - setFieldValue(struct, "nestedArray", "definitely not a set of char arrays"); - assertNull(struct.stringList); + // Test object to incompatible array type + setFieldValue(struct, "doubleArray", "not a double array"); + assertNotNull(struct.doubleArray); + assertEquals(1, struct.doubleArray.length); + assertNull(struct.doubleArray[0]); + + // Test object to incompatible List type + setFieldValue(struct, "numberList", "not actually a list of numbers"); + List expectedList = Arrays.asList((Number) null); + assertEquals(expectedList, struct.numberList); + + // Test object to incompatible Set type + setFieldValue(struct, "setOfIntegerArrays", // + "definitely not a set of Integer[]"); + assertNotNull(struct.setOfIntegerArrays); + assertEquals(1, struct.setOfIntegerArrays.size()); + Integer[] singleton = struct.setOfIntegerArrays.iterator().next(); + assertNotNull(singleton); + assertEquals(1, singleton.length); + assertNull(singleton[0]); } /** @@ -564,6 +596,126 @@ public void testGetCompatibleInputs() { assertEquals(StringHisListConverter.S4, compatibleInputs.get(3)); } + /** + * Tests that the {@link NullConverter} is chosen for null src and/or dest. + */ + @Test + public void testNullConverterMatching() { + final Converter c = convertService.getHandler(new ConversionRequest( + null, List.class)); + assertEquals(NullConverter.class, c.getClass()); + + final Converter cc = convertService.getHandler(new ConversionRequest( + new Object(), (Class) null)); + assertEquals(NullConverter.class, cc.getClass()); + + final Converter ccc = convertService.getHandler(new ConversionRequest( + null, (Class) null)); + assertEquals(NullConverter.class, ccc.getClass()); + } + + /** + * Tests the the appropriate wrapping ArrayConverter is chosen for converting + * primitive arrays to scijava wrappers. + */ + @Test + public void testArrayConverterWrappingMatching() { + final byte[] b = new byte[] { -128, 0, 127 }; + final long[] l = new long[] { 13, 17, -103209, 0, 6 }; + final float[] f = new float[] { 12.125f, -0.0625f, 2.5f }; + + final Converter c = convertService.getHandler(b, ByteArray.class); + assertEquals(ByteArrayWrapper.class, c.getClass()); + + final Converter cc = convertService.getHandler(l, LongArray.class); + assertEquals(LongArrayWrapper.class, cc.getClass()); + + final Converter ccc = convertService.getHandler(f, FloatArray.class); + assertEquals(FloatArrayWrapper.class, ccc.getClass()); + } + + /** + * Tests that the appropriate unwrapping ArrayConverter is chosen for + * converting scijava arrays to primitive arrays. + */ + @Test + public void testArrayConverterUnwrappingMatching() { + final ShortArray s = new ShortArray(); + final DoubleArray d = new DoubleArray(); + + final Converter c = convertService.getHandler(s, short[].class); + assertEquals(ShortArrayUnwrapper.class, c.getClass()); + + final Converter cc = convertService.getHandler(d, double[].class); + assertEquals(DoubleArrayUnwrapper.class, cc.getClass()); + } + + /** + * Tests that the {@link CastingConverter} is called when casting is possible. + */ + @Test + public void testCastingConverterMatching() { + final ArrayList al = new ArrayList<>(); + + final Converter c = convertService.getHandler(al, Collection.class); + assertEquals(CastingConverter.class, c.getClass()); + } + + /** + * Tests the that the appropriate {@link NumberToNumberConverter} is chosen. + */ + @Test + public void testNumberConverterMatching() { + final double d = -24312926.0625; + final byte b = 64; + final short s = 32625; + + // Number converters only handle widening conversions + final Converter c = convertService.getHandler(d, BigDecimal.class); + assertEquals(DoubleToBigDecimalConverter.class, c.getClass()); + + final Converter cc = convertService.getHandler(b, long.class); + assertEquals(ByteToLongConverter.class, cc.getClass()); + + final Converter ccc = convertService.getHandler(s, float.class); + assertEquals(ShortToFloatConverter.class, ccc.getClass()); + } + + /** + * Tests that the {@link DefaultConverter} is chosen when no other suitable + * converter is available. + */ + @Test + public void testDefaultConverterMatching() { + final float f = 13624292.25f; + final List l = new ArrayList<>(); + + // Narrowing number conversion + final Converter c = convertService.getHandler(f, byte.class); + assertEquals(DefaultConverter.class, c.getClass()); + + // List to Array + final Converter cc = convertService.getHandler(l, String[].class); + assertEquals(DefaultConverter.class, cc.getClass()); + + // Object to String + final Converter os = convertService.getHandler(new Object(), + String.class); + assertEquals(DefaultConverter.class, os.getClass()); + + // String to Character + final Converter ss = convertService.getHandler("hello", char.class); + assertEquals(DefaultConverter.class, ss.getClass()); + + // String to Enum + final Converter se = convertService.getHandler("bye", Words.class); + assertEquals(DefaultConverter.class, se.getClass()); + + // Source which can be wrapped as destination + final Converter w = convertService.getHandler(10122017l, Date.class); + assertEquals(DefaultConverter.class, w.getClass()); + } + // -- Helper Methods -- /** @@ -573,7 +725,7 @@ public void testGetCompatibleInputs() { private void setFieldValue(final Object o, final String fieldName, final Object value) { - ClassUtils.setValue(ClassUtils.getField(o.getClass(), fieldName), o, value); + ClassUtils.setValue(Types.field(o.getClass(), fieldName), o, value); } /** @@ -592,11 +744,8 @@ private List getValueList(final T... values) { * Helper class for testing conversion of one {@link ArrayList} subclass to * another. */ - public static class HisList extends ArrayList { - public HisList() { - super(); - } - public HisList(final Collection c) { + public static class HerList extends ArrayList { + public HerList(final Collection c) { super(c); } } @@ -605,8 +754,11 @@ public HisList(final Collection c) { * Helper class for testing conversion of one {@link ArrayList} subclass to * another. */ - public static class HerList extends ArrayList { - public HerList(final Collection c) { + public static class HisList extends ArrayList { + public HisList() { + super(); + } + public HisList(final Collection c) { super(c); } } @@ -702,7 +854,7 @@ public static enum Words { * input candidates for converting to {@link HisList}s. The actual conversion * methods are not implemented and are unnecessary. */ - @Plugin(type = Converter.class, priority = Priority.LAST_PRIORITY) + @Plugin(type = Converter.class, priority = Priority.LAST) public static class StringHisListConverter extends AbstractConverter { @@ -750,7 +902,7 @@ public T convert(Object src, Class dest) { // -- Helper methods -- /** - * Verify bi-direciotnal conversion is supported between the two classes + * Verify bi-directional conversion is supported between the two classes */ private void testIntechangeable(final Class c1, final Class c2) { assertTrue(convertService.supports(c1, c2)); diff --git a/src/test/java/org/scijava/convert/ConverterTest.java b/src/test/java/org/scijava/convert/ConverterTest.java index 0d77baab4..3b29ac76f 100644 --- a/src/test/java/org/scijava/convert/ConverterTest.java +++ b/src/test/java/org/scijava/convert/ConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -41,8 +39,7 @@ import java.util.Collection; import org.junit.Test; -import org.scijava.util.ClassUtils; -import org.scijava.util.GenericUtils; +import org.scijava.util.Types; /** * Tests individual {@link Converter}s. @@ -60,7 +57,6 @@ public class ConverterTest { /** * Test case for the {@link NullConverter} */ - @SuppressWarnings("deprecation") @Test public void testNullConverter() { final NullConverter nc = new NullConverter(); @@ -93,11 +89,11 @@ public void testCanConvert() { @Test public void testCanConvertToGenericCollection() { - final DefaultConverter dc = new DefaultConverter(); + final CastingConverter cc = new CastingConverter(); - final Field destField = ClassUtils.getField(getClass(), "collection"); - final Type destType = GenericUtils.getFieldType(destField, getClass()); - assertTrue(dc.canConvert(ArrayList.class, destType)); + final Field destField = Types.field(getClass(), "collection"); + final Type destType = Types.fieldType(destField, getClass()); + assertTrue(cc.canConvert(ArrayList.class, destType)); } private static class NumberConverter extends AbstractConverter { diff --git a/src/test/java/org/scijava/convert/DefaultConverterTest.java b/src/test/java/org/scijava/convert/DefaultConverterTest.java new file mode 100644 index 000000000..e591ae82b --- /dev/null +++ b/src/test/java/org/scijava/convert/DefaultConverterTest.java @@ -0,0 +1,279 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.scijava.convert; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.junit.Before; +import org.junit.Test; + +/** + * Tests {@link DefaultConverter}. + * + * @author Curtis Rueden + * */ +public class DefaultConverterTest { + private DefaultConverter converter; + + @Before + public void setUp() { + converter = new DefaultConverter(); + } + + @Test + public void testObjectToObjectArray() { + Object o = new Object(); + assertTrue(converter.canConvert(o, Object[].class)); + Object[] result = converter.convert(o, Object[].class); + assertNotNull(result); + assertEquals(1, result.length); + assertSame(o, result[0]); + } + + @Test + public void testIntToObjectArray() { + int v = 1; + assertTrue(converter.canConvert(v, Object[].class)); + Object[] result = converter.convert(v, Object[].class); + assertNotNull(result); + assertEquals(1, result.length); + assertSame(v, result[0]); + } + + @Test + public void testIntToPrimitiveIntArray() { + int v = 2; + assertTrue(converter.supports(new ConversionRequest(v, int[].class))); + assertTrue(converter.canConvert(v, int[].class)); + int[] result = converter.convert(v, int[].class); + assertNotNull(result); + assertEquals(1, result.length); + assertSame(v, result[0]); + } + + @Test + public void testIntToBoxedIntegerArray() { + int v = 3; + assertTrue(converter.canConvert(v, Integer[].class)); + Integer[] result = converter.convert(v, Integer[].class); + assertNotNull(result); + assertEquals(1, result.length); + assertSame(v, result[0]); + } + + @Test + public void testByteToPrimitiveDoubleArray() { + byte v = 4; + assertTrue(converter.canConvert(v, double[].class)); + double[] result = converter.convert(v, double[].class); + assertNotNull(result); + assertEquals(1, result.length); + assertEquals(v, result[0], 0.0); + } + + @Test + public void testByteToBoxedDoubleArray() { + byte v = 4; + assertTrue(converter.canConvert(v, Double[].class)); + Double[] result = converter.convert(v, Double[].class); + assertNotNull(result); + assertEquals(1, result.length); + assertEquals(v, result[0], 0.0); + } + + @Test + public void testStringToObjectArray() { + String s = "Pumpernickel"; + assertTrue(converter.canConvert(s, Object[].class)); + Object[] result = converter.convert(s, Object[].class); + assertNotNull(result); + assertEquals(1, result.length); + assertSame(s, result[0]); + } + + @Test + public void testStringToStringArray() { + String s = "smorgasbord"; + assertTrue(converter.canConvert(s, String[].class)); + String[] result = converter.convert(s, String[].class); + assertNotNull(result); + assertEquals(1, result.length); + assertSame(s, result[0]); + } + + @Test + public void testObjectToCollection() throws NoSuchFieldException { + @SuppressWarnings("unused") + class Struct { + private Collection collectionOfObjects; + private List listOfObjects; + private List listOfStrings; + private List listOfDoubles; + private Set setOfObjects; + } + + Type collectionOfObjectsType = Struct.class.getDeclaredField("collectionOfObjects").getGenericType(); + Type listOfObjectsType = Struct.class.getDeclaredField("listOfObjects").getGenericType(); + Type listOfStringsType = Struct.class.getDeclaredField("listOfStrings").getGenericType(); + Type listOfDoublesType = Struct.class.getDeclaredField("listOfDoubles").getGenericType(); + Type setOfObjectsType = Struct.class.getDeclaredField("setOfObjects").getGenericType(); + + Object o = new Object(); + + assertTrue(converter.canConvert(o, Collection.class)); + assertTrue(converter.canConvert(o, List.class)); + assertTrue(converter.canConvert(o, Set.class)); + + assertCollection(o, o, converter.convert(o, Collection.class), Collection.class); + assertCollection(o, o, converter.convert(o, List.class), List.class); + assertCollection(o, o, converter.convert(o, Set.class), Set.class); + + assertTrue(converter.canConvert(o, collectionOfObjectsType)); + assertTrue(converter.canConvert(o, listOfObjectsType)); + assertTrue(converter.canConvert(o, listOfStringsType)); + assertTrue(converter.canConvert(o, listOfDoublesType)); + assertTrue(converter.canConvert(o, setOfObjectsType)); + + assertCollection(o, o, converter.convert(o, collectionOfObjectsType), Collection.class); + assertCollection(o, o, converter.convert(o, listOfObjectsType), List.class); + assertCollection(o, o.toString(), converter.convert(o, listOfStringsType), List.class); + assertCollection(o, null, converter.convert(o, listOfDoublesType), List.class); + assertCollection(o, o, converter.convert(o, setOfObjectsType), Set.class); + + String s = "Thingamawhatsit"; + assertTrue(converter.canConvert(s, collectionOfObjectsType)); + assertTrue(converter.canConvert(s, listOfObjectsType)); + assertTrue(converter.canConvert(s, listOfStringsType)); + assertTrue(converter.canConvert(s, listOfDoublesType)); + assertTrue(converter.canConvert(s, setOfObjectsType)); + + assertCollection(s, s, converter.convert(s, collectionOfObjectsType), Collection.class); + assertCollection(s, s, converter.convert(s, listOfObjectsType), List.class); + assertCollection(s, s, converter.convert(s, listOfStringsType), List.class); + assertCollection(s, null, converter.convert(s, listOfDoublesType), List.class); + assertCollection(s, s, converter.convert(s, setOfObjectsType), Set.class); + + // TODO: Test more things, covering the equivalent of all *To*Array above. + } + + @Test + public void testNumberToNumber() { + double d = -5.6; + assertTrue(converter.canConvert(d, int.class)); + assertEquals(-5, converter.convert(d, int.class), 0.0); + // TODO: Test many more combinations of numeric types. + } + + @Test + public void testObjectToString() { + Object friendly = new Object() { + @Override + public String toString() { return "Hello"; } + }; + assertTrue(converter.canConvert(friendly, String.class)); + assertEquals("Hello", converter.convert(friendly, String.class)); + } + + @Test + public void testStringToCharacter() { + String plan = "Step 0: there is no plan"; + + assertTrue(converter.canConvert(plan, char.class)); + assertEquals('S', (char) converter.convert(plan, char.class)); + + assertTrue(converter.canConvert(plan, Character.class)); + assertEquals(new Character('S'), converter.convert(plan, Character.class)); + } + + @Test + public void testStringToCharacterArray() { + String plan = "Step 0: there is no plan"; + + assertTrue(converter.canConvert(plan, char[].class)); + final char[] converted = converter.convert(plan, char[].class); + assertArrayEquals(plan.toCharArray(), converted); + + // NB: Conversion to Character[] does not work the same way. + } + + private enum Gem { + RUBY, DIAMOND, EMERALD; + } + + @Test + public void testStringToEnum() { + assertTrue(converter.canConvert("RUBY", Gem.class)); + assertTrue(converter.canConvert("DIAMOND", Gem.class)); + assertTrue(converter.canConvert("EMERALD", Gem.class)); + assertTrue(converter.canConvert("QUARTZ", Gem.class)); + assertEquals(Gem.RUBY, converter.convert("RUBY", Gem.class)); + assertEquals(Gem.DIAMOND, converter.convert("DIAMOND", Gem.class)); + assertEquals(Gem.EMERALD, converter.convert("EMERALD", Gem.class)); + assertNull(converter.convert("QUARTZ", Gem.class)); + } + + public static class StringWrapper { + public String s; + public StringWrapper(String s) { this.s = s; } + } + + @Test + public void testConstructorConversion() { + String s = "Juggernaut"; + assertTrue(converter.canConvert(s, StringWrapper.class)); + assertFalse(converter.canConvert(7, StringWrapper.class)); + StringWrapper sw = converter.convert(s, StringWrapper.class); + assertNotNull(sw); + assertSame(s, sw.s); + } + + // -- Helper methods -- + + private static void assertCollection(Object o, Object expected, + Object collection, Class collectionClass) + { + assertNotNull(collection); + assertTrue(collectionClass.isInstance(collection)); + assertEquals(1, ((Collection) collection).size()); + Object actual = ((Collection) collection).iterator().next(); + if (o == expected) assertSame(expected, actual); + else assertEquals(expected, actual); // o was converted to element type + } +} diff --git a/src/test/java/org/scijava/convert/DelegateConverterTest.java b/src/test/java/org/scijava/convert/DelegateConverterTest.java new file mode 100644 index 000000000..22470f4a3 --- /dev/null +++ b/src/test/java/org/scijava/convert/DelegateConverterTest.java @@ -0,0 +1,146 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.scijava.convert; + +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.scijava.Context; +import org.scijava.plugin.Plugin; + + +public class DelegateConverterTest { + private Context context; + + @Before + public void setUp() { + context = new Context(); + } + + @After + public void tearDown() { + context.dispose(); + context = null; + } + + @Test + public void testDelegateConverters() { + ConvertService convertService = context.getService(ConvertService.class); + + // Test conversion from AType to BType + AType a = new AType(); + assertTrue(convertService.supports(a, BType.class)); + BType b = convertService.convert(a, BType.class); + assertSame(BType.class, b.getClass()); + + // Test conversion from BType to CType + assertTrue(convertService.supports(b, CType.class)); + CType c = convertService.convert(b, CType.class); + assertSame(CType.class, c.getClass()); + + // Test chained conversion + assertTrue(convertService.supports(a, CType.class)); + CType converted = convertService.convert(a, CType.class); + assertSame(c.getClass(), converted.getClass()); + } + + public static class AType { + // empty class + } + + public static class BType { + // empty class + } + + public static class CType { + // empty class + } + + @Plugin(type=Converter.class) + public static class ABConverter extends AbstractConverter { + + @SuppressWarnings("unchecked") + @Override + public T convert(Object src, Class dest) { + return (T) new BType(); + } + + @Override + public Class getOutputType() { + return BType.class; + } + + @Override + public Class getInputType() { + return AType.class; + } + } + + @Plugin(type=Converter.class) + public static class BCConverter extends AbstractConverter { + + @SuppressWarnings("unchecked") + @Override + public T convert(Object src, Class dest) { + return (T) new CType(); + } + + @Override + public Class getOutputType() { + return CType.class; + } + + @Override + public Class getInputType() { + return BType.class; + } + } + + @Plugin(type=Converter.class) + public static class DelegateConverter extends AbstractDelegateConverter { + + @Override + public Class getOutputType() { + return CType.class; + } + + @Override + public Class getInputType() { + return AType.class; + } + + @Override + protected Class getDelegateType() { + return BType.class; + } + } +} diff --git a/src/test/java/org/scijava/convert/DoubleToBigDecimalConverterTest.java b/src/test/java/org/scijava/convert/DoubleToBigDecimalConverterTest.java index 5d8df5b77..6f2f09886 100644 --- a/src/test/java/org/scijava/convert/DoubleToBigDecimalConverterTest.java +++ b/src/test/java/org/scijava/convert/DoubleToBigDecimalConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/FileListConverterTest.java b/src/test/java/org/scijava/convert/FileListConverterTest.java new file mode 100644 index 000000000..8d077c8a4 --- /dev/null +++ b/src/test/java/org/scijava/convert/FileListConverterTest.java @@ -0,0 +1,101 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.convert; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; + +import java.io.File; + +import org.junit.Test; +import org.scijava.convert.FileListConverters.FileArrayToStringConverter; +import org.scijava.convert.FileListConverters.FileToStringConverter; +import org.scijava.convert.FileListConverters.StringToFileArrayConverter; +import org.scijava.convert.FileListConverters.StringToFileConverter; + +public class FileListConverterTest { + + @Test + public void testStringToFileConverter() { + final StringToFileConverter conv = new StringToFileConverter(); + final String path = "C:\\temp\\f,i;l-ename.txt"; + assertTrue("Cannot convert from String to File", + conv.canConvert(String.class, File.class)); + assertFalse("Can erroneously convert from String to File[]", + conv.canConvert(String.class, File[].class)); + assertEquals(new File(path), + conv.convert(path, File.class)); + } + + @Test + public void testStringToFileArrayConverter() { + final StringToFileArrayConverter conv = new StringToFileArrayConverter(); + final String path = "\"C:\\temp\\f,i;l-ename.txt\",C:\\temp"; + assertTrue("Cannot convert from String to File[]", + conv.canConvert(String.class, File[].class)); + assertFalse("Can erroneously convert from String to File", + conv.canConvert(String.class, File.class)); + assertEquals("Wrong array length", 2, + conv.convert(path, File[].class).length); + assertEquals("Wrong file name", new File("C:\\temp\\f,i;l-ename.txt"), + conv.convert(path, File[].class)[0]); + assertEquals("Wrong file name", new File("C:\\temp"), + conv.convert(path, File[].class)[1]); + assertEquals( 0, conv.convert( "", File[].class ).length ); + } + + @Test + public void testFileToStringConverter() { + final FileToStringConverter conv = new FileToStringConverter(); + final File file = new File("C:\\temp\\f,i;l-ename.txt"); + assertTrue("Cannot convert from File to String", + conv.canConvert(File.class, String.class)); + assertFalse("Can erroneously convert from File[] to String", + conv.canConvert(File[].class, String.class)); + assertEquals(file.getAbsolutePath(), + conv.convert(file, String.class)); + } + + @Test + public void testFileArrayToStringConverter() { + final FileArrayToStringConverter conv = new FileArrayToStringConverter(); + final File[] fileArray = new File[2]; + fileArray[0] = new File("C:\\temp\\f,i;l-ename.txt"); + fileArray[1] = new File("C:\\temp"); + final String expected = "\"" + fileArray[0].getAbsolutePath() + "\"," + fileArray[1].getAbsolutePath(); + assertTrue("Cannot convert from File[] to String", + conv.canConvert(File[].class, String.class)); + assertFalse("Can erroneously convert from File to String", + conv.canConvert(File.class, String.class)); + assertEquals("Wrong output string", expected, + conv.convert(fileArray, String.class)); + } +} diff --git a/src/test/java/org/scijava/convert/FileToPathConversionTest.java b/src/test/java/org/scijava/convert/FileToPathConversionTest.java new file mode 100644 index 000000000..ef020a279 --- /dev/null +++ b/src/test/java/org/scijava/convert/FileToPathConversionTest.java @@ -0,0 +1,85 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.convert; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.scijava.Context; +import org.scijava.parse.ParseService; + +/** + * Tests conversion between {@link File}s and {@link Path}s. + * + * @author Gabriel Selzer + */ +public class FileToPathConversionTest { + + private ConvertService convertService; + private Context context; + + @Before + public void setUp() { + context = new Context(ParseService.class, ConvertService.class); + convertService = context.getService(ConvertService.class); + } + + @After + public void tearDown() { + context.dispose(); + } + + /** + * Tests the ability of to convert from {@link File} to {@link Path}. + */ + @Test + public void fileToPathConversion() { + File f = new File("tmp.java"); + assertTrue(convertService.supports(f, Path.class)); + Path p = convertService.convert(f, Path.class); + assertEquals(f.toPath(), p); + } + + @Test + public void pathToFileConversion() { + Path p = Paths.get("tmp.java"); + assertTrue(convertService.supports(p, File.class)); + File f = convertService.convert(p, File.class); + assertEquals(f.toPath(), p); + } + +} diff --git a/src/test/java/org/scijava/convert/FloatToBigDecimalConverterTest.java b/src/test/java/org/scijava/convert/FloatToBigDecimalConverterTest.java index 9350e6564..426f5af68 100644 --- a/src/test/java/org/scijava/convert/FloatToBigDecimalConverterTest.java +++ b/src/test/java/org/scijava/convert/FloatToBigDecimalConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/FloatToDoubleConverterTest.java b/src/test/java/org/scijava/convert/FloatToDoubleConverterTest.java index beb7d5968..79ab0ddb1 100644 --- a/src/test/java/org/scijava/convert/FloatToDoubleConverterTest.java +++ b/src/test/java/org/scijava/convert/FloatToDoubleConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/IntegerToBigDecimalConverterTest.java b/src/test/java/org/scijava/convert/IntegerToBigDecimalConverterTest.java index 8788bab96..8405d7489 100644 --- a/src/test/java/org/scijava/convert/IntegerToBigDecimalConverterTest.java +++ b/src/test/java/org/scijava/convert/IntegerToBigDecimalConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/IntegerToBigIntegerConverterTest.java b/src/test/java/org/scijava/convert/IntegerToBigIntegerConverterTest.java index 8b7ba5235..5f58d847b 100644 --- a/src/test/java/org/scijava/convert/IntegerToBigIntegerConverterTest.java +++ b/src/test/java/org/scijava/convert/IntegerToBigIntegerConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/IntegerToDoubleConverterTest.java b/src/test/java/org/scijava/convert/IntegerToDoubleConverterTest.java index e0b2dc0a8..a480aa4b2 100644 --- a/src/test/java/org/scijava/convert/IntegerToDoubleConverterTest.java +++ b/src/test/java/org/scijava/convert/IntegerToDoubleConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/IntegerToLongConverterTest.java b/src/test/java/org/scijava/convert/IntegerToLongConverterTest.java index d0f89e0df..e9ded24c7 100644 --- a/src/test/java/org/scijava/convert/IntegerToLongConverterTest.java +++ b/src/test/java/org/scijava/convert/IntegerToLongConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/LongToBigDecimalConverterTest.java b/src/test/java/org/scijava/convert/LongToBigDecimalConverterTest.java index 879230b73..4ea3bd95e 100644 --- a/src/test/java/org/scijava/convert/LongToBigDecimalConverterTest.java +++ b/src/test/java/org/scijava/convert/LongToBigDecimalConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/LongToBigIntegerConverterTest.java b/src/test/java/org/scijava/convert/LongToBigIntegerConverterTest.java index a5946bf21..e5d8a412e 100644 --- a/src/test/java/org/scijava/convert/LongToBigIntegerConverterTest.java +++ b/src/test/java/org/scijava/convert/LongToBigIntegerConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/ShortToBigDecimalConverterTest.java b/src/test/java/org/scijava/convert/ShortToBigDecimalConverterTest.java index f6b140e44..fe4aea074 100644 --- a/src/test/java/org/scijava/convert/ShortToBigDecimalConverterTest.java +++ b/src/test/java/org/scijava/convert/ShortToBigDecimalConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/ShortToBigIntegerConverterTest.java b/src/test/java/org/scijava/convert/ShortToBigIntegerConverterTest.java index 502e384fc..dcd7145b6 100644 --- a/src/test/java/org/scijava/convert/ShortToBigIntegerConverterTest.java +++ b/src/test/java/org/scijava/convert/ShortToBigIntegerConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/ShortToDoubleConverterTest.java b/src/test/java/org/scijava/convert/ShortToDoubleConverterTest.java index 4409e6510..05b3e93c3 100644 --- a/src/test/java/org/scijava/convert/ShortToDoubleConverterTest.java +++ b/src/test/java/org/scijava/convert/ShortToDoubleConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/ShortToFloatConverterTest.java b/src/test/java/org/scijava/convert/ShortToFloatConverterTest.java index 02288d693..e4fc125eb 100644 --- a/src/test/java/org/scijava/convert/ShortToFloatConverterTest.java +++ b/src/test/java/org/scijava/convert/ShortToFloatConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/ShortToIntegerConverterTest.java b/src/test/java/org/scijava/convert/ShortToIntegerConverterTest.java index 959e32105..43d50e217 100644 --- a/src/test/java/org/scijava/convert/ShortToIntegerConverterTest.java +++ b/src/test/java/org/scijava/convert/ShortToIntegerConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/ShortToLongConverterTest.java b/src/test/java/org/scijava/convert/ShortToLongConverterTest.java index d79cbd6b6..03cb6e6d3 100644 --- a/src/test/java/org/scijava/convert/ShortToLongConverterTest.java +++ b/src/test/java/org/scijava/convert/ShortToLongConverterTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/convert/StringToArrayConverterTest.java b/src/test/java/org/scijava/convert/StringToArrayConverterTest.java new file mode 100644 index 000000000..2107a85a9 --- /dev/null +++ b/src/test/java/org/scijava/convert/StringToArrayConverterTest.java @@ -0,0 +1,273 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.convert; + +import static org.junit.Assert.assertArrayEquals; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.scijava.Context; +import org.scijava.parse.ParseService; + +/** + * Tests {@link StringToArrayConverter}. + * + * @author Gabriel Selzer + */ +public class StringToArrayConverterTest { + + private final StringToArrayConverter converter = new StringToArrayConverter(); + private ConvertService convertService; + private Context context; + + @Before + public void setUp() { + context = new Context(ParseService.class, ConvertService.class); + context.inject(converter); + convertService = context.getService(ConvertService.class); + } + + @After + public void tearDown() { + context.dispose(); + } + + /** + * Tests the ability of {@link StringToArrayConverter} in converting to arrays + * of various types + */ + @Test + public void testArrayConversion() { + // Array types for array conversions + List> classes = Arrays.asList( // + byte[].class, // + Byte[].class, // + short[].class, // + Short[].class, // + int[].class, // + Integer[].class, // + long[].class, // + Long[].class, // + float[].class, // + Float[].class, // + double[].class, // + Double[].class // + ); + // String input + String s = "{0, 1, 2}"; + for (Class arrayClass : classes) { + // Ensure our Converter can do the conversion + Assert.assertTrue(converter.canConvert(s, arrayClass)); + // Do the conversion + Object converted = converter.convert(s, arrayClass); + // Ensure the output is the expected type + Assert.assertEquals(arrayClass, converted.getClass()); + Class c = arrayClass.getComponentType(); + for (int i = 0; i < 3; i++) { + // Ensure element correctness + Object expected = convertService.convert(i, c); + Assert.assertEquals(expected, Array.get(converted, i)); + } + } + } + + /** + * Tests the ability of {@link StringToArrayConverter} in converting + * 2-dimensional arrays + */ + @Test + public void test2DArrayConversion() { + String s = "{{0, 1}, {2, 3}}"; + Assert.assertTrue(converter.canConvert(s, byte[][].class)); + byte[][] actual = converter.convert(s, byte[][].class); + Assert.assertEquals(0, actual[0][0]); + Assert.assertEquals(1, actual[0][1]); + Assert.assertEquals(2, actual[1][0]); + Assert.assertEquals(3, actual[1][1]); + } + /** + * Tests the ability of {@link StringToArrayConverter} in converting + * 3-dimensional arrays + */ + @Test + public void test3DArrayConversion() { + String s = "{{{0, 1}, {1, 2}},{{1, 2}, {2, 3}}}"; + Assert.assertTrue(converter.canConvert(s, byte[][][].class)); + byte[][][] actual = converter.convert(s, byte[][][].class); + + for (int i = 0; i < 2; i++) + for (int j = 0; j < 2; j++) + for (int k = 0; k < 2; k++) + Assert.assertEquals(i + j + k, actual[i][j][k]); + } + + /** + * Tests the ability of {@link StringToArrayConverter} in converting empty + * arrays + */ + @Test + public void testEmptyArrayConversion() { + String s = "{}"; + Assert.assertTrue(converter.canConvert(s, byte[].class)); + byte[] actual = converter.convert(s, byte[].class); + Assert.assertEquals(0, actual.length); + } + + /** + * Tests the special case of {@link String}s + */ + @Test + public void testStringArrayConversion() { + String[] expected = new String[] { // + "{foo", "bar}", // + "ha\nha", // + "foo,bar", // + "lol\"lol", // + "foo\\\"bar" // + }; + String converted = convertService.convert(expected, String.class); + Assert.assertEquals("{\"{foo\", \"bar}\", " + // + "\"ha\nha\", " + // + "\"foo,bar\", " + // + "\"lol\\\"lol\", " + // + "\"foo\\\\\\\"bar\"}", converted); + String[] actual = convertService.convert(converted, String[].class); + Assert.assertArrayEquals(expected, actual); + } + + /** + * Tests the special case of {@link Character}s + */ + @Test + public void testCharacterArrayConversion() { + Character[] expected = new Character[] { // + 's', // + '\n', // + ',', // + '{', // + '}' // + }; + String converted = convertService.convert(expected, String.class); + Assert.assertEquals("{\"s\", \"\n\", \",\", \"{\", \"}\"}", converted); + Character[] actual = convertService.convert(converted, Character[].class); + Assert.assertArrayEquals(expected, actual); + } + + @Test + public void testStringToDoubleArraySingleValue() { + assertArrayEquals(new double[] {5}, + converter.convert("5", double[].class), 0); + assertArrayEquals(new Double[] {6d}, + converter.convert("6", Double[].class)); + assertArrayEquals(new double[][] {{7}}, + converter.convert("7", double[][].class)); + assertArrayEquals(new Double[][] {{8d}}, + converter.convert("8", Double[][].class)); + + assertArrayEquals(new double[] {0}, + converter.convert("spinach", double[].class), 0); + assertArrayEquals(new Double[] {null}, + converter.convert("kale", Double[].class)); + assertArrayEquals(new double[][] {{0}}, + converter.convert("broccoli", double[][].class)); + assertArrayEquals(new Double[][] {{null}}, + converter.convert("lettuce", Double[][].class)); + } + + @Test + public void testStringToDoubleArray1D() { + // all numbers + assertArrayEquals(new double[] {0, 1, 2, 3}, + converter.convert("{0, 1, 2, 3}", double[].class), 0); + assertArrayEquals(new Double[] {7d, 11d}, + converter.convert("{7, 11}", Double[].class)); + assertArrayEquals(new Double[] {0d, 1d, 2d, 3d}, + converter.convert("{0, 1, 2, 3}", Double[].class)); + + // mixed numbers/non-numbers + assertArrayEquals(new double[] {0, 1, 0, 3}, + converter.convert("{0, 1, kumquat, 3}", double[].class), 0); + assertArrayEquals(new Double[] {4d, null, 5d}, + converter.convert("{4, eggplant, 5}", Double[].class)); + + // all non-numbers + assertArrayEquals(new double[] {0, 0, 0}, + converter.convert("{uno, dos, tres}", double[].class), 0); + assertArrayEquals(new Double[] {null, null, null, null}, + converter.convert("{cuatro, cinco, seis, siete}", Double[].class)); + } + + @Test + public void testStringToDoubleArray2D() { + // all numbers + assertArrayEquals(new double[][] {{0, 1}, {2, 3, 4}}, + converter.convert("{{0, 1}, {2, 3, 4}}", double[][].class)); + assertArrayEquals(new Double[][] {{7d, 11d}, {13d, 17d, 19d}}, + converter.convert("{{7, 11}, {13, 17, 19}}", Double[][].class)); + assertArrayEquals(new Double[][] {{0d, 1d}, {2d, 3d}}, + converter.convert("{{0, 1}, {2, 3}}", Double[][].class)); + + // mixed numbers/non-numbers + assertArrayEquals(new double[][] {{0, 1}, {0, 3}}, + converter.convert("{{0, 1}, {kumquat, 3}}", double[][].class)); + assertArrayEquals(new Double[][] {{4d}, {null, 5d}, {null}}, + converter.convert("{{4}, {eggplant, 5}, {squash}}", Double[][].class)); + + // all non-numbers + assertArrayEquals(new double[][] {{0}, {0, 0}}, + converter.convert("{{uno}, {dos, tres}}", double[][].class)); + assertArrayEquals(new Double[][] {{null, null}, {null, null}}, + converter.convert("{{cuatro, cinco}, {seis, siete}}", Double[][].class)); + } + + @Test + public void testStringToDoubleArray3D() { + final Random r = new Random(0xDA7ABA5E); + final double[][][] ds = new double[r.nextInt(10)][][]; + for (int i=0; i conv; + + @Before + public void setUp() { + conv = new StringToNumberConverter(); + } + + @Test + public void stringToByteTest() { + String s = "0"; + Assert.assertTrue(conv.canConvert(s, Byte.class)); + Assert.assertEquals(new Byte((byte) 0), conv.convert(s, Byte.class)); + } + + @Test + public void stringToPrimitiveByteTest() { + String s = "0"; + Assert.assertTrue(conv.canConvert(s, byte.class)); + Assert.assertEquals(0, (int) conv.convert(s, byte.class)); + } + + @Test + public void stringToShortTest() { + String s = "0"; + Assert.assertTrue(conv.canConvert(s, Short.class)); + Assert.assertEquals(new Short((short) 0), conv.convert(s, Short.class)); + } + + @Test + public void stringToPrimitiveShortTest() { + String s = "0"; + Assert.assertTrue(conv.canConvert(s, short.class)); + Assert.assertEquals(0, (int) conv.convert(s, short.class)); + } + + @Test + public void stringToIntegerTest() { + String s = "0"; + Assert.assertTrue(conv.canConvert(s, Integer.class)); + Assert.assertEquals(new Integer(0), conv.convert(s, Integer.class)); + } + + @Test + public void stringToPrimitiveIntegerTest() { + String s = "0"; + Assert.assertTrue(conv.canConvert(s, int.class)); + Assert.assertEquals(0, (int) conv.convert(s, int.class)); + } + + @Test + public void stringToLongTest() { + String s = "0"; + Assert.assertTrue(conv.canConvert(s, Long.class)); + Assert.assertEquals(new Long(0), conv.convert(s, Long.class)); + } + + @Test + public void stringToPrimitiveLongTest() { + String s = "0"; + Assert.assertTrue(conv.canConvert(s, long.class)); + Assert.assertEquals(0L, (long) conv.convert(s, long.class)); + } + + @Test + public void stringToFloatTest() { + String s = "0"; + Assert.assertTrue(conv.canConvert(s, Float.class)); + Assert.assertEquals(new Float(0), conv.convert(s, Float.class)); + } + + @Test + public void stringToPrimitiveFloat() { + String s = "0"; + Assert.assertTrue(conv.canConvert(s, float.class)); + Assert.assertEquals(0f, conv.convert(s, float.class), 1e-6); + } + + @Test + public void stringToDoubleTest() { + String s = "0"; + Assert.assertTrue(conv.canConvert(s, Double.class)); + Assert.assertEquals(new Double(0), conv.convert(s, Double.class)); + } + + @Test + public void stringToPrimitiveDouble() { + String s = "0"; + Assert.assertTrue(conv.canConvert(s, double.class)); + Assert.assertEquals(0d, conv.convert(s, double.class), 1e-6); + } + + @Test + public void stringToNumberTest() { + String s = "0"; + Assert.assertTrue(conv.canConvert(s, Number.class)); + Assert.assertEquals(0d, conv.convert(s, Number.class)); + } + + @Test + public void invalidStringToNumberTest() { + String s = "invalid"; + Assert.assertFalse(conv.canConvert(s, Number.class)); + Assert.assertThrows(IllegalArgumentException.class, () -> conv.convert(s, + Number.class)); + } +} diff --git a/src/test/java/org/scijava/display/DisplayTest.java b/src/test/java/org/scijava/display/DisplayTest.java index 520948b1c..16e43f04c 100644 --- a/src/test/java/org/scijava/display/DisplayTest.java +++ b/src/test/java/org/scijava/display/DisplayTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/download/DownloadServiceTest.java b/src/test/java/org/scijava/download/DownloadServiceTest.java new file mode 100644 index 000000000..4d6fb2f6e --- /dev/null +++ b/src/test/java/org/scijava/download/DownloadServiceTest.java @@ -0,0 +1,177 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.scijava.download; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.scijava.Context; +import org.scijava.io.ByteBank; +import org.scijava.io.location.BytesLocation; +import org.scijava.io.location.FileLocation; +import org.scijava.io.location.Location; +import org.scijava.test.TestUtils; +import org.scijava.util.FileUtils; +import org.scijava.util.MersenneTwisterFast; + +/** + * Tests {@link DownloadService}. + * + * @author Curtis Rueden + */ +public class DownloadServiceTest { + + private DownloadService downloadService; + + @Before + public void setUp() { + final Context ctx = new Context(DownloadService.class); + downloadService = ctx.service(DownloadService.class); + } + + @After + public void tearDown() { + downloadService.context().dispose(); + } + + @Test + public void testDownload() throws IOException, InterruptedException, + ExecutionException + { + final byte[] data = randomBytes(0xbabebabe); + + final String prefix = getClass().getName(); + final File inFile = File.createTempFile(prefix, "testDownloadIn"); + final File outFile = File.createTempFile(prefix, "testDownloadOut"); + + try { + FileUtils.writeFile(inFile, data); + + final Location src = new FileLocation(inFile); + final Location dest = new FileLocation(outFile); + + final Download download = downloadService.download(src, dest); + download.task().waitFor(); + + final byte[] result = FileUtils.readFile(outFile); + assertArrayEquals(data, result); + } + finally { + inFile.delete(); + outFile.delete(); + } + } + + @Test + public void testDownloadCache() throws IOException, InterruptedException, + ExecutionException + { + // Create some data. + final byte[] data = randomBytes(0xcafecafe); + + // Create source location. + final String prefix = getClass().getName(); + final File inFile = File.createTempFile(prefix, "testDownloadCacheIn"); + final Location src = new FileLocation(inFile); + + // Create destination location. + final BytesLocation dest = new BytesLocation(data.length); + + // Create a disk cache. + final File cacheDir = TestUtils.createTemporaryDirectory( + "testDownloadCacheBase", getClass()); + final DiskLocationCache cache = new DiskLocationCache(); + cache.setBaseDirectory(cacheDir); + cache.setFileLocationCachingEnabled(true); + + try { + // Write the data to the source location. + FileUtils.writeFile(inFile, data); + + // Sanity check: the cache should be empty. + assertNull(cache.loadChecksum(src)); + final Location cachedSource = cache.cachedLocation(src); + assertTrue(cachedSource instanceof FileLocation); + final FileLocation cachedFile = (FileLocation) cachedSource; + assertFalse(cachedFile.getFile().exists()); + + // Download + cache the source. + final Download download = downloadService.download(src, dest, cache); + download.task().waitFor(); + + // Check that the data was read. + assertBytesEqual(data, dest.getByteBank()); + + // Check that the data was cached. + assertEquals(cachedSource, cache.cachedLocation(src)); + assertTrue(cachedFile.getFile().exists()); + final byte[] cachedData = FileUtils.readFile(cachedFile.getFile()); + assertArrayEquals(data, cachedData); + + // Check that the cache works, even after the source file is deleted. + inFile.delete(); + assertFalse(inFile.exists()); + final BytesLocation dest2 = new BytesLocation(data.length); + final Download download2 = downloadService.download(src, dest2, cache); + download2.task().waitFor(); + assertBytesEqual(data, dest2.getByteBank()); + } + finally { + if (inFile.exists()) inFile.delete(); + FileUtils.deleteRecursively(cacheDir); + } + } + + // -- Helper methods -- + + private byte[] randomBytes(final long seed) { + final MersenneTwisterFast r = new MersenneTwisterFast(seed); + final byte[] data = new byte[2938740]; + for (int i = 0; i < data.length; i++) { + data[i] = r.nextByte(); + } + return data; + } + + private void assertBytesEqual(byte[] data, ByteBank byteBank) { + for (int i=0; i { +} diff --git a/src/test/java/org/scijava/event/bushe/EBTestCounter.java b/src/test/java/org/scijava/event/bushe/EBTestCounter.java new file mode 100644 index 000000000..8ebf75160 --- /dev/null +++ b/src/test/java/org/scijava/event/bushe/EBTestCounter.java @@ -0,0 +1,10 @@ +package org.scijava.event.bushe; + +/** + * @author Michael Bushe + * @since Nov 19, 2005 11:12:35 PM + */ +public class EBTestCounter { + public int eventsHandledCount; + public int subscribeExceptionCount; +} diff --git a/src/test/java/org/scijava/event/bushe/EDTUtil.java b/src/test/java/org/scijava/event/bushe/EDTUtil.java new file mode 100644 index 000000000..c74d4846f --- /dev/null +++ b/src/test/java/org/scijava/event/bushe/EDTUtil.java @@ -0,0 +1,36 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.scijava.event.bushe; + +import java.awt.EventQueue; +import java.awt.Toolkit; + +/** + * + * @author Michael Bushe + */ +public class EDTUtil { + + /** + * Since we are using the event bus from a non-awt thread, stay alive for a sec to give time for the EDT to start and + * post the message + */ + public static void waitForEDT() { + EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue(); + long start = System.currentTimeMillis(); + do { + //wait at least once - plenty of time for the event sent to the queue to get there + long now = System.currentTimeMillis(); + if (now > start + (1000*5) ) { + throw new RuntimeException("Waited too long for the EDT to finish."); + } + try { + Thread.sleep(100); + } catch (Throwable e) { + } + } while(eventQueue.peekEvent() != null); + } +} diff --git a/src/test/java/org/scijava/event/bushe/GenericReflection.java b/src/test/java/org/scijava/event/bushe/GenericReflection.java new file mode 100644 index 000000000..d3d2954b3 --- /dev/null +++ b/src/test/java/org/scijava/event/bushe/GenericReflection.java @@ -0,0 +1,125 @@ +package org.scijava.event.bushe; + +import java.lang.reflect.TypeVariable; + +import java.lang.reflect.*; +import java.io.*; + +/** + * From OReilly Book Java Generics + */ +public class GenericReflection { + DataRequestEvent dre; + private final static PrintStream out = System.out; + public static void printSuperclass (Type sup) { + if (sup != null && !sup.equals(Object.class)) { + out.print("extends "); + printType(sup); + out.println(); + } + } + public static void printInterfaces (Type[] implementations) { + if (implementations != null && implementations.length > 0) { + out.print("implements "); + int i = 0; + for (Type impl : implementations) { + if (i++ > 0) out.print(","); + printType(impl); + } + out.println(); + } + } + public static void printTypeParameters (TypeVariable[] vars) { + if (vars != null && vars.length > 0) { + out.print("<"); + int i = 0; + for (TypeVariable var : vars) { + if (i++ > 0) out.print(","); + out.print(var.getName()); + printBounds(var.getBounds()); + } + out.print(">"); + } + } + public static void printBounds (Type[] bounds) { + if (bounds != null && bounds.length > 0 + && !(bounds.length==1 && bounds[0]==Object.class)) { + out.print(" extends "); + int i = 0; + for (Type bound : bounds) { + if (i++ > 0) out.print("&"); + printType(bound); + } + } + } + public static void printParams (Type[] types) { + if (types != null && types.length > 0) { + out.print("<"); + int i = 0; + for (Type type : types) { + if (i++ > 0) out.print(","); + printType(type); + } + out.print(">"); + } + } + public static void printType (Type type) { + if (type instanceof Class) { + Class c = (Class)type; + out.print(c.getName()); + } else if (type instanceof ParameterizedType) { + ParameterizedType p = (ParameterizedType)type; + Class c = (Class)p.getRawType(); + Type o = p.getOwnerType(); + if (o != null) { printType(o); out.print("."); } + out.print(c.getName()); + printParams(p.getActualTypeArguments()); + } else if (type instanceof TypeVariable) { + TypeVariable v = (TypeVariable)type; + out.print(v.getName()); + } else if (type instanceof GenericArrayType) { + GenericArrayType a = (GenericArrayType)type; + printType(a.getGenericComponentType()); + out.print("[]"); + } else if (type instanceof WildcardType) { + WildcardType w = (WildcardType)type; + Type[] upper = w.getUpperBounds(); + Type[] lower = w.getLowerBounds(); + if (upper.length==1 && lower.length==0) { + out.print("? extends "); + printType(upper[0]); + } else if (upper.length==0 && lower.length==1) { + out.print("? super "); + printType(lower[0]); + } else assert false; + } + } + public static void printClass (Class c) { + out.print("class "); + out.print(c.getName()); + printTypeParameters(c.getTypeParameters()); + out.println(); + printSuperclass(c.getGenericSuperclass()); + printInterfaces(c.getGenericInterfaces()); + /* + out.println("{"); + for (Field f : c.getFields()) { + out.println(" "+f.toGenericString()+";"); + } + for (Constructor k : c.getConstructors()) { + out.println(" "+k.toGenericString()+";"); + } + for (Method m : c.getMethods()) { + out.println(" "+m.toGenericString()+";"); + } + out.println("}"); + */ + } + public static void main (String[] args) throws ClassNotFoundException { + for (String name : args) { + Class c = Class.forName(name); + printClass(c); + } + } +} + diff --git a/src/test/java/org/scijava/event/bushe/MyData.java b/src/test/java/org/scijava/event/bushe/MyData.java new file mode 100644 index 000000000..43be650c8 --- /dev/null +++ b/src/test/java/org/scijava/event/bushe/MyData.java @@ -0,0 +1,17 @@ +package org.scijava.event.bushe; + +/** + * Intended to answer this post: + * https://eventbus.dev.java.net/servlets/ProjectForumMessageView?messageID=30702&forumID=1834 + */ +public class MyData { + private String classification = "foo"; + + public String getClassification() { + return classification; + } + + public void setClassification(String classification) { + this.classification = classification; + } +} diff --git a/src/test/java/org/scijava/event/bushe/SubscriberForTest.java b/src/test/java/org/scijava/event/bushe/SubscriberForTest.java new file mode 100644 index 000000000..2fac88e57 --- /dev/null +++ b/src/test/java/org/scijava/event/bushe/SubscriberForTest.java @@ -0,0 +1,43 @@ +package org.scijava.event.bushe; + +import java.util.Date; + +/** + * @author Michael Bushe + * @since Nov 19, 2005 11:01:06 PM + */ +public class SubscriberForTest implements EventSubscriber { + private boolean throwException; + private Long waitTime; + private EBTestCounter testDefaultEventService; + Date callTime = null; + + public SubscriberForTest(EBTestCounter testDefaultEventService, Long waitTime) { + this.testDefaultEventService = testDefaultEventService; + this.waitTime = waitTime; + } + + public SubscriberForTest(EBTestCounter testDefaultEventService, boolean throwException) { + this.testDefaultEventService = testDefaultEventService; + this.throwException = throwException; + } + + public void onEvent(Object evt) { + callTime = new Date(); + if (waitTime != null) { + try { + Thread.sleep(waitTime.longValue()); + } catch (InterruptedException e) { + } + } + testDefaultEventService.eventsHandledCount++; + if (throwException) { + testDefaultEventService.subscribeExceptionCount++; + throw new IllegalArgumentException(); + } + } + + public boolean equals(Object obj) { + return (this == obj); + } +} diff --git a/src/test/java/org/scijava/event/bushe/SubscriberForTesting.java b/src/test/java/org/scijava/event/bushe/SubscriberForTesting.java new file mode 100644 index 000000000..a0a0fdf2c --- /dev/null +++ b/src/test/java/org/scijava/event/bushe/SubscriberForTesting.java @@ -0,0 +1,5 @@ +package org.scijava.event.bushe; + +public interface SubscriberForTesting { + long getTimesCalled(); +} diff --git a/src/test/java/org/scijava/event/bushe/TestPerformance.java b/src/test/java/org/scijava/event/bushe/TestPerformance.java new file mode 100644 index 000000000..a936200ee --- /dev/null +++ b/src/test/java/org/scijava/event/bushe/TestPerformance.java @@ -0,0 +1,75 @@ +package org.scijava.event.bushe; + +import junit.framework.TestCase; +import junit.framework.Assert; + +import javax.swing.*; +import java.awt.*; +import java.awt.List; +import java.util.*; + +/** + * For proving performance. + */ +public class TestPerformance extends TestCase { + private EventSubscriber doNothingSubscriber = new EventSubscriber() { + public void onEvent(Object event) { + } + }; + + private EventTopicSubscriber doNothingTopicSubscriber = new EventTopicSubscriber() { + public void onEvent(String topic, Object payload) { + } + }; + + public void testClassPerformance() { + ThreadSafeEventService eventService = new ThreadSafeEventService(); + Class[] classes = {Color.class, String.class, JTextField.class, List.class, JButton.class, + Boolean.class, Integer.class, Boolean.class, Set.class, Date.class}; + Object[] payloads = {Color.BLUE, "foo", new JTextField(), new ArrayList(), new JButton(), + Boolean.TRUE, 35, 36L, new HashSet(), new Date()}; + for (Class aClass : classes) { + eventService.subscribe(aClass, doNothingSubscriber); + } + + long start = System.currentTimeMillis(); + int count = 100000; + for (int i=0; i < count; i++) { + for (Object payload : payloads) { + eventService.publish(payload); + } + } + long end = System.currentTimeMillis(); + long duration = (end - start)/1000; + int numPubs = count * payloads.length; + System.out.println("Time for "+ numPubs +" publications with subscribers to "+classes.length + +" different classes subscribed to was "+ duration +" s. Average:"+((double)duration/(double)numPubs)); + Assert.assertTrue("Things are slowing down, "+numPubs+" class publications used to take 3.3 seconds, it now takes " +duration, duration < 7); + } + + public void testStringPerformance() { + ThreadSafeEventService eventService = new ThreadSafeEventService(); + String[] strings = {"Color", "String", "JTextField", "List", "JButton", + "Boolean", "Integer", "Boolean", "Set", "Date"}; + Object[] payloads = {Color.BLUE, "foo", new JTextField(), new ArrayList(), new JButton(), + Boolean.TRUE, 35, 36L, new HashSet(), new Date()}; + for (String aString : strings) { + eventService.subscribe(aString, doNothingTopicSubscriber); + } + + long start = System.currentTimeMillis(); + int count = 100000; + for (int i=0; i < count; i++) { + for (int j=0; j < strings.length; j++) { + eventService.publish(strings[j], payloads[j]); + } + } + long end = System.currentTimeMillis(); + long duration = (end - start)/1000; + int numPubs = count * payloads.length; + System.out.println("Time for "+ numPubs +" topic publications with topic subscribers to "+ strings.length + +" different strings subscribed to was "+ duration +" s. Average:"+((double)duration/(double)numPubs)); + Assert.assertTrue("Things are slowing down, "+numPubs+" string publications used to take 1.3 seconds, it now takes "+duration, duration < 4); + } + +} diff --git a/src/test/java/org/scijava/event/bushe/TopicSubscriberForTest.java b/src/test/java/org/scijava/event/bushe/TopicSubscriberForTest.java new file mode 100644 index 000000000..3b7836df8 --- /dev/null +++ b/src/test/java/org/scijava/event/bushe/TopicSubscriberForTest.java @@ -0,0 +1,35 @@ +package org.scijava.event.bushe; + +/** + * @author Michael Bushe + * @since Nov 19, 2005 11:00:53 PM + */ +public class TopicSubscriberForTest implements EventTopicSubscriber { + private boolean throwException; + private Long waitTime; + private EBTestCounter testDefaultEventService; + + public TopicSubscriberForTest(EBTestCounter testDefaultEventService, Long waitTime) { + this.testDefaultEventService = testDefaultEventService; + this.waitTime = waitTime; + } + + public TopicSubscriberForTest(EBTestCounter testDefaultEventService, boolean throwException) { + this.testDefaultEventService = testDefaultEventService; + this.throwException = throwException; + } + + public void onEvent(String topic, Object evt) { + if (waitTime != null) { + try { + Thread.sleep(waitTime.longValue()); + } catch (InterruptedException e) { + } + } + testDefaultEventService.eventsHandledCount++; + if (throwException) { + testDefaultEventService.subscribeExceptionCount++; + throw new IllegalArgumentException(); + } + } +} diff --git a/src/test/java/org/scijava/event/bushe/VetoEventListenerForTest.java b/src/test/java/org/scijava/event/bushe/VetoEventListenerForTest.java new file mode 100644 index 000000000..81a481185 --- /dev/null +++ b/src/test/java/org/scijava/event/bushe/VetoEventListenerForTest.java @@ -0,0 +1,24 @@ +package org.scijava.event.bushe; + +/** + * @author Michael Bushe + * @since Nov 19, 2005 11:00:42 PM + */ +public class VetoEventListenerForTest implements VetoEventListener { + private boolean throwException; + + public VetoEventListenerForTest() { + this(false); + } + + public VetoEventListenerForTest(boolean throwException) { + this.throwException = throwException; + } + + public boolean shouldVeto(Object evt) { + if (throwException) { + throw new IllegalArgumentException("veto ex"); + } + return true; + } +} diff --git a/src/test/java/org/scijava/event/bushe/VetoTopicEventListenerForTest.java b/src/test/java/org/scijava/event/bushe/VetoTopicEventListenerForTest.java new file mode 100644 index 000000000..af8bf79e5 --- /dev/null +++ b/src/test/java/org/scijava/event/bushe/VetoTopicEventListenerForTest.java @@ -0,0 +1,24 @@ +package org.scijava.event.bushe; + +/** + * @author Michael Bushe + * @since Nov 19, 2005 11:00:42 PM + */ +public class VetoTopicEventListenerForTest implements VetoTopicEventListener { + private boolean throwException; + + public VetoTopicEventListenerForTest() { + this(false); + } + + VetoTopicEventListenerForTest(boolean throwException) { + this.throwException = throwException; + } + + public boolean shouldVeto(String topic, Object data) { + if (throwException) { + throw new IllegalArgumentException("veto ex"); + } + return true; + } +} diff --git a/src/test/java/org/scijava/input/AcceleratorTest.java b/src/test/java/org/scijava/input/AcceleratorTest.java new file mode 100644 index 000000000..762489389 --- /dev/null +++ b/src/test/java/org/scijava/input/AcceleratorTest.java @@ -0,0 +1,82 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.input; + +import org.junit.Test; +import org.scijava.util.PlatformUtils; + +import static org.junit.Assert.*; + +/** + * Tests {@link Accelerator}. + * + * @author Curtis Rueden + */ +public class AcceleratorTest { + + /** Tests {@link Accelerator#create}. */ + @Test + public void testCreate() { + assertAccelerator("*", KeyCode.ASTERISK, false, false, false, false, false); + assertAccelerator("0", KeyCode.NUM0, false, false, false, false, false); + assertAccelerator("NUMPAD_0", KeyCode.NUMPAD_0, false, false, false, false, false); + assertAccelerator("+", KeyCode.PLUS, false, false, false, false, false); + assertAccelerator("shift minus", KeyCode.MINUS, false, false, false, false, true); + assertAccelerator("ctrl shift +", KeyCode.PLUS, false, false, true, false, true); + assertAccelerator("meta /", KeyCode.SLASH, false, false, false, true, false); + assertAccelerator("alt altGr ctrl meta shift a", KeyCode.A, true, true, true, true, true); + + // Test caret shortcut symbol. + final boolean macos = PlatformUtils.isMac(); + assertAccelerator("^Z", KeyCode.Z, false, false, !macos, macos, false); + } + + private void assertAccelerator(String shortcut, + KeyCode keyCode, + final boolean alt, + final boolean altGr, + final boolean ctrl, + final boolean meta, + final boolean shift) + { + Accelerator acc = Accelerator.create(shortcut); + assertEquals(acc.getKeyCode(), keyCode); + InputModifiers mods = acc.getModifiers(); + assertNotNull(mods); + assertEquals(alt, mods.isAltDown()); + assertEquals(altGr, mods.isAltGrDown()); + assertEquals(ctrl, mods.isCtrlDown()); + assertEquals(meta, mods.isMetaDown()); + assertEquals(shift, mods.isShiftDown()); + assertFalse(mods.isLeftButtonDown()); + assertFalse(mods.isMiddleButtonDown()); + assertFalse(mods.isRightButtonDown()); + } +} diff --git a/src/test/java/org/scijava/input/KeyCodeTest.java b/src/test/java/org/scijava/input/KeyCodeTest.java new file mode 100644 index 000000000..e094dc551 --- /dev/null +++ b/src/test/java/org/scijava/input/KeyCodeTest.java @@ -0,0 +1,91 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.input; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +/** + * Tests {@link KeyCode}. + * + * @author Curtis Rueden + */ +public class KeyCodeTest { + + @Test + public void testGetInt() { + assertEquals(KeyCode.ENTER, KeyCode.get(0x0a)); + assertEquals(KeyCode.PLUS, KeyCode.get(0x0209)); + assertEquals(KeyCode.NUM0, KeyCode.get(0x30)); + assertEquals(KeyCode.NUMPAD_0, KeyCode.get(0x60)); + assertEquals(KeyCode.A, KeyCode.get(0x41)); + assertEquals(KeyCode.Z, KeyCode.get(0x5a)); + + assertEquals(KeyCode.UNDEFINED, KeyCode.get(0xaaaa)); + assertEquals(KeyCode.UNDEFINED, KeyCode.get(0xffff)); + } + + @Test + public void testGetChar() { + assertEquals(KeyCode.ENTER, KeyCode.get('\n')); + assertEquals(KeyCode.ENTER, KeyCode.get('\r')); + assertEquals(KeyCode.PLUS, KeyCode.get('+')); + assertEquals(KeyCode.NUM0, KeyCode.get('0')); + assertEquals(KeyCode.A, KeyCode.get('a')); + assertEquals(KeyCode.A, KeyCode.get('A')); + assertEquals(KeyCode.Z, KeyCode.get('z')); + assertEquals(KeyCode.Z, KeyCode.get('Z')); + + assertEquals(KeyCode.UNDEFINED, KeyCode.get('\0')); + + // The following should maybe be considered a bug. + assertEquals(KeyCode.UNDEFINED, KeyCode.get('|')); + } + + @Test + public void testGetString() { + assertEquals(KeyCode.PLUS, KeyCode.get("PLUS")); + assertEquals(KeyCode.NUM0, KeyCode.get("NUM0")); + assertEquals(KeyCode.NUMPAD_0, KeyCode.get("NUMPAD_0")); + assertEquals(KeyCode.A, KeyCode.get("A")); + assertEquals(KeyCode.Z, KeyCode.get("Z")); + + // The next ones should fall back to get(char). + assertEquals(KeyCode.NUM0, KeyCode.get("0")); + assertEquals(KeyCode.A, KeyCode.get("a")); + assertEquals(KeyCode.Z, KeyCode.get("z")); + + assertEquals(KeyCode.UNDEFINED, KeyCode.get("UNDEFINED")); + assertEquals(KeyCode.UNDEFINED, KeyCode.get("")); + assertEquals(KeyCode.UNDEFINED, KeyCode.get("aa")); + assertEquals(KeyCode.UNDEFINED, KeyCode.get("asdf")); + } +} diff --git a/src/test/java/org/scijava/io/ByteArrayByteBankTest.java b/src/test/java/org/scijava/io/ByteArrayByteBankTest.java new file mode 100644 index 000000000..e648071ca --- /dev/null +++ b/src/test/java/org/scijava/io/ByteArrayByteBankTest.java @@ -0,0 +1,52 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io; + +import org.junit.Test; + +/** + * Tests {@link ByteArrayByteBank} + * + * @author Gabriel Einsdorf + * @see ByteBankTest + */ +public class ByteArrayByteBankTest extends ByteBankTest { + + @Override + public ByteBank createByteBank() { + return new ByteArrayByteBank(); + } + + @Test(expected = ArrayIndexOutOfBoundsException.class) + public void testCreateByteBankWithCapacity() { + ByteArrayByteBank bank = new ByteArrayByteBank(50); + bank.getByte(0); + } +} diff --git a/src/test/java/org/scijava/io/ByteBankTest.java b/src/test/java/org/scijava/io/ByteBankTest.java new file mode 100644 index 000000000..8956d1b01 --- /dev/null +++ b/src/test/java/org/scijava/io/ByteBankTest.java @@ -0,0 +1,158 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; + +/** + * Abstract superclass for testing {@link ByteBank} implementations. + * + * @author Gabriel Einsdorf + */ +public abstract class ByteBankTest { + + private static byte[] testBytes = { 0, -1, 2, -3, 4, 120, -128, 127, 32, 42 }; + private ByteBank bank; + + /** + * @return the ByteBank implementation to test + */ + public abstract ByteBank createByteBank(); + + @Before + public void setup() { + bank = createByteBank(); + } + + @Test + public void testSetGetBytesArray() { + // read in full array + bank.setBytes(0l, testBytes.clone(), 0, testBytes.length); + + // read out full array + assertEqualRead(testBytes.length, 0); + assertEqualRead(testBytes.length - 4, 2); + } + + @Test + public void testToByteArray() { + // read in full array + bank.setBytes(0l, testBytes.clone(), 0, testBytes.length); + + assertArrayEquals(testBytes, bank.toByteArray()); + + byte[] actuals = bank.toByteArray(0, testBytes.length); + assertArrayEquals(actuals, testBytes); + } + + @Test + public void testSetGetBytesPartialArray() { + // read in the partial array + bank.setBytes(0l, testBytes, 0, testBytes.length - 4); + + // read out the partial array + assertEqualRead(testBytes.length - 4, 0); + assertEqualRead(testBytes.length - 4, 2); + } + + @Test + public void testSetGetByte() { + final int numElements = 200_000; + for (int i = 0; i < numElements; i++) { + bank.setByte(i, (byte) i); + } + + for (int i = 0; i < numElements; i++) { + assertEquals((byte) i, bank.getByte(i)); + } + } + + @Test + public void testClear() { + bank.setBytes(0, testBytes, 0, testBytes.length); + assertEquals(testBytes.length, bank.size()); + + bank.clear(); + assertEquals(0, bank.size()); + } + + @Test + public void testAppendBytes() { + // simple append + bank.appendBytes(testBytes, testBytes.length); + assertEqualRead(testBytes.length, 0); + + // append to buffer that already contains data + bank.clear(); + bank.setByte(0l, (byte) 42); + bank.setByte(1l, (byte) 43); + bank.appendBytes(testBytes, testBytes.length); + + final byte[] expected = new byte[testBytes.length + 2]; + expected[0] = 42; + expected[1] = 43; + System.arraycopy(testBytes, 0, expected, 2, testBytes.length); + + final byte[] actuals = new byte[expected.length]; + bank.getBytes(0, actuals); + + assertArrayEquals(expected, actuals); + } + + /** + * Asserts that {@link #bank} contains the same bytes as {@link #testBytes}, + * allows to specify an offset. + * + * @param length how many bytes (starting from the offset) of + * {@link #testBytes} are tested, this allows to test partial reads. + * @param offset the offset position + */ + private void assertEqualRead(final int length, final int offset) { + // read from offset up to the length of the given array + final byte[] bytes = new byte[length]; + final int read = bank.getBytes(offset, bytes); + + final byte[] expected = new byte[length]; + System.arraycopy(testBytes, offset, expected, 0, read); + assertArrayEquals(expected, bytes); + + // read from offset to offset + final byte[] offsetBytes = new byte[testBytes.length]; + final int readOffset = bank.getBytes(offset, offsetBytes, offset, length); + + final byte[] offsetExpected = new byte[testBytes.length]; + System.arraycopy(testBytes, offset, offsetExpected, offset, readOffset); + assertArrayEquals(expected, bytes); + } +} diff --git a/src/test/java/org/scijava/io/DataHandleTest.java b/src/test/java/org/scijava/io/DataHandleTest.java deleted file mode 100644 index 185f47881..000000000 --- a/src/test/java/org/scijava/io/DataHandleTest.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * #%L - * SciJava Common shared library for SciJava software. - * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ - -package org.scijava.io; - -import static org.junit.Assert.assertEquals; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import org.junit.Test; -import org.scijava.Context; -import org.scijava.util.Bytes; - -/** - * Abstract base class for {@link DataHandle} implementation tests. - * - * @author Curtis Rueden - */ -public abstract class DataHandleTest { - - private static final byte[] BYTES = { // - 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '\n', // - 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, -128, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, // - 125, 127, -127, -125, -3, -2, -1 }; - - // -- Test methods -- - - @Test - public void testDataHandle() throws IOException { - final Context context = new Context(DataHandleService.class); - final DataHandleService dataHandleService = - context.service(DataHandleService.class); - - final Location loc = createLocation(); - try (final DataHandle handle = // - dataHandleService.create(loc)) - { - assertEquals(getExpectedHandleType(), handle.getClass()); - - checkReads(handle); - checkWrites(handle); - } - } - - // -- DataHandleTest methods -- - - public abstract Class> getExpectedHandleType(); - - public abstract Location createLocation() throws IOException; - - // -- Internal methods -- - - protected void populateData(final OutputStream out) throws IOException { - out.write(BYTES); - out.close(); - } - - protected void checkReads(final DataHandle handle) - throws IOException - { - assertEquals(0, handle.offset()); - assertEquals(BYTES.length, handle.length()); - assertEquals("UTF-8", handle.getEncoding()); - assertEquals(ByteOrder.BIG_ENDIAN, handle.getOrder()); - assertEquals(false, handle.isLittleEndian()); - - // test read() - for (int i = 0; i < BYTES.length; i++) { - assertEquals(msg(i), 0xff & BYTES[i], handle.read()); - } - assertEquals(-1, handle.read()); - handle.seek(10); - assertEquals(10, handle.offset()); - assertEquals(BYTES[10], handle.read()); - - // test read(byte[]) - final byte[] buf = new byte[10]; - handle.seek(1); - assertBytesMatch(1, handle.read(buf), buf); - - // test read(ByteBuffer) - Arrays.fill(buf, (byte) 0); - final ByteBuffer byteBuffer = ByteBuffer.wrap(buf); - handle.seek(2); - assertBytesMatch(2, handle.read(byteBuffer), byteBuffer.array()); - - // test readByte() - handle.seek(0); - for (int i = 0; i < BYTES.length; i++) { - assertEquals(msg(i), BYTES[i], handle.readByte()); - } - - // test readShort() - handle.seek(0); - for (int i = 0; i < BYTES.length / 2; i += 2) { - assertEquals(msg(i), Bytes.toShort(BYTES, i, false), handle.readShort()); - } - - // test readInt() - handle.seek(0); - for (int i = 0; i < BYTES.length / 4; i += 4) { - assertEquals(msg(i), Bytes.toInt(BYTES, i, false), handle.readInt()); - } - - // test readLong() - handle.seek(0); - for (int i = 0; i < BYTES.length / 8; i += 8) { - assertEquals(msg(i), Bytes.toLong(BYTES, i, false), handle.readLong()); - } - - // test readFloat() - handle.seek(0); - for (int i = 0; i < BYTES.length / 4; i += 4) { - assertEquals(msg(i), Bytes.toFloat(BYTES, i, false), handle.readFloat(), - 0); - } - - // test readDouble() - handle.seek(0); - for (int i = 0; i < BYTES.length / 8; i += 8) { - assertEquals(msg(i), Bytes.toDouble(BYTES, i, false), - handle.readDouble(), 0); - } - - // test readBoolean() - handle.seek(0); - for (int i = 0; i < BYTES.length; i++) { - assertEquals(msg(i), BYTES[i] == 0 ? false : true, handle.readBoolean()); - } - - // test readChar() - handle.seek(0); - for (int i = 0; i < BYTES.length / 2; i += 2) { - assertEquals(msg(i), (char) Bytes.toInt(BYTES, i, 2, false), handle - .readChar()); - } - - // test readFully(byte[]) - Arrays.fill(buf, (byte) 0); - handle.seek(3); - handle.readFully(buf); - assertBytesMatch(3, buf.length, buf); - - // test readCString() - _includes_ the null terminator! - handle.seek(16); - assertBytesMatch(16, 7, handle.readCString().getBytes()); - - // test readLine() - _excludes_ the newline terminator! - handle.seek(7); - assertBytesMatch(7, 5, handle.readLine().getBytes()); - - // test readString(String) - _includes_ the matching terminator! - handle.seek(7); - assertBytesMatch(7, 5, handle.readString("abcdefg").getBytes()); - - // test findString(String) - _includes_ the matching terminator! - handle.seek(1); - assertBytesMatch(1, 11, handle.findString("world").getBytes()); - } - - protected void checkWrites(final DataHandle handle) - throws IOException - { - final byte[] copy = BYTES.clone(); - - // change the data - handle.seek(7); - final String splice = "there"; - for (int i = 0; i < splice.length(); i++) { - final char c = splice.charAt(i); - handle.write(c); - copy[7 + i] = (byte) c; - } - - // verify the changes - handle.seek(0); - for (int i = 0; i < copy.length; i++) { - assertEquals(msg(i), 0xff & copy[i], handle.read()); - } - } - - // -- Helper methods -- - - private void assertBytesMatch(final int offset, final int length, - final byte[] b) - { - assertEquals(length, b.length); - for (int i = 0; i < length; i++) { - assertEquals(msg(i), BYTES[i + offset], b[i]); - } - } - - private String msg(final int i) { - return "[" + i + "]:"; - } - -} diff --git a/src/test/java/org/scijava/io/IOServiceTest.java b/src/test/java/org/scijava/io/IOServiceTest.java new file mode 100644 index 000000000..6ac3b1cb9 --- /dev/null +++ b/src/test/java/org/scijava/io/IOServiceTest.java @@ -0,0 +1,81 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.scijava.io; + +import org.junit.Test; +import org.scijava.Context; +import org.scijava.io.location.FileLocation; +import org.scijava.plugin.PluginInfo; +import org.scijava.text.AbstractTextFormat; +import org.scijava.text.TextFormat; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class IOServiceTest { + + @Test + public void testTextFile() throws IOException { + // create context, add dummy text format + final Context ctx = new Context(); + ctx.getPluginIndex().add(new PluginInfo<>(DummyTextFormat.class, TextFormat.class)); + final IOService io = ctx.getService(IOService.class); + + // open text file from resources as String + String localFile = getClass().getResource("test.txt").getPath(); + Object obj = io.open(localFile); + assertNotNull(obj); + String content = obj.toString(); + assertTrue(content.contains("content")); + + // open text file from resources as FileLocation + obj = io.open(new FileLocation(localFile)); + assertNotNull(obj); + assertEquals(content, obj.toString()); + } + + + public static class DummyTextFormat extends AbstractTextFormat { + + @Override + public List getExtensions() { + return Collections.singletonList("txt"); + } + + @Override + public String asHTML(String text) { + return text; + } + } +} diff --git a/src/test/java/org/scijava/io/TypedIOServiceTest.java b/src/test/java/org/scijava/io/TypedIOServiceTest.java new file mode 100644 index 000000000..d1268e2e3 --- /dev/null +++ b/src/test/java/org/scijava/io/TypedIOServiceTest.java @@ -0,0 +1,77 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.scijava.io; + +import org.junit.Test; +import org.scijava.Context; +import org.scijava.plugin.PluginInfo; +import org.scijava.text.AbstractTextFormat; +import org.scijava.text.TextFormat; +import org.scijava.text.io.TextIOService; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class TypedIOServiceTest { + + @Test + public void testTextFile() throws IOException { + // create context, add dummy text format + final Context ctx = new Context(); + ctx.getPluginIndex().add(new PluginInfo<>(DummyTextFormat.class, TextFormat.class)); + + // try to get the TextIOService + final TextIOService io = ctx.service(TextIOService.class); + assertNotNull(io); + + // open text file from resources as String + String localFile = getClass().getResource("test.txt").getPath(); + String obj = io.open(localFile); + assertNotNull(obj); + assertTrue(obj.contains("content")); + } + + public static class DummyTextFormat extends AbstractTextFormat { + + @Override + public List getExtensions() { + return Collections.singletonList("txt"); + } + + @Override + public String asHTML(String text) { + return text; + } + + } +} diff --git a/src/test/java/org/scijava/io/event/DataEventTest.java b/src/test/java/org/scijava/io/event/DataEventTest.java new file mode 100644 index 000000000..5318ea04e --- /dev/null +++ b/src/test/java/org/scijava/io/event/DataEventTest.java @@ -0,0 +1,58 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.scijava.io.event; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.IOException; + +import org.junit.Test; + +public class DataEventTest { + + @Test + public void testDeprecatedMethods() throws IOException { + File tmpFile = File.createTempFile("path", "txt"); + tmpFile.deleteOnExit(); + String localPath = tmpFile.getAbsolutePath(); + Object obj = null; + DataOpenedEvent openedEvent = new DataOpenedEvent(localPath, obj); + DataSavedEvent savedEvent = new DataSavedEvent(localPath, obj); + assertEquals(localPath, openedEvent.getSource()); + assertEquals(localPath, savedEvent.getDestination()); + +// String remotepath = "https://remote.org/path.txt"; +// openedEvent = new DataOpenedEvent(remotepath, obj); +// savedEvent = new DataSavedEvent(remotepath, obj); +// assertEquals(remotepath, openedEvent.getSource()); +// assertEquals(remotepath, savedEvent.getDestination()); + } + +} diff --git a/src/test/java/org/scijava/io/FileHandleTest.java b/src/test/java/org/scijava/io/handle/BytesHandleTest.java similarity index 72% rename from src/test/java/org/scijava/io/FileHandleTest.java rename to src/test/java/org/scijava/io/handle/BytesHandleTest.java index ed8effa58..23c8631bc 100644 --- a/src/test/java/org/scijava/io/FileHandleTest.java +++ b/src/test/java/org/scijava/io/handle/BytesHandleTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -29,31 +27,31 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.handle; -import java.io.File; -import java.io.FileOutputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import org.scijava.io.location.BytesLocation; +import org.scijava.io.location.Location; + /** - * Tests {@link FileHandle}. + * Tests {@link BytesHandle}. * * @author Curtis Rueden */ -public class FileHandleTest extends DataHandleTest { +public class BytesHandleTest extends DataHandleTest { @Override public Class> getExpectedHandleType() { - return FileHandle.class; + return BytesHandle.class; } @Override public Location createLocation() throws IOException { - // create and populate a temp file - final File tmpFile = File.createTempFile("FileHandleTest", "test-file"); - tmpFile.deleteOnExit(); - populateData(new FileOutputStream(tmpFile)); - return new FileLocation(tmpFile); + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + populateData(out); + return new BytesLocation(out.toByteArray()); } } diff --git a/src/test/java/org/scijava/io/handle/DataHandleEdgeCaseTests.java b/src/test/java/org/scijava/io/handle/DataHandleEdgeCaseTests.java new file mode 100644 index 000000000..4ac6dfe70 --- /dev/null +++ b/src/test/java/org/scijava/io/handle/DataHandleEdgeCaseTests.java @@ -0,0 +1,139 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.handle; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.io.IOException; +import java.util.Arrays; + +import org.junit.Test; +import org.scijava.Context; +import org.scijava.io.location.BytesLocation; +import org.scijava.io.location.Location; + +/** + * Additional Tests for edge case behavior of {@link DataHandle}. + * + * @author Gabriel Einsdorf + */ +public class DataHandleEdgeCaseTests { + + private static final byte[] BYTES = { // + 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '\n', // + 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, -128, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, // + 125, 127, -127, -125, -3, 'h', 'e' }; + + /** + * Test to ensure {@link DataHandle#findString(String...)} and + * {@link DataHandle#readCString()} work with {@link DataHandle} + * implementations that have unknown length. + * + * @throws IOException + */ + @Test + public void testFindStringsOnUnknonwLengthHandle() throws IOException { + final Context ctx = new Context(); + final DataHandleService dhs = ctx.getService(DataHandleService.class); + final DummyHandle dummy = new DummyHandle(dhs.create(new BytesLocation( + BYTES))); + + assertEquals("Hello,", dummy.findString(",")); + assertEquals(" world\n", dummy.findString("\n")); + + dummy.seek(41); + assertEquals("he", dummy.findString("\n")); + + dummy.seek(16); + assertArrayEquals(Arrays.copyOfRange(BYTES, 16, 23), dummy.readCString() + .getBytes()); + dummy.seek(42); + assertNull(dummy.readCString()); + } + + private class DummyHandle extends AbstractHigherOrderHandle { + + public DummyHandle(final DataHandle handle) { + super(handle); + } + + @Override + public long length() throws IOException { + return -1; + } + + @Override + public long offset() throws IOException { + return handle().offset(); + } + + @Override + public void seek(final long pos) throws IOException { + handle().seek(pos); + } + + @Override + public void setLength(final long length) throws IOException { + handle().setLength(length); + } + + @Override + public int read(final byte[] b, final int off, final int len) + throws IOException + { + return handle().read(b, off, len); + } + + @Override + public byte readByte() throws IOException { + return handle().readByte(); + } + + @Override + public void write(final int b) throws IOException { + handle().write(b); + } + + @Override + public void write(final byte[] b, final int off, final int len) + throws IOException + { + handle().write(b, off, len); + } + + @Override + protected void cleanup() throws IOException { + // + } + } + +} diff --git a/src/test/java/org/scijava/io/handle/DataHandleTest.java b/src/test/java/org/scijava/io/handle/DataHandleTest.java new file mode 100644 index 000000000..8bdd827a7 --- /dev/null +++ b/src/test/java/org/scijava/io/handle/DataHandleTest.java @@ -0,0 +1,579 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.handle; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.function.Supplier; + +import org.junit.Before; +import org.junit.Test; +import org.scijava.Context; +import org.scijava.io.handle.DataHandle.ByteOrder; +import org.scijava.io.location.Location; +import org.scijava.util.Bytes; + +/** + * Abstract base class for {@link DataHandle} implementation tests. + * + * @author Curtis Rueden + */ +public abstract class DataHandleTest { + + protected static final byte[] BYTES = { // + 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '\n', // + 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, -128, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, // + 125, 127, -127, -125, -3, -2, -1 }; + + protected DataHandleService dataHandleService; + + @Before + public void init() { + final Context context = new Context(DataHandleService.class); + dataHandleService = context.service(DataHandleService.class); + } + + @Test + public void checkSkip() throws IOException { + try (final DataHandle handle = createHandle()) { + handle.seek(0); + handle.skip(10); + assertEquals(10, handle.offset()); + handle.skipBytes(11); + assertEquals(21, handle.offset()); + } + } + + @Test + public void testEndianesSettings() throws IOException { + + try (final DataHandle handle = createHandle()) { + final ByteOrder original = handle.getOrder(); + + handle.setOrder(ByteOrder.BIG_ENDIAN); + assertEquals(ByteOrder.BIG_ENDIAN, handle.getOrder()); + assertTrue(handle.isBigEndian()); + assertFalse(handle.isLittleEndian()); + + handle.setOrder(ByteOrder.LITTLE_ENDIAN); + assertEquals(ByteOrder.LITTLE_ENDIAN, handle.getOrder()); + assertFalse(handle.isBigEndian()); + assertTrue(handle.isLittleEndian()); + + handle.setLittleEndian(false); + assertEquals(ByteOrder.BIG_ENDIAN, handle.getOrder()); + assertTrue(handle.isBigEndian()); + assertFalse(handle.isLittleEndian()); + + handle.setLittleEndian(true); + assertEquals(ByteOrder.LITTLE_ENDIAN, handle.getOrder()); + assertFalse(handle.isBigEndian()); + assertTrue(handle.isLittleEndian()); + + handle.setOrder(original); + } + } + + @Test + public void testReading() throws IOException { + try (final DataHandle handle = createHandle()) { + checkBasicReadMethods(handle, true); + checkEndiannessReading(handle); + } + } + + @Test + public void testWriting() throws IOException { + try (final DataHandle handle = createHandle()) { + checkBasicWriteMethods(handle); + final Location loc = createLocation(); + checkWriteEndianes(() -> dataHandleService.create(loc), + ByteOrder.LITTLE_ENDIAN); + checkWriteEndianes(() -> dataHandleService.create(loc), + ByteOrder.BIG_ENDIAN); + checkAdvancedStringWriting(() -> dataHandleService.create(loc)); + } + } + + public abstract Class> getExpectedHandleType(); + + public abstract Location createLocation() throws IOException; + + // -- Internal methods -- + + /** + * Creates a handle for testing + */ + public DataHandle createHandle() { + Location loc; + try { + loc = createLocation(); + } + catch (final IOException exc) { + throw new RuntimeException(exc); + } + final DataHandle handle = dataHandleService.create(loc); + assertEquals(getExpectedHandleType(), handle.getClass()); + return handle; + } + + /** + * Populates the provided {@link OutputStream} with test data. + * + * @param out the {@link OutputStream} to fill + * @throws IOException + */ + public void populateData(final OutputStream out) throws IOException { + out.write(BYTES); + out.close(); + } + + /** + * Checks basic byte reading methods. + * + * @param handle the handle to test + * @param lengthKnown whether the length of the handle is know + * @throws IOException + */ + public void checkBasicReadMethods( + final DataHandle handle, boolean lengthKnown) throws IOException + { + assertEquals(0, handle.offset()); + assertEquals(lengthKnown ? BYTES.length : -1, handle.length()); + assertEquals("UTF-8", handle.getEncoding()); + + // test read() + for (int i = 0; i < BYTES.length; i++) { + assertEquals(msg(i), 0xff & BYTES[i], handle.read()); + } + assertEquals(-1, handle.read()); + handle.seek(10); + assertEquals(10, handle.offset()); + assertEquals(BYTES[10], handle.read()); + + // test read(byte[]) + final byte[] buf = new byte[10]; + handle.seek(1); + assertBytesMatch(1, handle.read(buf), buf); + + // test readByte() + handle.seek(0); + for (int i = 0; i < BYTES.length; i++) { + assertEquals(msg(i), BYTES[i], handle.readByte()); + } + + // test readUnsignedByte() + handle.seek(0); + for (int i = 0; i < BYTES.length; i++) { + assertEquals(msg(i), BYTES[i] & 0xff, handle.readUnsignedByte()); + } + + // test readFully(byte[]) + Arrays.fill(buf, (byte) 0); + handle.seek(3); + handle.readFully(buf); + assertBytesMatch(3, buf.length, buf); + + // test readCString() - _includes_ the null terminator! + handle.seek(16); + assertBytesMatch(16, 7, handle.readCString().getBytes()); + handle.seek(42); + assertNull(handle.readCString()); + + // test readBoolean + handle.seek(21); + assertTrue(handle.readBoolean()); + assertFalse(handle.readBoolean()); + + // test readLine() - _excludes_ the newline terminator! + handle.seek(7); + assertBytesMatch(7, 5, handle.readLine().getBytes()); + + // test readString(String) - _includes_ the matching terminator! + handle.seek(7); + assertBytesMatch(7, 5, handle.readString("abcdefg").getBytes()); + + // test readString() + handle.seek(7); + assertBytesMatch(7, 5, handle.readString("d").getBytes()); + + // test readString(int + handle.seek(7); + assertBytesMatch(7, 5, handle.readString(5).getBytes()); + + // test findString(String) - _includes_ the matching terminator! + handle.seek(1); + assertBytesMatch(1, 11, handle.findString("world").getBytes()); + + handle.seek(0); + handle.findString(false, "world"); + assertEquals(12, handle.offset()); + + handle.seek(0); + handle.findString(false, "w"); + assertEquals(8, handle.offset()); + } + + /** + * Checks reading methods effected by endianness. Tests both + * {@link ByteOrder#LITTLE_ENDIAN} and {@link ByteOrder#BIG_ENDIAN}. + * + * @param handle the handle to check + * @throws IOException + */ + public void checkEndiannessReading( + final DataHandle handle) throws IOException + { + checkEndiannessReading(handle, ByteOrder.LITTLE_ENDIAN); + checkEndiannessReading(handle, ByteOrder.BIG_ENDIAN); + } + + /** + * Checks reading methods effected by endianness. + * + * @param handle the handle to check + * @param order the {@link ByteOrder} to check + * @throws IOException + */ + public void checkEndiannessReading( + final DataHandle handle, final ByteOrder order) + throws IOException + { + handle.setOrder(order); + handle.seek(0); + final boolean little = order == ByteOrder.LITTLE_ENDIAN; + + // test readChar() + + handle.seek(0); + for (int i = 0; i < BYTES.length / 2; i += 2) { + assertEquals(msg(i), (char) Bytes.toShort(BYTES, i, little), handle + .readChar()); + } + + // test readShort() + handle.seek(0); + for (int i = 0; i < BYTES.length / 2; i += 2) { + assertEquals(msg(i), Bytes.toShort(BYTES, i, little), handle.readShort()); + } + + // test readInt() + handle.seek(0); + for (int i = 0; i < BYTES.length / 4; i += 4) { + assertEquals(msg(i), Bytes.toInt(BYTES, i, little), handle.readInt()); + } + + // test readLong() + handle.seek(0); + for (int i = 0; i < BYTES.length / 8; i += 8) { + assertEquals(msg(i), Bytes.toLong(BYTES, i, little), handle.readLong()); + } + + // test readFloat() + handle.seek(0); + for (int i = 0; i < BYTES.length / 4; i += 4) { + assertEquals(msg(i), Bytes.toFloat(BYTES, i, little), handle.readFloat(), + 0); + } + + // test readDouble() + handle.seek(0); + for (int i = 0; i < BYTES.length / 8; i += 8) { + assertEquals(msg(i), Bytes.toDouble(BYTES, i, little), handle + .readDouble(), 0); + } + } + + /** + * Check basic write methods for bytes. + * + * @param handle the handle to write to and read from + * @throws IOException + */ + public void checkBasicWriteMethods( + final DataHandle handle) throws IOException + { + checkBasicWrites(handle, handle); + } + + /** + * Tests basic write methods for bytes, both provided handles must point to + * the same location! + * + * @param readHandle the handle to read from + * @param writeHandle the handle to write from + * @throws IOException + */ + public void checkBasicWrites( + final DataHandle readHandle, final DataHandle writeHandle) + throws IOException + { + final byte[] copy = BYTES.clone(); + + // change the data + writeHandle.seek(7); + final String splice = "there"; + for (int i = 0; i < splice.length(); i++) { + final char c = splice.charAt(i); + writeHandle.write(c); + copy[7 + i] = (byte) c; + } + + writeHandle.writeBoolean(true); + copy[12] = 1; + writeHandle.writeBoolean(false); + copy[13] = 0; + + writeHandle.writeByte(42); + copy[14] = 42; + + if (writeHandle != readHandle) { + writeHandle.close(); // to ensure data is flushed + } + + // verify the changes + readHandle.seek(0); + for (int i = 0; i < copy.length; i++) { + assertEquals(msg(i), 0xff & copy[i], readHandle.read()); + } + } + + /** + * Checks advanced string writing methods. + * + * @param handleCreator a supplier that creates properly initialized handles + * for reading and writing, all created handles must point to the + * same location! + * @throws IOException + */ + public void checkAdvancedStringWriting( + final Supplier> handleCreator) throws IOException + { + checkAdvancedStringWriting(handleCreator, handleCreator); + } + + /** + * Checks advanced string writing methods. + * + * @param readHandleCreator a supplier that creates properly initialized + * handles for reading, all created handles must point to the same + * location! + * @param writeHandleCreator a supplier that creates properly initialized + * handles for reading, all created handles must point to the same + * location! + * @throws IOException + */ + public void checkAdvancedStringWriting( + final Supplier> readHandleCreator, + final Supplier> writeHandleCreator) throws IOException + { + // test writeUTF() / readUTF() + final String utfTestString = "abc\u00E4\u00FA\u00F6\u00E4" + + "\u00E9\u00EB\u00E5\u00E1\u00F0\u00DF\u00EF\u0153\u0153" + + "\u00F8\u00B6\uD83E\uDD13\uD83C\uDF55\uD83D\uDE0B"; + try (final DataHandle writeHandle = writeHandleCreator.get()) { + writeHandle.writeUTF(utfTestString); + } + try (final DataHandle readHandle = readHandleCreator.get()) { + assertEquals(utfTestString, readHandle.readUTF()); + } + + // test writeLine() + final String testString = "The quick brown fox jumps over the lazy dog."; + try (final DataHandle writeHandle = writeHandleCreator.get()) { + writeHandle.writeLine(testString); + } + try (final DataHandle readHandle = readHandleCreator.get()) { + assertEquals(testString, readHandle.readLine()); + } + + // test writeChars / findString + final String testString2 = "The five boxing wizards jump quickly."; + try (final DataHandle writeHandle = writeHandleCreator.get()) { + writeHandle.writeChars(testString2); + } + try (final DataHandle readHandle = readHandleCreator.get()) { + for (int i = 0; i < testString2.length(); i++) { + assertEquals(testString2.charAt(i), readHandle.readChar()); + } + } + } + + /** + * Checks writing methods affected by endianness. + * + * @param handleCreator a supplier that creates properly initialized + * handles. All created handles must point to the same + * location! + * @param order Byte order to use when writing to the handles. + */ + public void checkWriteEndianes( + final Supplier> handleCreator, final ByteOrder order) + throws IOException + { + checkWriteEndianes(handleCreator, handleCreator, order); + } + + /** + * Checks writing methods affected by endianness. + * + * @param readHandleCreator a supplier that creates properly initialized + * handles for reading. All created handles must point to the same + * location! + * @param writeHandleCreator a supplier that creates properly initialized + * handles for writing. All created handles must point to the same + * location! + * @param order Byte order to use when writing to the handles. + * @throws IOException + */ + public void checkWriteEndianes( + final Supplier> readHandleCreator, + final Supplier> writeHandleCreator, final ByteOrder order) + throws IOException + { + final boolean little = order == ByteOrder.LITTLE_ENDIAN; + + // test writeChar() + try (final DataHandle writeHandle = writeHandleCreator.get()) { + writeHandle.setOrder(order); + for (int i = 0; i < BYTES.length / 2; i += 2) { + writeHandle.writeChar(Bytes.toInt(BYTES, i, 2, little)); + } + } + + try (final DataHandle readHandle = readHandleCreator.get()) { + readHandle.setOrder(order); + for (int i = 0; i < BYTES.length / 2; i += 2) { + assertEquals(msg(i), Bytes.toShort(BYTES, i, little), readHandle + .readChar()); + } + } + + // test writeShort() + try (final DataHandle writeHandle = writeHandleCreator.get()) { + writeHandle.setOrder(order); + for (int i = 0; i < BYTES.length / 2; i += 2) { + writeHandle.writeShort(Bytes.toShort(BYTES, i, little)); + } + } + + try (final DataHandle readHandle = readHandleCreator.get()) { + readHandle.setOrder(order); + for (int i = 0; i < BYTES.length / 2; i += 2) { + assertEquals(msg(i), Bytes.toShort(BYTES, i, little), readHandle + .readShort()); + } + } + + // test writeInt() + try (final DataHandle writeHandle = writeHandleCreator.get()) { + writeHandle.setOrder(order); + for (int i = 0; i < BYTES.length / 4; i += 4) { + writeHandle.writeInt(Bytes.toInt(BYTES, i, little)); + } + } + try (final DataHandle readHandle = readHandleCreator.get()) { + readHandle.setOrder(order); + for (int i = 0; i < BYTES.length / 4; i += 4) { + assertEquals(msg(i), Bytes.toInt(BYTES, i, little), readHandle + .readInt()); + } + } + + // test writeLong() + try (final DataHandle writeHandle = writeHandleCreator.get()) { + writeHandle.setOrder(order); + for (int i = 0; i < BYTES.length / 8; i += 8) { + writeHandle.writeLong(Bytes.toLong(BYTES, i, little)); + } + } + try (final DataHandle readHandle = readHandleCreator.get()) { + readHandle.setOrder(order); + for (int i = 0; i < BYTES.length / 8; i += 8) { + assertEquals(msg(i), Bytes.toLong(BYTES, i, little), readHandle + .readLong()); + } + } + + // test writeFloat() + try (final DataHandle writeHandle = writeHandleCreator.get()) { + writeHandle.setOrder(order); + for (int i = 0; i < BYTES.length / 4; i += 4) { + writeHandle.writeFloat(Bytes.toFloat(BYTES, i, little)); + } + } + try (final DataHandle readHandle = readHandleCreator.get()) { + readHandle.setOrder(order); + for (int i = 0; i < BYTES.length / 4; i += 4) { + assertEquals(msg(i), Bytes.toFloat(BYTES, i, little), readHandle + .readFloat(), 0); + } + } + + // test writeDouble() + try (final DataHandle writeHandle = writeHandleCreator.get()) { + writeHandle.setOrder(order); + for (int i = 0; i < BYTES.length / 8; i += 8) { + writeHandle.writeDouble(Bytes.toDouble(BYTES, i, little)); + } + } + try (final DataHandle readHandle = readHandleCreator.get()) { + readHandle.setOrder(order); + for (int i = 0; i < BYTES.length / 8; i += 8) { + assertEquals(msg(i), Bytes.toDouble(BYTES, i, little), readHandle + .readDouble(), 0); + } + } + } + + // -- Internal methods -- + + public void assertBytesMatch(final int offset, final int length, + final byte[] b) + { + assertEquals(length, b.length); + for (int i = 0; i < length; i++) { + assertEquals(msg(i), BYTES[i + offset], b[i]); + } + } + + // -- Test methods -- + + public String msg(final int i) { + return "[" + i + "]:"; + } + +} diff --git a/src/test/java/org/scijava/io/handle/DataHandlesTest.java b/src/test/java/org/scijava/io/handle/DataHandlesTest.java new file mode 100644 index 000000000..003f25265 --- /dev/null +++ b/src/test/java/org/scijava/io/handle/DataHandlesTest.java @@ -0,0 +1,149 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.handle; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.scijava.Context; +import org.scijava.event.EventService; +import org.scijava.io.location.BytesLocation; +import org.scijava.io.location.Location; +import org.scijava.task.DefaultTask; +import org.scijava.task.Task; +import org.scijava.thread.ThreadService; +import org.scijava.util.MersenneTwisterFast; + +/** + * Tests for utility methods in the {@link DataHandles} class. + * + * @author Gabriel Einsdorf + */ +public class DataHandlesTest { + + private static final int TEST_SIZE = 2_938_740; + private DataHandleService handles; + private Location inFile; + private BytesLocation outFile; + private byte[] data; + private ThreadService threadService; + private EventService eventService; + + @Before + public void classSetup() { + final Context ctx = new Context(DataHandleService.class, + ThreadService.class, EventService.class); + handles = ctx.service(DataHandleService.class); + threadService = ctx.service(ThreadService.class); + eventService = ctx.service(EventService.class); + + data = randomBytes(0xbabebabe); + inFile = new BytesLocation(data); + outFile = new BytesLocation(TEST_SIZE); + } + + @After + public void cleanup() { + handles.context().dispose(); + } + + @Test + public void testDefaultCopy() throws IOException { + try (DataHandle src = handles.create(inFile); + final DataHandle dest = handles.create(outFile)) + { + DataHandles.copy(src, dest); + assertHandleEquals(data, dest); + } + } + + @Test + public void testCopyTask() throws IOException { + try (DataHandle src = handles.create(inFile); + final DataHandle dest = handles.create(outFile)) + { + final Task t = new DefaultTask(threadService, eventService); + DataHandles.copy(src, dest, t); + assertEquals(t.getProgressValue(), src.length()); + assertHandleEquals(data, dest); + } + } + + @Test + public void testCopyLength() throws IOException { + final int sliceSize = 50_000; + try (DataHandle src = handles.create(inFile); + final DataHandle dest = handles.create(outFile)) + { + DataHandles.copy(src, dest, sliceSize); + final byte[] expected = new byte[sliceSize]; + System.arraycopy(data, 0, expected, 0, sliceSize); + assertHandleEquals(expected, dest); + } + } + + @Test + public void testCopyLengthTask() throws IOException { + final int sliceSize = 50_000; + try (DataHandle src = handles.create(inFile); + final DataHandle dest = handles.create(outFile)) + { + final Task t = new DefaultTask(threadService, eventService); + DataHandles.copy(src, dest, sliceSize, t); + assertEquals(t.getProgressValue(), sliceSize); + + final byte[] expected = new byte[sliceSize]; + System.arraycopy(data, 0, expected, 0, sliceSize); + assertHandleEquals(expected, dest); + } + } + + private void assertHandleEquals(final byte[] expected, + final DataHandle handle) throws IOException + { + handle.seek(0); + for (int i = 0; i < expected.length; i++) { + assertEquals(expected[i], handle.readByte()); + } + } + + private byte[] randomBytes(final long seed) { + final MersenneTwisterFast r = new MersenneTwisterFast(seed); + final byte[] ldata = new byte[TEST_SIZE]; + for (int i = 0; i < ldata.length; i++) { + ldata[i] = r.nextByte(); + } + return ldata; + } +} diff --git a/src/test/java/org/scijava/io/handle/FileHandleTest.java b/src/test/java/org/scijava/io/handle/FileHandleTest.java new file mode 100644 index 000000000..83a65cb79 --- /dev/null +++ b/src/test/java/org/scijava/io/handle/FileHandleTest.java @@ -0,0 +1,134 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.handle; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URI; + +import org.junit.Test; +import org.scijava.Context; +import org.scijava.io.location.FileLocation; +import org.scijava.io.location.Location; + +/** + * Tests {@link FileHandle}. + * + * @author Curtis Rueden + */ +public class FileHandleTest extends DataHandleTest { + + @Override + public Class> getExpectedHandleType() { + return FileHandle.class; + } + + @Override + public Location createLocation() throws IOException { + // create and populate a temp file + final File tmpFile = File.createTempFile("FileHandleTest", "test-file"); + tmpFile.deleteOnExit(); + populateData(new FileOutputStream(tmpFile)); + return new FileLocation(tmpFile); + } + + @Test + public void testExists() throws IOException { + final Context ctx = new Context(); + final DataHandleService dhs = ctx.service(DataHandleService.class); + + final File nonExistentFile = // + File.createTempFile("FileHandleTest", "nonexistent-file"); + assertTrue(nonExistentFile.delete()); + assertFalse(nonExistentFile.exists()); + + final FileLocation loc = new FileLocation(nonExistentFile); + try (final DataHandle handle = dhs.create(loc)) { + assertTrue(handle instanceof FileHandle); + assertFalse(handle.exists()); + assertEquals(-1, handle.length()); + + handle.writeBoolean(true); + assertTrue(handle.exists()); + assertEquals(1, handle.length()); + } + + // Clean up. + assertTrue(nonExistentFile.delete()); + } + + @Test + public void testNotCreatedByClose() throws IOException { + final Context ctx = new Context(); + final DataHandleService dhs = ctx.service(DataHandleService.class); + + final File nonExistentFile = // + File.createTempFile("FileHandleTest", "nonexistent-file"); + assertTrue(nonExistentFile.delete()); + assertFalse(nonExistentFile.exists()); + + final FileLocation loc = new FileLocation(nonExistentFile); + try (final DataHandle handle = dhs.create(loc)) { + assertTrue(handle instanceof FileHandle); + assertFalse(handle.exists()); + } + assertFalse(nonExistentFile.exists()); + } + + @Test + public void testNotCreatedByRead() throws IOException { + final Context ctx = new Context(); + final DataHandleService dhs = ctx.service(DataHandleService.class); + + final File nonExistentFile = // + File.createTempFile("FileHandleTest", "none xistent file"); + final URI uri = nonExistentFile.toURI(); + assertTrue(nonExistentFile.delete()); + assertFalse(nonExistentFile.exists()); + + final FileLocation loc = new FileLocation(uri); + try (final DataHandle handle = dhs.create(loc)) { + assertFalse(handle.exists()); + handle.read(); // this will fail as there is no underlying file! + fail("Read successfully from non-existing file!"); + } + catch (final IOException exc) { + // should be thrown + } + assertFalse(nonExistentFile.exists()); + // reading from the non-existing file should not create it! + } +} diff --git a/src/test/java/org/scijava/io/handle/ReadBufferDataHandleMockTest.java b/src/test/java/org/scijava/io/handle/ReadBufferDataHandleMockTest.java new file mode 100644 index 000000000..458faa566 --- /dev/null +++ b/src/test/java/org/scijava/io/handle/ReadBufferDataHandleMockTest.java @@ -0,0 +1,168 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.handle; + +import static org.junit.Assert.assertEquals; +import static org.mockito.AdditionalMatchers.aryEq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; + +import org.junit.Before; +import org.junit.Test; +import org.scijava.io.location.DummyLocation; +import org.scijava.io.location.Location; + +public class ReadBufferDataHandleMockTest { + + private DataHandle mock; + private AbstractDataHandle buf; + private byte[] byteArrayLen10; + private long innerOffset; + + @SuppressWarnings("unchecked") + @Before + public void setup() throws IOException { + innerOffset = 0l; + mock = mock(DataHandle.class); + + // needed to get around type checking in AbstractWrapperPlugin + when(mock.get()).thenReturn(new DummyLocation()); + when(mock.getType()).thenReturn(Location.class); + + buf = new ReadBufferDataHandle(mock, 10, 2); + byteArrayLen10 = new byte[10]; + + // update offset on mock read + when(mock.read(any(byte[].class))).thenAnswer(inv -> { + innerOffset += inv. getArgument(0).length; + return null; + }); + + // update offset on mock seek + doAnswer(inv -> { + innerOffset = inv.getArgument(0); + return null; + }).when(mock).seek(anyLong()); + + // mock offset + when(mock.offset()).then(inv -> { + return innerOffset; + }); + } + + @Test + public void testBufferingSequence() throws IOException { + + // set length of stubbed handle + when(mock.length()).thenReturn(30l); + byte[] value = new byte[10]; + when(mock.read(aryEq(value), eq(0), eq(10))).thenReturn(10); + when(mock.read(aryEq(value), anyInt(), anyInt())).thenReturn(10); + + // read the first byte + buf.read(); + verify(mock, times(0)).seek(0); + // buffer should read a whole page + verify(mock).read(aryEq(byteArrayLen10), eq(0), eq(10)); + + buf.seek(0); + // ensure seek was not called again + verify(mock, times(0)).seek(0); + + when(mock.offset()).thenReturn(10l); + + // read over the edge of the current page + buf.read(new byte[12]); + verify(mock, times(0)).seek(anyLong()); + verify(mock, times(2)).read(aryEq(byteArrayLen10), eq(0), eq(10)); + + assertEquals(12, buf.offset()); + + // read the last page + when(mock.offset()).thenReturn(20l); + buf.read(new byte[12]); + verify(mock, times(0)).seek(anyLong()); + verify(mock, times(3)).read(aryEq(byteArrayLen10), eq(0), eq(10)); + + // first page should no longer be buffered, must be reread in + buf.seek(0); + buf.read(); + verify(mock).seek(0); + verify(mock, times(4)).read(aryEq(byteArrayLen10), eq(0), eq(10)); + } + + /** + * Tests that we do not buffer pages that are not needed and + * + * @throws IOException + */ + @Test + public void testSkipForward() throws IOException { + + // set length of stubbed handle + when(mock.length()).thenReturn(40l); + when(mock.read(any(), anyInt(), anyInt())).thenReturn(10); + + // read the first byte + buf.read(); + verify(mock, times(0)).seek(anyLong()); + verify(mock, times(1)).read(aryEq(byteArrayLen10), eq(0), eq(10)); + + // skip the second page + buf.seek(30l); + buf.read(); + + // read the third page + verify(mock).seek(30l); + verify(mock, times(2)).read(aryEq(byteArrayLen10), eq(0), eq(10)); + when(mock.offset()).thenReturn(40l); + + // go back to already buffered page + buf.seek(0l); + buf.read(); + + verify(mock, times(1)).seek(anyLong()); + + // go back to third page + buf.seek(35); + buf.read(); + verify(mock, times(1)).seek(anyLong()); + verify(mock, times(2)).read(any(), anyInt(), anyInt()); + } +} diff --git a/src/test/java/org/scijava/io/handle/ReadBufferDataHandleTest.java b/src/test/java/org/scijava/io/handle/ReadBufferDataHandleTest.java new file mode 100644 index 000000000..f7bd863f2 --- /dev/null +++ b/src/test/java/org/scijava/io/handle/ReadBufferDataHandleTest.java @@ -0,0 +1,144 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.handle; + +import static org.junit.Assert.assertArrayEquals; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.AbstractMap.SimpleEntry; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import org.junit.Ignore; +import org.junit.Test; +import org.scijava.io.location.BytesLocation; +import org.scijava.io.location.Location; + +/** + * Tests {@link ReadBufferDataHandle} + * + * @author Gabriel Einsdorf + */ +public class ReadBufferDataHandleTest extends DataHandleTest { + + @Test + public void testSmallBuffer() throws IOException { + + final Location loc = createLocation(); + try (final DataHandle handle = // + dataHandleService.create(loc); + AbstractDataHandle bufferedHandle = // + new ReadBufferDataHandle(handle, 5)) + { + // check with small buffersize + checkBasicReadMethods(bufferedHandle, true); + checkEndiannessReading(bufferedHandle); + } + } + + @Test(expected = IOException.class) + public void ensureNotWritable() throws IOException { + createHandle().write(1); + } + + @Override + public DataHandle createHandle() { + Location loc; + try { + loc = createLocation(); + } + catch (final IOException exc) { + throw new RuntimeException(exc); + } + final DataHandle handle = // + dataHandleService.create(loc); + final AbstractDataHandle bufferedHandle = // + new ReadBufferDataHandle(handle, 5); + return bufferedHandle; + } + + @Test + public void testLargeRead() throws Exception { + + final int size = 10_00; + final byte[] bytes = new byte[size]; + final Random r = new Random(42); + r.nextBytes(bytes); + + final Location loc = new BytesLocation(bytes); + try (final DataHandle handle = // + dataHandleService.create(loc); + AbstractDataHandle bufferedHandle = // + new ReadBufferDataHandle(handle, 12, 3)) + { + // check with small buffersize + final byte[] actual = new byte[size]; + + // create evenly sized slice ranges + final int slices = 60; + final int range = (size + slices - 1) / slices; + final List> ranges = new ArrayList<>(); + for (int i = 0; i < slices; i++) { + final int start = range * i; + final int end = range * (i + 1); + ranges.add(new SimpleEntry<>(start, end)); + } + Collections.shuffle(ranges, r); + + for (final SimpleEntry e : ranges) { + bufferedHandle.seek(e.getKey()); + bufferedHandle.read(actual, e.getKey(), e.getValue() - e.getKey()); + } + assertArrayEquals(bytes, actual); + } + } + + @Test + @Override + public void testWriting() throws IOException { + // nothing to do here + } + + @Override + public Class> getExpectedHandleType() { + throw new UnsupportedOperationException(); + } + + @Override + public Location createLocation() throws IOException { + try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) { + populateData(out); + return new BytesLocation(out.toByteArray()); + } + } +} diff --git a/src/test/java/org/scijava/io/handle/WriteBufferDataHandleTest.java b/src/test/java/org/scijava/io/handle/WriteBufferDataHandleTest.java new file mode 100644 index 000000000..380c789bb --- /dev/null +++ b/src/test/java/org/scijava/io/handle/WriteBufferDataHandleTest.java @@ -0,0 +1,110 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.handle; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.function.Supplier; + +import org.junit.Ignore; +import org.junit.Test; +import org.scijava.io.handle.DataHandle.ByteOrder; +import org.scijava.io.location.BytesLocation; +import org.scijava.io.location.Location; + +public class WriteBufferDataHandleTest extends DataHandleTest { + + @Override + public Class> getExpectedHandleType() { + // not needed + throw new UnsupportedOperationException(); + } + + @Override + public Location createLocation() throws IOException { + // not needed + throw new UnsupportedOperationException(); + } + + @Override + public DataHandle createHandle() { + final DataHandle handle = // + dataHandleService.create(new BytesLocation(new byte[42])); + return dataHandleService.writeBuffer(handle); + } + + @Test + @Override + public void testReading() throws IOException { + // nothing to do + } + + @Test + @Override + public void checkSkip() throws IOException { + // nothing to do + } + + @Test(expected = IOException.class) + public void ensureNotReadable() throws IOException { + createHandle().read(); + } + + @Override + @Test + public void testWriting() throws IOException { + final ByteArrayOutputStream os = new ByteArrayOutputStream(42); + populateData(os); + final BytesLocation location = new BytesLocation(os.toByteArray()); + final DataHandle handle = // + dataHandleService.create(location); + final DataHandle writeHandle = dataHandleService.writeBuffer( + handle); + + checkBasicWrites(handle, writeHandle); + } + + @Test + public void testEndiannessWriting() throws IOException { + final BytesLocation location = new BytesLocation(new byte[42]); + final Supplier> readHandleSupplier = + () -> dataHandleService.create(location); + final Supplier> writeHandleSupplier = () -> { + final DataHandle h = dataHandleService.create(location); + return dataHandleService.writeBuffer(h); + }; + + checkWriteEndianes(readHandleSupplier, writeHandleSupplier, + ByteOrder.LITTLE_ENDIAN); + checkWriteEndianes(readHandleSupplier, writeHandleSupplier, + ByteOrder.LITTLE_ENDIAN); + checkAdvancedStringWriting(readHandleSupplier, writeHandleSupplier); + } +} diff --git a/src/test/java/org/scijava/io/location/BytesLocationTest.java b/src/test/java/org/scijava/io/location/BytesLocationTest.java new file mode 100644 index 000000000..28cffaccf --- /dev/null +++ b/src/test/java/org/scijava/io/location/BytesLocationTest.java @@ -0,0 +1,101 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.location; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.scijava.io.ByteArrayByteBank; +import org.scijava.util.ByteArray; + +/** + * Tests {@link BytesLocation}. + * + * @author Curtis Rueden + */ +public class BytesLocationTest { + + /** Tests {@link BytesLocation#BytesLocation(byte[])}. */ + @Test + public void testBytes() { + final byte[] digits = { 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9 }; + final BytesLocation loc = new BytesLocation(digits); + + final byte[] testDigits = new byte[digits.length]; + loc.getByteBank().getBytes(0, testDigits); + assertEquals(digits.length, loc.getByteBank().size()); + assertArrayEquals(digits, testDigits); + } + + /** Tests {@link BytesLocation#BytesLocation(byte[], int, int)}. */ + @Test + public void testBytesOffsetLength() { + final byte[] digits = { 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9 }; + final int offset = 3, length = 5; + final BytesLocation loc = new BytesLocation(digits, offset, length); + + final byte[] testDigits = new byte[digits.length]; + loc.getByteBank().getBytes(0, testDigits); + assertEquals(length, loc.getByteBank().size()); + + final byte[] expectedDigits = new byte[digits.length]; + System.arraycopy(digits, offset, expectedDigits, 0, length); + assertArrayEquals(expectedDigits, testDigits); + } + + /** + * Tests getName() + */ + @Test + public void getNameTest() { + + final BytesLocation loc1 = new BytesLocation(0); + assertEquals(loc1.defaultName(), loc1.getName()); + assertEquals("Location.defaultName", loc1.defaultName()); + + final String expectedName = "test.name"; + BytesLocation loc2 = new BytesLocation(0, expectedName); + assertEquals(expectedName, loc2.getName()); + + loc2 = new BytesLocation(new byte[0], expectedName); + assertEquals(expectedName, loc2.getName()); + + loc2 = new BytesLocation(new ByteArray(), expectedName); + assertEquals(expectedName, loc2.getName()); + + loc2 = new BytesLocation(new ByteArrayByteBank(), expectedName); + assertEquals(expectedName, loc2.getName()); + + loc2 = new BytesLocation(new byte[0], 0, 0, expectedName); + assertEquals(expectedName, loc2.getName()); + } + +} diff --git a/src/test/java/org/scijava/io/location/FileLocationResolverTest.java b/src/test/java/org/scijava/io/location/FileLocationResolverTest.java new file mode 100644 index 000000000..00a68d04a --- /dev/null +++ b/src/test/java/org/scijava/io/location/FileLocationResolverTest.java @@ -0,0 +1,69 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.location; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; + +import org.junit.Test; +import org.scijava.Context; + +/** + * Test for {@link FileLocationResolver}. + * + * @author Gabriel Einsdorf + */ +public class FileLocationResolverTest { + + Context ctx = new Context(LocationService.class); + LocationService resolver = ctx.getService(LocationService.class); + + @Test + public void testStringResolve() throws URISyntaxException { + final String uri = new File(new File(".").getAbsolutePath()) // + .toURI().toString(); + final Location loc = resolver.resolve(uri); + assertTrue(loc instanceof FileLocation); + assertEquals(uri, loc.getURI().toString()); + } + + @Test + public void testURIResolve() throws URISyntaxException { + final URI uri = new File(new File(".").getAbsolutePath()).toURI(); + final Location loc = resolver.resolve(uri); + assertTrue(loc instanceof FileLocation); + assertEquals(uri, loc.getURI()); + } + +} diff --git a/src/test/java/org/scijava/io/FileLocationTest.java b/src/test/java/org/scijava/io/location/FileLocationTest.java similarity index 90% rename from src/test/java/org/scijava/io/FileLocationTest.java rename to src/test/java/org/scijava/io/location/FileLocationTest.java index 60b95e1b0..d558bb338 100644 --- a/src/test/java/org/scijava/io/FileLocationTest.java +++ b/src/test/java/org/scijava/io/location/FileLocationTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -29,13 +27,14 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.location; import static org.junit.Assert.assertEquals; import java.io.File; import org.junit.Test; +import org.scijava.io.location.FileLocation; /** * Tests {@link FileLocation}. diff --git a/src/test/java/org/scijava/io/location/LocationServiceTest.java b/src/test/java/org/scijava/io/location/LocationServiceTest.java new file mode 100644 index 000000000..1943a0f35 --- /dev/null +++ b/src/test/java/org/scijava/io/location/LocationServiceTest.java @@ -0,0 +1,95 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.location; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; + +import org.junit.Test; +import org.scijava.Context; + +/** + * Tests {@link LocationService}. + * + * @author Gabriel Einsdorf + * @author Curtis Rueden + */ +public class LocationServiceTest { + + @Test + public void testResolve() throws URISyntaxException { + final Context ctx = new Context(LocationService.class); + final LocationService loc = ctx.getService(LocationService.class); + + final URI uri = new File(new File(".").getAbsolutePath()).toURI(); + final LocationResolver res = loc.getHandler(uri); + + assertTrue(res instanceof FileLocationResolver); + assertEquals(uri, res.resolve(uri).getURI()); + assertEquals(uri, loc.resolve(uri).getURI()); + assertEquals(uri, loc.resolve(uri.toString()).getURI()); + } + + @Test + public void testResolveWindowsPath() throws URISyntaxException { + final Context ctx = new Context(LocationService.class); + final LocationService loc = ctx.getService(LocationService.class); + + String pSlash = "C:/Windows/FilePath/image.tif"; + final Location locSlash = loc.resolve(pSlash); + assertTrue(locSlash instanceof FileLocation); + + String pBackslash = pSlash.replace('/', '\\'); + final Location locBackslash = loc.resolve(pBackslash); + assertTrue(locBackslash instanceof FileLocation); + + final Location locSlashURI = loc.resolve(new URI(pSlash)); + assertNull(locSlashURI); + } + + @Test + public void testFallBack() throws URISyntaxException { + final Context ctx = new Context(LocationService.class); + final LocationService loc = ctx.getService(LocationService.class); + + final String uri = new File(".").getAbsolutePath(); + final Location res = loc.resolve(uri); + + assertTrue(res instanceof FileLocation); + FileLocation resFile = (FileLocation) res; + assertEquals(uri, resFile.getFile().getAbsolutePath()); + } + +} diff --git a/src/test/java/org/scijava/io/URILocationTest.java b/src/test/java/org/scijava/io/location/URILocationTest.java similarity index 91% rename from src/test/java/org/scijava/io/URILocationTest.java rename to src/test/java/org/scijava/io/location/URILocationTest.java index 866837b04..9bb1bfcc7 100644 --- a/src/test/java/org/scijava/io/URILocationTest.java +++ b/src/test/java/org/scijava/io/location/URILocationTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -29,7 +27,7 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.location; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; @@ -39,6 +37,7 @@ import java.util.Map; import org.junit.Test; +import org.scijava.io.location.URILocation; /** * Tests {@link URILocation}. diff --git a/src/test/java/org/scijava/io/URLLocationTest.java b/src/test/java/org/scijava/io/location/URLLocationTest.java similarity index 89% rename from src/test/java/org/scijava/io/URLLocationTest.java rename to src/test/java/org/scijava/io/location/URLLocationTest.java index 0fde7d087..d3d0e24c0 100644 --- a/src/test/java/org/scijava/io/URLLocationTest.java +++ b/src/test/java/org/scijava/io/location/URLLocationTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -29,7 +27,7 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.location; import static org.junit.Assert.assertSame; @@ -37,6 +35,7 @@ import java.net.URL; import org.junit.Test; +import org.scijava.io.location.URLLocation; /** * Tests {@link URLLocation}. diff --git a/src/test/java/org/scijava/io/nio/ByteBufferByteBankTest.java b/src/test/java/org/scijava/io/nio/ByteBufferByteBankTest.java new file mode 100644 index 000000000..cd904fe7f --- /dev/null +++ b/src/test/java/org/scijava/io/nio/ByteBufferByteBankTest.java @@ -0,0 +1,99 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.io.nio; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.nio.ByteBuffer; +import java.util.function.Function; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; +import org.scijava.io.ByteBank; +import org.scijava.io.ByteBankTest; + +/** + * Tests {@link ByteBufferByteBank}. + * + * @author Curtis Rueden + * @author Gabriel Einsdorf + * @see ByteBankTest + */ +@RunWith(Parameterized.class) +public class ByteBufferByteBankTest extends ByteBankTest { + + @Parameter + public Function supplier; + + @Parameters + public static Object[] params() { + final Function alloc = ByteBuffer::allocate; + final Function allocDirect = + ByteBuffer::allocateDirect; + return new Function[] { alloc, allocDirect }; + } + + @Override + public ByteBank createByteBank() { + return new ByteBufferByteBank(supplier); + } + + @Test + public void testReadOnlyDefault() { + final ByteBufferByteBank bank = new ByteBufferByteBank(); + assertFalse(bank.isReadOnly()); + } + + @Test + public void testReadOnlyAllocate() { + final ByteBufferByteBank bank = new ByteBufferByteBank( + ByteBuffer::allocate); + assertFalse(bank.isReadOnly()); + + final ByteBufferByteBank readOnlyBank = new ByteBufferByteBank( + capacity -> ByteBuffer.allocate(capacity).asReadOnlyBuffer()); + assertTrue(readOnlyBank.isReadOnly()); + } + + @Test + public void testReadOnlyAllocateDirect() { + final ByteBufferByteBank bank = new ByteBufferByteBank( + ByteBuffer::allocateDirect); + assertFalse(bank.isReadOnly()); + + final ByteBufferByteBank readOnlyBank = new ByteBufferByteBank( + capacity -> ByteBuffer.allocateDirect(capacity).asReadOnlyBuffer()); + assertTrue(readOnlyBank.isReadOnly()); + } +} diff --git a/src/test/java/org/scijava/log/CallingClassUtilsTest.java b/src/test/java/org/scijava/log/CallingClassUtilsTest.java new file mode 100644 index 000000000..a2aa73763 --- /dev/null +++ b/src/test/java/org/scijava/log/CallingClassUtilsTest.java @@ -0,0 +1,68 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.log; + +import org.junit.Test; + +import java.util.function.Supplier; + +import static org.junit.Assert.assertEquals; + +/** + * Tests {@link CallingClassUtils}. + * + * @author Matthias Arzt + */ +public class CallingClassUtilsTest { + @Test + public void testGetCallingClass() { + String callingClass = CallingClassUtils.getCallingClassName(); + assertEquals(this.getClass().getName(), callingClass); + } + + @Test + public void testIgnoreAsCallingClass() { + assertEquals(ClassA.class.getName(), ClassA.returnGetCallingClass()); + assertEquals(this.getClass().getName(), ClassB.returnGetCallingClass()); + } + + public static class ClassA { + static String returnGetCallingClass() { + return CallingClassUtils.getCallingClassName(); + } + } + + @IgnoreAsCallingClass + private static class ClassB { + static String returnGetCallingClass() { + return CallingClassUtils.getCallingClassName(); + } + } +} diff --git a/src/test/java/org/scijava/log/DefaultLoggerTest.java b/src/test/java/org/scijava/log/DefaultLoggerTest.java new file mode 100644 index 000000000..d19a8189b --- /dev/null +++ b/src/test/java/org/scijava/log/DefaultLoggerTest.java @@ -0,0 +1,106 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.log; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; + +import org.junit.Before; +import org.junit.Test; + +/** + * Tests {@link DefaultLogger} + * + * @author Matthias Arzt + */ +public class DefaultLoggerTest { + + private Logger logger; + private TestLogListener listener; + + @Before + public void setup() { + logger = new DefaultLogger(message -> {}, LogSource.newRoot(), LogLevel.INFO); + listener = new TestLogListener(); + logger.addLogListener(listener); + } + + @Test + public void test() { + listener.clear(); + + logger.error("Hello World!"); + + assertTrue(listener.hasLogged(m -> m.text().equals("Hello World!"))); + assertTrue(listener.hasLogged(m -> m.level() == LogLevel.ERROR)); + } + + @Test + public void testSubLogger() { + listener.clear(); + Logger sub = logger.subLogger("sub"); + + sub.error("Hello World!"); + + assertTrue(listener.hasLogged(m -> m.source().path().contains("sub"))); + } + + @Test + public void testHierarchicLogger() { + listener.clear(); + Logger subA = logger.subLogger("subA"); + Logger subB = subA.subLogger("subB"); + + subB.error("Hello World!"); + + assertTrue(listener.hasLogged(m -> m.source().equals(subB.getSource()))); + assertEquals(Arrays.asList("subA"), subA.getSource().path()); + assertEquals(Arrays.asList("subA", "subB"), subB.getSource().path()); + } + + @Test + public void testLogForwarding() { + listener.clear(); + Logger sub = logger.subLogger("xyz"); + TestLogListener subListener = new TestLogListener(); + sub.addLogListener(subListener); + + sub.error("Hello World!"); + logger.error("Goodbye!"); + + assertTrue(subListener.hasLogged(m -> m.text().equals("Hello World!"))); + assertFalse(subListener.hasLogged(m -> m.text().equals("Goodbye!"))); + assertTrue(listener.hasLogged(m -> m.text().equals("Hello World!"))); + assertTrue(listener.hasLogged(m -> m.text().equals("Goodbye!"))); + } +} diff --git a/src/test/java/org/scijava/log/LogMessageTest.java b/src/test/java/org/scijava/log/LogMessageTest.java new file mode 100644 index 000000000..40bf7aeab --- /dev/null +++ b/src/test/java/org/scijava/log/LogMessageTest.java @@ -0,0 +1,84 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.log; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; + +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertEquals; + +/** + * Tests {@link LogMessage}. + * + * @author Matthias Arzt + */ +public class LogMessageTest { + + /** Tests {@link LogMessage#toString()}. */ + @Test + public void testToString() { + String nameOfThisMethod = "testToString"; + // setup + LogMessage message = new LogMessage(LogSource.newRoot(), LogLevel.DEBUG, 42, new NullPointerException()); + // process + String s = message.toString(); + //test + Assert.assertTrue("Log message contains level", s.contains(LogLevel.prefix(message.level()))); + Assert.assertTrue("Log message contains msg", s.contains(message.text())); + Assert.assertTrue("Log message contains throwable", s.contains(message.throwable().toString())); + Assert.assertTrue("Log message contains stack trace", s.contains(nameOfThisMethod)); + } + + @Test + public void testToStringOptionalParameters() { + // setup + LogMessage message = new LogMessage(LogSource.newRoot(), LogLevel.WARN, null, null); + + // process + // Can it still format the message if optional parameters are null? + String s = message.toString(); + + // test + Assert.assertTrue("Log message contains level", s.contains(LogLevel.prefix(message.level()))); + } + + @Test + public void testAttachments() { + LogMessage message = new LogMessage(LogSource.newRoot(), LogLevel.ERROR, "Message") ; + assertTrue(message.attachments().isEmpty()); + Object object = new Object(); + message.attach(object); + assertEquals(Collections.singletonList(object), new ArrayList<>(message.attachments())); + } +} diff --git a/src/test/java/org/scijava/log/LogServiceTest.java b/src/test/java/org/scijava/log/LogServiceTest.java index 515e44a1d..b0fe0e83c 100644 --- a/src/test/java/org/scijava/log/LogServiceTest.java +++ b/src/test/java/org/scijava/log/LogServiceTest.java @@ -1,10 +1,8 @@ -/* +/*- * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -28,24 +26,263 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ - package org.scijava.log; -import static org.junit.Assert.assertTrue; -import static org.scijava.log.LogService.WARN; - import org.junit.Test; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.function.BiConsumer; +import java.util.function.Function; + +import static org.junit.Assert.*; + /** * Tests {@link LogService}. - * - * @author Johannes Schindelin + * + * @author Matthias Arzt */ public class LogServiceTest { + + @Test + public void testGetPrefix() { + TestableLogService logService = new TestableLogService(); + assertEquals("[ERROR]", logService.getPrefix(LogLevel.ERROR)); + assertEquals("[TRACE]", logService.getPrefix(LogLevel.TRACE)); + } + + @Test + public void testCompleteLogMethod() { + testCompleteLogMethod("ERROR", (logService, msg, t) -> logService.error(msg, t)); + testCompleteLogMethod("WARN", (logService, msg, t) -> logService.warn(msg, t)); + testCompleteLogMethod("INFO", (logService, msg, t) -> logService.info(msg, t)); + testCompleteLogMethod("DEBUG", (logService, msg, t) -> logService.debug(msg, t)); + testCompleteLogMethod("TRACE", (logService, msg, t) -> logService.trace(msg, t)); + } + + @Test + public void testMessageLogMethod() { + testMessageLogMethod("ERROR", (logService, msg) -> logService.error(msg)); + testMessageLogMethod("WARN", (logService, msg) -> logService.warn(msg)); + testMessageLogMethod("INFO", (logService, msg) -> logService.info(msg)); + testMessageLogMethod("DEBUG", (logService, msg) -> logService.debug(msg)); + testMessageLogMethod("TRACE", (logService, msg) -> logService.trace(msg)); + } + + @Test + public void testExceptionLogMethod() { + testExceptionLogMethod("ERROR", (logService, t) -> logService.error(t)); + testExceptionLogMethod("WARN", (logService, t) -> logService.warn(t)); + testExceptionLogMethod("INFO", (logService, t) -> logService.info(t)); + testExceptionLogMethod("DEBUG", (logService, t) -> logService.debug(t)); + testExceptionLogMethod("TRACE", (logService, t) -> logService.trace(t)); + } + + private void testCompleteLogMethod(String prefix, LogMethodCall logMethodCall) { + testLogMethod(prefix, logMethodCall, true, true); + } + + private void testMessageLogMethod(String prefix, BiConsumer call) { + testLogMethod(prefix, (log, text, exception) -> call.accept(log, text), true, false); + } + + private void testExceptionLogMethod(String prefix, BiConsumer call) { + testLogMethod(prefix, (log, text, exception) -> call.accept(log, exception), false, true); + + } + + private void testLogMethod(String prefix, LogMethodCall logMethodCall, boolean testMessage, boolean testException) { + // setup + TestableLogService logService = new TestableLogService(); + logService.setLevel(LogLevel.TRACE); + String text = "Message"; + NullPointerException exception = new NullPointerException(); + // process + logMethodCall.run(logService, text, exception); + // test + if(testMessage) { + assertTrue(logService.message().contains(prefix)); + assertTrue(logService.message().contains(text)); + } + if(testException) + assertEquals(exception, logService.exception()); + } + + @Test + public void testSetLevel() { + TestableLogService logService = new TestableLogService(); + logService.setLevel(LogLevel.TRACE); + assertEquals(LogLevel.TRACE, logService.getLevel()); + logService.setLevel(LogLevel.ERROR); + assertEquals(LogLevel.ERROR, logService.getLevel()); + } + + @Test + public void testSetClassSpecificLevel() { + TestableLogService logService = new TestableLogService(); + MyTestClass testClass = new MyTestClass(logService); + logService.setLevel(LogLevel.ERROR); + logService.setLevel(MyTestClass.class.getName(), LogLevel.TRACE); + assertEquals(LogLevel.ERROR, logService.getLevel()); + assertEquals(LogLevel.TRACE, testClass.getLevel()); + } + + @Test + public void testIsWarn() { + testIsLevel(LogLevel.ERROR, LogService::isError); + testIsLevel(LogLevel.WARN, LogService::isWarn); + testIsLevel(LogLevel.INFO, LogService::isInfo); + testIsLevel(LogLevel.DEBUG, LogService::isDebug); + testIsLevel(LogLevel.TRACE, LogService::isTrace); + } + + private void testIsLevel(int level, Function isLevel) { + TestableLogService logService = new TestableLogService(); + logService.setLevel(LogLevel.NONE); + assertFalse(isLevel.apply(logService)); + logService.setLevel(level); + assertTrue(isLevel.apply(logService)); + logService.setLevel(LogLevel.TRACE); + assertTrue(isLevel.apply(logService)); + } + @Test public void testDefaultLevel() { - final LogService log = new StderrLogService(); + assertEquals(LogLevel.INFO, new TestableLogService().getLevel()); + } + + @Test + public void testMainSystemProperty() { + Properties properties = new Properties(); + properties.setProperty(LogService.LOG_LEVEL_PROPERTY, "error"); + int level = new TestableLogService(properties).getLevel(); + assertEquals(LogLevel.ERROR, level); + } + + static class Dummy { + public static int getLevel(LogService log) { + return log.getLevel(); + } + } + + @Test + public void testClassLogLevel() { + final TestableLogService log = new TestableLogService(); + log.setLevel(LogLevel.DEBUG); + log.setLevel(Dummy.class.getName(), LogLevel.ERROR); + int level = Dummy.getLevel(log); + assertEquals(LogLevel.ERROR, level); + } + + @Test + public void testClassLogLevelViaProperties() { + Properties properties = new Properties(); + properties.setProperty(LogService.LOG_LEVEL_PROPERTY + ":" + Dummy.class.getName(), LogLevel.prefix(LogLevel.ERROR)); + properties.setProperty(LogService.LOG_LEVEL_PROPERTY + ":" + this.getClass().getName(), LogLevel.prefix(LogLevel.TRACE)); + final LogService log = new TestableLogService(properties); + log.setLevel(LogLevel.DEBUG); + int level = Dummy.getLevel(log); + assertEquals(LogLevel.ERROR, level); + } + + @Test + public void testSubLoggerLogLevel() { + final TestableLogService log = new TestableLogService(); + log.setLevel(LogLevel.ERROR); + log.setLevelForLogger("foo:bar", LogLevel.TRACE); + Logger sub = log.subLogger("foo").subLogger("bar"); + assertEquals(LogLevel.TRACE, sub.getLevel()); + } + + @Test + public void testSubLoggerLogLevelViaProperties() { + Properties properties = new Properties(); + properties.setProperty(LogService.LOG_LEVEL_BY_SOURCE_PROPERTY + ":Hello:World", LogLevel.prefix(LogLevel.ERROR)); + properties.setProperty(LogService.LOG_LEVEL_BY_SOURCE_PROPERTY + ":foo:bar", LogLevel.prefix(LogLevel.TRACE)); + final LogService log = new TestableLogService(properties); + Logger sub = log.subLogger("foo").subLogger("bar"); + assertEquals(LogLevel.TRACE, sub.getLevel()); + } + + @Test + public void testPackageLogLevel() { + final LogService log = new TestableLogService(); + log.setLevel("org.scijava.log", LogLevel.TRACE); + log.setLevel("xyz.foo.bar", LogLevel.ERROR); int level = log.getLevel(); - assertTrue("default level (" + level + ") is at least INFO(" + WARN + ")", level >= WARN); + assertEquals(LogLevel.TRACE, level); + } + + @Test + public void testListener() { + // setup + TestableLogService logService = new TestableLogService(); + TestLogListener listener = new TestLogListener(); + String msg1 = "Hello World!"; + String msg2 = "foo bar"; + // process + logService.addLogListener(listener); + logService.error(msg1); + logService.subLogger("xyz").debug(msg2); + // test + listener.hasLogged(m -> msg1.equals(m.text())); + listener.hasLogged(m -> msg2.equals(m.text())); + } + + @Test + public void testLogListenerIsNotifiedOnce() { + List list = new ArrayList<>(); + LogService logService = new TestableLogService(); + logService.addLogListener(list::add); + logService.error("dummy"); + assertEquals(1, list.size()); + } + + // -- Helper classes -- + + private static class MyTestClass { + + private final LogService log; + + MyTestClass(LogService log) { + this.log = log; + } + + int getLevel() { + return log.getLevel(); + } + } + + private interface LogMethodCall { + void run(LogService logService, Object text, Throwable exception); + } + + private static class TestableLogService extends AbstractLogService { + + String message = null; + Throwable exception = null; + + public TestableLogService() { + this(new Properties()); + } + + public TestableLogService(Properties properties) { + super(properties); + } + + public String message() { + return message; + } + + public Throwable exception() { + return exception; + } + + @Override + protected void messageLogged(LogMessage message) { + this.message = message.toString(); + this.exception = message.throwable(); + } } } diff --git a/src/test/java/org/scijava/log/LogSourceTest.java b/src/test/java/org/scijava/log/LogSourceTest.java new file mode 100644 index 000000000..d9bed1ef4 --- /dev/null +++ b/src/test/java/org/scijava/log/LogSourceTest.java @@ -0,0 +1,116 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.log; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.util.Collections; + +import org.junit.Test; + +/** + * Tests {@link LogSource} + */ +public class LogSourceTest { + + @Test + public void testRoot() { + LogSource root = LogSource.newRoot(); + assertEquals(Collections.emptyList(), root.path()); + assertTrue(root.isRoot()); + } + + @Test + public void testIsRoot() { + LogSource source = LogSource.newRoot().subSource("sub"); + assertFalse(source.isRoot()); + } + + @Test + public void testChildIsUnique() { + String name = "foo"; + LogSource root = LogSource.newRoot(); + LogSource a = root.subSource(name); + LogSource b = root.subSource(name); + assertSame(a, b); + } + + @Test + public void testLogSourceParse() { + LogSource foo = LogSource.newRoot().subSource("foo"); + LogSource result = foo.subSource("Hello:World"); + LogSource expected = foo.subSource("Hello").subSource("World"); + assertEquals(expected, result); + } + + @Test + public void testToString() { + LogSource source = LogSource.newRoot().subSource("Hello").subSource("World"); + String result = source.toString(); + assertEquals("Hello:World", result); + } + + @Test + public void testRootToString() { + LogSource source = LogSource.newRoot(); + String result = source.toString(); + assertEquals("", result); + } + + @Test + public void testName() { + LogSource source = LogSource.newRoot().subSource("Hello").subSource("World"); + assertEquals("World", source.name()); + } + + @Test + public void testParent() { + LogSource root = LogSource.newRoot(); + LogSource source = root.subSource("sub"); + assertSame(root, source.parent()); + } + + @Test + public void testUnsetLogLevel() { + LogSource source = LogSource.newRoot(); + assertFalse(source.hasLogLevel()); + } + + @Test + public void testSetLogLevel() { + LogSource source = LogSource.newRoot(); + source.setLogLevel(LogLevel.INFO); + assertTrue(source.hasLogLevel()); + assertEquals(LogLevel.INFO, source.logLevel()); + } +} diff --git a/src/test/java/org/scijava/log/StderrLogServiceTest.java b/src/test/java/org/scijava/log/StderrLogServiceTest.java new file mode 100644 index 000000000..6745f0aaf --- /dev/null +++ b/src/test/java/org/scijava/log/StderrLogServiceTest.java @@ -0,0 +1,75 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.log; + +import static org.junit.Assert.assertTrue; +import static org.scijava.log.LogLevel.WARN; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import org.junit.Test; + +/** + * Tests {@link StderrLogService}. + * + * @author Johannes Schindelin + * @author Matthias Arzt + */ +public class StderrLogServiceTest { + + @Test + public void testDefaultLevel() { + final LogService log = new StderrLogService(); + int level = log.getLevel(); + assertTrue("default level (" + level + // + ") is at least INFO(" + WARN + ")", level >= WARN); + } + + @Test + public void testOutputToStream() { + // setup + final StderrLogService logService = new StderrLogService(); + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + final PrintStream p = new PrintStream(outputStream); + logService.setPrintStreams(ignore -> p); + + final String text1 = "Hello World!"; + final String text2 = "foo bar"; + + // process + logService.warn(text1); + logService.subLogger("sub").error(text2); + + // test + assertTrue(outputStream.toString().contains(text1)); + assertTrue(outputStream.toString().contains(text2)); + } +} diff --git a/src/test/java/org/scijava/log/TestLogListener.java b/src/test/java/org/scijava/log/TestLogListener.java new file mode 100644 index 000000000..8368e0142 --- /dev/null +++ b/src/test/java/org/scijava/log/TestLogListener.java @@ -0,0 +1,61 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.log; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +/** + * TestLogListener is a {@ling LogListener} usable for testing. It stores all + * the LogMessages it receives and allows to test if any LogMessage fulfills a + * given predicate. + * + * @author Matthias Arzt + */ + +public class TestLogListener implements LogListener { + + List messages = new ArrayList<>(); + + @Override + public void messageLogged(LogMessage message) { + messages.add(message); + } + + public boolean hasLogged(Predicate predicate) { + return messages.stream().anyMatch(predicate); + } + + public void clear() { + messages.clear(); + } + +} diff --git a/src/test/java/org/scijava/main/MainServiceTest.java b/src/test/java/org/scijava/main/MainServiceTest.java index 542dd3cc9..f1bc97136 100644 --- a/src/test/java/org/scijava/main/MainServiceTest.java +++ b/src/test/java/org/scijava/main/MainServiceTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/main/run/MainCodeRunnerTest.java b/src/test/java/org/scijava/main/run/MainCodeRunnerTest.java index 8c74eec92..523fc3bf8 100644 --- a/src/test/java/org/scijava/main/run/MainCodeRunnerTest.java +++ b/src/test/java/org/scijava/main/run/MainCodeRunnerTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/menu/MenuServiceTest.java b/src/test/java/org/scijava/menu/MenuServiceTest.java index bfaa74f23..ea23052c8 100644 --- a/src/test/java/org/scijava/menu/MenuServiceTest.java +++ b/src/test/java/org/scijava/menu/MenuServiceTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/menu/ShadowMenuTest.java b/src/test/java/org/scijava/menu/ShadowMenuTest.java index 4962136a0..8972dacfa 100644 --- a/src/test/java/org/scijava/menu/ShadowMenuTest.java +++ b/src/test/java/org/scijava/menu/ShadowMenuTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/module/ModuleServiceTest.java b/src/test/java/org/scijava/module/ModuleServiceTest.java index 73a991970..e0fee870f 100644 --- a/src/test/java/org/scijava/module/ModuleServiceTest.java +++ b/src/test/java/org/scijava/module/ModuleServiceTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -31,11 +29,15 @@ package org.scijava.module; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ExecutionException; @@ -44,6 +46,7 @@ import org.junit.Before; import org.junit.Test; import org.scijava.Context; +import org.scijava.parse.ParseService; /** * Tests {@link ModuleService}. @@ -56,7 +59,7 @@ public class ModuleServiceTest { @Before public void setUp() { - final Context context = new Context(ModuleService.class); + final Context context = new Context(ModuleService.class, ParseService.class); moduleService = context.service(ModuleService.class); } @@ -160,6 +163,67 @@ public void testGetSingleInput() throws ModuleException { assertSame(info.getInput("double2"), singleDouble); } + @Test + public void testSaveAndLoad() { + List objects = Arrays.asList( // + new double[] {}, // + new double[] { 1., 2., 3. }, // + new Double[] {}, // + new Double[] { 1., 2., 3. } // + ); + objects.forEach(this::assertParamSavedAndLoaded); + } + + private void assertParamSavedAndLoaded(T object) { + @SuppressWarnings("unchecked") + Class c = (Class) object.getClass(); + // Get a ModuleItem of the right type + MutableModule m = new DefaultMutableModule(); + m.getInfo().addInput(new DefaultMutableModuleItem<>(m, "a", c)); + final ModuleItem item = moduleService.getSingleInput(m, c); + // Save a value to the ModuleItem + moduleService.save(item, object); + // Load that value from the ModuleItem + Object actual = moduleService.load(item); + // Assert equality + if (object.getClass().isArray()) assertArrayEquality(object, actual); + else assertEquals(object, actual); + } + + private void assertArrayEquality(Object arr1, Object arr2) { + // Ensure that both Objects are arrays of the same type! + assertEquals(arr1.getClass(), arr2.getClass()); + assertTrue(arr1.getClass().isArray()); + + // We must check primitive arrays as they cannot be cast to Object[] + if (arr1 instanceof boolean[]) { + assertArrayEquals((boolean[]) arr1, (boolean[]) arr2); + } + else if (arr1 instanceof byte[]) { + assertArrayEquals((byte[]) arr1, (byte[]) arr2); + } + else if (arr1 instanceof short[]) { + assertArrayEquals((short[]) arr1, (short[]) arr2); + } + else if (arr1 instanceof int[]) { + assertArrayEquals((int[]) arr1, (int[]) arr2); + } + else if (arr1 instanceof long[]) { + assertArrayEquals((long[]) arr1, (long[]) arr2); + } + else if (arr1 instanceof float[]) { + assertArrayEquals((float[]) arr1, (float[]) arr2, 1e-6f); + } + else if (arr1 instanceof double[]) { + assertArrayEquals((double[]) arr1, (double[]) arr2, 1e-6); + } + else if (arr1 instanceof char[]) { + assertArrayEquals((char[]) arr1, (char[]) arr2); + } + // Otherwise we can just cast to Object[] + else assertArrayEquals((Object[]) arr1, (Object[]) arr2); + } + // -- Helper methods -- private Object[] createInputArray() { @@ -169,7 +233,7 @@ private Object[] createInputArray() { "integer1", -2, // "integer2", 7, // "double1", Math.E, // - "double2", Math.PI // + "double2", Math.PI, // }; } diff --git a/src/test/java/org/scijava/module/event/ModuleErroredEventTest.java b/src/test/java/org/scijava/module/event/ModuleErroredEventTest.java new file mode 100644 index 000000000..25bb195ba --- /dev/null +++ b/src/test/java/org/scijava/module/event/ModuleErroredEventTest.java @@ -0,0 +1,119 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.module.event; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertNotNull; + +import org.junit.Before; +import org.junit.Test; +import org.scijava.Context; +import org.scijava.event.EventHandler; +import org.scijava.event.EventService; +import org.scijava.module.AbstractModule; +import org.scijava.module.AbstractModuleInfo; +import org.scijava.module.Module; +import org.scijava.module.ModuleException; +import org.scijava.module.ModuleInfo; +import org.scijava.module.ModuleService; + +/** + * Tests {@link ModuleErroredEvent} behavior. + * + * @author Gabriel Selzer + * @author Curtis Rueden + */ +public class ModuleErroredEventTest { + + private EventService es; + private ModuleService module; + + @Before + public void setUp() { + Context ctx = new Context(); + es = ctx.getService(EventService.class); + module = ctx.getService(ModuleService.class); + } + + @Test + public void testModuleErroredEvent() { + + // Must be a final boolean array to be included in the below closure + final Throwable[] caughtException = { null }; + + // Add a new EventHandler to change our state + final Object interestedParty = new Object() { + + @EventHandler + void onEvent(final ModuleErroredEvent e) { + caughtException[0] = e.getException(); + e.consume(); // Prevent exception from being emitted to stderr. + } + }; + es.subscribe(interestedParty); + + // Run the module, ensure we get the exception + assertThrows(Exception.class, // + () -> module.run(new TestModuleInfo(), false).get()); + assertNotNull(caughtException[0]); + assertEquals("Yay!", caughtException[0].getMessage()); + } + + static class TestModuleInfo extends AbstractModuleInfo { + + @Override + public String getDelegateClassName() { + return this.getClass().getName(); + } + + @Override + public Class loadDelegateClass() throws ClassNotFoundException { + return this.getClass(); + } + + @Override + public Module createModule() throws ModuleException { + ModuleInfo thisInfo = this; + return new AbstractModule() { + + @Override + public ModuleInfo getInfo() { + return thisInfo; + } + + @Override + public void run() { + throw new RuntimeException("Yay!"); + } + }; + } + } +} diff --git a/src/test/java/org/scijava/module/process/LoggerPreprocessorTest.java b/src/test/java/org/scijava/module/process/LoggerPreprocessorTest.java new file mode 100644 index 000000000..030413018 --- /dev/null +++ b/src/test/java/org/scijava/module/process/LoggerPreprocessorTest.java @@ -0,0 +1,114 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.module.process; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.concurrent.ExecutionException; + +import org.junit.Test; +import org.scijava.Context; +import org.scijava.command.Command; +import org.scijava.command.CommandService; +import org.scijava.log.DefaultLogger; +import org.scijava.log.LogLevel; +import org.scijava.log.LogService; +import org.scijava.log.LogSource; +import org.scijava.log.Logger; +import org.scijava.log.TestLogListener; +import org.scijava.plugin.Parameter; + +/** + * Tests {@link LoggerPreprocessor}. + * + * @author Matthias Arzt + */ +public class LoggerPreprocessorTest { + + @Test + public void testInjection() throws InterruptedException, ExecutionException { + final Context context = new Context(CommandService.class); + final CommandService commandService = context.service(CommandService.class); + final TestLogListener listener = new TestLogListener(); + context.service(LogService.class).addLogListener(listener); + + commandService.run(CommandWithLogger.class, true).get(); + assertTrue(listener.hasLogged(m -> m.source().path().contains(CommandWithLogger.class.getSimpleName()))); + } + + /** Tests redirection of a command's log output. */ + @Test + public void testCustomLogger() throws ExecutionException, + InterruptedException + { + // setup + final Context context = new Context(CommandService.class); + final CommandService commandService = context.service(CommandService.class); + final TestLogListener listener = new TestLogListener(); + final LogSource source = LogSource.newRoot(); + final DefaultLogger customLogger = new DefaultLogger(listener, source, + LogLevel.TRACE); + // process + commandService.run(CommandWithLogger.class, true, "log", customLogger) + .get(); + // test + assertTrue(listener.hasLogged(m -> m.source().equals(source))); + } + + public static class CommandWithLogger implements Command { + + @Parameter + public Logger log; + + @Override + public void run() { + log.info("log from the command."); + } + } + + @Test + public void testLoggerNameByAnnotation() throws ExecutionException, InterruptedException { + final Context context = new Context(CommandService.class); + final CommandService commandService = context.service(CommandService.class); + commandService.run(CommandWithNamedLogger.class, true).get(); + } + + public static class CommandWithNamedLogger implements Command { + + @Parameter(label = "MyLoggerName") + public Logger log; + + @Override + public void run() { + assertEquals("MyLoggerName", log.getName()); + } + } +} diff --git a/src/test/java/org/scijava/module/run/ModuleCodeRunnerTest.java b/src/test/java/org/scijava/module/run/ModuleCodeRunnerTest.java index cdd88db72..62507cdc1 100644 --- a/src/test/java/org/scijava/module/run/ModuleCodeRunnerTest.java +++ b/src/test/java/org/scijava/module/run/ModuleCodeRunnerTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/io/BytesLocationTest.java b/src/test/java/org/scijava/object/NamedObjectIndexTest.java similarity index 55% rename from src/test/java/org/scijava/io/BytesLocationTest.java rename to src/test/java/org/scijava/object/NamedObjectIndexTest.java index 014dff813..15a995519 100644 --- a/src/test/java/org/scijava/io/BytesLocationTest.java +++ b/src/test/java/org/scijava/object/NamedObjectIndexTest.java @@ -1,10 +1,8 @@ -/* +/*- * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -28,40 +26,46 @@ * POSSIBILITY OF SUCH DAMAGE. * #L% */ - -package org.scijava.io; +package org.scijava.object; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import org.junit.Test; -/** - * Tests {@link BytesLocation}. - * - * @author Curtis Rueden - */ -public class BytesLocationTest { +public class NamedObjectIndexTest { - /** Tests {@link BytesLocation#BytesLocation(byte[])}. */ @Test - public void testBytes() { - final byte[] digits = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9}; - final BytesLocation loc = new BytesLocation(digits); - assertSame(digits, loc.getByteBuffer().array()); - assertEquals(0, loc.getByteBuffer().position()); - assertEquals(digits.length, loc.getByteBuffer().remaining()); + public void testNamedObjects() { + NamedObjectIndex index = new NamedObjectIndex<>(String.class); + String obj1 = "obj1"; + String name1 = "name1"; + String obj2 = "obj1"; + String name2 = "name1"; + assertTrue(index.add(obj1, name1)); + assertTrue(index.add(obj2, String.class, name2, false)); + assertTrue(index.contains(obj1)); + assertTrue(index.contains(obj2)); + assertEquals(name1, index.getName(obj1)); + assertEquals(name2, index.getName(obj2)); + assertTrue(index.remove(obj1)); + assertTrue(index.remove(obj2)); + assertFalse(index.contains(obj1)); + assertFalse(index.contains(obj2)); } - /** Tests {@link BytesLocation#BytesLocation(byte[], int, int)}. */ @Test - public void testBytesOffsetLength() { - final byte[] digits = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9}; - final int offset = 3, length = 5; - final BytesLocation loc = new BytesLocation(digits, offset, length); - assertSame(digits, loc.getByteBuffer().array()); - assertEquals(offset, loc.getByteBuffer().position()); - assertEquals(length, loc.getByteBuffer().remaining()); + public void testNullNames() { + NamedObjectIndex index = new NamedObjectIndex<>(String.class); + String obj1 = "object1"; + String name1 = null; + String obj2 = "object2"; + String name2 = ""; + assertTrue(index.add(obj1, name1)); + assertTrue(index.add(obj2, name2)); + assertEquals(name1, index.getName(obj1)); + assertEquals(name2, index.getName(obj2)); } - } diff --git a/src/test/java/org/scijava/object/ObjectIndexTest.java b/src/test/java/org/scijava/object/ObjectIndexTest.java index 08296c0da..7bf973ea8 100644 --- a/src/test/java/org/scijava/object/ObjectIndexTest.java +++ b/src/test/java/org/scijava/object/ObjectIndexTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -38,6 +36,7 @@ import static org.junit.Assert.assertTrue; import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; import java.util.List; @@ -211,15 +210,27 @@ public void testToString() { objectIndex.add(new Integer(5)); objectIndex.add(new Float(2.5f)); objectIndex.add(new Integer(3)); - final String[] expected = - { "java.io.Serializable: {5, 2.5, 3}", - "java.lang.Comparable: {5, 2.5, 3}", "java.lang.Float: {2.5}", - "java.lang.Integer: {5, 3}", "java.lang.Number: {5, 2.5, 3}", - "java.lang.Object: {5, 2.5, 3}", - "org.scijava.object.ObjectIndex$All: {5, 2.5, 3}" }; + + final List expected = new ArrayList<>(); + expected.addAll(Arrays.asList( + "java.io.Serializable: {5, 2.5, 3}", + "java.lang.Comparable: {5, 2.5, 3}", + "java.lang.Float: {2.5}", + "java.lang.Integer: {5, 3}", + "java.lang.Number: {5, 2.5, 3}", + "java.lang.Object: {5, 2.5, 3}" + )); + final String[] javaVersion = System.getProperty("java.version").split("\\."); + final int majorVersion = Integer.parseInt(javaVersion[0]); + if (majorVersion >= 12) { + expected.add("java.lang.constant.Constable: {5, 2.5, 3}"); + expected.add("java.lang.constant.ConstantDesc: {5, 2.5, 3}"); + } + expected.add("org.scijava.object.ObjectIndex$All: {5, 2.5, 3}"); + final String[] actual = objectIndex.toString().split(System.getProperty("line.separator")); - assertArrayEquals(expected, actual); + assertArrayEquals(expected.toArray(), actual); } } diff --git a/src/test/java/org/scijava/object/ObjectServiceTest.java b/src/test/java/org/scijava/object/ObjectServiceTest.java new file mode 100644 index 000000000..871babfe1 --- /dev/null +++ b/src/test/java/org/scijava/object/ObjectServiceTest.java @@ -0,0 +1,103 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.scijava.object; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.scijava.Context; +import org.scijava.plugin.PluginInfo; +import org.scijava.plugin.SciJavaPlugin; + +public class ObjectServiceTest { + + private Context context; + private ObjectService objectService; + + @Before + public void setUp() { + context = new Context(ObjectService.class); + objectService = context.getService(ObjectService.class); + } + + @After + public void tearDown() { + context.dispose(); + } + + @Test + public void testAddRemoveObjects() { + Object obj1 = new Object(); + String name1 = "Object 1"; + Object obj2 = ""; + Object obj3 = new Double(0.3); + PluginInfo obj4 = PluginInfo.create(TestPlugin.class, SciJavaPlugin.class); + obj4.setName("TestPlugin name"); + + objectService.addObject(obj1, name1); + assertEquals("Name of object 1", name1, objectService.getName(obj1)); + objectService.addObject(obj2); + assertEquals("Name of object 2", obj2.toString(), objectService.getName(obj2)); + objectService.addObject(obj3, null); + assertEquals("Name of object 3", obj3.toString(), objectService.getName(obj3)); + objectService.addObject(obj4); + assertNotNull(objectService.getName(obj4)); + assertEquals("Name of object 4", obj4.getName(), objectService.getName(obj4)); + + assertTrue("Object 1 registered", objectService.getObjects(Object.class).contains(obj1)); + assertTrue("Object 2 registered", objectService.getObjects(Object.class).contains(obj2)); + assertTrue("Object 3 registered", objectService.getObjects(Object.class).contains(obj3)); + assertTrue("Object 4 registered", objectService.getObjects(Object.class).contains(obj4)); + + objectService.removeObject(obj1); + objectService.removeObject(obj2); + objectService.removeObject(obj3); + objectService.removeObject(obj4); + + assertFalse("Object 1 removed", objectService.getObjects(Object.class).contains(obj1)); + assertFalse("Object 2 removed", objectService.getObjects(Object.class).contains(obj2)); + assertFalse("Object 3 removed", objectService.getObjects(Object.class).contains(obj3)); + assertFalse("Object 4 removed", objectService.getObjects(Object.class).contains(obj4)); + } + + @Test + public void testNamedObjectIndex() { + ObjectIndex index = objectService.getIndex(); + assertTrue(index instanceof NamedObjectIndex); + } + + private class TestPlugin implements SciJavaPlugin { + + } +} diff --git a/src/test/java/org/scijava/object/SortedObjectIndexTest.java b/src/test/java/org/scijava/object/SortedObjectIndexTest.java index 3b2ceb06d..7086dc505 100644 --- a/src/test/java/org/scijava/object/SortedObjectIndexTest.java +++ b/src/test/java/org/scijava/object/SortedObjectIndexTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/options/OptionsTest.java b/src/test/java/org/scijava/options/OptionsTest.java index 46d4c1348..8ca77769c 100644 --- a/src/test/java/org/scijava/options/OptionsTest.java +++ b/src/test/java/org/scijava/options/OptionsTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -96,14 +94,14 @@ public void testPersistence() { assertEquals(0, fooOptions.getBar()); // verify that we can set bar to a desired value at all - fooOptions.setBar(0xcafebabe); - assertEquals(0xcafebabe, fooOptions.getBar()); + fooOptions.setBar(0xc0ffee); + assertEquals(0xc0ffee, fooOptions.getBar()); // verify that save and load work as expected in the same context fooOptions.save(); fooOptions.setBar(0xdeadbeef); fooOptions.load(); - assertEquals(0xcafebabe, fooOptions.getBar()); + assertEquals(0xc0ffee, fooOptions.getBar()); // throw away the 1st context optionsService.getContext().dispose(); @@ -114,7 +112,7 @@ public void testPersistence() { final FooOptions fooOptions = optionsService.getOptions(FooOptions.class); // verify that persisted values are loaded correctly in a new context - assertEquals(0xcafebabe, fooOptions.getBar()); + assertEquals(0xc0ffee, fooOptions.getBar()); // clean up for next time fooOptions.reset(); // FIXME: If this test fails, reset will not happen! diff --git a/src/test/java/org/scijava/parse/ParseServiceTest.java b/src/test/java/org/scijava/parse/ParseServiceTest.java index 1f0c40a34..47d77939a 100644 --- a/src/test/java/org/scijava/parse/ParseServiceTest.java +++ b/src/test/java/org/scijava/parse/ParseServiceTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/plugin/PluginFinderTest.java b/src/test/java/org/scijava/plugin/PluginFinderTest.java index f6db17850..20fc70ce7 100644 --- a/src/test/java/org/scijava/plugin/PluginFinderTest.java +++ b/src/test/java/org/scijava/plugin/PluginFinderTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -45,33 +43,33 @@ public class PluginFinderTest { /** - * Tests that the {@code scijava.plugin.blacklist} system property works to + * Tests that the {@code scijava.plugin.blocklist} system property works to * exclude plugins from the index, even when they are on the classpath. */ @Test - public void testPluginBlacklistSystemProperty() { + public void testPluginBlocklistSystemProperty() { // check that the plugin is there, normally Context context = new Context(PluginService.class); PluginService pluginService = context.service(PluginService.class); PluginInfo plugin = // - pluginService.getPlugin(BlacklistedPlugin.class); - assertSame(BlacklistedPlugin.class.getName(), plugin.getClassName()); + pluginService.getPlugin(BlocklistedPlugin.class); + assertSame(BlocklistedPlugin.class.getName(), plugin.getClassName()); context.dispose(); - // blacklist the plugin, then check that it is absent - System.setProperty("scijava.plugin.blacklist", ".*BlacklistedPlugin"); + // blocklist the plugin, then check that it is absent + System.setProperty("scijava.plugin.blocklist", ".*BlocklistedPlugin"); context = new Context(PluginService.class); pluginService = context.service(PluginService.class); - plugin = pluginService.getPlugin(BlacklistedPlugin.class); + plugin = pluginService.getPlugin(BlocklistedPlugin.class); assertNull(plugin); context.dispose(); // reset the system - System.getProperties().remove("scijava.plugin.blacklist"); + System.getProperties().remove("scijava.plugin.blocklist"); } @Plugin(type = SciJavaPlugin.class) - public static class BlacklistedPlugin implements SciJavaPlugin { + public static class BlocklistedPlugin implements SciJavaPlugin { // NB: No implementation needed. } diff --git a/src/test/java/org/scijava/plugin/PluginIndexTest.java b/src/test/java/org/scijava/plugin/PluginIndexTest.java index afe868fb9..f4727f067 100644 --- a/src/test/java/org/scijava/plugin/PluginIndexTest.java +++ b/src/test/java/org/scijava/plugin/PluginIndexTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/plugin/PluginInfoTest.java b/src/test/java/org/scijava/plugin/PluginInfoTest.java index 06f934666..7df6a64d7 100644 --- a/src/test/java/org/scijava/plugin/PluginInfoTest.java +++ b/src/test/java/org/scijava/plugin/PluginInfoTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -32,10 +30,16 @@ package org.scijava.plugin; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; +import static org.junit.Assert.fail; import java.util.List; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.scijava.Context; import org.scijava.InstantiableException; @@ -48,11 +52,24 @@ */ public class PluginInfoTest { + private Context context; + private PluginIndex pluginIndex; + + @Before + public void setUp() { + context = new Context(true); + pluginIndex = context.getPluginIndex(); + } + + @After + public void tearDown() { + context.dispose(); + context = null; + pluginIndex = null; + } + @Test public void testNames() throws InstantiableException { - final Context context = new Context(true); - final PluginIndex pluginIndex = context.getPluginIndex(); - final List> infos = pluginIndex.get(IceCream.class); assertEquals(3, infos.size()); @@ -61,9 +78,74 @@ public void testNames() throws InstantiableException { assertPlugin(Flavorless.class, IceCream.class, "", infos.get(2)); } + @Test + public void testGet() throws InstantiableException { + final PluginInfo chocolateInfo = // + PluginInfo.get(Chocolate.class, pluginIndex); + assertPlugin(Chocolate.class, IceCream.class, "chocolate", chocolateInfo); + + final PluginInfo chocolateInfoWithType = // + PluginInfo.get(Chocolate.class, IceCream.class, pluginIndex); + assertSame(chocolateInfo, chocolateInfoWithType); + + class Sherbet implements IceCream {} + assertNull(PluginInfo.get(Sherbet.class, pluginIndex)); + } + + @Test + public void testCreate() throws InstantiableException { + final PluginInfo chocolateInfo = PluginInfo.create(Chocolate.class); + assertPlugin(Chocolate.class, IceCream.class, "chocolate", chocolateInfo); + + final PluginInfo chocolateInfoWithType = // + PluginInfo.create(Chocolate.class, IceCream.class); + assertPlugin(Chocolate.class, IceCream.class, "chocolate", + chocolateInfoWithType); + assertNotSame(chocolateInfo, chocolateInfoWithType); + + class Sherbet implements IceCream {} + final PluginInfo sherbetInfoWithType = // + PluginInfo.create(Sherbet.class, IceCream.class); + assertPlugin(Sherbet.class, IceCream.class, null, sherbetInfoWithType); + + try { + final PluginInfo result = PluginInfo.create(Sherbet.class); + fail("Expected IllegalArgumentException but got: " + result); + } + catch (final IllegalArgumentException exc) { + // NB: Expected. + } + } + + @Test + public void testGetOrCreate() throws InstantiableException { + final PluginInfo chocolateInfo = // + PluginInfo.getOrCreate(Chocolate.class, pluginIndex); + assertPlugin(Chocolate.class, IceCream.class, "chocolate", chocolateInfo); + + final PluginInfo chocolateInfoWithType = // + PluginInfo.getOrCreate(Chocolate.class, IceCream.class, pluginIndex); + assertSame(chocolateInfo, chocolateInfoWithType); + + class Sherbet implements IceCream {} + final PluginInfo sherbetInfoWithType = // + PluginInfo.getOrCreate(Sherbet.class, IceCream.class, pluginIndex); + assertPlugin(Sherbet.class, IceCream.class, null, sherbetInfoWithType); + + try { + final PluginInfo result = // + PluginInfo.getOrCreate(Sherbet.class, pluginIndex); + fail("Expected IllegalArgumentException but got: " + result); + } + catch (final IllegalArgumentException exc) { + // NB: Expected. + } + } + private void assertPlugin(Class pluginClass, Class pluginType, String name, PluginInfo info) throws InstantiableException { + assertNotNull(info); assertSame(pluginClass, info.loadClass()); assertSame(pluginType, info.getPluginType()); assertEquals(name, info.getName()); @@ -73,19 +155,18 @@ public static interface IceCream extends SciJavaPlugin { // NB: Marker interface. } - @Plugin(type = IceCream.class, priority = Priority.VERY_LOW_PRIORITY) + @Plugin(type = IceCream.class, priority = Priority.VERY_LOW) public static class Flavorless implements SciJavaPlugin { // NB: No implementation needed. } - @Plugin(type = IceCream.class, name = "vanilla", - priority = Priority.LOW_PRIORITY) + @Plugin(type = IceCream.class, name = "vanilla", priority = Priority.LOW) public static class Vanilla implements SciJavaPlugin { // NB: No implementation needed. } @Plugin(type = IceCream.class, name = "chocolate", - priority = Priority.VERY_HIGH_PRIORITY) + priority = Priority.VERY_HIGH) public static class Chocolate implements IceCream { // NB: No implementation needed. } diff --git a/src/test/java/org/scijava/plugin/SingletonServiceTest.java b/src/test/java/org/scijava/plugin/SingletonServiceTest.java new file mode 100644 index 000000000..59d035333 --- /dev/null +++ b/src/test/java/org/scijava/plugin/SingletonServiceTest.java @@ -0,0 +1,286 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.plugin; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.scijava.Context; +import org.scijava.convert.AbstractConverter; +import org.scijava.convert.ConvertService; +import org.scijava.convert.Converter; +import org.scijava.plugin.event.PluginsAddedEvent; +import org.scijava.plugin.event.PluginsRemovedEvent; + +/** + * Tests for the {@link SingletonService} + * + * @author Gabriel Einsdorf KNIME GmbH + * @author Stefan Helfrich KNIME GmbH + */ +public class SingletonServiceTest { + + private PluginService pluginService; + private ConvertService convertService; + + @Before + public void setUp() { + final Context context = new Context(PluginService.class, + ConvertService.class); + pluginService = context.service(PluginService.class); + convertService = context.service(ConvertService.class); + } + + @After + public void tearDown() { + pluginService.context().dispose(); + } + + /** + * Tests that the {@link AbstractSingletonService} properly handles + * {@link PluginsAddedEvent}s originating from the {@link PluginService}. + */ + @Test + public void testSingletonServicePluginsAddedHandling() { + @SuppressWarnings("rawtypes") + final PluginInfo converterInfo = new PluginInfo<>( + FoodConverter.class, Converter.class); + + pluginService.addPlugin(converterInfo); + + assertNotNull(pluginService.getPlugin(FoodConverter.class)); + assertTrue(convertService.supports(new Apple() {}, Peach.class)); + } + + /** + * Tests that the {@link AbstractSingletonService} properly handles + * {@link PluginsAddedEvent}s that replace an instance. + */ + @Test + public void testSingletonServicePluginsAddedHandlingDuplicates() { + @SuppressWarnings("rawtypes") + final PluginInfo converterInfo = new PluginInfo<>( + FoodConverter.class, Converter.class); + + pluginService.addPlugin(converterInfo); + final FoodConverter firstInstance = convertService.getInstance( + FoodConverter.class); + + pluginService.addPlugin(converterInfo); + final FoodConverter secondInstance = convertService.getInstance( + FoodConverter.class); + + assertNotSame(firstInstance, secondInstance); + assertTrue(convertService.supports(new Apple() {}, Peach.class)); + } + + /** + * Tests that the {@link AbstractSingletonService} properly handles + * {@link PluginsRemovedEvent}s originating from the {@link PluginService}. + */ + @Test + public void testSingletonServiceManuallyAddedPluginsRemovedHandling() { + @SuppressWarnings("rawtypes") + final PluginInfo converterInfo = new PluginInfo<>( + FoodConverter.class, Converter.class); + + pluginService.addPlugin(converterInfo); + + // De-register DummyStringConverter + pluginService.removePlugin(converterInfo); + + assertNull(pluginService.getPlugin(FoodConverter.class)); + assertFalse(convertService.supports(new Apple() {}, Peach.class)); + } + + /** + * Tests that the {@link AbstractSingletonService} properly handles + * {@link PluginsRemovedEvent}s originating from the {@link PluginService}. + */ + @Test + public void testSingletonServiceCompileTimePluginsRemovedHandling() { + final PluginInfo pluginInfo = pluginService.getPlugin( + DiscoveredFoodConverter.class); + + // De-register DiscoveredFoodConverter + pluginService.removePlugin(pluginInfo); + + assertNull(pluginService.getPlugin(DiscoveredFoodConverter.class)); + assertFalse(convertService.supports(new Orange() {}, Peach.class)); + } + + /** + * Dummy {@link Converter}. + */ + public static class FoodConverter extends AbstractConverter { + + @Override + public T convert(final Object src, final Class dest) { + return null; + } + + @Override + public Class getOutputType() { + return Peach.class; + } + + @Override + public Class getInputType() { + return Apple.class; + } + } + + /** + * Dummy {@link Converter} that is added automatically. + */ + @Plugin(type = Converter.class) + public static class DiscoveredFoodConverter extends + AbstractConverter + { + + @Override + public T convert(final Object src, final Class dest) { + return null; + } + + @Override + public Class getOutputType() { + return Peach.class; + } + + @Override + public Class getInputType() { + return Orange.class; + } + } + + /** + * Type interface for conversion + */ + public interface Apple { + // NB + } + + /** + * Type interface for conversion + */ + public interface Orange { + // NB + } + + /** + * Type interface for conversion + */ + public interface Peach { + // NB + } + + /** + * Tests that plugins are added to and removed from the correct singleton + * service + */ + @Test + public void testListenToRemove() { + + final Context ctx = new Context(PluginService.class, + DummySingletonService.class, DummySingletonService2.class); + + final DummySingletonService dss = ctx.getService( + DummySingletonService.class); + + final DummySingletonService2 dss2 = ctx.getService( + DummySingletonService2.class); + + final List instances = dss.getInstances(); + final DummyPlugin dummy = instances.get(0); + + assertFalse("Service not correctly initialized", dss2.getInstances() + .isEmpty()); + + // test successful removal + final PluginService ps = ctx.getService(PluginService.class); + ps.removePlugin(dummy.getInfo()); + + assertFalse("Plugin was removed from wrong service!", dss2.getInstances() + .isEmpty()); + assertTrue("Plugin was not removed!", dss.getInstances().isEmpty()); + + // test successful add + ps.addPlugin(dummy.getInfo()); + assertEquals("Wrong number of plugins in service:", 1, dss.getInstances() + .size()); + assertEquals("Wrong number of plugins in independent service:", 1, dss2 + .getInstances().size()); + } + + @Plugin(type = DummyPlugin.class) + public static class DummyPlugin extends AbstractRichPlugin implements + SingletonPlugin + { + // NB: No implementation needed. + } + + public static class DummySingletonService extends + AbstractSingletonService + { + + @Override + public Class getPluginType() { + return DummyPlugin.class; + } + } + + @Plugin(type = DummyPlugin2.class) + public static class DummyPlugin2 extends AbstractRichPlugin implements + SingletonPlugin + { + // NB: No implementation needed. + } + + public static class DummySingletonService2 extends + AbstractSingletonService + { + + @Override + public Class getPluginType() { + return DummyPlugin2.class; + } + } + +} diff --git a/src/test/java/org/scijava/prefs/PrefServiceTest.java b/src/test/java/org/scijava/prefs/PrefServiceTest.java index c6dfdaa10..d0635a962 100644 --- a/src/test/java/org/scijava/prefs/PrefServiceTest.java +++ b/src/test/java/org/scijava/prefs/PrefServiceTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -141,7 +139,7 @@ public void testLong() { } /** - * Tests {@link PrefService#putMap(Class, Map, String)} and + * Tests {@link PrefService#put(Class, String, Map)} and * {@link PrefService#getMap(Class, String)}. */ @Test @@ -152,28 +150,47 @@ public void testMap() { map.put("2", "C"); map.put("3", "D"); map.put("5", "f"); - final String mapKey = "MapKey"; - prefService.putMap(getClass(), map, mapKey); - final Map result = prefService.getMap(getClass(), mapKey); + final String mapName = "MapKey"; + prefService.put(getClass(), mapName, map); + final Map result = prefService.getMap(getClass(), mapName); assertEquals(map, result); } /** - * Tests {@link PrefService#putList(Class, List, String)} and + * Tests {@link PrefService#put(Class, String, Iterable)} and * {@link PrefService#getList(Class, String)}. */ @Test public void testList() { - final String recentFilesKey = "RecentFiles"; + final String recentFilesName = "RecentFiles"; final List recentFiles = new ArrayList<>(); recentFiles.add("some/path1"); recentFiles.add("some/path2"); recentFiles.add("some/path3"); - prefService.putList(getClass(), recentFiles, recentFilesKey); - final List result = prefService.getList(getClass(), recentFilesKey); + prefService.put(getClass(), recentFilesName, recentFiles); + final List result = prefService.getList(getClass(), recentFilesName); assertEquals(recentFiles, result); } + @Test + public void testClear() { + prefService.put(getClass(), "dog", "lazy"); + prefService.put(getClass(), "fox", "quick"); + assertEquals("lazy", prefService.get(getClass(), "dog")); + assertEquals("quick", prefService.get(getClass(), "fox")); + prefService.clear(getClass()); + assertNull(prefService.get(getClass(), "dog")); + assertNull(prefService.get(getClass(), "fox")); + } + + @Test + public void testRemove() { + prefService.put(getClass(), "hello", "world"); + assertEquals("world", prefService.get(getClass(), "hello")); + prefService.remove(getClass(), "hello"); + assertNull(prefService.get(getClass(), "hello")); + } + /** * The Java Preferences API does not support keys longer than 80 characters. * Let's test that our service does not fall victim to this limitation. @@ -189,9 +206,75 @@ public void testLongKeys() { "zyxwvutsrqponmlkjihgfedcba"; final String lyrics = "Now I know my ABC's. Next time won't you sing with me?"; - prefService.put(longKey, lyrics); - final String recovered = prefService.get(longKey); + prefService.put(getClass(), longKey, lyrics); + final String recovered = prefService.get(getClass(), longKey); assertEquals(lyrics, recovered); } + @Test + public void testClassesInSamePackage() { + final String blueDog = "lazy", blueFox = "quick"; + final String redDog = "friendly", redCat = "snuggly"; + + assertNull(prefService.get(BlueNode.class, "cat")); + assertNull(prefService.get(BlueNode.class, "dog")); + assertNull(prefService.get(BlueNode.class, "fox")); + assertNull(prefService.get(RedNode.class, "cat")); + assertNull(prefService.get(RedNode.class, "dog")); + assertNull(prefService.get(RedNode.class, "fox")); + + prefService.put(BlueNode.class, "dog", "lazy"); + prefService.put(BlueNode.class, "fox", "quick"); + + assertNull(prefService.get(BlueNode.class, "cat")); + assertEquals(blueDog, prefService.get(BlueNode.class, "dog")); + assertEquals(blueFox, prefService.get(BlueNode.class, "fox")); + assertNull(prefService.get(RedNode.class, "cat")); + assertNull(prefService.get(RedNode.class, "dog")); + assertNull(prefService.get(RedNode.class, "fox")); + + prefService.put(RedNode.class, "dog", redDog); + prefService.put(RedNode.class, "cat", redCat); + + assertNull(prefService.get(BlueNode.class, "cat")); + assertEquals(blueDog, prefService.get(BlueNode.class, "dog")); + assertEquals(blueFox, prefService.get(BlueNode.class, "fox")); + assertEquals(redCat, prefService.get(RedNode.class, "cat")); + assertEquals(redDog, prefService.get(RedNode.class, "dog")); + assertNull(prefService.get(RedNode.class, "fox")); + + prefService.clear(BlueNode.class); + + assertNull(prefService.get(BlueNode.class, "cat")); + assertNull(prefService.get(BlueNode.class, "dog")); + assertNull(prefService.get(BlueNode.class, "fox")); + assertEquals(redCat, prefService.get(RedNode.class, "cat")); + assertEquals(redDog, prefService.get(RedNode.class, "dog")); + assertNull(prefService.get(RedNode.class, "fox")); + + prefService.clear(RedNode.class); + + assertNull(prefService.get(BlueNode.class, "cat")); + assertNull(prefService.get(BlueNode.class, "dog")); + assertNull(prefService.get(BlueNode.class, "fox")); + assertNull(prefService.get(RedNode.class, "cat")); + assertNull(prefService.get(RedNode.class, "dog")); + assertNull(prefService.get(RedNode.class, "fox")); + } + + // -- Helper classes -- + + /** + * A class to use for anchoring preferences. Needed to test that preferences + * are stored with each class specifically, rather than the package as a + * whole. (Because the Java Preferences API uses packages for nodes, a + * potential pitfall here is that clear(Class) might delete too much.) + */ + private interface BlueNode {} + + /** + * Another class for anchoring preferences, in the same package as + * {@link BlueNode}. + */ + private interface RedNode {} } diff --git a/src/test/java/org/scijava/run/RunServiceTest.java b/src/test/java/org/scijava/run/RunServiceTest.java index 55f35e89e..16103455d 100644 --- a/src/test/java/org/scijava/run/RunServiceTest.java +++ b/src/test/java/org/scijava/run/RunServiceTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/script/AbstractScriptLanguageTest.java b/src/test/java/org/scijava/script/AbstractScriptLanguageTest.java index fd3f3f239..731d9182a 100644 --- a/src/test/java/org/scijava/script/AbstractScriptLanguageTest.java +++ b/src/test/java/org/scijava/script/AbstractScriptLanguageTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/script/ScriptEngineTest.java b/src/test/java/org/scijava/script/ScriptEngineTest.java index 1f75dcfa6..f8ad95821 100644 --- a/src/test/java/org/scijava/script/ScriptEngineTest.java +++ b/src/test/java/org/scijava/script/ScriptEngineTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/script/ScriptFinderTest.java b/src/test/java/org/scijava/script/ScriptFinderTest.java index c24fad50c..04ca2d02d 100644 --- a/src/test/java/org/scijava/script/ScriptFinderTest.java +++ b/src/test/java/org/scijava/script/ScriptFinderTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -68,7 +66,7 @@ public class ScriptFinderTest { public static void setUp() throws IOException { scriptsDir = TestUtils.createTemporaryDirectory("script-finder-"); final String[] scriptPaths = { // - "ignored.foo", // + "script_in_base_dir.foo", // "Scripts/quick.foo", // "Scripts/brown.foo", // "Scripts/fox.foo", // @@ -110,6 +108,7 @@ public void testFindScripts() { "Math > multiply", // "Math > pow", // "Scripts > quick", // + "Plugins > Scripts > script in base dir", // "Math > Trig > sin", // "Math > subtract", // "Math > Trig > tan", // @@ -141,10 +140,10 @@ public void testMenuPrefixes() { "Foo > Bar > Math > Trig > cos", // "Foo > Bar > Math > divide", // "Foo > Bar > Scripts > fox", // - "Foo > Bar > ignored", // "Foo > Bar > Math > multiply", // "Math > pow", // "Foo > Bar > Scripts > quick", // + "Foo > Bar > script in base dir", // "Foo > Bar > Math > Trig > sin", // "Foo > Bar > Math > subtract", // "Foo > Bar > Math > Trig > tan", // @@ -179,6 +178,7 @@ public void testOverlappingDirectories() { "Math > multiply", // "Math > pow", // "Plugins > quick", // + "Plugins > Scripts > script in base dir", // "Math > Trig > sin", // "Math > subtract", // "Math > Trig > tan", // diff --git a/src/test/java/org/scijava/script/ScriptInfoTest.java b/src/test/java/org/scijava/script/ScriptInfoTest.java index 8d022f748..a2cf4c375 100644 --- a/src/test/java/org/scijava/script/ScriptInfoTest.java +++ b/src/test/java/org/scijava/script/ScriptInfoTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -33,6 +31,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -53,40 +53,116 @@ import javax.script.ScriptEngine; import javax.script.ScriptException; -import org.junit.AfterClass; -import org.junit.BeforeClass; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.scijava.Context; import org.scijava.ItemIO; import org.scijava.ItemVisibility; +import org.scijava.MenuPath; +import org.scijava.Priority; import org.scijava.log.LogService; import org.scijava.module.ModuleItem; import org.scijava.plugin.Plugin; import org.scijava.test.TestUtils; import org.scijava.util.DigestUtils; import org.scijava.util.FileUtils; +import org.scijava.widget.WidgetStyle; -/** Tests {@link ScriptInfo}. */ +/** + * Tests {@link ScriptInfo}. + * + * @author Curtis Rueden + * @author Mark Hiner + */ public class ScriptInfoTest { - private static Context context; - private static ScriptService scriptService; + private Context context; + private ScriptService scriptService; // -- Test setup -- - @BeforeClass - public static void setUp() { + @Before + public void setUp() { context = new Context(); scriptService = context.service(ScriptService.class); } - @AfterClass - public static void tearDown() { + @After + public void tearDown() { context.dispose(); } // -- Tests -- + /** Tests script identifiers. */ + @Test + public void testGetIdentifier() { + final String name = "strategerize"; + + final String namedPath = "victory.bsizes"; + final String named = "" + // + "#@script(name = '" + name + "')\n" + // + "zxywvutsrqponmlkjihgfdcba\n"; + + final String unnamedPath = "alphabet.bsizes"; + final String unnamed = "" + // + "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n" + // + "0123456789\n"; + + // Test named, with explicit path. + assertEquals("script:" + name, id(namedPath, named)); + + // Test named, and no path given. + assertEquals("script:" + name, id(null, named)); + + // Test unnamed, with explicit path. + assertEquals("script:" + unnamedPath, id(unnamedPath, unnamed)); + + // Test unnamed, and no path given. + final String hex = DigestUtils.bestHex(unnamed); + assertEquals("script:<" + hex + ">", id(null, unnamed)); + } + + /** Tests whether new-style parameter syntax are parsed correctly. */ + @Test + public void testNewStyle() throws Exception { + final String script = "" + // + "##########\n" + // + "# Inputs #\n" + // + "##########\n" + // + "#@input int stuff\n" + // + "#@input int things\n" + // + "\n" + // + "###########\n" + // + "# Credits #\n" + // + "###########\n" + // + "Brought to you by:\n" + // + "person@example.com\n" + // + "\n" + // + "###########\n" + // + "# Outputs #\n" + // + "###########\n" + // + "#@output String blackHoles\n" + + "#@output String revelations\n" + + "\n" + // + "THE END!\n"; + final ScriptModule scriptModule = + scriptService.run("newStyle.bsizes", script, true).get(); + + final String expectedProcessed = script.replaceAll("#@.*", ""); + final String actualProcessed = scriptModule.getInfo().getProcessedScript(); + assertEquals(expectedProcessed, actualProcessed); + + final Object output = scriptModule.getReturnValue(); + + if (output == null) fail("null result"); + else if (!(output instanceof Integer)) { + fail("result is a " + output.getClass().getName()); + } + else assertEquals(4, ((Integer) output).intValue()); + } + /** * Tests that the return value is appended as an extra output when no * explicit outputs were declared. @@ -122,7 +198,6 @@ public void testReturnValueExcluded() throws Exception { assertFalse(outputs.containsKey(ScriptModule.RETURN_VALUE)); } - /** * Ensures parameters are parsed correctly from scripts, even in the presence * of noise like e-mail addresses. @@ -177,13 +252,14 @@ public void testVersion() throws IOException { @Test public void testParameters() { final String script = "" + // - "% @LogService(required = false) log\n" + // - "% @int(label=\"Slider Value\", softMin=5, softMax=15, " + // - "stepSize=3, value=11, style=\"slider\") sliderValue\n" + // - "% @String(persist = false, family='Carnivora', " + // + "#@ LogService (required = false) log\n" + // + "#@ int (label=\"Slider Value\", softMin=5, softMax=15, " + // + "stepSize=3, value=11, style=\" slidEr,\") sliderValue\n" + // + "#@ String (persist = false, family='Carnivora', " + // "choices={'quick brown fox', 'lazy dog'}) animal\n" + // - "% @String(visibility=MESSAGE) msg\n" + // - "% @BOTH java.lang.StringBuilder buffer"; + "#@ Double (autoFill = false) notAutoFilled\n" + // + "#@ String (visibility=MESSAGE) msg\n" + // + "#@BOTH java.lang.StringBuilder buffer"; final ScriptInfo info = new ScriptInfo(context, "params.bsizes", new StringReader(script)); @@ -196,7 +272,8 @@ public void testParameters() { final ModuleItem sliderValue = info.getInput("sliderValue"); assertItem("sliderValue", int.class, "Slider Value", ItemIO.INPUT, true, - true, null, "slider", 11, null, null, 5, 15, 3.0, noChoices, sliderValue); + true, null, " slidEr,", 11, null, null, 5, 15, 3.0, noChoices, sliderValue); + assertTrue("Case-insensitive trimmed style", WidgetStyle.isStyle(sliderValue, "slider")); final ModuleItem animal = info.getInput("animal"); final List animalChoices = // @@ -205,6 +282,9 @@ public void testParameters() { null, null, null, null, null, null, null, null, animalChoices, animal); assertEquals(animal.get("family"), "Carnivora"); // test custom attribute + final ModuleItem notAutoFilled = info.getInput("notAutoFilled"); + assertFalse(notAutoFilled.isAutoFill()); + final ModuleItem msg = info.getInput("msg"); assertSame(ItemVisibility.MESSAGE, msg.getVisibility()); @@ -213,7 +293,7 @@ public void testParameters() { null, null, null, null, null, null, null, null, noChoices, buffer); int inputCount = 0; - final ModuleItem[] inputs = { log, sliderValue, animal, msg, buffer }; + final ModuleItem[] inputs = { log, sliderValue, animal, notAutoFilled, msg, buffer }; for (final ModuleItem inItem : info.inputs()) { assertSame(inputs[inputCount++], inItem); } @@ -225,6 +305,96 @@ public void testParameters() { } } + /** Tests {@code #@script} directives. */ + @Test + public void testScriptDirective() { + final String script = "" + // + "#@script(name = \"my_script\"" + // + ", label = \"My Script\"" + // + ", description = \"What a great script.\"" + // + ", menuPath = \"Plugins > Do All The Things\"" + // + ", menuRoot = \"special\"" + // + ", iconPath = \"/path/to/myIcon.png\"" + // + ", priority = \"extremely-high\"" + // + ", headless = true" + // + ", foo = \"bar\"" + // + ")\n" + + "WOOT\n"; + + ScriptInfo info = null; + info = + new ScriptInfo(context, "scriptHeader.bsizes", new StringReader(script)); + info.inputs(); // HACK: Force lazy initialization. + + assertEquals("my_script", info.getName()); + assertEquals("My Script", info.getLabel()); + assertEquals("What a great script.", info.getDescription()); + final MenuPath menuPath = info.getMenuPath(); + assertEquals(2, menuPath.size()); + assertEquals("Plugins", menuPath.get(0).getName()); + assertEquals("Do All The Things", menuPath.get(1).getName()); + assertEquals("/path/to/myIcon.png", info.getIconPath()); + assertEquals(Priority.EXTREMELY_HIGH, info.getPriority(), 0.0); + assertTrue(info.canRunHeadless()); + assertEquals("bar", info.get("foo")); + } + + /** Tests the {@code language} key of the {@code #@script} directive. */ + @Test + public void testScriptDirectiveLanguage() { + // Use a .txt extension so language is NOT auto-inferred from the path. + // Confirm it starts out null (no language for .txt). + final String scriptByName = "#@script(language=\"BindingSizes\")\nWOOT\n"; + final ScriptInfo infoByName = + new ScriptInfo(context, "test.txt", new StringReader(scriptByName)); + assertNull(infoByName.getLanguage()); // no language yet + infoByName.inputs(); // trigger parsing + assertNotNull(infoByName.getLanguage()); + assertTrue(infoByName.getLanguage().getNames().contains("BindingSizes")); + + // Also confirm lookup by file extension works. + final String scriptByExt = "#@script(language=\"bsizes\")\nWOOT\n"; + final ScriptInfo infoByExt = + new ScriptInfo(context, "test.txt", new StringReader(scriptByExt)); + infoByExt.inputs(); + assertNotNull(infoByExt.getLanguage()); + assertTrue(infoByExt.getLanguage().getExtensions().contains("bsizes")); + + // Unknown language name should leave language unset (null for .txt path). + final String scriptBogus = "#@script(language=\"no-such-language\")\nWOOT\n"; + final ScriptInfo infoBogus = + new ScriptInfo(context, "test.txt", new StringReader(scriptBogus)); + infoBogus.inputs(); + assertNull(infoBogus.getLanguage()); + } + + /** + * Ensures the ScriptInfos Reader can be reused for multiple executions of the + * script. + */ + @Test + public void testReaderSanity() throws Exception { + final String script = "" + // + "% @LogService log\n" + // + "% @OUTPUT Integer output"; + + final ScriptInfo info = + new ScriptInfo(context, "hello.bsizes", new StringReader(script)); + final BufferedReader reader1 = info.getReader(); + final BufferedReader reader2 = info.getReader(); + + assertEquals("Readers are not independent.", reader1.read(), reader2.read()); + } + + // -- Helper methods -- + + private String id(final String path, final String script) { + final ScriptInfo info = // + new ScriptInfo(context, path, new StringReader(script)); + info.inputs(); // NB: Force parameter parsing. + return info.getIdentifier(); + } + private void assertItem(final String name, final Class type, final String label, final ItemIO ioType, final boolean required, final boolean persist, final String persistKey, final String style, @@ -249,25 +419,6 @@ private void assertItem(final String name, final Class type, assertEquals(choices, item.getChoices()); } - /** - * Ensures the ScriptInfos Reader can be reused for multiple executions of the - * script. - */ - @Test - public void testReaderSanity() throws Exception { - final String script = "" + // - "% @LogService log\n" + // - "% @OUTPUT Integer output"; - - final ScriptInfo info = - new ScriptInfo(context, "hello.bsizes", new StringReader(script)); - final BufferedReader reader1 = info.getReader(); - final BufferedReader reader2 = info.getReader(); - - assertEquals("Readers are not independent.", reader1.read(), reader2.read()); - - } - @Plugin(type = ScriptLanguage.class) public static class BindingSizes extends AbstractScriptLanguage { @@ -287,7 +438,7 @@ public List getExtensions() { } } - // -- Test script langauge -- + // -- Test script language -- private static class BindingSizesEngine extends AbstractScriptEngine { diff --git a/src/test/java/org/scijava/script/ScriptServiceTest.java b/src/test/java/org/scijava/script/ScriptServiceTest.java index 3e1c06711..6379c0fb0 100644 --- a/src/test/java/org/scijava/script/ScriptServiceTest.java +++ b/src/test/java/org/scijava/script/ScriptServiceTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -128,5 +126,4 @@ public void testArrayAliases() throws ScriptException { ctx.dispose(); } - } diff --git a/src/test/java/org/scijava/script/process/ParameterScriptProcessorTest.java b/src/test/java/org/scijava/script/process/ParameterScriptProcessorTest.java new file mode 100644 index 000000000..df50fe820 --- /dev/null +++ b/src/test/java/org/scijava/script/process/ParameterScriptProcessorTest.java @@ -0,0 +1,82 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.scijava.script.process; + +import static org.junit.Assert.*; + +import java.io.StringReader; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.scijava.Context; +import org.scijava.script.ScriptInfo; + +public class ParameterScriptProcessorTest { + + private Context context; + + @Before + public void setUp() { + context = new Context(); + } + + @After + public void tearDown() { + context.dispose(); + } + + @Test + public void testScriptParameterParsing() { + String script = "" + // + "% @String legacyStyleParameter\n" + + "% #@ String commentedHeaderParameter\n" + + "% ############## Some Comment ###########\n" + + "#@ String implicitInputParameter\n" + + "#@input String explicitInputParameter\n" + + "\n" + + "% @String legacyStyleBodyParameter\n" + + "% #@ String commentedBodyParameter\n" + + "\n" + + "#@output implicitlyTypedOutputParameter\n" + + "#@output String explicitlyTypedOutputParameter\n"; + final ScriptInfo info = new ScriptInfo(context, ".bsizes", new StringReader(script)); + assertEquals("legacyStyleParameter", info.getInput("legacyStyleParameter").getName()); + assertEquals("implicitInputParameter", info.getInput("implicitInputParameter").getName()); + assertEquals("explicitInputParameter", info.getInput("explicitInputParameter").getName()); + + assertEquals("implicitlyTypedOutputParameter", info.getOutput("implicitlyTypedOutputParameter").getName()); + assertEquals("explicitlyTypedOutputParameter", info.getOutput("explicitlyTypedOutputParameter").getName()); + + assertNull(info.getInput("commentedHeaderParameter")); + assertNull(info.getInput("legacyStyleBodyParameter")); + assertNull(info.getInput("commentedBodyParameter")); + } + +} diff --git a/src/test/java/org/scijava/service/ServiceIndexTest.java b/src/test/java/org/scijava/service/ServiceIndexTest.java index fd643e259..595c57581 100644 --- a/src/test/java/org/scijava/service/ServiceIndexTest.java +++ b/src/test/java/org/scijava/service/ServiceIndexTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/task/TaskEventTest.java b/src/test/java/org/scijava/task/TaskEventTest.java new file mode 100644 index 000000000..542a8a52a --- /dev/null +++ b/src/test/java/org/scijava/task/TaskEventTest.java @@ -0,0 +1,139 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.scijava.task; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.scijava.Context; +import org.scijava.event.EventHandler; +import org.scijava.task.event.TaskEvent; + +import java.util.HashSet; +import java.util.Set; + +import static org.junit.Assert.assertEquals; + +/** + * Tests whether many tasks run in parallel consistently trigger an Event + * when each task is started and when each task is ended. + * + * The test fails inconsistently, sometimes with a few tasks remaining, sometimes with almost all tasks remaining. + */ + +public class TaskEventTest { + + private TaskService taskService; + private TaskEventListener eventListener; + + static int nTasks = 500; // Putting higher value can lead to issues because too many threads cannot be launched in parallel + + @Before + public void setUp() { + final Context ctx = new Context(TaskService.class); + taskService = ctx.service(TaskService.class); + eventListener = new TaskEventListener(); + ctx.inject(eventListener); + } + + @After + public void tearDown() { + taskService.context().dispose(); + } + + @Test + public void testManyTasks() throws InterruptedException { + for (int i=0;i { + try { + Thread.sleep(msBeforeStart); + + // Task started + task.setProgressMaximum(100); + + task.run(() -> { + int totalMs = 0; + while(totalMs tasks = new HashSet<>(); + + @EventHandler + private synchronized void onEvent(final TaskEvent evt) { + Task task = evt.getTask(); + if (task.isDone()) { + tasks.remove(task); + } else { + tasks.add(task); + } + } + + public synchronized Set getLeftOvers() { + return new HashSet<>(tasks); + } + } +} diff --git a/src/test/java/org/scijava/task/TaskServiceTest.java b/src/test/java/org/scijava/task/TaskServiceTest.java new file mode 100644 index 000000000..14ab2f27f --- /dev/null +++ b/src/test/java/org/scijava/task/TaskServiceTest.java @@ -0,0 +1,74 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.task; + +import static org.junit.Assert.assertEquals; + +import java.util.concurrent.ExecutionException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.scijava.Context; + +/** + * Tests {@link TaskService}. + * + * @author Curtis Rueden + */ +public class TaskServiceTest { + + private TaskService taskService; + + @Before + public void setUp() { + final Context ctx = new Context(TaskService.class); + taskService = ctx.service(TaskService.class); + } + + @After + public void tearDown() { + taskService.context().dispose(); + } + + @Test + public void testTask() throws InterruptedException, ExecutionException { + final int[] result = new int[1]; + final Task task = taskService.createTask("hello"); + task.run(() -> { + task.setStatusMessage("Hello"); + task.setProgressMaximum(10); + task.setProgressValue(5); + result[0] = 100; + }); + task.waitFor(); + assertEquals(100, result[0]); + } +} diff --git a/src/test/java/org/scijava/test/AbstractSciJavaTest.java b/src/test/java/org/scijava/test/AbstractSciJavaTest.java index d2ab485fa..fdc4a8375 100644 --- a/src/test/java/org/scijava/test/AbstractSciJavaTest.java +++ b/src/test/java/org/scijava/test/AbstractSciJavaTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/test/TestUtilsTest.java b/src/test/java/org/scijava/test/TestUtilsTest.java index f8b7f94b6..19717064b 100644 --- a/src/test/java/org/scijava/test/TestUtilsTest.java +++ b/src/test/java/org/scijava/test/TestUtilsTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/text/TextServiceTest.java b/src/test/java/org/scijava/text/TextServiceTest.java new file mode 100644 index 000000000..999779004 --- /dev/null +++ b/src/test/java/org/scijava/text/TextServiceTest.java @@ -0,0 +1,86 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.scijava.text; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.io.File; +import java.util.Arrays; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.scijava.Context; +import org.scijava.plugin.Plugin; + +/** Tests {@link TextService}. */ +public class TextServiceTest { + + private Context ctx; + private TextService textService; + + @Before + public void setUp() { + ctx = new Context(TextService.class); + textService = ctx.service(TextService.class); + } + + @After + public void tearDown() { + ctx.dispose(); + } + + @Test + public void testGetHandler() { + final TextFormat fooHandler = textService.getHandler(new File("data.foo")); + assertNotNull(fooHandler); + assertEquals(FooTextFormat.class, fooHandler.getClass()); + System.out.println(fooHandler); + + final TextFormat barHandler = textService.getHandler(new File("data.bar")); + assertNull(barHandler); + } + + @Plugin(type = TextFormat.class) + public static class FooTextFormat extends AbstractTextFormat { + + @Override + public List getExtensions() { + return Arrays.asList("foo"); + } + + @Override + public String asHTML(final String text) { + return "[FOO]" + text + "[/FOO]"; + } + } +} diff --git a/src/test/java/org/scijava/thread/ThreadServiceTest.java b/src/test/java/org/scijava/thread/ThreadServiceTest.java index 9a29ead8e..569c15314 100644 --- a/src/test/java/org/scijava/thread/ThreadServiceTest.java +++ b/src/test/java/org/scijava/thread/ThreadServiceTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/ui/UIServiceTest.java b/src/test/java/org/scijava/ui/UIServiceTest.java index 03bae44f1..69a9ec6e7 100644 --- a/src/test/java/org/scijava/ui/UIServiceTest.java +++ b/src/test/java/org/scijava/ui/UIServiceTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -33,6 +31,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.util.List; @@ -81,19 +80,40 @@ public void testAvailableUIs() { @Test public void testHeadlessUI() { + // If true here, before we messed with it, the assumption is that we are + // in a truly headless environment, not just forced via setHeadless(true). + boolean reallyHeadless = uiService.isHeadless(); + final MockUserInterface mockUI = new MockUserInterface(); uiService.setDefaultUI(mockUI); // test non-headless behavior - uiService.setHeadless(false); - assertFalse(uiService.isHeadless()); - assertTrue(uiService.getDefaultUI() instanceof MockUserInterface); - - // test headless behavior - uiService.setHeadless(true); - assertTrue(uiService.isHeadless()); - assertTrue("UIService should return HeadlessUI when running \"headless\"", - uiService.getDefaultUI() instanceof HeadlessUI); + if (reallyHeadless) { + // This environment is truly headless, and + // we should not be able to override it. + uiService.setHeadless(false); + assertTrue(uiService.isHeadless()); + assertTrue("UIService should return HeadlessUI when running \"headless\"", + uiService.getDefaultUI() instanceof HeadlessUI); + } + else { + // This environment is not headless! We can test more things. + assertSame("UIService default UI override failed", + mockUI, uiService.getDefaultUI()); + + // This environment isn't headless now; + // let's test overriding it to be so. + uiService.setHeadless(true); + assertTrue(uiService.isHeadless()); + assertTrue("UIService should return HeadlessUI when running \"headless\"", + uiService.getDefaultUI() instanceof HeadlessUI); + + // Now we put it back! + uiService.setHeadless(false); + assertFalse(uiService.isHeadless()); + assertSame("UIService default UI override was not restored", + mockUI, uiService.getDefaultUI()); + } } private static final class MockUserInterface extends AbstractUserInterface { diff --git a/src/test/java/org/scijava/util/AppUtilsTest.java b/src/test/java/org/scijava/util/AppUtilsTest.java index 2fd9342ef..23412f0e7 100644 --- a/src/test/java/org/scijava/util/AppUtilsTest.java +++ b/src/test/java/org/scijava/util/AppUtilsTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -52,6 +50,8 @@ public void testBaseDirectory() { final String tmp = PlatformUtils.isWindows() ? "c:/tmp" : "/tmp"; assertEquals(new File(tmp), AppUtils.getBaseDirectory(new File( tmp + "/app/target/classes"), "app")); + assertEquals(new File(tmp), AppUtils.getBaseDirectory(new File( + tmp + "/app/target/test-classes"), "app")); assertEquals(new File(tmp), AppUtils.getBaseDirectory(new File( tmp + "/app/target/ij-app-1.57.jar"), "app")); } diff --git a/src/test/java/org/scijava/util/ArrayUtilsTest.java b/src/test/java/org/scijava/util/ArrayUtilsTest.java index 59b463ac2..69a277f7a 100644 --- a/src/test/java/org/scijava/util/ArrayUtilsTest.java +++ b/src/test/java/org/scijava/util/ArrayUtilsTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/util/BoolArrayTest.java b/src/test/java/org/scijava/util/BoolArrayTest.java index f29d1d5ab..952a6d2bc 100644 --- a/src/test/java/org/scijava/util/BoolArrayTest.java +++ b/src/test/java/org/scijava/util/BoolArrayTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/util/ByteArrayTest.java b/src/test/java/org/scijava/util/ByteArrayTest.java index cdf6fc2f6..160124723 100644 --- a/src/test/java/org/scijava/util/ByteArrayTest.java +++ b/src/test/java/org/scijava/util/ByteArrayTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/util/CharArrayTest.java b/src/test/java/org/scijava/util/CharArrayTest.java index 8e31741ee..7fa1827fb 100644 --- a/src/test/java/org/scijava/util/CharArrayTest.java +++ b/src/test/java/org/scijava/util/CharArrayTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/util/ClassUtilsTest.java b/src/test/java/org/scijava/util/ClassUtilsTest.java index 0dba2d7f2..25a673326 100644 --- a/src/test/java/org/scijava/util/ClassUtilsTest.java +++ b/src/test/java/org/scijava/util/ClassUtilsTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -32,8 +30,6 @@ package org.scijava.util; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.scijava.test.TestUtils.createTemporaryDirectory; @@ -57,86 +53,6 @@ */ public class ClassUtilsTest { - @Test - public void testLoadClass() { - assertLoaded(boolean.class, "boolean"); - assertLoaded(byte.class, "byte"); - assertLoaded(char.class, "char"); - assertLoaded(double.class, "double"); - assertLoaded(float.class, "float"); - assertLoaded(int.class, "int"); - assertLoaded(long.class, "long"); - assertLoaded(short.class, "short"); - assertLoaded(void.class, "void"); - assertLoaded(String.class, "string"); - assertLoaded(Number.class, "java.lang.Number"); - assertLoaded(boolean[].class, "boolean[]"); - assertLoaded(byte[].class, "byte[]"); - assertLoaded(char[].class, "char[]"); - assertLoaded(double[].class, "double[]"); - assertLoaded(float[].class, "float[]"); - assertLoaded(int[].class, "int[]"); - assertLoaded(long[].class, "long[]"); - assertLoaded(short[].class, "short[]"); - assertLoaded(null, "void[]"); - assertLoaded(String[].class, "string[]"); - assertLoaded(Number[].class, "java.lang.Number[]"); - assertLoaded(boolean[][].class, "boolean[][]"); - assertLoaded(byte[][].class, "byte[][]"); - assertLoaded(char[][].class, "char[][]"); - assertLoaded(double[][].class, "double[][]"); - assertLoaded(float[][].class, "float[][]"); - assertLoaded(int[][].class, "int[][]"); - assertLoaded(long[][].class, "long[][]"); - assertLoaded(short[][].class, "short[][]"); - assertLoaded(null, "void[][]"); - assertLoaded(String[][].class, "string[][]"); - assertLoaded(Number[][].class, "java.lang.Number[][]"); - assertLoaded(boolean[].class, "[Z"); - assertLoaded(byte[].class, "[B"); - assertLoaded(char[].class, "[C"); - assertLoaded(double[].class, "[D"); - assertLoaded(float[].class, "[F"); - assertLoaded(int[].class, "[I"); - assertLoaded(long[].class, "[J"); - assertLoaded(short[].class, "[S"); - assertLoaded(null, "[V"); - assertLoaded(String[].class, "[Lstring;"); - assertLoaded(Number[].class, "[Ljava.lang.Number;"); - assertLoaded(boolean[][].class, "[[Z"); - assertLoaded(byte[][].class, "[[B"); - assertLoaded(char[][].class, "[[C"); - assertLoaded(double[][].class, "[[D"); - assertLoaded(float[][].class, "[[F"); - assertLoaded(int[][].class, "[[I"); - assertLoaded(long[][].class, "[[J"); - assertLoaded(short[][].class, "[[S"); - assertLoaded(null, "[[V"); - assertLoaded(String[][].class, "[[Lstring;"); - assertLoaded(Number[][].class, "[[Ljava.lang.Number;"); - } - - @Test - public void testFailureQuiet() { - assertNull(ClassUtils.loadClass("a.non.existent.class")); - } - - @Test(expected = IllegalArgumentException.class) - public void testFailureLoud() { - ClassUtils.loadClass("a.non.existent.class", false); - } - - @Test - public void testGetArrayClass() { - assertSame(boolean[].class, ClassUtils.getArrayClass(boolean.class)); - assertSame(String[].class, ClassUtils.getArrayClass(String.class)); - assertSame(Number[].class, ClassUtils.getArrayClass(Number.class)); - assertSame(boolean[][].class, ClassUtils.getArrayClass(boolean[].class)); - assertSame(String[][].class, ClassUtils.getArrayClass(String[].class)); - assertSame(Number[][].class, ClassUtils.getArrayClass(Number[].class)); - assertNull(ClassUtils.getArrayClass(void.class)); - } - @Test public void testUnpackedClass() throws IOException { final File tmpDir = createTemporaryDirectory("class-utils-test-"); @@ -194,8 +110,4 @@ private void copy(final InputStream in, final OutputStream out, if (closeOut) out.close(); } - private void assertLoaded(final Class c, final String name) { - assertSame(c, ClassUtils.loadClass(name)); - } - } diff --git a/src/test/java/org/scijava/util/ColorRGBTest.java b/src/test/java/org/scijava/util/ColorRGBTest.java index 02942c614..fea5f3d29 100644 --- a/src/test/java/org/scijava/util/ColorRGBTest.java +++ b/src/test/java/org/scijava/util/ColorRGBTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/util/ConversionUtilsTest.java b/src/test/java/org/scijava/util/ConversionUtilsTest.java index 139fd015b..5c63c2d03 100644 --- a/src/test/java/org/scijava/util/ConversionUtilsTest.java +++ b/src/test/java/org/scijava/util/ConversionUtilsTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -32,21 +30,15 @@ package org.scijava.util; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; -import java.math.BigDecimal; -import java.math.BigInteger; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Random; import java.util.Set; @@ -60,196 +52,6 @@ */ public class ConversionUtilsTest { - /** Tests {@link ConversionUtils#canCast(Class, Class)}. */ - @Test - public void testCanCast() { - // check casting to superclass - assertTrue(ConversionUtils.canCast(String.class, Object.class)); - - // check casting to interface - assertTrue(ConversionUtils.canCast(ArrayList.class, Collection.class)); - - // casting numeric primitives is not supported - assertFalse(ConversionUtils.canCast(double.class, float.class)); - assertFalse(ConversionUtils.canCast(float.class, double.class)); - - // check casting with boxing - assertTrue(ConversionUtils.canCast(int.class, Number.class)); - - // casting from null always works - final Class nullClass = null; - assertTrue(ConversionUtils.canCast(nullClass, Object.class)); - assertTrue(ConversionUtils.canCast(nullClass, int[].class)); - final Object nullObject = null; - assertTrue(ConversionUtils.canCast(nullObject, Object.class)); - assertTrue(ConversionUtils.canCast(nullObject, int[].class)); - - // casting to null is not allowed - assertFalse(ConversionUtils.canCast(nullClass, null)); - assertFalse(ConversionUtils.canCast(Object.class, null)); - assertFalse(ConversionUtils.canCast(nullObject, null)); - assertFalse(ConversionUtils.canCast(new Object(), null)); - } - - /** Tests {@link ConversionUtils#cast(Object, Class)}. */ - @Test - public void testCast() { - // check casting to superclass - final String string = "Hello"; - final Object stringToObject = ConversionUtils.cast(string, Object.class); - assertSame(string, stringToObject); - - // check casting to interface - final ArrayList arrayList = new ArrayList<>(); - final Collection arrayListToCollection = - ConversionUtils.cast(arrayList, Collection.class); - assertSame(arrayList, arrayListToCollection); - - // casting numeric primitives is not supported - final Float doubleToFloat = ConversionUtils.cast(5.1, float.class); - assertNull(doubleToFloat); - final Double floatToDouble = ConversionUtils.cast(5.1f, double.class); - assertNull(floatToDouble); - - // boxing works though - final Number intToNumber = ConversionUtils.cast(5, Number.class); - assertSame(Integer.class, intToNumber.getClass()); - assertEquals(5, intToNumber.intValue()); - } - - /** Tests {@link ConversionUtils#convertToEnum(String, Class)}. */ - @Test - public void testConvertToEnum() { - final Words foo = ConversionUtils.convertToEnum("FOO", Words.class); - assertSame(Words.FOO, foo); - final Words bar = ConversionUtils.convertToEnum("BAR", Words.class); - assertSame(Words.BAR, bar); - final Words fubar = ConversionUtils.convertToEnum("FUBAR", Words.class); - assertSame(Words.FUBAR, fubar); - final Words noConstant = ConversionUtils.convertToEnum("NONE", Words.class); - assertNull(noConstant); - final String notAnEnum = - ConversionUtils.convertToEnum("HOOYAH", String.class); - assertNull(notAnEnum); - } - - /** Tests {@link ConversionUtils#getNonprimitiveType(Class)}. */ - @Test - public void testGetNonprimitiveType() { - final Class booleanType = - ConversionUtils.getNonprimitiveType(boolean.class); - assertSame(Boolean.class, booleanType); - - final Class byteType = - ConversionUtils.getNonprimitiveType(byte.class); - assertSame(Byte.class, byteType); - - final Class charType = - ConversionUtils.getNonprimitiveType(char.class); - assertSame(Character.class, charType); - - final Class doubleType = - ConversionUtils.getNonprimitiveType(double.class); - assertSame(Double.class, doubleType); - - final Class floatType = - ConversionUtils.getNonprimitiveType(float.class); - assertSame(Float.class, floatType); - - final Class intType = - ConversionUtils.getNonprimitiveType(int.class); - assertSame(Integer.class, intType); - - final Class longType = - ConversionUtils.getNonprimitiveType(long.class); - assertSame(Long.class, longType); - - final Class shortType = - ConversionUtils.getNonprimitiveType(short.class); - assertSame(Short.class, shortType); - - final Class voidType = - ConversionUtils.getNonprimitiveType(void.class); - assertSame(Void.class, voidType); - - final Class[] types = { // - Boolean.class, Byte.class, Character.class, Double.class, // - Float.class, Integer.class, Long.class, Short.class, // - Void.class, // - String.class, // - Number.class, BigInteger.class, BigDecimal.class, // - boolean[].class, byte[].class, char[].class, double[].class, // - float[].class, int[].class, long[].class, short[].class, // - Boolean[].class, Byte[].class, Character[].class, Double[].class, // - Float[].class, Integer[].class, Long[].class, Short[].class, // - Void[].class, // - Object.class, Object[].class, String[].class, // - Object[][].class, String[][].class, // - Collection.class, // - List.class, ArrayList.class, LinkedList.class, // - Set.class, HashSet.class, // - Map.class, HashMap.class, // - Collection[].class, List[].class, Set[].class, Map[].class }; - for (final Class c : types) { - final Class type = ConversionUtils.getNonprimitiveType(c); - assertSame(c, type); - } - } - - /** Tests {@link ConversionUtils#getNullValue(Class)}. */ - @Test - public void testGetNullValue() { - final boolean booleanNull = ConversionUtils.getNullValue(boolean.class); - assertFalse(booleanNull); - - final byte byteNull = ConversionUtils.getNullValue(byte.class); - assertEquals(0, byteNull); - - final char charNull = ConversionUtils.getNullValue(char.class); - assertEquals('\0', charNull); - - final double doubleNull = ConversionUtils.getNullValue(double.class); - assertEquals(0.0, doubleNull, 0.0); - - final float floatNull = ConversionUtils.getNullValue(float.class); - assertEquals(0f, floatNull, 0f); - - final int intNull = ConversionUtils.getNullValue(int.class); - assertEquals(0, intNull); - - final long longNull = ConversionUtils.getNullValue(long.class); - assertEquals(0, longNull); - - final short shortNull = ConversionUtils.getNullValue(short.class); - assertEquals(0, shortNull); - - final Void voidNull = ConversionUtils.getNullValue(void.class); - assertNull(voidNull); - - final Class[] types = { // - Boolean.class, Byte.class, Character.class, Double.class, // - Float.class, Integer.class, Long.class, Short.class, // - Void.class, // - String.class, // - Number.class, BigInteger.class, BigDecimal.class, // - boolean[].class, byte[].class, char[].class, double[].class, // - float[].class, int[].class, long[].class, short[].class, // - Boolean[].class, Byte[].class, Character[].class, Double[].class, // - Float[].class, Integer[].class, Long[].class, Short[].class, // - Void[].class, // - Object.class, Object[].class, String[].class, // - Object[][].class, String[][].class, // - Collection.class, // - List.class, ArrayList.class, LinkedList.class, // - Set.class, HashSet.class, // - Map.class, HashMap.class, // - Collection[].class, List[].class, Set[].class, Map[].class }; - for (final Class c : types) { - final Object nullValue = ConversionUtils.getNullValue(c); - assertNull("Expected null for " + c.getName(), nullValue); - } - } - /** * Tests populating a primitive array. */ @@ -261,7 +63,7 @@ class Struct { } final Struct struct = new Struct(); - final List intVals = getValueList(4, 3, 7); + final List intVals = Arrays.asList(4, 3, 7); setFieldValue(struct, "intArray", intVals); for (int i = 0; i < struct.intArray.length; i++) { @@ -288,7 +90,7 @@ class Struct { final Struct struct = new Struct(); // Verify behavior setting an array of Objects (Doubles) - final List doubleVals = getValueList(1.0, 2.0, 3.0); + final List doubleVals = Arrays.asList(1.0, 2.0, 3.0); setFieldValue(struct, "doubleArray", doubleVals); for (int i = 0; i < struct.doubleArray.length; i++) { @@ -308,7 +110,7 @@ class Struct { final Struct struct = new Struct(); // Verify behavior setting a List of Objects (Strings) - final List stringVals = getValueList("ok", "still ok"); + final List stringVals = Arrays.asList("ok", "still ok"); setFieldValue(struct, "stringList", stringVals); for (int i = 0; i < struct.stringList.size(); i++) { @@ -375,11 +177,14 @@ class Struct { assertEquals(123456789012.0, struct.myDoubles.get(0), 0.0); assertEquals(987654321098.0, struct.myDoubles.get(1), 0.0); - // Conversion to a list of strings (with no generic parameter) fails. + // Conversion to a list of strings (with no generic parameter) succeeds. setFieldValue(struct, "myStrings", longArray); - assertNull(struct.myStrings); + assertNotNull(struct.myStrings); + assertEquals(2, struct.myStrings.size()); + assertEquals("123456789012", struct.myStrings.get(0)); + assertEquals("987654321098", struct.myStrings.get(1)); } /** @@ -434,16 +239,15 @@ class Struct { /** * Tests setting an incompatible element value for a primitive array. */ - @Test(expected = IllegalArgumentException.class) public void testBadPrimitiveArray() { class Struct { - @SuppressWarnings("unused") private int[] intArray; } final Struct struct = new Struct(); setFieldValue(struct, "intArray", "not an int array"); + assertEquals(null, struct.intArray); } /** @@ -451,23 +255,46 @@ class Struct { * and a collection. */ @Test - public void testBadObjectElements() { + public void testIncompatibleCollections() { class Struct { - private Double[] doubleArray; - private List stringList; - @SuppressWarnings("unused") - private Set nestedArray; + private double[] primitiveDoubleArray; + private Double[] boxedDoubleArray; + private List numberList; + private Set setOfIntegerArrays; } final Struct struct = new Struct(); - // Test abnormal behavior for an object array - setFieldValue(struct, "doubleArray", "not a double array"); - assertEquals(null, struct.doubleArray[0]); - - // Test abnormal behavior for a list - setFieldValue(struct, "nestedArray", "definitely not a set of char arrays"); - assertNull(struct.stringList); + // NB: DefaultConverter converts non-collection/array objects to + // collection/array objects, even if some or all of the constituent elements + // cannot be converted to the array/collection component/element type. + + // Test object to incompatible primitive array type + setFieldValue(struct, "primitiveDoubleArray", "not a double array"); + assertNotNull(struct.primitiveDoubleArray); + assertEquals(1, struct.primitiveDoubleArray.length); + assertEquals(0.0, struct.primitiveDoubleArray[0], 0.0); + + // Test object to incompatible non-primitive array type + setFieldValue(struct, "boxedDoubleArray", "not a double array"); + assertNotNull(struct.boxedDoubleArray); + assertEquals(1, struct.boxedDoubleArray.length); + assertNull(struct.boxedDoubleArray[0]); + + // Test object to incompatible List type + setFieldValue(struct, "numberList", "not actually a list of numbers"); + List expectedList = Arrays.asList((Number) null); + assertEquals(expectedList, struct.numberList); + + // Test object to incompatible Set type + setFieldValue(struct, "setOfIntegerArrays", // + "definitely not a set of Integer[]"); + assertNotNull(struct.setOfIntegerArrays); + assertEquals(1, struct.setOfIntegerArrays.size()); + Integer[] singleton = struct.setOfIntegerArrays.iterator().next(); + assertNotNull(singleton); + assertEquals(1, singleton.length); + assertNull(singleton[0]); } /** @@ -514,7 +341,7 @@ class Struct { } final Struct struct = new Struct(); - setFieldValue(struct, "listWrapper", getValueList(4, 8, 2)); + setFieldValue(struct, "listWrapper", Arrays.asList(4, 8, 2)); assertNotNull(struct.listWrapper); } @@ -527,17 +354,7 @@ class Struct { private void setFieldValue(final Object o, final String fieldName, final Object value) { - ClassUtils.setValue(ClassUtils.getField(o.getClass(), fieldName), o, value); - } - - /** - * Convenience method to convert an array of values to a collection. - */ - private List getValueList(final T... values) { - final List list = new ArrayList<>(); - for (final T value : values) - list.add(value); - return list; + ClassUtils.setValue(Types.field(o.getClass(), fieldName), o, value); } // -- Helper Classes -- @@ -634,9 +451,4 @@ public static class StringListExtension extends ArrayList { // NB: No implementation needed. } - /** Enumeration for testing conversion to enum types. */ - public static enum Words { - FOO, BAR, FUBAR - } - } diff --git a/src/test/java/org/scijava/util/DigestUtilsTest.java b/src/test/java/org/scijava/util/DigestUtilsTest.java index 4610931ae..c19510ab5 100644 --- a/src/test/java/org/scijava/util/DigestUtilsTest.java +++ b/src/test/java/org/scijava/util/DigestUtilsTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -43,11 +41,11 @@ */ public class DigestUtilsTest { - private static final byte[] CAFEBABE_SHA1 = { 20, 101, -38, -47, 38, -45, 43, - -9, -86, 93, 59, -107, -91, -57, -61, 49, -51, -1, 52, -33 }; + private static final byte[] COFFEE_SHA1 = { -71, 2, 27, -126, -23, -70, -89, + 35, -65, -15, -108, 66, 72, 113, 29, -32, -12, -42, -49, 6 }; - private static final byte[] CAFEBABE_MD5 = { 45, 27, -67, -30, -84, -84, 10, - -3, 7, 100, 109, -104, 21, 79, 64, 46 }; + private static final byte[] COFFEE_MD5 = { -39, -98, 9, -40, -39, 44, 31, + -62, 23, 9, 38, 101, 85, -57, 121, -110 }; private static final byte[] HELLO_WORLD_SHA1 = { 123, 80, 44, 58, 31, 72, -56, 96, -102, -30, 18, -51, -5, 99, -99, -18, 57, 103, 63, 94 }; @@ -55,14 +53,14 @@ public class DigestUtilsTest { private static final String HELLO_WORLD_SHA1_HEX = "7b502c3a1f48c8609ae212cdfb639dee39673f5e"; - private static final String CAFEBABE_SHA1_HEX = - "1465dad126d32bf7aa5d3b95a5c7c331cdff34df"; + private static final String COFFEE_SHA1_HEX = + "b9021b82e9baa723bff1944248711de0f4d6cf06"; private static final String HELLO_WORLD_SHA1_BASE64 = "e1AsOh9IyGCa4hLN+2Od7jlnP14="; - private static final String CAFEBABE_SHA1_BASE64 = - "FGXa0SbTK/eqXTuVpcfDMc3/NN8="; + private static final String COFFEE_SHA1_BASE64 = + "uQIbgum6pyO/8ZRCSHEd4PTWzwY="; /** Tests {@link DigestUtils#bytes(String)}. */ @Test @@ -77,15 +75,15 @@ public void testBytesString() { /** Tests {@link DigestUtils#bytes(int)}. */ @Test public void testBytesInt() { - final byte[] bytes = DigestUtils.bytes(0xcafebabe); - final byte[] expected = { -54, -2, -70, -66 }; + final byte[] bytes = DigestUtils.bytes(0xc0ffee); + final byte[] expected = { 0, -64, -1, -18 }; assertArrayEquals(expected, bytes); } /** Tests {@link DigestUtils#hex(byte[])}. */ @Test public void testHex() { - assertEquals("cafebabe", DigestUtils.hex(DigestUtils.bytes(0xcafebabe))); + assertEquals("00c0ffee", DigestUtils.hex(DigestUtils.bytes(0xc0ffee))); assertEquals("deadbeef", DigestUtils.hex(DigestUtils.bytes(0xdeadbeef))); assertEquals("00000000", DigestUtils.hex(DigestUtils.bytes(0x00000000))); assertEquals("ffffffff", DigestUtils.hex(DigestUtils.bytes(0xffffffff))); @@ -94,7 +92,7 @@ public void testHex() { /** Tests {@link DigestUtils#base64(byte[])}. */ @Test public void testBase64() { - assertEquals("yv66vg==", DigestUtils.base64(DigestUtils.bytes(0xcafebabe))); + assertEquals("AMD/7g==", DigestUtils.base64(DigestUtils.bytes(0xc0ffee))); assertEquals("3q2+7w==", DigestUtils.base64(DigestUtils.bytes(0xdeadbeef))); assertEquals("AAAAAA==", DigestUtils.base64(DigestUtils.bytes(0x00000000))); assertEquals("/////w==", DigestUtils.base64(DigestUtils.bytes(0xffffffff))); @@ -120,23 +118,23 @@ public void testHashBytes() { /** Tests {@link DigestUtils#sha1(byte[])}. */ @Test public void testSHA1() { - final byte[] bytes = DigestUtils.bytes(0xcafebabe); + final byte[] bytes = DigestUtils.bytes(0xc0ffee); final byte[] sha1 = DigestUtils.sha1(bytes); - assertArrayEquals(CAFEBABE_SHA1, sha1); + assertArrayEquals(COFFEE_SHA1, sha1); } /** Tests {@link DigestUtils#md5(byte[])}. */ @Test public void testMD5() { - final byte[] bytes = DigestUtils.bytes(0xcafebabe); + final byte[] bytes = DigestUtils.bytes(0xc0ffee); final byte[] md5 = DigestUtils.md5(bytes); - assertArrayEquals(CAFEBABE_MD5, md5); + assertArrayEquals(COFFEE_MD5, md5); } /** Tests {@link DigestUtils#digest(String, byte[])}. */ @Test public void testDigest() { - final byte[] bytes = DigestUtils.bytes(0xcafebabe); + final byte[] bytes = DigestUtils.bytes(0xc0ffee); final byte[] sha1 = DigestUtils.digest("SHA-1", bytes); final byte[] expectedSHA1 = DigestUtils.sha1(bytes); @@ -157,9 +155,9 @@ public void testBestString() { /** Tests {@link DigestUtils#best(byte[])}. */ @Test public void testBestBytes() { - final byte[] bytes = DigestUtils.bytes(0xcafebabe); + final byte[] bytes = DigestUtils.bytes(0xc0ffee); final byte[] best = DigestUtils.best(bytes); - assertArrayEquals(CAFEBABE_SHA1, best); + assertArrayEquals(COFFEE_SHA1, best); } /** Tests {@link DigestUtils#bestHex(String)}. */ @@ -171,8 +169,8 @@ public void testBestHexString() { /** Tests {@link DigestUtils#hex(byte[])}. */ @Test public void testBestHexBytes() { - final byte[] bytes = DigestUtils.bytes(0xcafebabe); - assertEquals(CAFEBABE_SHA1_HEX, DigestUtils.bestHex(bytes)); + final byte[] bytes = DigestUtils.bytes(0xc0ffee); + assertEquals(COFFEE_SHA1_HEX, DigestUtils.bestHex(bytes)); } /** Tests {@link DigestUtils#bestBase64(String)}. */ @@ -184,8 +182,8 @@ public void testBestBase64String() { /** Tests {@link DigestUtils#bestBase64(byte[])}. */ @Test public void testBestBase64Bytes() { - final byte[] bytes = DigestUtils.bytes(0xcafebabe); - assertEquals(CAFEBABE_SHA1_BASE64, DigestUtils.bestBase64(bytes)); + final byte[] bytes = DigestUtils.bytes(0xc0ffee); + assertEquals(COFFEE_SHA1_BASE64, DigestUtils.bestBase64(bytes)); } } diff --git a/src/test/java/org/scijava/util/DoubleArrayTest.java b/src/test/java/org/scijava/util/DoubleArrayTest.java index ce980ff89..aeeaeb9ff 100644 --- a/src/test/java/org/scijava/util/DoubleArrayTest.java +++ b/src/test/java/org/scijava/util/DoubleArrayTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/util/FileUtilsTest.java b/src/test/java/org/scijava/util/FileUtilsTest.java index 1d9c7a2e0..bbeb81232 100644 --- a/src/test/java/org/scijava/util/FileUtilsTest.java +++ b/src/test/java/org/scijava/util/FileUtilsTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -159,8 +157,8 @@ public void testShortenPath() { .shortenPath("\\\\server\\p1\\p2\\p3\\p4\\p5\\p6")); assertEquals("\\\\server\\p1\\p2\\p3", FileUtils .shortenPath("\\\\server\\p1\\p2\\p3")); - assertEquals("http://www.rgagnon.com/p1/p2/p3/.../pb.html", FileUtils - .shortenPath("http://www.rgagnon.com/p1/p2/p3/p4/p5/pb.html")); + assertEquals("https://www.rgagnon.com/p1/p2/p3/.../pb.html", FileUtils + .shortenPath("https://www.rgagnon.com/p1/p2/p3/p4/p5/pb.html")); } @Test @@ -179,8 +177,8 @@ public void testLimitPath() { "C:/1/2/3/4/5/test.txt", 15)); assertEquals("\\\\server\\p1\\p2\\...p6", FileUtils.limitPath( "\\\\server\\p1\\p2\\p3\\p4\\p5\\p6", 20)); - assertEquals("http://www...pb.html", FileUtils.limitPath( - "http://www.rgagnon.com/p1/p2/p3/p4/p5/pb.html", 20)); + assertEquals("https://www...pb.html", FileUtils.limitPath( + "https://www.rgagnon.com/p1/p2/p3/p4/p5/pb.html", 21)); } @Test @@ -311,6 +309,12 @@ public void testStripVersionFromFilename() { assertEquals("jars/jogl-all-natives-solaris-i586.jar", FileUtils.stripFilenameVersion("jars/jogl-all-2.3.0-natives-solaris-i586.jar")); assertEquals("jars/jogl-all-natives-windows-amd64.jar", FileUtils.stripFilenameVersion("jars/jogl-all-2.3.0-natives-windows-amd64.jar")); assertEquals("jars/jogl-all-natives-windows-i586.jar", FileUtils.stripFilenameVersion("jars/jogl-all-2.3.0-natives-windows-i586.jar")); + + // Test jinput style of native binary .jars + assertEquals("jars/jinput-natives-all.jar", FileUtils.stripFilenameVersion("jars/jinput-2.0.9-natives-all.jar")); + + // Test that native-lib-loader is not misrecognized as a native classifier + assertEquals("jars/native-lib-loader.jar", FileUtils.stripFilenameVersion("jars/native-lib-loader-2.3.2.jar")); } @Test diff --git a/src/test/java/org/scijava/util/FloatArrayTest.java b/src/test/java/org/scijava/util/FloatArrayTest.java index 4f8992c32..2f5274973 100644 --- a/src/test/java/org/scijava/util/FloatArrayTest.java +++ b/src/test/java/org/scijava/util/FloatArrayTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/util/GenericArrayTypesTest.java b/src/test/java/org/scijava/util/GenericArrayTypesTest.java new file mode 100644 index 000000000..f153471e0 --- /dev/null +++ b/src/test/java/org/scijava/util/GenericArrayTypesTest.java @@ -0,0 +1,78 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.scijava.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Type; +import java.util.List; + +import org.junit.Test; + +public class GenericArrayTypesTest { + + @Test + public void testArrayFields() throws NoSuchFieldException, SecurityException { + Field rawField = Types.field(ClassWithFields.class, "rawListArray"); + Field genericWildcardField = Types.field(ClassWithFields.class, "wildcardListArray"); + Field genericTypedField = Types.field(ClassWithFields.class, "integerListArray"); + + Type rawFieldType = Types.fieldType(rawField, ClassWithFields.class); + Type genericWildcardFieldType = Types.fieldType(genericWildcardField, ClassWithFields.class); + Type genericTypedFieldType = Types.fieldType(genericTypedField, ClassWithFields.class); + + // raw type + assertFalse(rawFieldType instanceof GenericArrayType); + assertTrue(rawFieldType instanceof Class); + + // generic array types + assertTrue(genericWildcardFieldType instanceof GenericArrayType); + assertTrue(genericTypedFieldType instanceof GenericArrayType); + + assertEquals(rawField.getGenericType(), rawFieldType); + assertEquals(genericWildcardField.getGenericType(), genericWildcardFieldType); + assertEquals(genericTypedField.getGenericType(), genericTypedFieldType); + + assertSame(List[].class, Types.raw(rawFieldType)); + assertSame(List[].class, Types.raw(genericWildcardFieldType)); + assertSame(List[].class, Types.raw(genericTypedFieldType)); + } + + @SuppressWarnings({ "rawtypes", "unused" }) + private static class ClassWithFields { + public List[] rawListArray; + public List[] wildcardListArray; + public List[] integerListArray; + } +} diff --git a/src/test/java/org/scijava/util/GenericUtilsTest.java b/src/test/java/org/scijava/util/GenericUtilsTest.java deleted file mode 100644 index b39c2cc71..000000000 --- a/src/test/java/org/scijava/util/GenericUtilsTest.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * #%L - * SciJava Common shared library for SciJava software. - * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ - -package org.scijava.util; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; - -import java.io.Serializable; -import java.lang.reflect.Field; -import java.lang.reflect.Type; -import java.util.HashMap; -import java.util.List; - -import org.junit.Test; - -/** - * Tests {@link GenericUtils}. - * - * @author Mark Hiner - * @author Curtis Rueden - */ -public class GenericUtilsTest { - - /** Tests {@link GenericUtils#getClass(Type)}. */ - @Test - public void testGetClass() { - @SuppressWarnings("unused") - class Struct { - - private int[] intArray; - private double d; - private String[][] strings; - private Void v; - private List list; - private HashMap map; - } - assertSame(int[].class, getClass(Struct.class, "intArray")); - assertSame(double.class, getClass(Struct.class, "d")); - assertSame(String[][].class, getClass(Struct.class, "strings")); - assertSame(Void.class, getClass(Struct.class, "v")); - assertSame(List.class, getClass(Struct.class, "list")); - assertSame(HashMap.class, getClass(Struct.class, "map")); - } - - /** Tests {@link GenericUtils#getComponentClass(Type)}. */ - @Test - public void testGetComponentClass() { - @SuppressWarnings("unused") - class Struct { - - private int[] intArray; - private double d; - private String[][] strings; - private Void v; - private List[] list; - private HashMap map; - } - assertSame(int.class, getComponentClass(Struct.class, "intArray")); - assertNull(getComponentClass(Struct.class, "d")); - assertSame(String[].class, getComponentClass(Struct.class, "strings")); - assertSame(null, getComponentClass(Struct.class, "v")); - assertSame(List.class, getComponentClass(Struct.class, "list")); - assertSame(null, getComponentClass(Struct.class, "map")); - } - - /** - * Tests {@link GenericUtils#getFieldClasses(java.lang.reflect.Field, Class)}. - */ - @Test - public void testGetFieldClasses() { - final Field field = ClassUtils.getField(Thing.class, "thing"); - - // T - final Type tType = GenericUtils.getFieldType(field, Thing.class); - assertEquals("capture of ?", tType.toString()); - - // N extends Number - final Type nType = GenericUtils.getFieldType(field, NumberThing.class); - assertEquals("capture of ?", nType.toString()); - - // Integer - final Type iType = GenericUtils.getFieldType(field, IntegerThing.class); - assertSame(Integer.class, iType); - } - - /** Tests {@link GenericUtils#getFieldClasses(Field, Class)}. */ - @Test - public void testGetGenericType() { - final Field field = ClassUtils.getField(Thing.class, "thing"); - - // Object - assertAllTheSame(GenericUtils.getFieldClasses(field, Thing.class), - Object.class); - - // N extends Number - assertAllTheSame(GenericUtils.getFieldClasses(field, NumberThing.class), - Number.class); - - // Integer - assertAllTheSame(GenericUtils.getFieldClasses(field, IntegerThing.class), - Integer.class); - - // Serializable & Cloneable - assertAllTheSame(GenericUtils.getFieldClasses(field, ComplexThing.class), - Serializable.class, Cloneable.class); - } - - // -- Helper classes -- - - private static class Thing { - @SuppressWarnings("unused") - private T thing; - } - - private static class NumberThing extends Thing { - // NB: No implementation needed. - } - - private static class IntegerThing extends NumberThing { - // NB: No implementation needed. - } - - private static class ComplexThing extends - Thing - { - // NB: No implementation needed. - } - - // -- Helper methods -- - - /** Convenience method to get the {@link Type} of a field. */ - private Type type(final Class c, final String fieldName) { - return ClassUtils.getField(c, fieldName).getGenericType(); - } - - /** - * Convenience method to call {@link GenericUtils#getClass(Type)} on a field. - */ - private Class getClass(final Class c, final String fieldName) { - return GenericUtils.getClass(type(c, fieldName)); - } - - /** - * Convenience method to call {@link GenericUtils#getComponentClass(Type)} on - * a field. - */ - private Class getComponentClass(final Class c, final String fieldName) { - return GenericUtils.getComponentClass(type(c, fieldName)); - } - - private void assertAllTheSame(final List list, final T... values) { - assertEquals(list.size(), values.length); - for (int i = 0; i < values.length; i++) { - assertSame(list.get(i), values[i]); - } - } - -} diff --git a/src/test/java/org/scijava/util/IntArrayTest.java b/src/test/java/org/scijava/util/IntArrayTest.java index 43bd6b610..7e0f8f42a 100644 --- a/src/test/java/org/scijava/util/IntArrayTest.java +++ b/src/test/java/org/scijava/util/IntArrayTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/util/LastRecentlyUsedTest.java b/src/test/java/org/scijava/util/LastRecentlyUsedTest.java index 46522d150..5cac06fea 100644 --- a/src/test/java/org/scijava/util/LastRecentlyUsedTest.java +++ b/src/test/java/org/scijava/util/LastRecentlyUsedTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/util/LongArrayTest.java b/src/test/java/org/scijava/util/LongArrayTest.java index 24df63313..1452fa43c 100644 --- a/src/test/java/org/scijava/util/LongArrayTest.java +++ b/src/test/java/org/scijava/util/LongArrayTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/util/NumberUtilsTest.java b/src/test/java/org/scijava/util/NumberUtilsTest.java new file mode 100644 index 000000000..67c9f3743 --- /dev/null +++ b/src/test/java/org/scijava/util/NumberUtilsTest.java @@ -0,0 +1,70 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.util; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Tests {@link NumberUtils} functionality + * + * @author Gabriel Selzer + */ +public class NumberUtilsTest { + + /** Tests {@link NumberUtils#getMinimumNumber(Class)} */ + @Test + public void testGetMinimumNumber() { + assertEquals(Byte.MIN_VALUE, NumberUtils.getMinimumNumber(Byte.class)); + assertEquals(Short.MIN_VALUE, NumberUtils.getMinimumNumber(Short.class)); + assertEquals(Integer.MIN_VALUE, NumberUtils.getMinimumNumber( + Integer.class)); + assertEquals(Long.MIN_VALUE, NumberUtils.getMinimumNumber(Long.class)); + assertEquals(-Float.MAX_VALUE, NumberUtils.getMinimumNumber(Float.class)); + assertEquals(-Double.MAX_VALUE, NumberUtils.getMinimumNumber(Double.class)); + // Number's minimum value should be the smallest of all the above -> Double + assertEquals(-Double.MAX_VALUE, NumberUtils.getMinimumNumber(Number.class)); + } + + /** Tests {@link NumberUtils#getMaximumNumber(Class)} */ + @Test + public void testGetMaximumNumber() { + assertEquals(Byte.MAX_VALUE, NumberUtils.getMaximumNumber(Byte.class)); + assertEquals(Short.MAX_VALUE, NumberUtils.getMaximumNumber(Short.class)); + assertEquals(Integer.MAX_VALUE, NumberUtils.getMaximumNumber( + Integer.class)); + assertEquals(Long.MAX_VALUE, NumberUtils.getMaximumNumber(Long.class)); + assertEquals(Float.MAX_VALUE, NumberUtils.getMaximumNumber(Float.class)); + assertEquals(Double.MAX_VALUE, NumberUtils.getMaximumNumber(Double.class)); + // Number's maximum value should be the largest of all the above -> Double + assertEquals(Double.MAX_VALUE, NumberUtils.getMaximumNumber(Number.class)); + } +} diff --git a/src/test/java/org/scijava/util/ObjectArrayTest.java b/src/test/java/org/scijava/util/ObjectArrayTest.java index ef8dad647..0e9a94f4c 100644 --- a/src/test/java/org/scijava/util/ObjectArrayTest.java +++ b/src/test/java/org/scijava/util/ObjectArrayTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/util/POMTest.java b/src/test/java/org/scijava/util/POMTest.java index f8d2e3f6d..7ac2f0eef 100644 --- a/src/test/java/org/scijava/util/POMTest.java +++ b/src/test/java/org/scijava/util/POMTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -94,16 +92,16 @@ public void testAccessors() throws ParserConfigurationException, assertEquals("org.scijava", pom.getGroupId()); assertEquals("scijava-common", pom.getArtifactId()); assertNotNull(pom.getVersion()); - assertEquals("Travis CI", pom.getCIManagementSystem()); + assertEquals("GitHub Actions", pom.getCIManagementSystem()); final String ciManagementURL = pom.getCIManagementURL(); - assertEquals("https://travis-ci.org/scijava/scijava-common", + assertEquals("https://github.com/scijava/scijava-common/actions", ciManagementURL); assertEquals("GitHub Issues", pom.getIssueManagementSystem()); final String issueManagementURL = pom.getIssueManagementURL(); assertEquals("https://github.com/scijava/scijava-common/issues", issueManagementURL); assertEquals("SciJava", pom.getOrganizationName()); - assertEquals("http://www.scijava.org/", pom.getOrganizationURL()); + assertEquals("https://scijava.org/", pom.getOrganizationURL()); assertTrue(pom.getPath().endsWith("pom.xml")); assertTrue(pom.getProjectDescription().startsWith( "SciJava Common is a shared library for SciJava software.")); @@ -112,7 +110,7 @@ public void testAccessors() throws ParserConfigurationException, assertEquals("https://github.com/scijava/scijava-common", // pom.getProjectURL()); final String scmConnection = pom.getSCMConnection(); - assertEquals("scm:git:git://github.com/scijava/scijava-common", + assertEquals("scm:git:https://github.com/scijava/scijava-common", scmConnection); final String scmDeveloperConnection = pom.getSCMDeveloperConnection(); assertEquals("scm:git:git@github.com:scijava/scijava-common", diff --git a/src/test/java/org/scijava/util/PrimitiveArrayTest.java b/src/test/java/org/scijava/util/PrimitiveArrayTest.java index 5446480e2..5612174d0 100644 --- a/src/test/java/org/scijava/util/PrimitiveArrayTest.java +++ b/src/test/java/org/scijava/util/PrimitiveArrayTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/util/ProcessUtilsTest.java b/src/test/java/org/scijava/util/ProcessUtilsTest.java index c2a4ae0b2..056ffac37 100644 --- a/src/test/java/org/scijava/util/ProcessUtilsTest.java +++ b/src/test/java/org/scijava/util/ProcessUtilsTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -71,11 +69,7 @@ private void assumePOSIX() { assumeTrue(PlatformUtils.isPOSIX()); } - /** - * A class executing a 'sleep' call, to be interrupted. - * - * @author Johannes Schindelin - */ + /** A class executing a 'sleep' call, to be interrupted. */ private static class SleepThread extends Thread { private int seconds; private Throwable result; diff --git a/src/test/java/org/scijava/util/PropertiesHelperTest.java b/src/test/java/org/scijava/util/PropertiesHelperTest.java new file mode 100644 index 000000000..528dc25dc --- /dev/null +++ b/src/test/java/org/scijava/util/PropertiesHelperTest.java @@ -0,0 +1,138 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.util; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Tests for {@link PropertiesHelper} + */ +public class PropertiesHelperTest { + + private static final String EXPECTED_1 = "a=b"; + private static final String EXPECTED_2 = "hello=goodbye"; + + private Map props; + private File temp; + + @Before + public void setup() throws IOException { + temp = File.createTempFile("PropertiesHelper", "txt"); + props = new HashMap<>(); + props.put("a", "b"); + props.put("hello", "goodbye"); + } + + @After + public void cleanup() { + temp.delete(); + } + + @Test + public void testWrite() throws IOException { + PropertiesHelper.put(props, temp); + + int count = 0; + boolean saw1 = false, saw2 = false; + + try (BufferedReader reader = new BufferedReader(new FileReader(temp))) { + String line; + while ((line = reader.readLine()) != null) { + if (line.equals(EXPECTED_1)) { + saw1 = true; + } + if (line.equals(EXPECTED_2)) { + saw2 = true; + } + count++; + } + } + assertTrue(saw1); + assertTrue(saw2); + assertEquals(2, count); + } + + @Test + public void testRead() throws IOException { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(temp))) { + writer.write(EXPECTED_1); + writer.newLine(); + writer.write(EXPECTED_2); + writer.newLine(); + } + + Map propsMap = PropertiesHelper.get(temp); + assertTrue(props.equals(propsMap)); + } + + @Test + public void testIO() throws IOException { + PropertiesHelper.put(props, temp); + Map propsMap = PropertiesHelper.get(temp); + assertTrue(props.equals(propsMap)); + } + + @Test + public void testMultipleEquals() throws IOException { + props.clear(); + final String K = "hello", V = "world=true"; + props.put(K, V); + PropertiesHelper.put(props, temp); + Map propsMap = PropertiesHelper.get(temp); + assertEquals(1, propsMap.size()); + assertEquals(props.get(K), propsMap.get(K)); + } + + @Test + public void testOverwrite() throws IOException { + PropertiesHelper.put(props, temp); + props.put("myname", "jonas"); + PropertiesHelper.put(props, temp); + Map loadedProps = PropertiesHelper.get(temp); + assertEquals(3, loadedProps.size()); + int count = 0; + try (BufferedReader reader = new BufferedReader(new FileReader(temp))) { + String line; + while ((line = reader.readLine()) != null) { + count++; + } + } + assertEquals(3, count); + } +} diff --git a/src/test/java/org/scijava/util/ShortArrayTest.java b/src/test/java/org/scijava/util/ShortArrayTest.java index 4a3fd60c6..24adec352 100644 --- a/src/test/java/org/scijava/util/ShortArrayTest.java +++ b/src/test/java/org/scijava/util/ShortArrayTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/util/StringUtilsTest.java b/src/test/java/org/scijava/util/StringUtilsTest.java index 395b23db2..a04bc3236 100644 --- a/src/test/java/org/scijava/util/StringUtilsTest.java +++ b/src/test/java/org/scijava/util/StringUtilsTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -31,6 +29,7 @@ package org.scijava.util; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -46,6 +45,21 @@ */ public class StringUtilsTest { + /** Tests {@link StringUtils#splitUnquoted}. */ + @Test + public void testSplitUnquoted() { + // See https://stackoverflow.com/a/1757107/1919049 + final String line = "foo,bar,c;qual=\"baz,blurb\",d;junk=\"quux,syzygy\""; + final String[] expected = { + "foo", + "bar", + "c;qual=\"baz,blurb\"", + "d;junk=\"quux,syzygy\"" + }; + final String[] actual = StringUtils.splitUnquoted(line, ","); + assertArrayEquals(expected, actual); + } + @Test public void isNullOrEmptyFalseIfString() throws Exception { assertFalse(StringUtils.isNullOrEmpty("Fresh out of Red Leicester")); diff --git a/src/test/java/org/scijava/util/TypesTest.java b/src/test/java/org/scijava/util/TypesTest.java new file mode 100644 index 000000000..1eb1707a5 --- /dev/null +++ b/src/test/java/org/scijava/util/TypesTest.java @@ -0,0 +1,777 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.scijava.test.TestUtils.createTemporaryDirectory; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarOutputStream; +import java.util.zip.ZipEntry; + +import org.junit.Test; +import org.scijava.util.FileUtils; +/** + * Tests {@link Types}. + * + * @author Curtis Rueden + * @author Mark Hiner + * @author Johannes Schindelin + */ +public class TypesTest { + + /** Tests {@link Types#load}. */ + @Test + public void testLoad() { + assertLoaded(boolean.class, "boolean"); + assertLoaded(byte.class, "byte"); + assertLoaded(char.class, "char"); + assertLoaded(double.class, "double"); + assertLoaded(float.class, "float"); + assertLoaded(int.class, "int"); + assertLoaded(long.class, "long"); + assertLoaded(short.class, "short"); + assertLoaded(void.class, "void"); + assertLoaded(String.class, "string"); + assertLoaded(Number.class, "java.lang.Number"); + assertLoaded(boolean[].class, "boolean[]"); + assertLoaded(byte[].class, "byte[]"); + assertLoaded(char[].class, "char[]"); + assertLoaded(double[].class, "double[]"); + assertLoaded(float[].class, "float[]"); + assertLoaded(int[].class, "int[]"); + assertLoaded(long[].class, "long[]"); + assertLoaded(short[].class, "short[]"); + assertLoaded(null, "void[]"); + assertLoaded(String[].class, "string[]"); + assertLoaded(Number[].class, "java.lang.Number[]"); + assertLoaded(boolean[][].class, "boolean[][]"); + assertLoaded(byte[][].class, "byte[][]"); + assertLoaded(char[][].class, "char[][]"); + assertLoaded(double[][].class, "double[][]"); + assertLoaded(float[][].class, "float[][]"); + assertLoaded(int[][].class, "int[][]"); + assertLoaded(long[][].class, "long[][]"); + assertLoaded(short[][].class, "short[][]"); + assertLoaded(null, "void[][]"); + assertLoaded(String[][].class, "string[][]"); + assertLoaded(Number[][].class, "java.lang.Number[][]"); + assertLoaded(boolean[].class, "[Z"); + assertLoaded(byte[].class, "[B"); + assertLoaded(char[].class, "[C"); + assertLoaded(double[].class, "[D"); + assertLoaded(float[].class, "[F"); + assertLoaded(int[].class, "[I"); + assertLoaded(long[].class, "[J"); + assertLoaded(short[].class, "[S"); + assertLoaded(null, "[V"); + assertLoaded(String[].class, "[Lstring;"); + assertLoaded(Number[].class, "[Ljava.lang.Number;"); + assertLoaded(boolean[][].class, "[[Z"); + assertLoaded(byte[][].class, "[[B"); + assertLoaded(char[][].class, "[[C"); + assertLoaded(double[][].class, "[[D"); + assertLoaded(float[][].class, "[[F"); + assertLoaded(int[][].class, "[[I"); + assertLoaded(long[][].class, "[[J"); + assertLoaded(short[][].class, "[[S"); + assertLoaded(null, "[[V"); + assertLoaded(String[][].class, "[[Lstring;"); + assertLoaded(Number[][].class, "[[Ljava.lang.Number;"); + } + + /** Tests {@link Types#load}. */ + @Test + public void testLoadFailureQuiet() { + // test quiet failure + assertNull(Types.load("a.non.existent.class")); + } + + /** Tests {@link Types#load}. */ + @Test(expected = IllegalArgumentException.class) + public void testLoadFailureLoud() { + Types.load("a.non.existent.class", false); + } + + /** Tests {@link Types#location} with a class on the file system. */ + @Test + public void testLocationUnpackedClass() throws IOException { + final File tmpDir = createTemporaryDirectory("class-utils-test-"); + final String path = getClass().getName().replace('.', '/') + ".class"; + final File classFile = new File(tmpDir, path); + assertTrue(classFile.getParentFile().exists() || + classFile.getParentFile().mkdirs()); + copy(getClass().getResource("/" + path).openStream(), + new FileOutputStream(classFile), true); + + final ClassLoader classLoader = + new URLClassLoader(new URL[] { tmpDir.toURI().toURL() }, null); + final Class c = Types.load(getClass().getName(), classLoader); + final URL location = Types.location(c); + assertEquals(tmpDir, FileUtils.urlToFile(location)); + FileUtils.deleteRecursively(tmpDir); + } + + /** Tests {@link Types#location} with a class in a JAR file. */ + @Test + public void testLocationClassInJar() throws IOException { + final File tmpDir = createTemporaryDirectory("class-utils-test-"); + final File jar = new File(tmpDir, "test.jar"); + final JarOutputStream out = new JarOutputStream(new FileOutputStream(jar)); + final String path = getClass().getName().replace('.', '/') + ".class"; + out.putNextEntry(new ZipEntry(path)); + copy(getClass().getResource("/" + path).openStream(), out, true); + + final ClassLoader classLoader = + new URLClassLoader(new URL[] { jar.toURI().toURL() }, null); + final Class c = Types.load(getClass().getName(), classLoader); + final URL location = Types.location(c); + assertEquals(jar, FileUtils.urlToFile(location)); + jar.deleteOnExit(); + } + + /** Tests quiet behavior of {@link Types#location(Class, boolean)}. */ + @Test + public void testLocationFailureQuiet() { + final Class weirdClass = loadCustomClass(); + assertEquals("Hello", weirdClass.getName()); + assertNull(Types.location(weirdClass)); + } + + /** Tests exceptions from {@link Types#location(Class, boolean)}. */ + @Test(expected = IllegalArgumentException.class) + public void testLocationFailureLoud() { + final Class weirdClass = loadCustomClass(); + assertEquals("Hello", weirdClass.getName()); + Types.location(weirdClass, false); + } + + /** Tests {@link Types#name}. */ + public void testName() { + @SuppressWarnings("unused") + class Struct { + + private List list; + } + assertEquals("boolean", Types.name(boolean.class)); + assertEquals("java.lang.String", Types.name(String.class)); + assertEquals("List[]", Types.name(type(Struct.class, "list"))); + } + + /** Tests {@link Types#raw(Type)}. */ + @Test + public void testRaw() { + @SuppressWarnings("unused") + class Struct { + + private int[] intArray; + private double d; + private String[][] strings; + private Void v; + private List list; + private List[] listArray; + private HashMap map; + } + assertSame(int[].class, raw(Struct.class, "intArray")); + assertSame(double.class, raw(Struct.class, "d")); + assertSame(String[][].class, raw(Struct.class, "strings")); + assertSame(Void.class, raw(Struct.class, "v")); + assertSame(List.class, raw(Struct.class, "list")); + assertSame(List[].class, raw(Struct.class, "listArray")); + assertSame(HashMap.class, raw(Struct.class, "map")); + } + + /** Tests {@link Types#raws}. */ + @Test + public void testRaws() { + final Field field = Types.field(Thing.class, "thing"); + + // Object + assertAllTheSame(Types.raws(Types.fieldType(field, Thing.class)), Object.class); + + // N extends Number + assertAllTheSame(Types.raws(Types.fieldType(field, NumberThing.class)), + Number.class); + + // Integer + assertAllTheSame(Types.raws(Types.fieldType(field, IntegerThing.class)), + Integer.class); + + // Serializable & Cloneable + assertAllTheSame(Types.raws(Types.fieldType(field, ComplexThing.class)), + Serializable.class, Cloneable.class); + } + + /** Tests {@link Types#box(Class)}. */ + @Test + public void testBox() { + final Class booleanType = Types.box(boolean.class); + assertSame(Boolean.class, booleanType); + + final Class byteType = Types.box(byte.class); + assertSame(Byte.class, byteType); + + final Class charType = Types.box(char.class); + assertSame(Character.class, charType); + + final Class doubleType = Types.box(double.class); + assertSame(Double.class, doubleType); + + final Class floatType = Types.box(float.class); + assertSame(Float.class, floatType); + + final Class intType = Types.box(int.class); + assertSame(Integer.class, intType); + + final Class longType = Types.box(long.class); + assertSame(Long.class, longType); + + final Class shortType = Types.box(short.class); + assertSame(Short.class, shortType); + + final Class voidType = Types.box(void.class); + assertSame(Void.class, voidType); + + final Class[] types = { // + Boolean.class, Byte.class, Character.class, Double.class, // + Float.class, Integer.class, Long.class, Short.class, // + Void.class, // + String.class, // + Number.class, BigInteger.class, BigDecimal.class, // + boolean[].class, byte[].class, char[].class, double[].class, // + float[].class, int[].class, long[].class, short[].class, // + Boolean[].class, Byte[].class, Character[].class, Double[].class, // + Float[].class, Integer[].class, Long[].class, Short[].class, // + Void[].class, // + Object.class, Object[].class, String[].class, // + Object[][].class, String[][].class, // + Collection.class, // + List.class, ArrayList.class, LinkedList.class, // + Set.class, HashSet.class, // + Map.class, HashMap.class, // + Collection[].class, List[].class, Set[].class, Map[].class }; + for (final Class c : types) { + final Class type = Types.box(c); + assertSame(c, type); + } + } + + /** Tests {@link Types#unbox(Class)}. */ + @Test + public void testUnbox() { + // TODO + } + + /** Tests {@link Types#nullValue(Class)}. */ + @Test + public void testNullValue() { + final boolean booleanNull = Types.nullValue(boolean.class); + assertFalse(booleanNull); + + final byte byteNull = Types.nullValue(byte.class); + assertEquals(0, byteNull); + + final char charNull = Types.nullValue(char.class); + assertEquals('\0', charNull); + + final double doubleNull = Types.nullValue(double.class); + assertEquals(0.0, doubleNull, 0.0); + + final float floatNull = Types.nullValue(float.class); + assertEquals(0f, floatNull, 0f); + + final int intNull = Types.nullValue(int.class); + assertEquals(0, intNull); + + final long longNull = Types.nullValue(long.class); + assertEquals(0, longNull); + + final short shortNull = Types.nullValue(short.class); + assertEquals(0, shortNull); + + final Void voidNull = Types.nullValue(void.class); + assertNull(voidNull); + + final Class[] types = { // + Boolean.class, Byte.class, Character.class, Double.class, // + Float.class, Integer.class, Long.class, Short.class, // + Void.class, // + String.class, // + Number.class, BigInteger.class, BigDecimal.class, // + boolean[].class, byte[].class, char[].class, double[].class, // + float[].class, int[].class, long[].class, short[].class, // + Boolean[].class, Byte[].class, Character[].class, Double[].class, // + Float[].class, Integer[].class, Long[].class, Short[].class, // + Void[].class, // + Object.class, Object[].class, String[].class, // + Object[][].class, String[][].class, // + Collection.class, // + List.class, ArrayList.class, LinkedList.class, // + Set.class, HashSet.class, // + Map.class, HashMap.class, // + Collection[].class, List[].class, Set[].class, Map[].class }; + for (final Class c : types) { + final Object nullValue = Types.nullValue(c); + assertNull("Expected null for " + c.getName(), nullValue); + } + } + + /** Tests {@link Types#field}. */ + @Test + public void testField() { + final Field field = Types.field(Thing.class, "thing"); + assertEquals("thing", field.getName()); + assertSame(Object.class, field.getType()); + assertTrue(field.getGenericType() instanceof TypeVariable); + assertEquals("T", ((TypeVariable) field.getGenericType()).getName()); + } + + /** Tests {@link Types#method}. */ + @Test + public void testMethod() { + final Method objectMethod = Types.method(Thing.class, "toString"); + assertSame(Object.class, objectMethod.getDeclaringClass()); + assertEquals("toString", objectMethod.getName()); + assertSame(String.class, objectMethod.getReturnType()); + assertEquals(0, objectMethod.getParameterTypes().length); + + final Method wordsMethod = // + Types.method(Words.class, "valueOf", String.class); + // NB: What is going on under the hood to make the Enum + // subtype Words be the declaring class for the 'valueOf' + // method? The compiler must internally override the valueOf + // method for each enum type, to narrow the return type... + assertSame(Words.class, wordsMethod.getDeclaringClass()); + assertEquals("valueOf", wordsMethod.getName()); + assertSame(Words.class, wordsMethod.getReturnType()); + assertEquals(1, wordsMethod.getParameterTypes().length); + assertSame(String.class, wordsMethod.getParameterTypes()[0]); + } + + /** Tests {@link Types#array}. */ + @Test + public void testArray() { + // 1-dimensional cases + assertSame(boolean[].class, Types.array(boolean.class)); + assertSame(String[].class, Types.array(String.class)); + assertSame(Number[].class, Types.array(Number.class)); + assertSame(boolean[][].class, Types.array(boolean[].class)); + assertSame(String[][].class, Types.array(String[].class)); + assertSame(Number[][].class, Types.array(Number[].class)); + try { + Types.array(void.class); + fail("Unexpected success creating void[]"); + } + catch (final IllegalArgumentException exc) { } + + // multidimensional cases + assertSame(Number[][].class, Types.array(Number.class, 2)); + assertSame(boolean[][][].class, Types.array(boolean.class, 3)); + assertSame(String.class, Types.array(String.class, 0)); + try { + Types.array(char.class, -1); + fail("Unexpected success creating negative dimensional array"); + } + catch (final IllegalArgumentException exc) { } + } + + /** Tests {@link Types#component(Type)}. */ + @Test + public void testComponent() { + @SuppressWarnings("unused") + class Struct { + + private int[] intArray; + private double d; + private String[][] strings; + private Void v; + private List[] list; + private HashMap map; + } + assertSame(int.class, componentType(Struct.class, "intArray")); + assertNull(componentType(Struct.class, "d")); + assertSame(String[].class, componentType(Struct.class, "strings")); + assertSame(null, componentType(Struct.class, "v")); + assertSame(List.class, componentType(Struct.class, "list")); + assertSame(null, componentType(Struct.class, "map")); + } + + /** Tests {@link Types#fieldType(Field, Class)}. */ + @Test + public void testFieldType() { + final Field field = Types.field(Thing.class, "thing"); + + // T + final Type tType = Types.fieldType(field, Thing.class); + assertEquals("capture of ?", tType.toString()); + + // N extends Number + final Type nType = Types.fieldType(field, NumberThing.class); + assertEquals("capture of ?", nType.toString()); + + // Integer + final Type iType = Types.fieldType(field, IntegerThing.class); + assertSame(Integer.class, iType); + } + + /** Tests {@link Types#param}. */ + @Test + public void testParam() { + class Struct { + + @SuppressWarnings("unused") + private List list; + } + final Type listType = type(Struct.class, "list"); + final Type paramType = Types.param(listType, List.class, 0); + final Class paramClass = Types.raw(paramType); + assertSame(int[].class, paramClass); + } + + /** Tests {@link Types#isAssignable(Type, Type)}. */ + @Test + public void testIsAssignable() { + // check casting to superclass + assertTrue(Types.isAssignable(String.class, Object.class)); + + // check casting to interface + assertTrue(Types.isAssignable(ArrayList.class, Collection.class)); + + // casting numeric primitives is not supported + assertFalse(Types.isAssignable(double.class, float.class)); + assertFalse(Types.isAssignable(float.class, double.class)); + + // boxing is not reported to work + // TODO: Consider changing this behavior. + assertFalse(Types.isAssignable(int.class, Number.class)); + + // casting from null always works + assertTrue(Types.isAssignable(null, Object.class)); + assertTrue(Types.isAssignable(null, int[].class)); + } + + /** Tests {@link Types#isAssignable(Type, Type)} from null to null. */ + @Test(expected = NullPointerException.class) + public void testIsAssignableNullToNull() { + Types.isAssignable(null, null); + } + + /** Tests {@link Types#isAssignable(Type, Type)} from Class to null. */ + @Test(expected = NullPointerException.class) + public void testIsAssignableClassToNull() { + Types.isAssignable(Object.class, null); + } + + /** Tests {@link Types#isAssignable(Type, Type)} with type variable. */ + @Test + public void testIsAssignableT() { + final Type t = genericTestType("t"); + final Type listT = genericTestType("listT"); + final Type listNumber = genericTestType("listNumber"); + final Type listInteger = genericTestType("listInteger"); + final Type listExtendsNumber = genericTestType("listExtendsNumber"); + + assertTrue(Types.isAssignable(t, t)); + assertTrue(Types.isAssignable(listT, listT)); + assertTrue(Types.isAssignable(listNumber, listNumber)); + assertTrue(Types.isAssignable(listInteger, listInteger)); + assertTrue(Types.isAssignable(listExtendsNumber, listExtendsNumber)); + + assertTrue(Types.isAssignable(listT, listExtendsNumber)); + assertTrue(Types.isAssignable(listNumber, listExtendsNumber)); + assertTrue(Types.isAssignable(listInteger, listExtendsNumber)); + + assertFalse(Types.isAssignable(listNumber, listT)); + assertFalse(Types.isAssignable(listInteger, listT)); + assertFalse(Types.isAssignable(listExtendsNumber, listT)); + assertFalse(Types.isAssignable(listExtendsNumber, listNumber)); + } + + /** Tests {@link Types#isInstance(Object, Class)}. */ + @Test + public void testIsInstance() { + // casting from null always works + final Object nullObject = null; + assertTrue(Types.isInstance(nullObject, Object.class)); + assertTrue(Types.isInstance(nullObject, int[].class)); + + // casting to null is not allowed + assertFalse(Types.isInstance(nullObject, null)); + assertFalse(Types.isInstance(new Object(), null)); + } + + /** Tests {@link Types#cast(Object, Class)}. */ + @Test + public void testCast() { + // check casting to superclass + final String string = "Hello"; + final Object stringToObject = Types.cast(string, Object.class); + assertSame(string, stringToObject); + + // check casting to interface + final ArrayList arrayList = new ArrayList<>(); + final Collection arrayListToCollection = // + Types.cast(arrayList, Collection.class); + assertSame(arrayList, arrayListToCollection); + + // casting numeric primitives is not supported + final Float doubleToFloat = Types.cast(5.1, float.class); + assertNull(doubleToFloat); + final Double floatToDouble = Types.cast(5.1f, double.class); + assertNull(floatToDouble); + + // boxing works though + final Number intToNumber = Types.cast(5, Number.class); + assertSame(Integer.class, intToNumber.getClass()); + assertEquals(5, intToNumber.intValue()); + } + + /** Tests {@link Types#enumValue(String, Class)}. */ + @Test + public void testEnumValue() { + final Words foo = Types.enumValue("FOO", Words.class); + assertSame(Words.FOO, foo); + final Words bar = Types.enumValue("BAR", Words.class); + assertSame(Words.BAR, bar); + final Words fubar = Types.enumValue("FUBAR", Words.class); + assertSame(Words.FUBAR, fubar); + } + + /** Tests {@link Types#enumValue(String, Class)} for invalid value. */ + @Test(expected = IllegalArgumentException.class) + public void testEnumValueNoConstant() { + Types.enumValue("OMG", Words.class); + } + + /** Tests {@link Types#enumValue(String, Class)} for non-enum class. */ + @Test(expected = IllegalArgumentException.class) + public void testEnumValueNonEnum() { + Types.enumValue("HOOYAH", String.class); + } + + /** Tests {@link Types#enumFromLabel(String, Class)}. */ + @Test + public void testEnumFromLabel() { + final Words foo = Types.enumFromLabel("Foo", Words.class); + assertSame(Words.FOO, foo); + final Words bar = Types.enumFromLabel("Bar", Words.class); + assertSame(Words.BAR, bar); + final Words fubar = Types.enumFromLabel("OMG", Words.class); + assertSame(Words.FUBAR, fubar); + } + + /** Tests {@link Types#enumFromString(String, Class)}. */ + @Test + public void testEnumFromString() { + { + final Words foo = Types.enumFromString("FOO", Words.class); + assertSame(Words.FOO, foo); + final Words bar = Types.enumFromString("BAR", Words.class); + assertSame(Words.BAR, bar); + final Words fubar = Types.enumFromString("FUBAR", Words.class); + assertSame(Words.FUBAR, fubar); + } + { + final Words foo = Types.enumFromString("Foo", Words.class); + assertSame(Words.FOO, foo); + final Words bar = Types.enumFromString("Bar", Words.class); + assertSame(Words.BAR, bar); + final Words fubar = Types.enumFromString("OMG", Words.class); + assertSame(Words.FUBAR, fubar); + } + } + + /** Tests {@link Types#parameterize(Class, Map)}. */ + @Test + public void testParameterizeMap() { + // TODO + } + + /** Tests {@link Types#parameterize(Class, Type...)}. */ + @Test + public void testParameterizeTypes() { + // TODO + } + + /** Tests {@link Types#parameterizeWithOwner(Type, Class, Type...)}. */ + @Test + public void testParameterizeWithOwner() { + // TODO + } + + // -- Helper classes -- + + private static class Thing { + + @SuppressWarnings("unused") + private T thing; + } + + private static class NumberThing extends Thing { + // NB: No implementation needed. + } + + private static class IntegerThing extends NumberThing { + // NB: No implementation needed. + } + + private static class ComplexThing extends + Thing + { + // NB: No implementation needed. + } + + /** Enumeration for testing conversion to enum types. */ + public static enum Words { + FOO("Foo"), BAR("Bar"), FUBAR("OMG"); + + private final String label; + + private Words(final String label) { + this.label = label; + } + + @Override + public String toString() { + return label; + } + } + + private interface TestTypes { + T t(); + List listT(); + List listNumber(); + List listInteger(); + List listExtendsNumber(); + } + + // -- Helper methods -- + + /** + * Copies bytes from an {@link InputStream} to an {@link OutputStream}. + * + * @param in the source + * @param out the sink + * @param closeOut whether to close the sink after we're done + * @throws IOException + */ + private void copy(final InputStream in, final OutputStream out, + final boolean closeOut) throws IOException + { + final byte[] buffer = new byte[16384]; + for (;;) { + final int count = in.read(buffer); + if (count < 0) break; + out.write(buffer, 0, count); + } + in.close(); + if (closeOut) out.close(); + } + + private Class loadCustomClass() { + // NB: The bytecode below was compiled from the following source: + // + // public class Hello {} + // + final byte[] bytecode = { -54, -2, -70, -66, 0, 0, 0, 52, 0, 13, 10, 0, 3, + 0, 10, 7, 0, 11, 7, 0, 12, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, + 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, 105, 110, 101, 78, + 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 10, 83, 111, 117, 114, + 99, 101, 70, 105, 108, 101, 1, 0, 10, 72, 101, 108, 108, 111, 46, 106, 97, + 118, 97, 12, 0, 4, 0, 5, 1, 0, 5, 72, 101, 108, 108, 111, 1, 0, 16, 106, + 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, + 0, 2, 0, 3, 0, 0, 0, 0, 0, 1, 0, 1, 0, 4, 0, 5, 0, 1, 0, 6, 0, 0, 0, 29, + 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 7, 0, 0, 0, 6, + 0, 1, 0, 0, 0, 1, 0, 1, 0, 8, 0, 0, 0, 2, 0, 9 }; + + class BytesClassLoader extends ClassLoader { + public Class load(final String name, final byte[] b) { + return defineClass(name, b, 0, b.length); + } + } + return new BytesClassLoader().load("Hello", bytecode); + } + + /** Convenience method to get the {@link Type} of a field. */ + private Type type(final Class c, final String fieldName) { + return Types.field(c, fieldName).getGenericType(); + } + + /** Convenience method to call {@link Types#raw} on a field. */ + private Class raw(final Class c, final String fieldName) { + return Types.raw(type(c, fieldName)); + } + + /** Convenience method to call {@link Types#component} on a field. */ + private Class componentType(final Class c, final String fieldName) { + return Types.raw(Types.component(type(c, fieldName))); + } + + private void assertLoaded(final Class c, final String name) { + assertSame(c, Types.load(name)); + } + + private void assertAllTheSame(final List list, final Object... values) { + assertEquals(list.size(), values.length); + for (int i = 0; i < values.length; i++) { + assertSame(list.get(i), values[i]); + } + } + + private Type genericTestType(final String name) { + return Types.method(TestTypes.class, name).getGenericReturnType(); + } +} diff --git a/src/test/java/org/scijava/util/UnitUtilsTest.java b/src/test/java/org/scijava/util/UnitUtilsTest.java index f2d005c2c..7977d0a7b 100644 --- a/src/test/java/org/scijava/util/UnitUtilsTest.java +++ b/src/test/java/org/scijava/util/UnitUtilsTest.java @@ -2,9 +2,7 @@ * #%L * SciJava Common shared library for SciJava software. * %% - * Copyright (C) 2009 - 2017 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2009 - 2026 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/scijava/util/VersionUtilsTest.java b/src/test/java/org/scijava/util/VersionUtilsTest.java new file mode 100644 index 000000000..de09b9888 --- /dev/null +++ b/src/test/java/org/scijava/util/VersionUtilsTest.java @@ -0,0 +1,95 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package org.scijava.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * Tests {@link VersionUtils}. + * + * @author Curtis Rueden + */ +public class VersionUtilsTest { + + /** Tests {@link VersionUtils#compare(String, String)}. */ + @Test + public void testCompare() { + // SemVer PATCH version. + assertTrue(VersionUtils.compare("1.5.1", "1.5.2") < 0); + assertTrue(VersionUtils.compare("1.5.2", "1.5.1") > 0); + + // SemVer MINOR version. + assertTrue(VersionUtils.compare("1.5.2", "1.6.2") < 0); + assertTrue(VersionUtils.compare("1.6.2", "1.5.2") > 0); + + // SemVer MAJOR version. + assertTrue(VersionUtils.compare("1.7.3", "2.7.3") < 0); + assertTrue(VersionUtils.compare("2.7.3", "1.7.3") > 0); + + // Suffix indicates version is older than final release. + assertTrue(VersionUtils.compare("1.5.2", "1.5.2-beta-1") > 0); + + // Check when number of version tokens does not match. + assertTrue(VersionUtils.compare("1.5", "1.5.1") < 0); + assertTrue(VersionUtils.compare("1.5.1", "1.5") > 0); + + // Check equality. + assertEquals(VersionUtils.compare("1.5", "1.5"), 0); + + // Check ImageJ 1.x style versions. + assertTrue(VersionUtils.compare("1.50a", "1.50b") < 0); + + // Check four version tokens. + assertTrue(VersionUtils.compare("1.5.1.3", "1.5.1.4") < 0); + assertTrue(VersionUtils.compare("1.5.1.6", "1.5.1.5") > 0); + assertEquals(VersionUtils.compare("10.4.9.8", "10.4.9.8"), 0); + + // Check non-numeric tokens. + assertTrue(VersionUtils.compare("a.b.c", "a.b.d") < 0); + + // Check for numerical (not lexicographic) comparison. + assertTrue(VersionUtils.compare("2.0", "23.0") < 0); + assertTrue(VersionUtils.compare("23.0", "2.0") > 0); + assertTrue(VersionUtils.compare("3.0", "23.0") < 0); + assertTrue(VersionUtils.compare("23.0", "3.0") > 0); + + // Check weird stuff. + assertTrue(VersionUtils.compare("1", "a") < 0); + assertTrue(VersionUtils.compare("1", "%") > 0); + assertTrue(VersionUtils.compare("", "1") < 0); + assertTrue(VersionUtils.compare("1", "1.") < 0); + assertTrue(VersionUtils.compare("", ".") < 0); + assertTrue(VersionUtils.compare("", "..") < 0); + assertTrue(VersionUtils.compare(".", "..") < 0); + } +} diff --git a/src/test/java/org/scijava/widget/WidgetStyleTest.java b/src/test/java/org/scijava/widget/WidgetStyleTest.java new file mode 100644 index 000000000..dbe4f8c83 --- /dev/null +++ b/src/test/java/org/scijava/widget/WidgetStyleTest.java @@ -0,0 +1,96 @@ +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2026 SciJava developers. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package org.scijava.widget; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Enclosed.class) +public class WidgetStyleTest { + + @RunWith(Parameterized.class) + public static class TestIsStyle { + + static String[] styleStrings = { "foo, bar, someThing", " FOO, BAR, SOMEthing ", "foo ", " bar", + "trash, sOmEtHiNg", null }; + + static String[] stylesToTest = { "foo", "bar", "someThing", null }; + + static boolean[][] stylesToHave = { // foo, bar, someThing + new boolean[] { true, true, true, false }, new boolean[] { true, true, true, false }, + new boolean[] { true, false, false, false }, new boolean[] { false, true, false, false }, + new boolean[] { false, false, true, false }, new boolean[] { false, false, false, true } }; + + @Parameters(name = "{0}") + public static List params() { + return IntStream.range(0, styleStrings.length) + .mapToObj(i -> new Object[] { styleStrings[i], stylesToHave[i] }).collect(Collectors.toList()); + } + + @Parameter + public String styleString; + + @Parameter(1) + public boolean[] targetStyles; + + @Test + public void testSimpleStyles() { + for (int i = 0; i < stylesToTest.length; i++) { + assertEquals("style: " + stylesToTest[i], targetStyles[i], + WidgetStyle.isStyle(styleString, stylesToTest[i])); + } + } + } + + public static class TestStyleModifiers { + @Test + public void testStyleModifiers() { + String style = "open, extensions:tiff/tif/jpeg/jpg"; + Set extensions = new HashSet<>(Arrays.asList(WidgetStyle.getStyleModifiers(style, "extensions"))); + Set expected = new HashSet<>(Arrays.asList("tiff", "jpg", "jpeg", "tif")); + assertEquals(expected, extensions); + } + } +} diff --git a/src/test/resources/org/scijava/io/test.txt b/src/test/resources/org/scijava/io/test.txt new file mode 100644 index 000000000..d95f3ad14 --- /dev/null +++ b/src/test/resources/org/scijava/io/test.txt @@ -0,0 +1 @@ +content