diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 37ed748..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,56 +0,0 @@ -version: 2.1 - -orbs: - github-maven-deploy: github-maven-deploy/github-maven-deploy@1.2.0 - -mvn-build-test-command: &mvn-build-test-command - mvn-build-test-command: mvn verify -Dmaven.javadoc.skip=true -Djacoco.skip=true -Dlicense.skip=true - -mvn-deploy-command: &mvn-deploy-command - mvn-deploy-command: | - mvn -s .circleci/maven-release-settings.xml clean deploy -DdeployAtEnd=true -DperformRelease=true -DskipTests -Dspotbugs.skip=true -Denforcer.skip=true -Djacoco.skip=true - mvn license:remove - context: RELEASE_PROFILE_BBOTTEMA - -workflows: - workflow: - jobs: - - github-maven-deploy/build-and-test: - <<: *mvn-build-test-command - filters: - branches: - only: master - - - github-maven-deploy/approve-deploy-patch-version: - type: approval - requires: - - github-maven-deploy/build-and-test - - github-maven-deploy/approve-deploy-minor-version: - type: approval - requires: - - github-maven-deploy/build-and-test - - github-maven-deploy/approve-deploy-major-version: - type: approval - requires: - - github-maven-deploy/build-and-test - - github-maven-deploy/approve-deploy-as-is-version: - type: approval - requires: - - github-maven-deploy/build-and-test - - - github-maven-deploy/deploy-patch-version: - requires: - - github-maven-deploy/approve-deploy-patch-version - <<: *mvn-deploy-command - - github-maven-deploy/deploy-minor-version: - requires: - - github-maven-deploy/approve-deploy-minor-version - <<: *mvn-deploy-command - - github-maven-deploy/deploy-major-version: - requires: - - github-maven-deploy/approve-deploy-major-version - <<: *mvn-deploy-command - - github-maven-deploy/deploy-as-is-version: - requires: - - github-maven-deploy/approve-deploy-as-is-version - <<: *mvn-deploy-command diff --git a/.circleci/maven-release-settings.xml b/.circleci/maven-release-settings.xml deleted file mode 100644 index f478962..0000000 --- a/.circleci/maven-release-settings.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - ossrh - ${env.SERVER_OSSRH_USERNAME} - ${env.SERVER_OSSRH_PASSWORD} - - - - - - gpg - - gpg - ${env.GPG_PASSPHRASE} - - - - - gpg - - \ No newline at end of file diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 6a45d03..0000000 --- a/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/.idea -/javadoc -/target -*.iml \ No newline at end of file diff --git a/Documentation.md b/Documentation.md new file mode 100644 index 0000000..b7668ce --- /dev/null +++ b/Documentation.md @@ -0,0 +1,34 @@ +## Introducing Java Reflection ## + +Java Reflection provides a small package with nifty Java reflection methods. + +Aside from a variety of utility functions, this library mainly aims to ease method/constructor lookups, using value conversions, such as autoboxing, auto casting, but also commons value conversions (such as Boolean to String and many more). + +Normal JDK method matching through reflection is very strict on the exact argument class types, but with this Java Reflection Library, this is much easier and more robust (and performs better). + +The main reason this project is available as open source is because it is a dependency of the [Swift Socket Server](https://code.google.com/p/swift-socket-server/) project, yet merited a project of its own. + +### Documentation ### + +Because of the size and nature of this little project, about four utility classes with only `static` methods, I will refer to the JavaDoc for all documentation. + +[JReflect](http://java-reflection.googlecode.com/svn/trunk/javadoc/users/org/codemonkey/javareflection/JReflect.html) | +[ValueConverter](http://java-reflection.googlecode.com/svn/trunk/javadoc/users/org/codemonkey/javareflection/ValueConverter.html) | +[FieldUtils](http://java-reflection.googlecode.com/svn/trunk/javadoc/users/org/codemonkey/javareflection/FieldUtils.html) | +[ExternalClassLoader](http://java-reflection.googlecode.com/svn/trunk/javadoc/users/org/codemonkey/javareflection/ExternalClassLoader.html) + +### What's inside ### + +#### Main features #### + + * an advanced utility class that finds and invokes methods/constructors based on a given list of values (optionally converting the values to the right type) + * a conversion class that is able to convert common value type to a different type (`boolean` to `Character` for example) + * an easy way to find an object's fields, optionally including setters/getters, restricted by a number of criteria (such as: should have a getter method, should be a `public` field etc.) + * an (experimental, but tested) ClassLoader that is able to resort to manually compile .class files and load them on the fly + +#### Extra features #### + + * a method that returns the autoboxed version of a value + * a convenience method that assign a value to an object's field directly + * a method that determines what the smallest `Number` type is that can hold a list of given number values of various types without losing precision + * various utility methods \ No newline at end of file diff --git a/LICENSE-2.0.txt b/LICENSE-2.0.txt deleted file mode 100644 index d645695..0000000 --- a/LICENSE-2.0.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://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 [yyyy] [name of copyright owner] - - 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. diff --git a/NOTICE.txt b/NOTICE.txt deleted file mode 100644 index daa17f0..0000000 --- a/NOTICE.txt +++ /dev/null @@ -1,11 +0,0 @@ - ========================================================================= - == NOTICE file for use with the Apache License, Version 2.0 == - ========================================================================= - - Java Reflection, copyright 2009-2020 Benny Bottema - - This product uses no commercial products. - - This product uses the following external (Open Source) libraries in the deployed code: - - - Two partial classes from Apache Commons Lang 2.5 (NumberUtils.isNumber, StringUtils.capitalize) \ No newline at end of file diff --git a/ProjectHome.md b/ProjectHome.md new file mode 100644 index 0000000..1281d30 --- /dev/null +++ b/ProjectHome.md @@ -0,0 +1,20 @@ +# Java Reflection # + +**Java Reflection provides a small package with nifty reflection features that will help with finding constructors, methods and value conversions**. + +The main reason this project is available as open source is because it is a dependency of the [Swift Socket Server](https://code.google.com/p/swift-socket-server/) project, yet merited a project of its own. + +Main features: + + * an advanced utility class that finds and invokes methods/constructors based on a given list of values (optionally converting the values to the right type) + * a conversion class that is able to convert common value type to a different type (`boolean` to `Character` for example) + * an easy way to find an object's fields, optionally including setters/getters, restricted by a number of criteria (such as: should have a getter method, should be a `public` field etc.) + * an (experimental, but tested) ClassLoader that is able to resort to manually compile .class files and load them on the fly + +Extra features: + + * a method that returns the autoboxed version of a value + * a convenience method that assign a value to an object's field directly + * a method that determines what the smallest `Number` type is that can hold a list of given number values of various types without losing precision + * various utility methods + diff --git a/README.md b/README.md deleted file mode 100644 index dc197f9..0000000 --- a/README.md +++ /dev/null @@ -1,194 +0,0 @@ -[![APACHE v2 License](https://img.shields.io/badge/license-apachev2-blue.svg?style=flat)](LICENSE-2.0.txt) -[![Latest Release](https://img.shields.io/maven-central/v/com.github.bbottema/java-reflection.svg?style=flat)](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.github.bbottema%22%20AND%20a%3A%22java-reflection%22) -[![Javadocs](http://www.javadoc.io/badge/com.github.bbottema/java-reflection.svg)](http://www.javadoc.io/doc/com.github.bbottema/java-reflection) -[![Codacy](https://img.shields.io/codacy/grade/d04a57e7f3184b47962e2666419683a1.svg?style=flat)](https://www.codacy.com/app/b-bottema/java-reflection) - -# java-reflection -*java-reflection* is an advanced toolbox for finding compatible methods, constructors, annotations and converting values. - -It defines utilities on Class level, Method level, Bean level, Package level, Type level and General utilities. - -In additions, it provides an advanced conversion framework, that uses Dijkstra's graph to find the most efficient conversion route, -through multiple converters if a direct one is not available. It contains many built in converters and allows for easy extension. - -``` - - com.github.bbottema - java-reflection - 4.1.1 - -``` - -v4.1.1 (05-March-2025) - -- some confusion in the release history, but this is now the correct version number - - -v4.1.0 (18-January-2024 ) - -- Updated Jakarta Activation to Angus Activation package - - -v4.0.0 (26-December-2021) - -- Updated to Java 8 and fixed recent log4j security issue -- Updated to Jakarta 2.0.1 - - -v3.13.1 (15-June-2020) - -- Made zipping method parameters and arguments more flexible - - -v3.13.0 (17-November-2019) - -- Added converter support for String to Date yyyy-MM-dd[ HH:mm] - - -v3.12.0 (4-November-2019) - -- For simple class lookups, In addition to java.lang, also try the packages java.util and java.math - - -v3.11.1 - v3.11.3 (29-October-2019 - 1-November-2019) - -- 3.11.3: Solved an NullPointerException in a collectMethods method -- 3.11.2: recompiled so the line numbers are correct with the released sources (due to license boilerplate added at the wrong time) -- 3.11.1: ClassUtils.collectMethods now returns methods define on implemented interfaces as well - - -v3.11.0 (27-October-2019) - -- Added support for UUID conversion -- The Jakarta Activation framework is now an explicit dependency - - -v3.10.1 (21-October-2019) - -- Made locateClass a little bit more user friendly by deferring return type T - - -v3.10.0 (28-September-2019) - -- Added methods for finding parameters by annotations type -- Moved to Intellij @Nullability annotations -- Added license boilerplate code in Maven build script -- Solved a bunch of static anlayses warnings -- Improved how compatibility work when passing null-arguments as argument lists for locating constructors/methods - - -v3.9.5 (3-June-2019) - -- Added method to check if a class has a method with a given name - - -v3.9.4 (27-May-2019) - -- Allow null-values when invoking setter - - -v3.9.3 (10-May-2019) - -- Added API for checking method compatibility based on actual arguments rather than by types only -- Added helper method to zip method parameters with their respective actual arguments -- Made the LookupMode arguments of type Set rather than EnumSet, so they can be made unmodifiable -- Added convenience method for returning the verify and return the only method in a findMethods result - - -v3.9.2 (30-April-2019) - -- Added API to easily invoke bean setters / getters or methods - - -v3.9.1 (27-April-2019) - -- Added convencience method to return the first method for a specified name -- Added method that returns an annotation of a specified type from a list -- Added method that returns an annotation of a specified type from an array -- Fixed visibility modifier on a public API - - -v3.9.0 (18-Januari-2019) - -- Added support for bean-like methods defined on interfaces - - -v3.8.1 - v3.8.5 (13-October-2018 - 8-Januari-2019) - -- Added support for same-type converters -- Performance update: implemented cache for generateComptibleSignatureLists -- Fixed Incompatible type exception if a number is outside of Byte or Short range -- Incompatible type exceptions are now gathered in case a conversion ultimately failed - - -v3.8.0 (9-October-2018) - -- Added File based converters to InputStream, DataSource and byte[] -- Fixed problem with conversion candidates failing during actual conversions, so now we try all candidates rather than just the first one -- Fixed nullability analysis issue - - -v3.7.0 (6-October-2018) - -- Added API for find declared Generic types in inheritance chains - - -v3.6.0 (4-October-2018) - -- Added API for resolving field values -- Fixed API for searching fields. Now fields of any visibility can be resolved - - -v3.5.1 (1-October-2018) - -- Added overloaded version of MethodUtils.findMatchingMethods(..) that also supports Collection in addition to varargs... -- Fixed name based type matching to properly work with arrays vs varargs... - - -v3.5.0 (30-September-2018) - -- Made method collection facilities in ClassUtil much more robust by allowing any combination of method modifiers to find methods for rather than just - a boolean "publicOnly". This includes modifiers other than for visibility as well. - - -v3.4.0 (26-September-2018) - -- Added BeanUtils API to verify if a given Method is a bean setter / getter - - -v3.3.0 (26-September-2018) - -- More robust class location facility -- Support any custom classloader for locating classes - - -v3.2.1 (24-September-2018) - -- Optimized recursive code and implemented some caches - - -v3.2.0 (21-September-2018) - -- Added alternative lookup method for Methods based on type names rather than types -- Streamlines ClassUtils API a bit - - -v3.1.0 (21-September-2018) - -Complete overhaul: -- Conversion now works with graph-based path finding resolution to find all possible conversion paths -- Converters can now be added by third parties -- fixed a bug with the cache not working properly -- restructured classes and packages so it makes a lot more sense - - -v2.x.x (28-August-2018) - -- Converted to Java 7 and added spotbugs -- Resolved a bunch of warnings -- Removed dependencies on external libraries - - -v1.0 (13-August-2011) - -Initial upload \ No newline at end of file diff --git a/RELEASE.txt b/RELEASE.txt deleted file mode 100644 index 9608d13..0000000 --- a/RELEASE.txt +++ /dev/null @@ -1,177 +0,0 @@ -https://github.com/bbottema/java-reflection - - -RELEASE NOTES Java Reflection - -v4.1.1 (05-March-2025) - -- some confusion in the release history, but this is now the correct version number - -v4.1.0 (18-January-2024) - -- Updated Jakarta Activation to Angus Activation package - - -v4.0.0 (26-December-2021) - -- Updated to Java 8 and fixed recent log4j security issue -- Updated to Jakarta 2.0.1 - - -v3.13.1 (15-June-2020) - -- Made zipping method parameters and arguments more flexible - - -v3.13.0 (17-November-2019) - -- Added converter support for String to Date yyyy-MM-dd[ HH:mm] - - -v3.12.0 (4-November-2019) - -- For simple class lookups, In addition to java.lang, also try the packages java.util and java.math - - -v3.11.1 - v3.11.3 (29-October-2019 - 1-November-2019) - -- 3.11.3: Solved an NullPointerException in a collectMethods method -- 3.11.2: recompiled so the line numbers are correct with the released sources (due to license boilerplate added at the wrong time) -- 3.11.1: ClassUtils.collectMethods now returns methods define on implemented interfaces as well - - -v3.11.0 (27-October-2019) - -- Added support for UUID conversion -- The Jakarta Activation framework is now an explicit dependency - - -v3.10.1 (21-October-2019) - -- Made locateClass a little bit more user friendly by deferring return type T - - -v3.10.0 (28-September-2019) - -- Added methods for finding parameters by annotations type -- Moved to Intellij @Nullability annotations -- Added license boilerplate code in Maven build script -- Solved a bunch of static anlayses warnings -- Improved how compatibility work when passing null-arguments as argument lists for locating constructors/methods - - -v3.9.5 (3-June-2019) - -- Added method to check if a class has a method with a given name - - -v3.9.4 (27-May-2019) - -- Allow null-values when invoking setter - - -v3.9.3 (10-May-2019) - -- Added API for checking method compatibility based on actual arguments rather than by types only -- Added helper method to zip method parameters with their respective actual arguments -- Made the LookupMode arguments of type Set rather than EnumSet, so they can be made unmodifiable -- Added convenience method for returning the verify and return the only method in a findMethods result - - -v3.9.2 (30-April-2019) - -- Added API to easily invoke bean setters / getters or methods - - -v3.9.1 (27-April-2019) - -- Added convencience method to return the first method for a specified name -- Added method that returns an annotation of a specified type from a list -- Added method that returns an annotation of a specified type from an array -- Fixed visibility modifier on a public API - - -v3.9.0 (18-Januari-2019) - -- Added support for bean-like methods defined on interfaces - - -v3.8.1 - v3.8.5 (13-October-2018 - 8-Januari-2019) - -- Added support for same-type converters -- Performance update: implemented cache for generateComptibleSignatureLists -- Fixed Incompatible type exception if a number is outside of Byte or Short range -- Incompatible type exceptions are now gathered in case a conversion ultimately failed - - -v3.8.0 (9-October-2018) - -- Added File based converters to InputStream, DataSource and byte[] -- Fixed problem with conversion candidates failing during actual conversions, so now we try all candidates rather than just the first one -- Fixed nullability analysis issue - - -v3.7.0 (6-October-2018) - -- Added API for find declared Generic types in inheritance chains - - -v3.6.0 (4-October-2018) - -- Added API for resolving field values -- Fixed API for searching fields. Now fields of any visibility can be resolved - - -v3.5.1 (1-October-2018) - -- Added overloaded version of MethodUtils.findMatchingMethods(..) that also supports Collection in addition to varargs... -- Fixed name based type matching to properly work with arrays vs varargs... - - -v3.5.0 (30-September-2018) - -- Made method collection facilities in ClassUtil much more robust by allowing any combination of method modifiers to find methods for rather than just - a boolean "publicOnly". This includes modifiers other than for visibility as well. - - -v3.4.0 (26-September-2018) - -- Added BeanUtils API to verify if a given Method is a bean setter / getter - - -v3.3.0 (26-September-2018) - -- More robust class location facility -- Support any custom classloader for locating classes - - -v3.2.1 (24-September-2018) - -- Optimized recursive code and implemented some caches - - -v3.2.0 (21-September-2018) - -- Added alternative lookup method for Methods based on type names rather than types -- Streamlines ClassUtils API a bit - - -v3.1.0 (21-September-2018) - -Complete overhaul: -- Conversion now works with graph-based path finding resolution to find all possible conversion paths -- Converters can now be added by third parties -- fixed a bug with the cache not working properly -- restructured classes and packages so it makes a lot more sense - - -v2.x.x (28-August-2018) - -- Converted to Java 7 and added spotbugs -- Resolved a bunch of warnings -- Removed dependencies on external libraries - - -v1.0 (13-August-2011) - -Initial upload \ No newline at end of file diff --git a/how to release.txt b/how to release.txt deleted file mode 100644 index 20373d0..0000000 --- a/how to release.txt +++ /dev/null @@ -1,42 +0,0 @@ -Prerequisite: - -You need GPG installed (comes along with GIT installation in the \bin folder) and you need to create a keyring, used for signing artifacts. -If you have an existing key, simply import it: - -gpg --allow-secret-key-import --import .gpg - -That, or you can install one of the binaries to import the key, from here: https://www.gnupg.org/download/index.html - -To release: - - 1. update release notes and github readme page (don't commit) - 2. remove SNAPSHOT version - 3. mvn -DperformRelease=true clean deploy - (set password in settings.xml or use local pgp key password, for which the public key must have been sent to a public key server, - eg: gpg --keyserver hkp://keyserver.ubuntu.com --send-keys 05AC6403) - server needed in settings.xml (see below) - 4. Go to https://oss.sonatype.org and release the artifact so it is submitted to Maven Central - 5. add new SNAPSHOT version and commit everything - -maven's settings.xml: - - - ossrh - sonatype user - sonatype password - - -To have a global gpg password so that it will use that automatically: - - - - gpg - - gpg - letmein - - - - - gpg - \ No newline at end of file diff --git a/lib/commons-lang-2.5.jar b/lib/commons-lang-2.5.jar deleted file mode 100644 index ae491da..0000000 Binary files a/lib/commons-lang-2.5.jar and /dev/null differ diff --git a/lib/junit-4.8.1.jar b/lib/junit-4.8.1.jar deleted file mode 100644 index 524cd65..0000000 Binary files a/lib/junit-4.8.1.jar and /dev/null differ diff --git a/pom.xml b/pom.xml deleted file mode 100644 index c953eb2..0000000 --- a/pom.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - 4.0.0 - - - com.github.bbottema - standard-project-parent - 1.0.42 - - - java-reflection - jar - Java Reflection - 4.1.1 - Java Reflection provides a small package with nifty reflection features that will help with finding constructors, methods and value conversions - https://github.com/bbottema/java-reflection - 2011 - - - scm:git:git://github.com/bbottema/java-reflection.git - scm:git:git@github.com:bbottema/java-reflection.git - https://github.com/bbottema/java-reflection - - - - GitHub Issues - https://github.com/bbottema/java-reflection/issues - - - - - org.jetbrains - annotations - 23.1.0 - provided - - - - org.eclipse.angus - angus-activation - 2.0.2 - - - org.projectlombok - lombok - 1.18.32 - provided - - - \ No newline at end of file diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml deleted file mode 100644 index ec8851b..0000000 --- a/spotbugs-exclude.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/BeanUtils.java b/src/main/java/org/bbottema/javareflection/BeanUtils.java deleted file mode 100644 index 5b1f15c..0000000 --- a/src/main/java/org/bbottema/javareflection/BeanUtils.java +++ /dev/null @@ -1,312 +0,0 @@ -package org.bbottema.javareflection; - -import lombok.experimental.UtilityClass; -import org.bbottema.javareflection.util.commonslang25.StringUtils; -import org.bbottema.javareflection.model.FieldWrapper; -import org.bbottema.javareflection.model.InvokableObject; -import org.bbottema.javareflection.valueconverter.IncompatibleTypeException; -import org.bbottema.javareflection.valueconverter.ValueConversionHelper; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.*; -import java.util.regex.Pattern; - -import static java.util.EnumSet.allOf; -import static java.util.EnumSet.of; -import static java.util.regex.Pattern.compile; -import static org.bbottema.javareflection.BeanUtils.BeanRestriction.YES_SETTER; -import static org.bbottema.javareflection.BeanUtils.BeanRestriction.YES_GETTER; - -/** - * A {@link Field} shorthand utility class used to collect fields from classes meeting Java Bean restrictions/requirements. - *

- * With this utility class you can perform field lookups, by combining lookup restriction criteria. - *

- * Example
- * "find all fields on a class Apple, not looking at its super classes, which should be protected, have a getter method, but not a setter - * method" - * - *

- * FieldUtils.collectFields(Apple.class, Apple.class, EnumSet.of(Visibility.PROTECTED),
- *         EnumSet.of(BeanRestriction.YES_GETTER, BeanRestriction.NO_SETTER));
- * 
- * - * @see #collectFields(Class, Class, EnumSet, EnumSet) - */ -@UtilityClass -public final class BeanUtils { - - /** - * Determines what visibility modifiers a field is allowed to have in {@link BeanUtils#collectFields(Class, Class, EnumSet, EnumSet)}. - */ - public enum Visibility { - /** - * Visibility flag that corresponds with java's keyword private. - */ - PRIVATE(Modifier.PRIVATE), - /** - * Visibility flag that corresponds with java's visibility modifier default (package protected). - */ - DEFAULT(-1), // no Java equivalent - /** - * Visibility flag that corresponds with java's keyword protected. - */ - PROTECTED(Modifier.PROTECTED), - /** - * Visibility flag that corresponds with java's keyword public. - */ - PUBLIC(Modifier.PUBLIC); - - private final int modifierFlag; - - Visibility(final int modifierFlag) { - this.modifierFlag = modifierFlag; - } - } - - /** - * Indicates whether a field needs a Bean setter or getter, exactly none or any combination thereof. Determines what kind of fields are - * potential collection candidates. - */ - public enum BeanRestriction { - /** - * Restriction flag that indicates a getter method is required. - */ - YES_GETTER, - /** - * Restriction flag that indicates a setter method is required. - */ - YES_SETTER, - /** - * Restriction flag that indicates no setter must be available. - */ - NO_SETTER, - /** - * Restriction flag that indicates a getter must be available. - */ - NO_GETTER - } - - /** - * Verifies is a given method occurs as setter or getter in the declaring class chain. Lookup works by finding actual properties with - * their respective getters/setters that follow bean convention. - *

- * Note that this is a strict lookup and interface methods are not considered bean methods. To include interfaces and their methods, - * use {@link #isBeanMethod(Method, Class, EnumSet, boolean)} with checkBeanLikeForInterfaces set to {@code true}. - *

- * Lookup can be configured to check only against specific visibility. - * - * @param method The method to match against getters/setters of a certain visibility - * @param boundaryMarker The last class or interface implementing class that methods are matched against. Can - * be used to prevent matching methods on a super class. - * @param visibility A set of visibility requirements (ie. {@link Visibility#PROTECTED} indicates a *field* for which getter/setter are checked - * is allowed to have protected visibility). Note: the visibility modifiers for methods are ignored. - * @return Whether given method is a setter / getter within given restriction boundaries. - */ - @SuppressWarnings({"unused", "WeakerAccess"}) - public static boolean isBeanMethod(final Method method, final Class boundaryMarker, - final EnumSet visibility) { - return isBeanMethod(method, boundaryMarker, visibility, false); - } - - /** - * @return Same as {@link #isBeanMethod(Method, Class, EnumSet)}, but may consider methods declared on interfaces as well. - */ - @SuppressWarnings("WeakerAccess") - public static boolean isBeanMethod(Method method, Class boundaryMarker, - EnumSet visibility, boolean checkBeanLikeForInterfaces) { - return method.getDeclaringClass().isInterface() - ? checkBeanLikeForInterfaces && methodIsBeanlike(method) - : isBeanMethodForField(method, boundaryMarker, visibility); - } - - private static boolean isBeanMethodForField(Method method, Class boundaryMarker, EnumSet visibility) { - Map, List> fields = collectFields(method.getDeclaringClass(), boundaryMarker, visibility, - EnumSet.noneOf(BeanRestriction.class)); - for (List fieldWrappers : fields.values()) { - for (FieldWrapper fieldWrapper : fieldWrappers) { - if (method.equals(fieldWrapper.getGetter()) || method.equals(fieldWrapper.getSetter())) { - return true; - } - } - } - return false; - } - - /** - * Determines if the method could be a bean method by looking just at its name, parameters and presence of return type. - * - * @return True, is the method starts with set/get/is, has exactly one parameter and in case of - * a primitive boolean the method should start with "isAbc" - */ - @SuppressWarnings("WeakerAccess") - public static boolean methodIsBeanlike(Method method) { - final Pattern SET_PATTERN = compile("set[A-Z].*?"); - final Pattern GET_PATTERN = compile("get[A-Z].*?"); - final Pattern IS_PATTERN = compile("is[A-Z].*?"); - - final String name = method.getName(); - final int paramCount = method.getParameterTypes().length; - final Class rt = method.getReturnType(); - - return - (rt == boolean.class && IS_PATTERN.matcher(name).matches() && paramCount == 0) || - (rt != void.class && rt != boolean.class && GET_PATTERN.matcher(name).matches() && paramCount == 0) || - (rt == void.class && SET_PATTERN.matcher(name).matches() && paramCount == 1); - } - - /** - * Returns a pool of {@link Field} wrappers including optional relevant setter/getter methods, collected from the given class tested - * against the given visibility and Bean restriction requirements. - *

- * The returned fields are mapped against the classes they were found on, since field names can be declared multiple times with the same - * name. - * - * @param _class The class (and chain) to harvest fields from. - * @param boundaryMarker The last class or interface implementing class that fields are collected from. Can - * be used to prevent finding fields on a super class. - * @param visibility A set of visibility requirements (ie. {@link Visibility#PROTECTED} indicates a field is allowed to have - * protected visibility). - * @param beanRestrictions A set of Bean restriction requirements indicating a field should or shouldn't have a setter, getter or both. - * @return A Map per class in the chain with the fields declared by that class. - * @see #meetsVisibilityRequirements(Field, EnumSet) - * @see #resolveBeanProperty(Field, EnumSet) - */ - @SuppressWarnings("WeakerAccess") - @NotNull - public static LinkedHashMap, List> collectFields(final Class _class, final Class boundaryMarker, - final EnumSet visibility, final EnumSet beanRestrictions) { - final LinkedHashMap, List> fields = new LinkedHashMap<>(); - final Field[] allFields = _class.getDeclaredFields(); - final List filteredFields = new LinkedList<>(); - for (final Field field : allFields) { - if (meetsVisibilityRequirements(field, visibility)) { - final FieldWrapper property = resolveBeanProperty(field, beanRestrictions); - if (property != null) { - filteredFields.add(property); - } - } - } - fields.put(_class, filteredFields); - // determine if we need to look deeper - final List> interfaces = Arrays.asList(_class.getInterfaces()); - if (!_class.equals(boundaryMarker) && !interfaces.contains(boundaryMarker)) { - fields.putAll(collectFields(_class.getSuperclass(), boundaryMarker, visibility, beanRestrictions)); - } - return fields; - } - - /** - * Determines if the visibility modifiers of a given {@link Field} is included in the set of flags. - * - * @param field The field who's visibility modifiers we want to test. - * @param visibility List of {@link Visibility} flags to test against. - * @return Whether a given field has one of the specified visibility flags. - */ - static boolean meetsVisibilityRequirements(final Field field, final EnumSet visibility) { - final int m = field.getModifiers(); - - for (Visibility visibilityModifier : visibility) { - if (visibilityModifier != Visibility.DEFAULT) { - if ((m & visibilityModifier.modifierFlag) != 0) { - return true; - } - } else { - if (!Modifier.isPrivate(m) && !Modifier.isProtected(m) && !Modifier.isPublic(m)) { - return true; - } - } - } - return false; - } - - /** - * Determines if a given Field meets the specified Bean restriction requirements and returns the field as a BeanProperty - * with optional getter/setter. - * - * @param field The field to test. - * @param beanRestrictions The Bean restrictions to apply (should/shouldn't have setter/getter). - * @return Whether the field fits the restrictions. - */ - @Nullable - static FieldWrapper resolveBeanProperty(final Field field, final EnumSet beanRestrictions) { - if (beanRestrictions.containsAll(EnumSet.of(BeanRestriction.NO_GETTER, BeanRestriction.YES_GETTER)) // - || beanRestrictions.containsAll(EnumSet.of(BeanRestriction.NO_SETTER, BeanRestriction.YES_SETTER))) { - throw new IllegalArgumentException("cannot both include and exclude a setter/getter requirement"); - } - - // since PropertyUtilsBean#getPropertyDescriptors(...) doesn't detect setters without getters (Bean convention) - // we'll just find setter/getters manually - final String setterName = "set" + StringUtils.capitalize(field.getName()); - final String getterName; - if (field.getType().equals(boolean.class)) { - getterName = "is" + StringUtils.capitalize(field.getName()); - } else { - getterName = "get" + StringUtils.capitalize(field.getName()); - } - final Set> iWriteMethod = MethodUtils.findSimpleCompatibleMethod(field.getDeclaringClass(), setterName, field.getType()); - final Set> iReadMethod = MethodUtils.findSimpleCompatibleMethod(field.getDeclaringClass(), getterName); - - if (!((!iReadMethod.isEmpty() && beanRestrictions.contains(BeanRestriction.NO_GETTER)) // - || (iReadMethod.isEmpty() && beanRestrictions.contains(BeanRestriction.YES_GETTER)) // - || (!iWriteMethod.isEmpty() && beanRestrictions.contains(BeanRestriction.NO_SETTER)) // - || (iWriteMethod.isEmpty() && beanRestrictions.contains(BeanRestriction.YES_SETTER)))) { - Method readMethod = !iReadMethod.isEmpty() ? iReadMethod.iterator().next().getMethod() : null; - Method writeMethod = !iWriteMethod.isEmpty() ? iWriteMethod.iterator().next().getMethod() : null; - return new FieldWrapper(field, readMethod, writeMethod); - } else { - return null; - } - } - - /** - * Calls the setter for the first field in the inheritance chain that matches given fieldName. - * Attempts to convert the value in case the type is incorrect. - * - * @return The actual value used in the bean setter. - */ - @SuppressWarnings("ConstantConditions") - @Nullable - static public Object invokeBeanSetter(Object o, String fieldName, @Nullable Object value) { - for (List fieldWrappers : collectFields(o.getClass(), Object.class, allOf(Visibility.class), of(YES_SETTER)).values()) { - for (FieldWrapper fieldWrapper : fieldWrappers) { - if (fieldWrapper.getField().getName().equals(fieldName) ) { - Object assignedValue = value; - try { - MethodUtils.invokeMethodSimple(fieldWrapper.getSetter(), o, value); - } catch (final IllegalArgumentException ie) { - try { - assignedValue = ValueConversionHelper.convert(value, fieldWrapper.getField().getType()); - } catch (IncompatibleTypeException e) { - throw new RuntimeException(new NoSuchMethodException(e.getMessage())); - } - MethodUtils.invokeMethodSimple(fieldWrapper.getSetter(), o, assignedValue); - } - return assignedValue; - } - } - } - throw new RuntimeException(new NoSuchMethodException("Bean setter for " + fieldName)); - } - - /** - * Calls the getter for the first field in the inheritance chain that matches given fieldName. - * - * @see #collectFields(Class, Class, EnumSet, EnumSet) - */ - @SuppressWarnings("ConstantConditions") - static public Object invokeBeanGetter(Object o, String fieldName) { - for (List fieldWrappers : collectFields(o.getClass(), Object.class, allOf(Visibility.class), of(YES_GETTER)).values()) { - for (FieldWrapper fieldWrapper : fieldWrappers) { - if (fieldWrapper.getField().getName().equals(fieldName) ) { - return MethodUtils.invokeMethodSimple(fieldWrapper.getGetter(), o); - } - } - } - throw new RuntimeException(new NoSuchMethodException("Bean getter for " + fieldName)); - } -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/ClassUtils.java b/src/main/java/org/bbottema/javareflection/ClassUtils.java deleted file mode 100644 index 29dbb92..0000000 --- a/src/main/java/org/bbottema/javareflection/ClassUtils.java +++ /dev/null @@ -1,342 +0,0 @@ -package org.bbottema.javareflection; - -import lombok.experimental.UtilityClass; -import org.bbottema.javareflection.model.MethodModifier; -import org.bbottema.javareflection.util.ExternalClassLoader; -import org.bbottema.javareflection.valueconverter.IncompatibleTypeException; -import org.bbottema.javareflection.valueconverter.ValueConversionHelper; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.LinkedHashMap; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import static org.bbottema.javareflection.LookupCaches.CLASS_CACHE; -import static org.bbottema.javareflection.util.MiscUtil.trustedNullableCast; - -/** - * Utility with convenience methods that operate on the class level. - *

- */ -@UtilityClass -@SuppressWarnings("WeakerAccess") -public final class ClassUtils { - - /** - * Searches the JVM and optionally all of its packages - * - * @param className The name of the class to locate. - * @param fullscan Whether a full scan through all available java packages is required. - * @param classLoader Optional user-provided classloader. - * @return The Class reference if found or null otherwise. - */ - @Nullable - @SuppressWarnings({"WeakerAccess", "unchecked"}) - public static Class locateClass(final String className, final boolean fullscan, @Nullable final ClassLoader classLoader) { - final String cacheKey = className + fullscan; - if (CLASS_CACHE.containsKey(cacheKey)) { - return (Class) CLASS_CACHE.get(cacheKey); - } - Class _class; - if (fullscan) { - _class = locateClass(className, null, classLoader); - } else { - // try standard package used for most common classes - _class = locateClass(className, "java.lang", classLoader); - if (_class == null) { - _class = locateClass(className, "java.util", classLoader); - } - if (_class == null) { - _class = locateClass(className, "java.math", classLoader); - } - } - CLASS_CACHE.put(cacheKey, _class); - return (Class) _class; - } - - @Nullable - @SuppressWarnings({"WeakerAccess", "unchecked"}) - public static Class locateClass(final String className, @Nullable final String inPackage, @Nullable final ClassLoader classLoader) { - final String cacheKey = className + inPackage; - if (CLASS_CACHE.containsKey(cacheKey)) { - return (Class) CLASS_CACHE.get(cacheKey); - } - - Class _class = locateClass(className, classLoader); - - if (_class == null) { - _class = PackageUtils.scanPackagesForClass(className, inPackage, classLoader); - } - - CLASS_CACHE.put(cacheKey, _class); - return (Class) _class; - } - - /** - * This function dynamically tries to locate a class. First it searches the class-cache list, then it tries to get it from the Virtual Machine - * using {@code Class.forName(String)}. - * - * @param fullClassName The Class that needs to be found. - * @param classLoader Optional user-provided classloader. - * @return The {@code Class} object found from cache or VM. - */ - @SuppressWarnings({"WeakerAccess", "unchecked"}) - @Nullable - public static Class locateClass(final String fullClassName, @Nullable final ClassLoader classLoader) { - try { - Class _class = null; - if (classLoader != null) { - _class = classLoader.loadClass(fullClassName); - } - if (_class == null) { - _class = Class.forName(fullClassName); - } - return (Class) _class; - } catch (final ClassNotFoundException e) { - return null; - } - } - - /** - * Simply calls {@link Class#newInstance()} and hides the exception handling boilerplate code. - * - * @param _class The datatype for which we need to create a new instance of. - * @param Type used to parameterize the return instance. - * @return A new parameterized instance of the given type. - */ - @NotNull - @SuppressWarnings("WeakerAccess") - public static T newInstanceSimple(final Class _class) { - try { - return ConstructorFactory.obtainConstructor(_class).newInstance(); - } catch (SecurityException e) { - throw new RuntimeException("unable to invoke parameterless constructor; security problem", e); - } catch (InstantiationException e) { - throw new RuntimeException("unable to complete instantiation of object", e); - } catch (IllegalAccessException e) { - throw new RuntimeException("unable to access parameterless constructor", e); - } catch (InvocationTargetException e) { - throw new RuntimeException("unable to invoke parameterless constructor", e); - } catch (NoSuchMethodException e) { - throw new RuntimeException("unable to find parameterless constructor (not public?)", e); - } - } - - // Workaround: mockito does not support mocking Class.class, so getConstructor() cannot be mocked and we mock this factory method instead. - static class ConstructorFactory { - static Constructor obtainConstructor(Class _class) throws NoSuchMethodException { - return _class.getConstructor(); - } - } - - /** - * Gets value from the field returned by {@link #solveField(Object, String)}.; - */ - @Nullable - @SuppressWarnings("WeakerAccess") - public static T solveFieldValue(final Object object, final String fieldName) { - final Field field = solveField(object, fieldName); - if (field == null) { - throw new RuntimeException(new NoSuchFieldException()); - } - field.setAccessible(true); - try { - return trustedNullableCast(field.get(object)); - } catch (IllegalAccessException e) { - throw new RuntimeException("Was unable to retrieve value from field %s", e); - } - } - - /** - * Delegates to {@link #solveField(Class, String)} by using the class of given object object or if object itself if it ss a class. - */ - @Nullable - @SuppressWarnings("WeakerAccess") - public static Field solveField(final Object object, final String fieldName) { - return object.getClass().equals(Class.class) - ? solveField((Class) object, fieldName) // Java static field - : solveField(object.getClass(), fieldName); // Java instance field - } - - /** - * Returns a field from the given Class that goes by the name of fieldName. Will search for fields on implemented interfaces and superclasses. - * - * @param _class The reference to the Class to fetch the field from. - * @param fieldName The identifier or name of the member field/property. - * @return The value of the Field. - */ - @Nullable - @SuppressWarnings("WeakerAccess") - public static Field solveField(final Class _class, final String fieldName) { - Field resolvedField = null; - try { - resolvedField = _class.getDeclaredField(fieldName); - } catch (NoSuchFieldException e) { - for (int i = 0; resolvedField == null && i < _class.getInterfaces().length; i++) { - resolvedField = solveField(_class.getInterfaces()[i], fieldName); - } - for (Class superclass = _class.getSuperclass(); resolvedField == null && superclass != null; superclass = superclass.getSuperclass()) { - resolvedField = solveField(superclass, fieldName); - } - } - return resolvedField; - } - - /** - * Assigns a value to a field id on the given object o. If a simple assignment fails, a common conversion will be - * attempted. - * - * @param o The object to find the field on. - * @param property The name of the field we're assigning the value to. - * @param value The value to assign to the field, may be converted to the field's type through common conversion. - * @return The actual value that was assigned (the original or the converted value). - * @throws IllegalAccessException Thrown by {@link Field#set(Object, Object)} - * @throws NoSuchFieldException Thrown if the {@link Field} could not be found, even after trying to convert the value to the target type. - * @see ValueConversionHelper#convert(Object, Class) - */ - @Nullable - @SuppressWarnings("WeakerAccess") - public static Object assignToField(final Object o, final String property, final Object value) throws IllegalAccessException, NoSuchFieldException { - final Field field = solveField(o, property); - if (field != null) { - field.setAccessible(true); - Object assignedValue = value; - try { - field.set(o, value); - } catch (final IllegalArgumentException ie) { - try { - assignedValue = ValueConversionHelper.convert(value, field.getType()); - } catch (IncompatibleTypeException e) { - throw new NoSuchFieldException(e.getMessage()); - } - field.set(o, assignedValue); - } - return assignedValue; - } else { - throw new NoSuchFieldException(); - } - } - - /** - * Returns a list of names that represent the fields on an Object. - * - * @param subject The Object who's properties/fields need to be reflected. - * @return A list of names that represent the fields on the given Object. - */ - @NotNull - @SuppressWarnings("WeakerAccess") - public static Collection collectPropertyNames(final Object subject) { - final Collection properties = new LinkedHashSet<>(); - final Field[] fields = subject.getClass().getFields(); - for (final Field f : fields) { - properties.add(f.getName()); - } - return properties; - } - - /** - * @return Returns the result of {@link #collectMethods(Class, Class, EnumSet)} mapped to the method names. - */ - @SuppressWarnings("WeakerAccess") - public static Set collectMethodNames(Class dataType, Class boundaryMarker, EnumSet methodModifiers) { - Set methodNames = new HashSet<>(); - for (Method m : collectMethods(dataType, boundaryMarker, methodModifiers)) { - methodNames.add(m.getName()); - } - return methodNames; - } - - /** - * @return The result of {@link #collectMethodsMappingToName(Class, Class, EnumSet)} filtered on method name. - */ - @SuppressWarnings("WeakerAccess") - public static List collectMethodsByName(final Class type, Class boundaryMarker, EnumSet methodModifiers, final String methodName) { - LinkedHashMap> methodsByName = collectMethodsMappingToName(type, boundaryMarker, methodModifiers); - return methodsByName.containsKey(methodName) ? methodsByName.get(methodName) : new ArrayList(); - } - - /** - * @return Whether {@link #collectMethodsMappingToName(Class, Class, EnumSet)} contains a method with the given name. - */ - @SuppressWarnings("WeakerAccess") - public static boolean hasMethodByName(final Class type, Class boundaryMarker, EnumSet methodModifiers, final String methodName) { - LinkedHashMap> methodsByName = collectMethodsMappingToName(type, boundaryMarker, methodModifiers); - return methodsByName.containsKey(methodName) && !methodsByName.get(methodName).isEmpty(); - } - - /** - * @return The first result of {@link #collectMethodsByName(Class, Class, EnumSet, String)}. - * Note: methods are ordered in groups (see {@link #collectMethods(Class, Class, EnumSet)})). - */ - @Nullable - @SuppressWarnings("WeakerAccess") - public static Method findFirstMethodByName(final Class type, Class boundaryMarker, EnumSet methodModifiers, final String methodName) { - List methods = collectMethodsByName(type, boundaryMarker, methodModifiers, methodName); - return methods.isEmpty() ? null : methods.iterator().next(); - } - - /** - * @return The result of {@link #collectMethods(Class, Class, EnumSet)} filtered on method name, - * ordered in groups (see {@link #collectMethods(Class, Class, EnumSet)})). - */ - @SuppressWarnings("WeakerAccess") - public static LinkedHashMap> collectMethodsMappingToName(Class type, Class boundaryMarker, EnumSet methodModifiers) { - LinkedHashMap> methodsMappedToName = new LinkedHashMap<>(); - for (Method method : collectMethods(type, boundaryMarker, methodModifiers)) { - if (!methodsMappedToName.containsKey(method.getName())) { - methodsMappedToName.put(method.getName(), new ArrayList()); - } - methodsMappedToName.get(method.getName()).add(method); - } - return methodsMappedToName; - } - - /** - * Returns a list of names that represent the methods on an Object. - *

- * Methods are ordered by their declaring type in the inheritance chain, but unordered for methods of the same type. - * In other words, considering type A, B and C each with methods 1, 2 and 3, the methods might be ordered as follows: - * {@code [A2,A1,B1,B2,C2,C1]}. - * - * @param methodModifiers List of method modifiers that will match any method that has one of them. - * @param boundaryMarker Optional type to limit (including) how far back up the inheritance chain we go for discovering methods. - * @return Returns a list with methods, either {@link Method}s. - */ - @SuppressWarnings("WeakerAccess") - public static List collectMethods(Class dataType, Class boundaryMarker, EnumSet methodModifiers) { - final List allMethods = new ArrayList<>(); - - for (Method declaredMethod : dataType.getDeclaredMethods()) { - if (MethodModifier.meetsModifierRequirements(declaredMethod, methodModifiers)) { - allMethods.add(declaredMethod); - } - } - - for (Class implementedInterface : dataType.getInterfaces()) { - allMethods.addAll(collectMethods(implementedInterface, boundaryMarker, methodModifiers)); - } - - if (dataType != boundaryMarker && dataType.getSuperclass() != null) { - allMethods.addAll(collectMethods(dataType.getSuperclass(), boundaryMarker, methodModifiers)); - } - return allMethods; - } -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/LookupCaches.java b/src/main/java/org/bbottema/javareflection/LookupCaches.java deleted file mode 100644 index ab90dbf..0000000 --- a/src/main/java/org/bbottema/javareflection/LookupCaches.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.bbottema.javareflection; - -import org.bbottema.javareflection.model.InvokableObject; -import org.bbottema.javareflection.model.LookupMode; -import org.bbottema.javareflection.util.ArrayKey; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * For internal use for improving repeated lookup performances. - */ -public class LookupCaches { - - /** - * {@link Class} cache optionally used when looking up classes with {@link ClassUtils#locateClass(String, boolean, ClassLoader)}. - */ - final static Map> CLASS_CACHE = new HashMap<>(); - - /** - * {@link Method} cache categorized by owning Classes (since several owners can have a method with the same name and signature). - * Methods are stored based on Method reference along with their unique signature (per owner), so multiple methods on one owner with - * the same name can coexist.
- *
- * This cache is being maintained to reduce lookup times when trying to find signature compatible Java methods. The possible signature - * combinations using autoboxing and/or automatic common conversions can become very large (7000+ with only three parameters) and can become a - * real problem. The more frequently a method is being called the larger the performance gain, especially for methods with long parameter lists - * - * @see "MethodUtils.addMethodToCache(Class, String, Set, Class[])" - * @see "MethodUtils#getMethodFromCache(Class, String, Class[])" - */ - final static Map, Map[], Set>>> METHOD_CACHE = new LinkedHashMap<>(); - - static final Map, Set>> CACHED_REGISTERED_COMPATIBLE_TARGET_TYPES = new HashMap<>(); - static final Map, Set>> CACHED_COMPATIBLE_TARGET_TYPES = new HashMap<>(); - private static final Map, Map[]>>> CACHED_COMPATIBLE_TYPE_LISTS = new HashMap<>(); - - @SuppressWarnings({"unused"}) - public static void resetCache() { - CLASS_CACHE.clear(); - METHOD_CACHE.clear(); - CACHED_REGISTERED_COMPATIBLE_TARGET_TYPES.clear(); - CACHED_COMPATIBLE_TARGET_TYPES.clear(); - CACHED_COMPATIBLE_TYPE_LISTS.clear(); - } - - @Nullable - static List[]> getCachedCompatibleSignatures(Set lookupMode, ArrayKey arrayKey) { - final Map[]>> cachedCompatibleSignatures = CACHED_COMPATIBLE_TYPE_LISTS.get(lookupMode); - if (cachedCompatibleSignatures != null) { - return cachedCompatibleSignatures.get(arrayKey); - } - return null; - } - - @NotNull - static List[]> addCompatiblesignaturesToCache(Set lookupMode, ArrayKey arrayKey, List[]> compatibleTypeLists) { - Map[]>> cachedCompatibleSignatures = CACHED_COMPATIBLE_TYPE_LISTS.get(lookupMode); - if (cachedCompatibleSignatures == null) { - CACHED_COMPATIBLE_TYPE_LISTS.put(lookupMode, cachedCompatibleSignatures = new HashMap<>()); - } - cachedCompatibleSignatures.put(arrayKey, compatibleTypeLists); - return compatibleTypeLists; - } -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/MethodUtils.java b/src/main/java/org/bbottema/javareflection/MethodUtils.java deleted file mode 100644 index 48a7276..0000000 --- a/src/main/java/org/bbottema/javareflection/MethodUtils.java +++ /dev/null @@ -1,600 +0,0 @@ -package org.bbottema.javareflection; - -import lombok.experimental.UtilityClass; -import org.bbottema.javareflection.model.InvokableObject; -import org.bbottema.javareflection.model.LookupMode; -import org.bbottema.javareflection.model.MethodModifier; -import org.bbottema.javareflection.model.MethodParameter; -import org.bbottema.javareflection.util.MiscUtil; -import org.bbottema.javareflection.valueconverter.IncompatibleTypeException; -import org.bbottema.javareflection.valueconverter.ValueConversionHelper; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; - -import java.lang.annotation.Annotation; -import java.lang.reflect.AccessibleObject; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.*; - -import static java.lang.String.format; -import static org.bbottema.javareflection.LookupCaches.METHOD_CACHE; -import static org.bbottema.javareflection.TypeUtils.containsAnnotation; -import static org.bbottema.javareflection.util.MiscUtil.trustedCast; -import static org.bbottema.javareflection.util.MiscUtil.trustedNullableCast; -import static org.slf4j.LoggerFactory.getLogger; - -/** - * This reflection tool is designed to perform advanced method or constructor lookups, - * using a combination of {@link LookupMode} strategies. - *

- * It tries to find a constructor of a given datatype, with a given argument - * datatypelist, where types do not have to match formal types (auto-boxing, supertypes, implemented interfaces and type conversions are allowed as - * they are included in the lookup cycles). This expanded version tries a simple call first (exact match, which is provided natively by the Java) and - * when this fails, it generates a list of datatype arrays (signatures) with all possible versions of any type in the original list possible, and - * combinations thereof. - *

- * Observe the following (trivial) example: - * - *

- * 	interface Foo {
- * 		void foo(Double value, Fruit fruit, char c);
- *        }
- * 	abstract class A implements Foo {
- *    }
- * 	abstract class B extends A {
- *    }
- *
- * ClassUtils.findCompatibleJavaMethod(B.class, "foo", EnumSet.allOf(LookupMode.class), double.class, Pear.class, String.class)}
- * 
- *

- * In the above example, the method foo will be found by finding all methods named "Foo" on the interfaces implemented by supertype A, - * and then foo's method signature will be matched using autoboxing on the double type, a cast to the Fruit supertype for - * the Pear type and finally by attempting a common conversion from String to char. This will give you a Java - * {@link Method}, but you won't be able to invoke it if it was found using a less strict lookup than one with a simple exact match. There are two - * ways to do this: use {@link #invokeCompatibleMethod(Object, Class, String, Object...)} instead or perform the conversion yourself using {@link - * ValueConversionHelper#convert(Object[], Class[], boolean)} prior to invoking the method. ValueConverter.convert(args, - * method.getParameterTypes()). - *

- * A reverse lookup is also possible: given an ordered list of possible types, is a given Method compatible? - *

- * Because this lookup is potentially very expensive, a cache is present to store lookup results. - */ -@UtilityClass -public final class MethodUtils { - - private static final Logger LOGGER = getLogger(MethodUtils.class); - - /** - * Delegates to {@link Method#invoke(Object, Object...)} while converting checked exceptions into runtime - * exceptions. - */ - @Nullable - @SuppressWarnings({"unchecked"}) - public static T invokeMethodSimple(final Method method, @Nullable final Object subject, final Object... args) { - try { - return (T) method.invoke(subject, args); - } catch (SecurityException e) { - throw new RuntimeException("unable to invoke method; security problem", e); - } catch (IllegalAccessException e) { - throw new RuntimeException("unable to access method", e); - } catch (InvocationTargetException e) { - throw new RuntimeException("unable to invoke method", e); - } - } - - /** - * Locates a method on an Object using serveral searchmodes for optimization. First of all a {@link Method} cache is being maintained to quickly - * fetch heavily used methods. If not cached before and if a simple search (autoboxing and supertype casts) fails a more complex search is done - * where all interfaces are searched for the method as well. If this fails as well, this method will try to autoconvert the types of the arguments - * and find a matching signature that way. - * - * @param context The object to call the method from (can be null). - * @param datatype The class to find the method on. - * @param identifier The name of the method to locate. - * @param args A list of [non-formal] arguments. - * @return The return value of the invoke method, if successful. - * @throws NoSuchMethodException Thrown by {@link #findCompatibleMethod(Class, String, Set, Class...)}. - * @throws IllegalArgumentException Thrown by {@link Method#invoke(Object, Object...)}. - * @throws IllegalAccessException Thrown by {@link Method#invoke(Object, Object...)}. - * @throws InvocationTargetException Thrown by {@link Method#invoke(Object, Object...)}. - */ - @SuppressWarnings({"WeakerAccess"}) - @Nullable - public static T invokeCompatibleMethod(@Nullable final Object context, final Class datatype, final String identifier, final Object... args) - throws NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { - // determine the signature we want to find a compatible java method for - final Class[] parameterSignature = TypeUtils.collectTypes(args); - - // setup lookup procedure starting with simple search mode - Set lookupMode = EnumSet.of(LookupMode.AUTOBOX, LookupMode.CAST_TO_SUPER); - Set> iMethods; - - // try to find a compatible Java method using various lookup modes - try { - iMethods = findCompatibleMethod(datatype, identifier, lookupMode, parameterSignature); - } catch (final NoSuchMethodException e1) { - try { - // moderate search mode - lookupMode.add(LookupMode.CAST_TO_INTERFACE); - iMethods = findCompatibleMethod(datatype, identifier, lookupMode, parameterSignature); - } catch (final NoSuchMethodException e2) { - try { - // limited conversions searchmode - lookupMode.add(LookupMode.COMMON_CONVERT); - iMethods = findCompatibleMethod(datatype, identifier, lookupMode, parameterSignature); - } catch (NoSuchMethodException e3) { - // full searchmode - lookupMode.add(LookupMode.SMART_CONVERT); - iMethods = findCompatibleMethod(datatype, identifier, lookupMode, parameterSignature); - } - } - } - - for (InvokableObject iMethod : iMethods) { - iMethod.getMethod().setAccessible(true); - - try { - Object[] convertedArgs = ValueConversionHelper.convert(args, iMethod.getCompatibleSignature(), false); - return trustedNullableCast(iMethod.getMethod().invoke(context, convertedArgs)); - } catch (IncompatibleTypeException e) { - // keep trying conversion candidates... - } - } - - LOGGER.error(format("Was unable to find a suitable method on %s for the parameter signature %s", datatype, Arrays.toString(parameterSignature))); - throw new NoSuchMethodException(); - } - - /** - * Locates and invokes a {@link Constructor}using {@link #invokeConstructor(Class, Class[], Object[])} - * - * @param Used to parameterize the returned object so that the caller doesn't need to cast. - * @param datatype The class to find the constructor for. - * @param args A list of [non-formal] arguments. - * @return The instantiated object of class datatype. - * @throws IllegalAccessException Thrown by {@link #invokeConstructor(Class, Class[], Object[])}. - * @throws InvocationTargetException Thrown by {@link #invokeConstructor(Class, Class[], Object[])}. - * @throws InstantiationException Thrown by {@link #invokeConstructor(Class, Class[], Object[])}. - * @throws NoSuchMethodException Thrown by {@link #invokeConstructor(Class, Class[], Object[])}. - * @see java.lang.reflect.Constructor#newInstance(Object[]) - */ - @SuppressWarnings({"UnusedReturnValue", "WeakerAccess", "unused"}) - @NotNull - public static T invokeCompatibleConstructor(final Class datatype, final Object... args) throws NoSuchMethodException, - IllegalAccessException, InvocationTargetException, InstantiationException { - final Class[] parameterList = TypeUtils.collectTypes(args); - return invokeConstructor(datatype, parameterList, args); - } - - /** - * Locates and invokes a {@link Constructor}, using a customized typelist. Avoids dynamically trying to find correct parameter type list. Can also - * be used to force up/down casting (ie. passing a specific type of List into a generic type) - * - * @param Used to parameterize the returned object so that the caller doesn't need to cast. - * @param datatype The class to find the constructor for. - * @param parameterSignature The typelist used to find correct constructor. - * @param args A list of [non-formal] arguments. - * @return The instantiated object of class datatype. - * @throws IllegalAccessException Thrown by {@link Constructor#newInstance(Object...)}. - * @throws InvocationTargetException Thrown by {@link Constructor#newInstance(Object...)}. - * @throws InstantiationException Thrown by {@link Constructor#newInstance(Object...)}. - * @throws NoSuchMethodException Thrown by {@link #findCompatibleConstructor(Class, Set, Class...)}. - * @see java.lang.reflect.Constructor#newInstance(Object[]) - */ - @SuppressWarnings("WeakerAccess") - @NotNull - public static T invokeConstructor(final Class datatype, final Class[] parameterSignature, final Object[] args) throws NoSuchMethodException, - IllegalAccessException, InvocationTargetException, InstantiationException { - // setup lookup procedure - Set lookupMode = EnumSet.of(LookupMode.AUTOBOX, LookupMode.CAST_TO_SUPER); - Set> iConstructors; - - // try to find a compatible Java constructor - try { - iConstructors = findCompatibleConstructor(datatype, lookupMode, parameterSignature); - } catch (final NoSuchMethodException e1) { - try { - lookupMode.add(LookupMode.CAST_TO_INTERFACE); - iConstructors = findCompatibleConstructor(datatype, lookupMode, parameterSignature); - } catch (final NoSuchMethodException e2) { - try { - lookupMode.add(LookupMode.COMMON_CONVERT); - iConstructors = findCompatibleConstructor(datatype, lookupMode, parameterSignature); - } catch (final NoSuchMethodException e3) { - lookupMode.add(LookupMode.SMART_CONVERT); - iConstructors = findCompatibleConstructor(datatype, lookupMode, parameterSignature); - } - } - } - - for (InvokableObject iConstructor : iConstructors) { - try { - Object[] convertedArgs = ValueConversionHelper.convert(args, iConstructor.getCompatibleSignature(), false); - return trustedCast(iConstructor.getMethod().newInstance(convertedArgs)); - } catch (IncompatibleTypeException e) { - // keep trying conversion candidates... - } - } - - LOGGER.error(format("Was unable to find a suitable constructor on %s for the parameter signature %s", datatype, Arrays.toString(parameterSignature))); - throw new NoSuchMethodException(); - } - - /** - * Tries to find a {@link Constructor} of a given type, with a given typelist, where types do not match due to formal types. - * This expanded version tries a simple call first and when it fails, it generates a list of type arrays with all possible (un)wraps of any type - * in the original list possible, and combinations thereof. - * - * @param Used to parameterize the returned constructor. - * @param datatype The class to get the constructor from. - * @param lookupMode Flag indicating the search steps that need to be done. - * @param signature The list of types as specified by the user. - * @return The constructor if found, otherwise exception is thrown. - * @exception NoSuchMethodException Thrown when the {@link Constructor} could not be found on the data type, even after performing optional - * conversions. - */ - @SuppressWarnings({"WeakerAccess"}) - public static Set> findCompatibleConstructor(final Class datatype, final Set lookupMode, final Class... signature) - throws NoSuchMethodException { - // first try to find the constructor in the method cache - Set> iConstructors = getConstructorFromCache(datatype, datatype.getName(), signature); - if (iConstructors != null) { - return iConstructors; - } else { - iConstructors = new HashSet<>(); - - try { - // try standard call - iConstructors.add(new InvokableObject(datatype.getConstructor(signature), signature, signature)); - } catch (final NoSuchMethodException e) { - for (final Class[] compatibleSignature : TypeUtils.generateCompatibleTypeLists(lookupMode, signature)) { - try { - iConstructors.add(new InvokableObject(datatype.getConstructor(compatibleSignature), signature, compatibleSignature)); - } catch (final NoSuchMethodException x) { - // do nothing - } - } - } - } - - if (!iConstructors.isEmpty()) { - return addMethodToCache(datatype, datatype.getName(), iConstructors, signature); - } else { - throw new NoSuchMethodException(); - } - } - - /** - * Delegates to {@link #findCompatibleMethod(Class, String, Set, Class...)}, using strict lookupmode (no autoboxing, casting etc.) and - * optional signature parameters. - * - * @param datatype The class to get the constructor from. - * @param methodName The name of the method to retrieve from the class. - * @param signature The list of types as specified by the user. - * @return null in case of a NoSuchMethodException exception. - * @see #findCompatibleMethod(Class, String, Set, Class...) - */ - @NotNull - @SuppressWarnings("WeakerAccess") - public static Set> findSimpleCompatibleMethod(final Class datatype, final String methodName, final Class... signature) { - try { - return findCompatibleMethod(datatype, methodName, EnumSet.noneOf(LookupMode.class), signature); - } catch (final NoSuchMethodException e) { - return new HashSet<>(); - } - } - - /** - * Delegates to {@link #findCompatibleMethod(Class, String, Set, Class[])}, with the types of the given arguments extracted using {@link TypeUtils#collectTypes(Object[])}. - */ - public static Set> findCompatibleMethod(final Class datatype, final String methodName, final Set lookupMode, - final Object... args) throws NoSuchMethodException { - return findCompatibleMethod(datatype, methodName, lookupMode, TypeUtils.collectTypes(args)); - } - - /** - * Same as getConstructor(), except for getting a {@link Method} of a classtype, using the name to indicate which method should be - * located. - * - * @param datatype The class to get the constructor from. - * @param methodName The name of the method to retrieve from the class. - * @param lookupMode Flag indicating the search steps that need to be done. - * @param signature The list of types as specified by the user. - * @return The method if found, otherwise exception is thrown. - * @exception NoSuchMethodException Thrown when the {@link Method} could not be found on the data type, even after performing optional - * conversions. - */ - @NotNull - @SuppressWarnings("WeakerAccess") - public static Set> findCompatibleMethod(final Class datatype, final String methodName, final Set lookupMode, - final Class... signature) throws NoSuchMethodException { - // first try to find the method in the method cache - Set> iMethods = getMethodFromCache(datatype, methodName, signature); - if (iMethods != null) { - return iMethods; - } else { - iMethods = new HashSet<>(); - try { - // try standard call - iMethods.add(new InvokableObject<>(getMethod(datatype, methodName, signature), signature, signature)); - } catch (final NoSuchMethodException e) { - for (final Class[] compatibleSignature : TypeUtils.generateCompatibleTypeLists(lookupMode, signature)) { - try { - iMethods.add(new InvokableObject<>(getMethod(datatype, methodName, compatibleSignature), signature, compatibleSignature)); - } catch (final NoSuchMethodException x) { - // do nothing - } - } - } - } - - if (!iMethods.isEmpty()) { - return addMethodToCache(datatype, methodName, iMethods, signature); - } else { - throw new NoSuchMethodException(); - } - } - - /** - * Searches a specific class object for a {@link Method} using java reflect using a specific signature. This method will first search all - * implemented interfaces for the method to avoid visibility problems.
- *
- * An example of such a problem is the Iterator as implemented by the ArrayList. The Iterator is implemented as a - * private innerclass and as such not accessible by java reflect (even though the implemented methods are declared public), unlike the - * interface's definition. - * - * @param datatype The class reference to locate the method on. - * @param name The name of the method to find. - * @param signature The signature the method should match. - * @return The Method found on the data type that matched the specified signature. - * @exception NoSuchMethodException Thrown when the {@link Method} could not be found on the interfaces implemented by the given data type. - * @see java.lang.Class#getMethod(String, Class[]) - */ - @SuppressWarnings("WeakerAccess") - @NotNull - public static Method getMethod(final Class datatype, final String name, final Class... signature) throws NoSuchMethodException { - for (final Class iface : datatype.getInterfaces()) { - try { - return iface.getMethod(name, signature); - } catch (final NoSuchMethodException e) { - // do nothing - } - } - try { - return datatype.getMethod(name, signature); - } catch (final NoSuchMethodException e) { - return datatype.getDeclaredMethod(name, signature); - } - } - - /** - * Tests if a list of classes is compatible with the signature of the given method, allowing for {@link LookupMode#SIMPLE} lookup mode. - */ - @SuppressWarnings({"unused"}) - public static boolean isMethodCompatible(Method method, final Object... signature) { - return isMethodCompatible(method, LookupMode.SIMPLE, signature); - } - - /** - * Tests if a list of classes is compatible with the signature of the given method, allowing for the given lookup modes. - */ - @SuppressWarnings({"unused"}) - public static boolean isMethodCompatible(Method method, Set lookupMode, final Object... signature) { - return isMethodCompatible(method, lookupMode, TypeUtils.collectTypes(signature)); - } - - /** - * Tests if a list of arguments is compatible with the signature of the given method, allowing for {@link LookupMode#SIMPLE} lookup mode. - */ - @SuppressWarnings({"unused"}) - public static boolean isMethodCompatible(Method method, final Class... signature) { - return isMethodCompatible(method, LookupMode.SIMPLE, signature); - } - - /** - * Tests if a list of arguments is compatible with the signature of the given method, allowing for the given lookup modes. - */ - @SuppressWarnings({"unused", "WeakerAccess"}) - public static boolean isMethodCompatible(Method method, Set lookupMode, final Class... signature) { - final Class[] targetSignature = method.getParameterTypes(); - if (signature.length != targetSignature.length) { - return false; - } - return TypeUtils.isTypeListCompatible(signature, targetSignature, lookupMode); - } - - /** - * Delegates to {@link #zipParametersAndArguments(boolean, Method, Object...)} with strict type checking. - */ - @SuppressWarnings({"unused"}) - @Nullable - public static LinkedHashMap zipParametersAndArguments(Method method, Object... arguments) { - return zipParametersAndArguments(true, method, arguments); - } - - /** - * Given a method and a list of arguments, return a map of parameters matching their arguments. - * - * @param strictTypeChecking Indicates whether the parameters types should be checked for compatibility with the - * arguments provided. If true and the signature doesn't match, return {@code null}, or else - * zip values as given using the lowest length. - */ - @SuppressWarnings({"unused"}) - @Nullable - public static LinkedHashMap zipParametersAndArguments(boolean strictTypeChecking, Method method, Object... arguments) { - if (!strictTypeChecking || isMethodCompatible(method, arguments)) { - final LinkedHashMap result = new LinkedHashMap<>(); - for (int i = 0; i < Math.min(method.getParameterTypes().length, arguments.length); i++) { - result.put(new MethodParameter(i, - method.getParameterTypes()[i], - method.getGenericParameterTypes()[i], - Arrays.asList(method.getParameterAnnotations()[i])), arguments[i]); - } - return result; - } - return null; - } - - /** - * Retrieves a {@link Method} from a cache. - * - * @param datatype The owning {@link Class} of the Method being searched for. - * @param method The name of the method that is being searched for. - * @param signature The parameter list of the method we need to match if a method was found by name. - * @return The Method found on the specified owner with matching name and signature. - * @see LookupCaches#METHOD_CACHE - * @see MethodUtils#addMethodToCache(Class, String, Set, Class[]) - */ - @Nullable - private static Set getInvokableObjectFromCache(final Class datatype, final String method, final Class... signature) { - final Map[], Set>> owner = METHOD_CACHE.get(datatype); - // we know only methods with parameter list are stored in the cache - if (signature.length > 0) { - // get owner, its methods matching specified name and match their signatures - if (owner != null && owner.containsKey(method)) { - return owner.get(method).get(signature); - } - } - // method not found or known not to be stored due to absent parameter list - return null; - } - - @Nullable - private static Set> getMethodFromCache(final Class datatype, final String method, final Class... signature) { - return trustedNullableCast(getInvokableObjectFromCache(datatype, method, signature)); - } - - @Nullable - private static Set> getConstructorFromCache(final Class datatype, final String method, final Class... signature) { - return trustedNullableCast(getInvokableObjectFromCache(datatype, method, signature)); - } - - /** - * Adds a specific Method to the cache. - * - * @param datatype The Class that owns the Method. - * @param method The Method's name by which methods can be found on the specified owner. - * @param methodInvocationCandidates The Method reference that's actually being stored in the cache. - * @param signature The parameter list of the Method being stored. - * @see LookupCaches#METHOD_CACHE - * @see MethodUtils#getMethodFromCache(Class, String, Class...) - */ - private static , T2 extends AccessibleObject> Set addMethodToCache(final Class datatype, final String method, - final Set methodInvocationCandidates, final Class... signature) { - // only store methods with a parameter list - if (signature.length > 0) { - // get or create owner entry - Map[], Set>> owner = METHOD_CACHE.get(datatype); - owner = owner != null ? owner : new LinkedHashMap[], Set>>(); - // get or create list of methods with specified method name - Map[], Set> methods = owner.get(method); - methods = methods != null ? methods : new LinkedHashMap[], Set>(); - // add or overwrite method entry - methods.put(signature, MiscUtil.>trustedCast(methodInvocationCandidates)); - // finally shelve all the stuff back - methods.put(signature, MiscUtil.>trustedCast(methodInvocationCandidates)); - owner.put(method, methods); - METHOD_CACHE.put(datatype, owner); - } - return methodInvocationCandidates; - } - - - /** - * Delegates to {@link #findMatchingMethods(Class, Class, String, String...)} - */ - @SuppressWarnings({ "unused", "WeakerAccess" }) - public static Set findMatchingMethods(final Class datatype, @Nullable Class boundaryMarker, String methodName, List paramTypeNames) { - return findMatchingMethods(datatype, boundaryMarker, methodName, paramTypeNames.toArray(new String[0])); - } - - /** - * @return Methods found using {@link ClassUtils#collectMethods(Class, Class, EnumSet)} - * and then filters based on the parameter type names. - */ - @SuppressWarnings({ "unused", "WeakerAccess" }) - public static Set findMatchingMethods(final Class datatype, @Nullable Class boundaryMarker, String methodName, String... paramTypeNames) { - Set matchingMethods = new HashSet<>(); - for (Method method : ClassUtils.collectMethods(datatype, boundaryMarker, MethodModifier.MATCH_ANY)) { - Class[] methodParameterTypes = method.getParameterTypes(); - if (method.getName().equals(methodName) && - methodParameterTypes.length == paramTypeNames.length && - typeNamesMatch(methodParameterTypes, paramTypeNames)) { - matchingMethods.add(method); - } - } - return matchingMethods; - } - - private static boolean typeNamesMatch(Class[] parameterTypes, String[] typeNamesToMatch) { - for (int i = 0; i < parameterTypes.length; i++) { - final Class parameterType = parameterTypes[i]; - final String typeNameToMatch = typeNamesToMatch[i]; - if (parameterType.isArray()) { - final String arrayTypeNameToMatch = typeNameToMatch.endsWith("...") - ? typeNameToMatch.substring(0, typeNameToMatch.indexOf("...")) - : typeNameToMatch; - if (typeNamesDontMatch(parameterType.getComponentType(), arrayTypeNameToMatch)) { - return false; - } - } else if (typeNamesDontMatch(parameterType, typeNameToMatch)) { - return false; - } - } - return true; - } - - private static boolean typeNamesDontMatch(Class parameterType, String typeNameToMatch) { - return !parameterType.getName().equals(typeNameToMatch) && !parameterType.getSimpleName().equals(typeNameToMatch); - } - - /** - * @return True if the given method contains a parameter that is an array of an {@link Iterable}. - */ - @SuppressWarnings("WeakerAccess") - public static boolean methodHasCollectionParameter(final Method m) { - for (Class parameterType : m.getParameterTypes()) { - if (parameterType.isArray() || - Iterable.class.isAssignableFrom(parameterType) || - Map.class.isAssignableFrom(parameterType)) { - return true; - } - } - return false; - } - - public static Method onlyMethod(Set> methods) { - if (methods.size() == 0) { - return null; - } else if (methods.size() == 1) { - return methods.iterator().next().getMethod(); - } else { - throw new AssertionError("Expected 1 or less methods, but found more than 1 methods: " + methods); - } - } - - @SuppressWarnings("unchecked") - @Nullable - public static Target firstParameterArgumentByAnnotation(Method method, Object[] arguments, Class annotationClass) { - if (isMethodCompatible(method, arguments)) { - for (int i = 0; i < method.getParameterTypes().length; i++) { - if (containsAnnotation(method.getParameterAnnotations()[i], annotationClass)) { - return (Target) arguments[i]; - } - } - } - return null; - } - - public static int firstParameterIndexByAnnotation(Method method, Class annotationClass) { - for (int i = 0; i < method.getParameterTypes().length; i++) { - if (containsAnnotation(method.getParameterAnnotations()[i], annotationClass)) { - return i; - } - } - return -1; - } -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/PackageUtils.java b/src/main/java/org/bbottema/javareflection/PackageUtils.java deleted file mode 100644 index a568a57..0000000 --- a/src/main/java/org/bbottema/javareflection/PackageUtils.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.bbottema.javareflection; - -import lombok.experimental.UtilityClass; -import org.jetbrains.annotations.Nullable; - -@SuppressWarnings("WeakerAccess") -@UtilityClass -public final class PackageUtils { - - @Nullable - static Class scanPackagesForClass(String className, @Nullable String inPackage, @Nullable ClassLoader classLoader) { - // cycle through all sub-packages and try allocating class dynamically - for (Package currentPackage : Package.getPackages()) { - final String packageName = currentPackage.getName(); - if (inPackage == null || packageName.startsWith(inPackage)) { - final Class _class = ClassUtils.locateClass(packageName + "." + className, classLoader); - if (_class != null) { - return _class; - } - } - } - return null; - } -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/ReflectionUtils.java b/src/main/java/org/bbottema/javareflection/ReflectionUtils.java deleted file mode 100644 index 3e66f1f..0000000 --- a/src/main/java/org/bbottema/javareflection/ReflectionUtils.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.bbottema.javareflection; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.HashMap; -import java.util.Map; - -/** - * This util is able to find Generic types as Class instances. This is useful for determining class types in runtime, which is (mostly) used by - * converters. - */ -public class ReflectionUtils { - - @SuppressWarnings("FieldCanBeLocal") - private static final String INVALID_GENERIC_TYPE_DEFINITION = "Unable to determine generic type, probably due to type erasure. Make sure the type is part of a class signature (it can not be a field or variable, or a nested generic type such as List)"; - - /** - * Inspects a inheritance chain of classes until the classOfInterest is found and then will look for the Generic type declared for the given (zero-based) index. - */ - @SuppressWarnings("unchecked") - public static Class findParameterType(Class instanceClass, Class classOfInterest, int parameterIndex) { - Map typeMap = new HashMap<>(); - while (classOfInterest != instanceClass.getSuperclass()) { - extractTypeArguments(typeMap, instanceClass); - instanceClass = instanceClass.getSuperclass(); - if (instanceClass == null) - throw new IllegalArgumentException(); - } - - ParameterizedType parameterizedType = (ParameterizedType) instanceClass.getGenericSuperclass(); - Type actualType = parameterizedType.getActualTypeArguments()[parameterIndex]; - if (typeMap.containsKey(actualType)) { - actualType = typeMap.get(actualType); - } - if (actualType instanceof Class) { - return (Class) actualType; - } else { - try { - return (Class) ((ParameterizedType) actualType).getRawType(); - } catch (ClassCastException e) { - throw new IllegalStateException(INVALID_GENERIC_TYPE_DEFINITION, e); - } - } - } - - private static void extractTypeArguments(Map typeMap, Class clazz) { - Type genericSuperclass = clazz.getGenericSuperclass(); - if (!(genericSuperclass instanceof ParameterizedType)) { - return; - } - - ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass; - Type[] typeParameter = ((Class) parameterizedType.getRawType()).getTypeParameters(); - Type[] actualTypeArgument = parameterizedType.getActualTypeArguments(); - for (int i = 0; i < typeParameter.length; i++) { - if (typeMap.containsKey(actualTypeArgument[i])) { - actualTypeArgument[i] = typeMap.get(actualTypeArgument[i]); - } - typeMap.put(typeParameter[i], actualTypeArgument[i]); - } - } -} diff --git a/src/main/java/org/bbottema/javareflection/TypeUtils.java b/src/main/java/org/bbottema/javareflection/TypeUtils.java deleted file mode 100644 index 09eecf1..0000000 --- a/src/main/java/org/bbottema/javareflection/TypeUtils.java +++ /dev/null @@ -1,339 +0,0 @@ -package org.bbottema.javareflection; - -import lombok.experimental.UtilityClass; -import org.bbottema.javareflection.model.LookupMode; -import org.bbottema.javareflection.util.ArrayKey; -import org.bbottema.javareflection.valueconverter.ValueConversionHelper; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.lang.annotation.Annotation; -import java.util.*; - -import static org.bbottema.javareflection.LookupCaches.*; - -/** - * Utility functions that deal with type information, conversions and autoboxing. - *

- * Particularly of interest is {@link #generateCompatibleTypeLists(Set, Class[])}, - * which generates a collection of type lists which can be derived from the input type - * list. This is usful for method matching, since Java reflection doesn't take into account - * autoboxing, casting or auto widening, let alone type conversions. - *

- * Types that are candidates for Autoboxing: - *

    - *
  • boolean java.lang.Boolean
  • - *
  • char java.lang.Character
  • - *
  • byte java.lang.Byte
  • - *
  • short java.lang.Short
  • - *
  • int java.lang.Integer
  • - *
  • long java.lang.Long
  • - *
  • float java.lang.Float
  • - *
  • double java.lang.Double
  • - *
- *

- * For types that are candidates for common conversion, please see {@link ValueConversionHelper}. - */ -@UtilityClass -public final class TypeUtils { - - /** - * A list with Number types in ascending order to wideness (or size) of each type (ie. double is wider than integer). - */ - private static final Map, Integer> numSizes; - - static { - numSizes = new LinkedHashMap<>(); - int size = 0; - numSizes.put(Byte.class, ++size); - numSizes.put(Short.class, ++size); - numSizes.put(Integer.class, ++size); - numSizes.put(Long.class, ++size); - numSizes.put(Float.class, ++size); - numSizes.put(Double.class, ++size); - } - - /** - * Creates a new array of class objects harvested from an array of objects. - *

- * NOTE: this method will never return primitive classes (such as double.class, as you can't put primitive values into an array of Objects (they - * will be autoboxes by the JVM). - * - * @param objects The array of objects to harvest classtypes from. - * @return The array with the harvested classtypes. - */ - @NotNull - @SuppressWarnings("WeakerAccess") - public static Class[] collectTypes(final Object[] objects) { - // collect classtypes of the arguments - final Class[] types = new Class[objects.length]; - for (int i = 0; i < objects.length; i++) { - final Object o = objects[i]; - types[i] = o != null ? o.getClass() : null; - } - return types; - } - - @SuppressWarnings({"unused", "WeakerAccess"}) - public static boolean isTypeListCompatible(Class[] inputTypeList, Class[] targetTypeList, Set lookupMode) { - List[]> derivableTypeLists = generateCompatibleTypeLists(lookupMode, inputTypeList); - - for (Class[] derivableTypeList : derivableTypeLists) { - boolean currentTypeListCompatible = true; - for (int i = 0; i < derivableTypeList.length && currentTypeListCompatible; i++) { - if (derivableTypeList[i] != null && !derivableTypeList[i].equals(targetTypeList[i])) { - currentTypeListCompatible = false; - } - } - if (currentTypeListCompatible) { - return true; - } - } - return false; - } - - /** - * Initializes the list with type-arrays and starts generating beginning from index 0. This method is used for (un)wrapping. - * - * @param lookupMode Flag indicating the search steps that need to be done. - * @param inputTypelist The list with original user specified types. - * @return The list with converted type-arrays. - */ - @NotNull - @SuppressWarnings({"unused", "WeakerAccess"}) - public static List[]> generateCompatibleTypeLists(final Set lookupMode, final Class... inputTypelist) { - final ArrayKey arrayKey = new ArrayKey(inputTypelist); - final List[]> cachedResult = getCachedCompatibleSignatures(lookupMode, arrayKey); - return cachedResult != null - ? cachedResult - : addCompatiblesignaturesToCache(lookupMode, arrayKey, - generateCompatibleTypeLists(0, lookupMode, new ArrayList[]>(), inputTypelist)); - } - - /** - * Recursively generates a complete list of all possible (un)wraps (autoboxing), supertypes, implemented interfaces, type conversions and any - * combination thereof with the specified typeLists's elements (the individual parameter types).
- *
- * The combination typeLists are generated in the following order: - *

    - *
  1. no conversion; highest priority as it comes closest to user's requirement/specification
  2. - *
  3. autoboxing; the autoboxed counterversion comes closest to the original datatype
  4. - *
  5. interface; where methods can't be found using original type, interface placeholders are attempted
  6. - *
  7. supertype; where methods can't be found using implemented interfaces, supertype placeholders are attempted
  8. - *
  9. conversions; if all else fails, try to convert the datatype for common types (ie. int to String)
  10. - *
- * @param index The current index to start mutating from. - * @param lookupMode Flag indicating the search steps that need to be done. - * @param inputTypelist The list with current types, to mutate further upon. - */ - private static List[]> generateCompatibleTypeLists(final int index, final Set lookupMode, final List[]> compatibleTypeLists, final Class... inputTypelist) { - // if new type array is completed - if (index == inputTypelist.length) { - compatibleTypeLists.add(inputTypelist); - } else { - // generate new array of types - final Class original = inputTypelist[index]; - - // 1. don't generate compatible list; just try the normal type first - // remember, in combinations types should be allowed to be converted) - generateCompatibleTypeLists(index + 1, lookupMode, compatibleTypeLists, inputTypelist.clone()); - - if (original != null) { - // 2. generate type in which the original can be (un)wrapped - if (lookupMode.contains(LookupMode.AUTOBOX) && !lookupMode.contains(LookupMode.SMART_CONVERT)) { - final Class autoboxed = autobox(original); - if (autoboxed != null) { - final Class[] newTypeList = replaceInArray(inputTypelist.clone(), index, autoboxed); - generateCompatibleTypeLists(index + 1, lookupMode, compatibleTypeLists, newTypeList); - } - } - - // autocast to supertype or interface? - if (lookupMode.contains(LookupMode.CAST_TO_INTERFACE)) { - // 3. generate implemented interfaces the original value could be converted (cast) into - for (final Class iface : original.getInterfaces()) { - final Class[] newTypeList = replaceInArray(inputTypelist.clone(), index, iface); - generateCompatibleTypeLists(index + 1, lookupMode, compatibleTypeLists, newTypeList); - } - } - - if (lookupMode.contains(LookupMode.CAST_TO_SUPER)) { - // 4. generate supertypes the original value could be converted (cast) into - Class supertype = original; - while ((supertype = supertype.getSuperclass()) != null) { - final Class[] newTypeList = replaceInArray(inputTypelist.clone(), index, supertype); - generateCompatibleTypeLists(index + 1, lookupMode, compatibleTypeLists, newTypeList); - } - } - - // 5. generate types the original value could be converted into - if (lookupMode.contains(LookupMode.COMMON_CONVERT) && !lookupMode.contains(LookupMode.SMART_CONVERT)) { - for (final Class convert : collectRegisteredCompatibleTargetTypes(original)) { - final Class[] newTypeList = replaceInArray(inputTypelist.clone(), index, convert); - generateCompatibleTypeLists(index + 1, lookupMode, compatibleTypeLists, newTypeList); - } - } - - // 6. generate types the original value could be converted into with intermediary conversions - if (lookupMode.contains(LookupMode.SMART_CONVERT)) { - for (final Class convert : collectCompatibleTargetTypes(original)) { - final Class[] newTypeList = replaceInArray(inputTypelist.clone(), index, convert); - generateCompatibleTypeLists(index + 1, lookupMode, compatibleTypeLists, newTypeList); - } - } - } - } - - return compatibleTypeLists; - } - - @NotNull - private static Set> collectRegisteredCompatibleTargetTypes(Class fromType) { - if (!CACHED_REGISTERED_COMPATIBLE_TARGET_TYPES.containsKey(fromType)) { - CACHED_REGISTERED_COMPATIBLE_TARGET_TYPES.put(fromType, ValueConversionHelper.collectRegisteredCompatibleTargetTypes(fromType)); - } - return CACHED_REGISTERED_COMPATIBLE_TARGET_TYPES.get(fromType); - } - - @NotNull - private static Set> collectCompatibleTargetTypes(Class fromType) { - if (!CACHED_COMPATIBLE_TARGET_TYPES.containsKey(fromType)) { - CACHED_COMPATIBLE_TARGET_TYPES.put(fromType, ValueConversionHelper.collectCompatibleTargetTypes(fromType)); - } - return CACHED_COMPATIBLE_TARGET_TYPES.get(fromType); - } - - /** - * Emulates Java's Autoboxing feature; tries to convert a type to its (un)wrapped counter version. - * - * @param c The datatype to convert (autobox). - * @return The converted version of the specified type, or null. - */ - @Nullable - @SuppressWarnings("WeakerAccess") - public static Class autobox(final Class c) { - // integer - if (c == Integer.class) { - return int.class; - } else if (c == int.class) { - return Integer.class; - } else if (c == Boolean.class) { - return boolean.class; - } else if (c == boolean.class) { - return Boolean.class; - } else if (c == Character.class) { - return char.class; - } else if (c == char.class) { - return Character.class; - } else if (c == Byte.class) { - return byte.class; - } else if (c == byte.class) { - return Byte.class; - } else if (c == Short.class) { - return short.class; - } else if (c == short.class) { - return Short.class; - } else if (c == Long.class) { - return long.class; - } else if (c == long.class) { - return Long.class; - } else if (c == Float.class) { - return float.class; - } else if (c == float.class) { - return Float.class; - } else if (c == Double.class) { - return double.class; - } else if (c == double.class) { - return Double.class; - } else { - return null; - } - } - /** - * Returns the smallest class that can hold all of the specified numbers. - * - * @param numbers The list with numbers that all should fit in the Number container. - * @return The Number container that is just large enough for all specified numbers. - */ - @NotNull - @SuppressWarnings("WeakerAccess") - public static Class widestNumberClass(final Number... numbers) { - Integer widest = 0; - Class widestNumberType = Byte.class; - for (final Number number : numbers) { - final Integer size = numSizes.get(number.getClass()); - if (size > widest) { - widestNumberType = number.getClass(); - widest = size; - } - } - return widestNumberType; - } - - /** - * Validates whether a string represents a valid package. - * - * @param name The string representing a list of packages. - * @return A boolean indicating whether name represents a valid package. - */ - @SuppressWarnings("WeakerAccess") - public static boolean isPackage(final String name) { - return name.equals("java") || Package.getPackage(name) != null; - } - - /** - * @return Whether a given list of Annotation contains a certain annotation type. - * @see #findAnnotation(Collection, Class) - */ - @SuppressWarnings("WeakerAccess") - public static boolean containsAnnotation(List myListOfAnnotations, Class annotationClass) { - return findAnnotation(myListOfAnnotations, annotationClass) != null; - } - - /** - * @return Whether a given list of Annotation contains a certain annotation type. - * @see #findAnnotation(Annotation[], Class) - */ - public static boolean containsAnnotation(Annotation[] myListOfAnnotations, Class annotationClass) { - return findAnnotation(myListOfAnnotations, annotationClass) != null; - } - - /** - * @return Whether a given list of Annotation contains a certain annotation type. - * @see #findAnnotation(Annotation[], Class) - */ - @SuppressWarnings({"WeakerAccess"}) - @Nullable - public static T findAnnotation(Collection myListOfAnnotations, Class annotationClass) { - return findAnnotation(myListOfAnnotations.toArray(new Annotation[0]), annotationClass); - } - - /** - * @return Whether a given list of Annotation contains a certain annotation type. - */ - @SuppressWarnings({"WeakerAccess", "unchecked"}) - @Nullable - public static T findAnnotation(Annotation[] myListOfAnnotations, Class annotationClass) { - for (Annotation annotation : myListOfAnnotations) { - if (annotation.annotationType() == annotationClass) { - return (T) annotation; - } - } - return null; - } - - /** - * Shortcut helper method that replaces an item in an array and returns the array itself. - * - * @param The type of object that goes into the array. - * @param array The array that needs an item replaced. - * @param index The index at which the new value should be inserted. - * @param value The value to insert at the specified index in the specified array. - * @return The specified array with the item replaced at specified index. - */ - @NotNull - static T[] replaceInArray(final T[] array, final int index, final T value) { - array[index] = value; - return array; - } -} diff --git a/src/main/java/org/bbottema/javareflection/model/FieldWrapper.java b/src/main/java/org/bbottema/javareflection/model/FieldWrapper.java deleted file mode 100644 index 02ac3f6..0000000 --- a/src/main/java/org/bbottema/javareflection/model/FieldWrapper.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.bbottema.javareflection.model; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import lombok.Value; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -/** - * A wrapper class that keeps a property ({@link Field}) and its setter/getter method(s) in one place. - */ -@Value -@SuppressFBWarnings(justification = "Generated code") -public class FieldWrapper { - - @NotNull - private final Field field; - @Nullable - private final Method getter; - @Nullable - private final Method setter; -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/model/InvokableObject.java b/src/main/java/org/bbottema/javareflection/model/InvokableObject.java deleted file mode 100644 index 958f9a6..0000000 --- a/src/main/java/org/bbottema/javareflection/model/InvokableObject.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.bbottema.javareflection.model; - - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import lombok.Data; -import lombok.NonNull; - -import java.lang.reflect.AccessibleObject; - -@Data -@SuppressFBWarnings(justification = "Generated code") -public class InvokableObject { - @NonNull T method; - @NonNull Class[] inputSignature; - @NonNull Class[] compatibleSignature; -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/model/LookupMode.java b/src/main/java/org/bbottema/javareflection/model/LookupMode.java deleted file mode 100644 index 1de1203..0000000 --- a/src/main/java/org/bbottema/javareflection/model/LookupMode.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.bbottema.javareflection.model; - -import org.bbottema.javareflection.valueconverter.ValueConversionHelper; - -import java.util.Set; - -import static java.util.Collections.unmodifiableSet; -import static java.util.EnumSet.allOf; -import static java.util.EnumSet.of; - -/** - * Defines lookup modes for matching Java methods and constructors. Each time a lookup failed on signature type, a less strict lookup - * is performed, in the following order (signature means: the list of parameters defined for a method or constructor): - *
    - *
  1. exact matching: the given type should exactly match the found types during lookup. This lookup cycle is always performed - * first.
  2. - *
  3. autobox matching: the given type should match its boxed/unboxed version.
  4. - *
  5. polymorphic interface matching: the given type should match one of the implemented interfaces
  6. - *
  7. polymorphic superclass matching: the given type should match one of the super classes (for each superclass, the cycle is - * repeated, so exact and interface matching come first again before the next superclass up in the chain)
  8. - *
  9. common conversion matching: if all other lookups fail, one last resort is to try to convert the given type, if a common - * type, to any other common type and then try to find a matching method or constructor. See {@link ValueConversionHelper} for more on the possibilities. - *
  10. - *
- */ -public enum LookupMode { - /** - * Indicates that looking for methods includes trying to find compatible signatures by autoboxing the specified arguments. - */ - AUTOBOX, - /** - * Indicates that looking for methods includes trying to find compatible signatures by casting the specified arguments to a super type. - */ - CAST_TO_SUPER, - /** - * Indicates that looking for methods includes trying to find compatible signatures by casting the specified arguments to an implemented - * interface. - */ - CAST_TO_INTERFACE, - /** - * Indicates that looking for methods includes trying to find compatible signatures by automatically converting the specified arguments. - */ - COMMON_CONVERT, - /** - * Like {@link #COMMON_CONVERT}, but now takes the registered converters and continues finding a conversion path based on the previous outcomes. - *

- * Examples: - *

    - *
  • Joda date object -> String -> Java8 date object. This would require only the toJava8 date object converter to work
  • - *
  • Calendar -> String -> char. Calendar is compatible with String and String with char, but conversion will fail of course.
  • - *
  • Boolean -> Character -> long. This simply works out of the box, resulting in 0 or 1.
  • - *
- */ - SMART_CONVERT; - - /** - * Defines a simple method lookup configuration that goes as far as casting and autoboxing, but no actual conversions are done to the values. - */ - public static final Set SIMPLE = of(AUTOBOX, CAST_TO_SUPER, CAST_TO_INTERFACE); - - /** - * Defines a complete method lookup configuration that combines all possible lookup modes. - */ - public static final Set FULL = unmodifiableSet(allOf(LookupMode.class)); -} diff --git a/src/main/java/org/bbottema/javareflection/model/MethodModifier.java b/src/main/java/org/bbottema/javareflection/model/MethodModifier.java deleted file mode 100644 index 6a9f3fb..0000000 --- a/src/main/java/org/bbottema/javareflection/model/MethodModifier.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.bbottema.javareflection.model; - -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.EnumSet; - -import static java.util.EnumSet.allOf; - -@SuppressWarnings("unused") -public enum MethodModifier { - PUBLIC(Modifier.PUBLIC), - PROTECTED(Modifier.PROTECTED), - PRIVATE(Modifier.PRIVATE), - ABSTRACT(Modifier.ABSTRACT), - DEFAULT(-1), - STATIC(Modifier.STATIC), - FINAL(Modifier.FINAL), - SYNCHRONIZED(Modifier.SYNCHRONIZED), - NATIVE(Modifier.NATIVE), - STRICT(Modifier.STRICT); - - public static final EnumSet MATCH_ANY = allOf(MethodModifier.class); - - private final int modifierFlag; - - MethodModifier(int modifierFlag) { - this.modifierFlag = modifierFlag; - } - - public static boolean meetsModifierRequirements(Method method, EnumSet modifiers) { - final int m = method.getModifiers(); - - for (MethodModifier methodModifier : modifiers) { - if (methodModifier != MethodModifier.DEFAULT) { - if ((m & methodModifier.modifierFlag) != 0) { - return true; - } - } else { - if (!Modifier.isPrivate(m) && !Modifier.isProtected(m) && !Modifier.isPublic(m)) { - return true; - } - } - } - return false; - } -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/model/MethodParameter.java b/src/main/java/org/bbottema/javareflection/model/MethodParameter.java deleted file mode 100644 index 98b701f..0000000 --- a/src/main/java/org/bbottema/javareflection/model/MethodParameter.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.bbottema.javareflection.model; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import lombok.Value; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Type; -import java.util.Collection; - -@Value -@SuppressFBWarnings(justification = "Generated code") -public class MethodParameter { - final int index; - final Class type; - final Type genericType; - final Collection annotations; -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/util/ArrayKey.java b/src/main/java/org/bbottema/javareflection/util/ArrayKey.java deleted file mode 100644 index 4a2d860..0000000 --- a/src/main/java/org/bbottema/javareflection/util/ArrayKey.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.bbottema.javareflection.util; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - -import java.util.Arrays; - -import static org.bbottema.javareflection.util.MiscUtil.requireNonNullOfType; - -/** - * Needed to make sure hashcode and equals are implemented properly for arrays as key in a map. - */ -public class ArrayKey { - - private final int hashCode; - private final Class[] array; - - public ArrayKey(Class[] array) { - this.array = array.clone(); - this.hashCode = Arrays.hashCode(this.array); - } - - @SuppressFBWarnings(value = "EQ_UNUSUAL", justification = "Equals is specifically implemented for performance reasons") - @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") - @Override - public boolean equals(Object o) { - return o != null && Arrays.equals(array, requireNonNullOfType(o, ArrayKey.class).array); - } - - @Override - public int hashCode() { - return hashCode; - } - - @Override - public String toString() { - return Arrays.toString(array); - } -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/util/ExternalClassLoader.java b/src/main/java/org/bbottema/javareflection/util/ExternalClassLoader.java deleted file mode 100644 index c570ee5..0000000 --- a/src/main/java/org/bbottema/javareflection/util/ExternalClassLoader.java +++ /dev/null @@ -1,222 +0,0 @@ -package org.bbottema.javareflection.util; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.DataInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -/** - * A toolkit that can read and compile .java sourcefiles on the fly in runtime. This class loader caches loaded classes for - * repeated requests. Also, if a .class file already exists for a specific .java sourcefile, the classloader will compare dates and see if - * the .java needs recompiling.
- *
- * A basepath can be specified for the classloader to look in for sourcefiles. - */ -public final class ExternalClassLoader extends ClassLoader { - /** - * base path used to locate [packaged] classes in - */ - @Nullable - private String basepath; - - /** - * List of classes. These are non-instances of a class, of which instances can be spawn. - */ - @NotNull - private final Map> classes = new HashMap<>(); - - /** - * The exception that was thrown when the class was actually found but some other error occurred. - */ - @Nullable - @SuppressWarnings("FieldCanBeLocal") - private CompileException exception; - - /** - * Constructor which initializes all properties. - */ - public ExternalClassLoader() { - super(ExternalClassLoader.class.getClassLoader()); - } - - /** - * Loads a class from the classes cache if available. Delegates to {@link #findClass(String)} otherwise. - * - * @see ClassLoader#loadClass(String) - */ - @Override - @Nullable - public final Class loadClass(final String className) - throws ClassNotFoundException { - final Class c = classes.get(className); - return (c != null) ? c : findClass(className); - } - - /** - * Loads a classfile from file in the following order: - *
    - *
  1. looks for the java source and if available, checks whether it needs to be compiled
  2. - *
  3. if no .java or .class file found, try to find it in the VM
  4. - *
- * If the class ultimately wasn't found a ClassNotFoundException is being thrown. If class was found, but an error - * occurred, store exception for later review and return null. This is done so because the overridden method can't throw an - * exception other than ClassNotFoundException. - * - * @param className The path and name to the classfile. - * @return The requested class reference. - * @throws ClassNotFoundException Thrown by {@link #findSystemClass(String)}. - */ - @SuppressWarnings("WeakerAccess") - @Override - @Nullable - public final Class findClass(final String className) - throws ClassNotFoundException { - exception = null; - try { - // try to load, cache and return the class from compiled .class file - checkForFile(basepath, className); - - // if a classfile is available then load it - final Class c = classes.get(className); - if (c != null) { - return c; - } else { - throw new ClassNotFoundException(); - } - } catch (final ClassNotFoundException e) { - // - see if class can be found in the VM - // - this check must come last, else you won't detect whether a classfile has been recompiled - final Class c = findSystemClass(className); - classes.put(className, c); - return c; - } catch (final IOException e) { - exception = new CompileException(e.getMessage(), e); - return null; - } catch (final CompileException e) { - exception = e; - return null; - } - } - - /** - * Reads and compiles the sourcefile from filesystem and creates the class inside the VM. - * - * @param className The path and name to the .java sourcefile. - */ - private void checkForFile(final String classPath, final String className) - throws IOException { - // figure paths... - final String resource = className.replace("..", "||").replace('.', File.separatorChar).replace("||", ".."); - final File javaSource = new File(classPath + File.separatorChar + resource + ".java"); - final File javaClass = new File(classPath + File.separatorChar + resource + ".class"); - - final String absoluteClassPath = javaClass.getAbsolutePath(); - - // see if there is a javafile and classfile - if (javaSource.exists()) { - // if classfile available, delete if outdated - if (javaClass.exists()) { - // determine if the java sourcefile has been modified since last compile - // else check if the .class file has already been loaded - if (javaSource.lastModified() > javaClass.lastModified()) { - if (!javaClass.delete()) { - throw new CompileException("runtime compiler: unable to removed outdated .class file"); - } - classes.remove(className); - } else if (classes.get(className) == null) { - classes.put(className, loadClass(absoluteClassPath, className)); - } - } - - // if no classfile available or became outdated - if (!javaClass.exists()) { - final int status = 0;// Main.compile(args); - - // load compiled classfile and cache it or return error that occured during compiling - switch (status) { - case 0: - classes.put(className, loadClass(absoluteClassPath, className)); - break; - case 1: - throw new CompileException("runtime compiler: ERROR"); - case 2: - throw new CompileException("runtime compiler: CMDERR"); - case 3: - throw new CompileException("runtime compiler: SYSERR"); - case 4: - throw new CompileException("runtime compiler: ABNORMAL"); - default: - throw new CompileException("Compile status: Unknown exit status"); - } - } - } - } - - /** - * Reads the classfile from filesystem. - * - * @param classPath The path and name to the classfile. - * @param className The name of the class to use as key for the Class reference value. - */ - private Class loadClass(final String classPath, final String className) - throws IOException { - // get data from file - final File f = new File(classPath); - final int size = (int) f.length(); - final byte[] buff = new byte[size]; - final DataInputStream dis = new DataInputStream(new FileInputStream(f)); - dis.readFully(buff); - dis.close(); - // convert data into class - return defineClass(className, buff, 0, buff.length, null); - } - - /** - * Parameterized exception used when Java's runtime compiler fails to compile a Java source file. - */ - public static class CompileException extends RuntimeException { - private static final long serialVersionUID = -7210219718456902667L; - - /** - * @param reason The description of the cause of the exception. - */ - CompileException(final String reason) { - super(reason); - } - - /** - * Used to create an exception with a copies stacktrace (using {@link #setStackTrace(StackTraceElement[])}). - * - * @param reason The description of the cause of the exception. - * @param cause A thrown exception that is the cause. - */ - CompileException(final String reason, final Throwable cause) { - super(reason + "\n " + cause.toString()); - setStackTrace(cause.getStackTrace()); - } - } - - /** - * Sets the base path this classloader will look for classes in. - * - * @param basepath A folder path to look for classes. - */ - public void setBasepath(@Nullable final String basepath) { - this.basepath = basepath; - } - - @Nullable - public String getBasepath() { - return basepath; - } - - @Nullable - public CompileException getException() { - return exception; - } -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/util/Function.java b/src/main/java/org/bbottema/javareflection/util/Function.java deleted file mode 100644 index e7a9ac2..0000000 --- a/src/main/java/org/bbottema/javareflection/util/Function.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.bbottema.javareflection.util; - -import static org.bbottema.javareflection.util.MiscUtil.trustedCast; - -public interface Function { - T apply(F value); - - class Functions { - - private static final Function IDENTITY_FUNCTION = new Function() { - @Override - public Object apply(Object value) { - return value; - } - }; - - private static final Function TOSTRING_FUNCTION = new Function() { - @Override - public String apply(Object value) { - return value.toString(); - } - }; - - public static Function identity() { - return trustedCast(IDENTITY_FUNCTION); - } - - public static Function simpleToString() { - return trustedCast(TOSTRING_FUNCTION); - } - } -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/util/MiscUtil.java b/src/main/java/org/bbottema/javareflection/util/MiscUtil.java deleted file mode 100644 index 1ac7b86..0000000 --- a/src/main/java/org/bbottema/javareflection/util/MiscUtil.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.bbottema.javareflection.util; - -import lombok.experimental.UtilityClass; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import static java.lang.String.format; -import static java.util.Objects.requireNonNull; - -@UtilityClass -public final class MiscUtil { - - @SafeVarargs - @SuppressWarnings("varargs") - public static List newArrayList(T... o) { - ArrayList l = new ArrayList<>(); - Collections.addAll(l, o); - return l; - } - - @SuppressWarnings("unchecked") - @NotNull - public static T trustedCast(Object o) { - return (T) o; - } - - @SuppressWarnings("unchecked") - @Nullable - public static T trustedNullableCast(@Nullable Object o) { - return (T) o; - } - - @SuppressWarnings({"unchecked", "WeakerAccess"}) - @NotNull - public static T requireNonNullOfType(Object o, Class type) { - if (requireNonNull(o).getClass() != type) { - throw new AssertionError(format("got type %s, expected type %s", o.getClass(), type)); - } - return (T) o; - } -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/util/commonslang25/NumberUtils.java b/src/main/java/org/bbottema/javareflection/util/commonslang25/NumberUtils.java deleted file mode 100644 index 6ec110d..0000000 --- a/src/main/java/org/bbottema/javareflection/util/commonslang25/NumberUtils.java +++ /dev/null @@ -1,130 +0,0 @@ -package org.bbottema.javareflection.util.commonslang25; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - -@SuppressFBWarnings(justification = "imported code as-is from Apache Commons Lang 2.5") -public class NumberUtils { - private NumberUtils() { - } - - /** - *

Checks whether the String a valid Java number.

- * - *

Valid numbers include hexadecimal marked with the 0x - * qualifier, scientific notation and numbers marked with a type - * qualifier (e.g. 123L).

- * - *

Null and empty String will return - * false.

- * - * @param str the String to check - * @return true if the string is a correctly formatted number - */ - public static boolean isNumber(String str) { - if (isEmpty(str)) { - return false; - } - char[] chars = str.toCharArray(); - int sz = chars.length; - boolean hasExp = false; - boolean hasDecPoint = false; - boolean allowSigns = false; - boolean foundDigit = false; - // deal with any possible sign up front - int start = (chars[0] == '-') ? 1 : 0; - if (sz > start + 1) { - if (chars[start] == '0' && chars[start + 1] == 'x') { - int i = start + 2; - if (i == sz) { - return false; // str == "0x" - } - // checking hex (it can't be anything else) - for (; i < chars.length; i++) { - if ((chars[i] < '0' || chars[i] > '9') - && (chars[i] < 'a' || chars[i] > 'f') - && (chars[i] < 'A' || chars[i] > 'F')) { - return false; - } - } - return true; - } - } - sz--; // don't want to loop to the last char, check it afterwords - // for type qualifiers - int i = start; - // loop to the next to last char or to the last char if we need another digit to - // make a valid number (e.g. chars[0..5] = "1234E") - while (i < sz || (i < sz + 1 && allowSigns && !foundDigit)) { - if (chars[i] >= '0' && chars[i] <= '9') { - foundDigit = true; - allowSigns = false; - - } else if (chars[i] == '.') { - if (hasDecPoint || hasExp) { - // two decimal points or dec in exponent - return false; - } - hasDecPoint = true; - } else if (chars[i] == 'e' || chars[i] == 'E') { - // we've already taken care of hex. - if (hasExp) { - // two E's - return false; - } - if (!foundDigit) { - return false; - } - hasExp = true; - allowSigns = true; - } else if (chars[i] == '+' || chars[i] == '-') { - if (!allowSigns) { - return false; - } - allowSigns = false; - foundDigit = false; // we need a digit after the E - } else { - return false; - } - i++; - } - if (i < chars.length) { - if (chars[i] >= '0' && chars[i] <= '9') { - // no type qualifier, OK - return true; - } - if (chars[i] == 'e' || chars[i] == 'E') { - // can't have an E at the last byte - return false; - } - if (chars[i] == '.') { - if (hasDecPoint || hasExp) { - // two decimal points or dec in exponent - return false; - } - // single trailing decimal point after non-exponent is ok - return foundDigit; - } - if (!allowSigns - && (chars[i] == 'd' - || chars[i] == 'D' - || chars[i] == 'f' - || chars[i] == 'F')) { - return foundDigit; - } - if (chars[i] == 'l' - || chars[i] == 'L') { - // not allowing L with an exponent - return foundDigit && !hasExp; - } - // last character is illegal - return false; - } - // allowSigns is true iff the val ends in 'E' - // found digit it to make sure weird stuff like '.' and '1E-' doesn't pass - return !allowSigns && foundDigit; - } - - private static boolean isEmpty(String str) { - return str == null || str.length() == 0; - } -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/util/commonslang25/StringUtils.java b/src/main/java/org/bbottema/javareflection/util/commonslang25/StringUtils.java deleted file mode 100644 index 63b1a85..0000000 --- a/src/main/java/org/bbottema/javareflection/util/commonslang25/StringUtils.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.bbottema.javareflection.util.commonslang25; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - -@SuppressFBWarnings(justification = "imported code as-is from Apache Commons Lang 2.5") -public class StringUtils { - private StringUtils() { - } - - /** - *

Capitalizes a String changing the first letter to title case as - * per {@link Character#toTitleCase(char)}. No other letters are changed.

- * - *

For a word based algorithm, see WordUtils#capitalize(String). - * A null input String returns null.

- * - *
-	 * StringUtils.capitalize(null)  = null
-	 * StringUtils.capitalize("")    = ""
-	 * StringUtils.capitalize("cat") = "Cat"
-	 * StringUtils.capitalize("cAt") = "CAt"
-	 * 
- * - * @param str the String to capitalize, may be null - * - * @return the capitalized String, null if null String input - * @since 2.0 - */ - public static String capitalize(String str) { - if (str == null || str.length() == 0) { - return str; - } - return Character.toTitleCase(str.charAt(0)) + str.substring(1); - } -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/util/graph/Dijkstra.java b/src/main/java/org/bbottema/javareflection/util/graph/Dijkstra.java deleted file mode 100644 index d2b38b4..0000000 --- a/src/main/java/org/bbottema/javareflection/util/graph/Dijkstra.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.bbottema.javareflection.util.graph; - -import java.util.HashSet; -import java.util.LinkedList; -import java.util.Map.Entry; -import java.util.Set; - -@SuppressWarnings("unused") -final class Dijkstra { - - private Dijkstra() { - } - - @SuppressWarnings("WeakerAccess") - public static void findShortestPathToAllOtherNodes(Node startingPoint) { - startingPoint.setCost(0); - - Set> settledNodes = new HashSet<>(); - Set> unsettledNodes = new HashSet<>(); - unsettledNodes.add(startingPoint); - - while (unsettledNodes.size() != 0) { - Node currentNode = getLowestDistanceNode(unsettledNodes); - unsettledNodes.remove(currentNode); - for (Entry, Integer> adjacencyPair : currentNode.getToNodes().entrySet()) { - Node adjacentNode = adjacencyPair.getKey(); - - if (!settledNodes.contains(adjacentNode)) { - Integer edgeWeight = adjacencyPair.getValue(); - calculateMinimumDistance(adjacentNode, edgeWeight, currentNode); - unsettledNodes.add(adjacentNode); - } - } - settledNodes.add(currentNode); - } - } - - private static void calculateMinimumDistance(Node evaluationNode, Integer edgeWeigh, Node sourceNode) { - if (sourceNode.getCost() + edgeWeigh < evaluationNode.getCost()) { - evaluationNode.setCost(sourceNode.getCost() + edgeWeigh); - LinkedList> shortestPath = new LinkedList<>(sourceNode.getLeastExpensivePath()); - shortestPath.add(sourceNode); - evaluationNode.setLeastExpensivePath(shortestPath); - } - } - - private static Node getLowestDistanceNode(Set> unsettledNodes) { - Node lowestDistanceNode = null; - int lowestDistance = Integer.MAX_VALUE; - for (Node node : unsettledNodes) { - if (node.getCost() < lowestDistance) { - lowestDistance = node.getCost(); - lowestDistanceNode = node; - } - } - return lowestDistanceNode; - } -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/util/graph/GraphHelper.java b/src/main/java/org/bbottema/javareflection/util/graph/GraphHelper.java deleted file mode 100644 index 9a1fab6..0000000 --- a/src/main/java/org/bbottema/javareflection/util/graph/GraphHelper.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.bbottema.javareflection.util.graph; - -import lombok.experimental.UtilityClass; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Deque; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -@UtilityClass -public final class GraphHelper { - - @SuppressWarnings("WeakerAccess") - public static boolean isPathPossible(Node startingPoint, Node destination) { - return findPossiblePaths(startingPoint, destination, new ArrayDeque>(), new ArrayList>>(), true, 4); - } - - @SuppressWarnings("WeakerAccess") - public static List>> findAllPathsAscending(Node startingPoint, Node destination) { - List>> allPaths = new ArrayList<>(); - findPossiblePaths(startingPoint, destination, new ArrayDeque>(), allPaths, false, 4); - Collections.sort(allPaths, NodePathComparator.INSTANCE()); // NodePathComparator needs the startingPoints included in the path - removeStartingPoints(allPaths); - return allPaths; - } - - private static void removeStartingPoints(List>> allPaths) { - for (List> path : allPaths) { - path.remove(0); - } - } - - @SuppressWarnings({"StatementWithEmptyBody"}) - private static boolean findPossiblePaths(Node currentNode, Node destination, Deque> currentPath, - List>> possiblePathsSoFar, boolean returnOnFirstPathFound, int cutOffEdgeCount) { - boolean foundAPath = false; - - if (!currentPath.contains(currentNode)) { - currentPath.addLast(currentNode); - - if (currentNode.equals(destination)) { - possiblePathsSoFar.add(new ArrayList<>(currentPath)); - foundAPath = true; - } else if (currentPath.size() <= cutOffEdgeCount) { - for (Node nextNode : currentNode.getToNodes().keySet()) { - foundAPath |= findPossiblePaths(nextNode, destination, currentPath, possiblePathsSoFar, returnOnFirstPathFound, cutOffEdgeCount); - if (foundAPath && returnOnFirstPathFound) { - break; - } - } - } - currentPath.removeLast(); - } else { - // cyclic path - } - - return foundAPath; - } - - @NotNull - public static Set> findReachableNodes(final Node fromNode) { - Set> reachableNodes = new HashSet<>(); - findReachableNodes(fromNode, reachableNodes); - reachableNodes.remove(fromNode); // in case of cyclic paths, remove fromNode - return reachableNodes; - } - - private static void findReachableNodes(final Node currentNode, final Set> reachableNodesSoFar) { - for (Node reachableNode : currentNode.getToNodes().keySet()) { - if (reachableNodesSoFar.add(reachableNode)) { - findReachableNodes(reachableNode, reachableNodesSoFar); - } - } - } -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/util/graph/Node.java b/src/main/java/org/bbottema/javareflection/util/graph/Node.java deleted file mode 100644 index 4fa2489..0000000 --- a/src/main/java/org/bbottema/javareflection/util/graph/Node.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.bbottema.javareflection.util.graph; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NonNull; -import lombok.ToString; - -import java.util.HashMap; -import java.util.LinkedList; -import java.util.Map; - -@Data -@EqualsAndHashCode(onlyExplicitlyIncluded = true) -@ToString(onlyExplicitlyIncluded = true) -@SuppressFBWarnings(justification = "Generated code") -public class Node { - @NonNull - @EqualsAndHashCode.Include - @ToString.Include - private final T type; - private final Map, Integer> toNodes = new HashMap<>(); - private LinkedList> leastExpensivePath = new LinkedList<>(); - private Integer cost = Integer.MAX_VALUE; -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/util/graph/NodePathComparator.java b/src/main/java/org/bbottema/javareflection/util/graph/NodePathComparator.java deleted file mode 100644 index 201db8c..0000000 --- a/src/main/java/org/bbottema/javareflection/util/graph/NodePathComparator.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.bbottema.javareflection.util.graph; - -import java.util.Comparator; -import java.util.List; - -import static org.bbottema.javareflection.util.MiscUtil.trustedCast; - -class NodePathComparator implements Comparator>> { - - private static final NodePathComparator INSTANCE = new NodePathComparator<>(); - - static NodePathComparator INSTANCE() { - return trustedCast(INSTANCE); - } - - private NodePathComparator() { - } - - @Override - public int compare(List> nodes1, List> nodes2) { - return sumNodes(nodes1).compareTo(sumNodes(nodes2)); - } - - /** - * Since given list of nodes represents a connected path, each subsequent nodes will exist in each previous node' toNodes collection. - *

- * We need to calculate the distance this way, since Dijkstra's algorithm mutates the distance property for a single shortes path to a - * starting node. - */ - private Integer sumNodes(List> nodes) { - Node currentFromNode = null; - int nodesCost = 0; - for (Node nodeInPath : nodes) { - if (currentFromNode != null) { - nodesCost += currentFromNode.getToNodes().get(nodeInPath); - } - currentFromNode = nodeInPath; - } - return nodesCost; - } -} diff --git a/src/main/java/org/bbottema/javareflection/valueconverter/IncompatibleTypeException.java b/src/main/java/org/bbottema/javareflection/valueconverter/IncompatibleTypeException.java deleted file mode 100644 index 778d5e8..0000000 --- a/src/main/java/org/bbottema/javareflection/valueconverter/IncompatibleTypeException.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.bbottema.javareflection.valueconverter; - -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; - -import static java.lang.String.format; - -/** - * This exception can be thrown in any of the conversion methods of {@link ValueConversionHelper}, to indicate a value could not be converted into the - * target datatype. It doesn't mean a failed attempt at a conversion, it means that there was no way to convert the input value to begin with. - */ -@SuppressWarnings("serial") -public final class IncompatibleTypeException extends RuntimeException { - private final List causes = new ArrayList<>(); - - public IncompatibleTypeException(Object value, Class fromType, Class targetType) { - this(value, fromType, targetType, (Throwable) null); - } - - public IncompatibleTypeException(Object value, Class fromType, Class targetType, @Nullable Throwable cause) { - super(format("error: unable to convert value '%s': '%s' to '%s'", value, fromType, targetType), cause); - } - - public IncompatibleTypeException(Object value, Class fromType, Class targetType, List causes) { - this(value, fromType, targetType, (Throwable) null); - this.causes.addAll(causes); - } - - public List getCauses() { - return causes; - } -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/valueconverter/ValueConversionHelper.java b/src/main/java/org/bbottema/javareflection/valueconverter/ValueConversionHelper.java deleted file mode 100644 index 5d0b567..0000000 --- a/src/main/java/org/bbottema/javareflection/valueconverter/ValueConversionHelper.java +++ /dev/null @@ -1,341 +0,0 @@ -package org.bbottema.javareflection.valueconverter; - -import lombok.experimental.UtilityClass; -import org.bbottema.javareflection.LookupCaches; -import org.bbottema.javareflection.util.MiscUtil; -import org.bbottema.javareflection.util.graph.GraphHelper; -import org.bbottema.javareflection.util.graph.Node; -import org.bbottema.javareflection.valueconverter.converters.BooleanConverters; -import org.bbottema.javareflection.valueconverter.converters.CharacterConverters; -import org.bbottema.javareflection.valueconverter.converters.FileConverters; -import org.bbottema.javareflection.valueconverter.converters.NumberConverters; -import org.bbottema.javareflection.valueconverter.converters.StringConverters; -import org.bbottema.javareflection.valueconverter.converters.UUIDConverters; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static java.util.Arrays.asList; -import static org.bbottema.javareflection.util.MiscUtil.trustedCast; - -/** - * This reflection utility class predicts (and converts) which types a specified value can be converted into. It can only do conversions of - * known 'common' types, which include: - *

    - *
  • Any {@link Number} type (Integer, Character, Double, byte, etc.)
  • - *
  • String
  • - *
  • Boolean
  • - *
  • Character
  • - *
- * In addition enums can be converted as well. - * - * @see IncompatibleTypeException - */ -@UtilityClass -public final class ValueConversionHelper { - - /** - * A list of all primitive number types. - */ - private static final List> PRIMITIVE_NUMBER_TYPES = asList(new Class[] { byte.class, short.class, int.class, long.class, - float.class, double.class }); - - /** - * Contains all user-provided converters. User converters also act as intermediate converters, ie. if a user converter can go to int, - * double is automatically supported as well as common conversion. - */ - // TODO make value converters name based instead of type based for lookups and naming in the conversion graph - // TODO once working by name, replace Map> to Map>>. - // The above enables us to have multiple converters for the same targetType - private static final Map, Map, ValueFunction>> valueConverters = new HashMap<>(); - - /** - * Graph of from-to type conversions so we can calculate shortes conversion path between two types. - */ - private static final Map, Node>> converterGraph = new HashMap<>(); - - private static final int LOW_CONVERTER_PRIORITY = 10; // higher edge weight, heavier in cost - private static final int HIGH_CONVERTER_PRIORITY = 1; // lower edge weight, lighter in cost - - static { - resetDefaultConverters(); - } - - public static void resetDefaultConverters() { - valueConverters.clear(); - - final Collection> defaultConverters = new HashSet<>(); - defaultConverters.addAll(NumberConverters.NUMBER_CONVERTERS); - defaultConverters.addAll(BooleanConverters.BOOLEAN_CONVERTERS); - defaultConverters.addAll(CharacterConverters.CHARACTER_CONVERTERS); - defaultConverters.addAll(StringConverters.STRING_CONVERTERS); - defaultConverters.addAll(UUIDConverters.UUID_CONVERTERS); - defaultConverters.addAll(FileConverters.FILE_CONVERTERS); - - for (ValueFunction numberConverter : defaultConverters) { - registerValueConverter(numberConverter); - } - } - - /** - * Registers a user-provided converter. User converters also act as intermediate converters, ie. if a user converter can go to int, - * double is automatically supported as well as common conversion. - */ - @SuppressWarnings({"unused", "WeakerAccess"}) - public static void registerValueConverter(final ValueFunction userConverter) { - if (!valueConverters.containsKey(userConverter.getFromType())) { - valueConverters.put(userConverter.getFromType(), new HashMap, ValueFunction>()); - } - valueConverters.get(userConverter.getFromType()).put(userConverter.getTargetType(), - MiscUtil.>trustedCast(userConverter)); - - updateTypeGraph(); - LookupCaches.resetCache(); - } - - private static void updateTypeGraph() { - converterGraph.clear(); - - // add nodes and edges - for (Map.Entry, Map, ValueFunction>> convertersForFromType : valueConverters.entrySet()) { - Class fromType = convertersForFromType.getKey(); - Node> fromNode = converterGraph.containsKey(fromType) ? converterGraph.get(fromType) : new Node>(fromType); - converterGraph.put(fromType, fromNode); - for (Class toType : convertersForFromType.getValue().keySet()) { - Node> toNode = converterGraph.containsKey(toType) ? converterGraph.get(toType) : new Node>(toType); - converterGraph.put(toType, toNode); - ValueFunction converter = convertersForFromType.getValue().get(toType); - fromNode.getToNodes().put(toNode, determineConversionCost(converter)); // edge - } - } - } - - private static Integer determineConversionCost(ValueFunction converter) { - if (converter.getFromType() == converter.getTargetType()) { - return 0; - } else { - String converterPackage = ValueConversionHelper.class.getPackage().toString(); - boolean isSystemConverter = converter.getClass().getPackage().toString().contains(converterPackage); - return isSystemConverter ? LOW_CONVERTER_PRIORITY : HIGH_CONVERTER_PRIORITY; - } - } - - @SuppressWarnings("WeakerAccess") - public static boolean isCommonType(final Class c) { - Map, ValueFunction> classValueFunctionMap = valueConverters.get(c); - return valueConverters.containsKey(c) && - (classValueFunctionMap.keySet().size() > 1 || - !classValueFunctionMap.keySet().contains(String.class)); - } - - /** - * Determines to which types the specified value (its type) can be converted to. Most common types can be converted to most other common - * types and all types can be converted into a String using {@link Object#toString()}. - * - * @param fromType The input type to find compatible conversion output types for - * @return The list with compatible conversion output types. - */ - @SuppressWarnings("WeakerAccess") - @NotNull - public static Set> collectRegisteredCompatibleTargetTypes(final Class fromType) { - Set> compatibleTypes = new HashSet<>(Collections.>singleton(fromType)); - if (converterGraph.containsKey(fromType)) { - for (Node> reachableNode : GraphHelper.findReachableNodes(converterGraph.get(fromType))) { - compatibleTypes.add(reachableNode.getType()); - } - } - return compatibleTypes; - } - - /** - * @return Whether targetType can be derived from fromType. - */ - @SuppressWarnings("unused") - public static boolean typesCompatible(final Class fromType, final Class targetType) { - if (targetType.isAssignableFrom(fromType)) { - return true; - } else { - for (Class registeredCompatibleTargetType : collectCompatibleTargetTypes(fromType)) { - if (targetType.isAssignableFrom(registeredCompatibleTargetType)) { - return true; - } - } - } - return false; - } - - public static Set> collectCompatibleTargetTypes(Class fromType) { - checkForAndRegisterToStringConverter(fromType); - - Set> compatibleTargetTypes = new HashSet<>(); - Node> fromNode = converterGraph.get(fromType); - for (Map, ValueFunction> convertersForFromTypes : valueConverters.values()) { - for (Class targetType : convertersForFromTypes.keySet()) { - if (isCompatibleTargetType(fromNode, targetType)) { - compatibleTargetTypes.add(targetType); - } - } - } - return compatibleTargetTypes; - } - - private static boolean isCompatibleTargetType(Node> fromNode, Class targetType) { - for (Node> toNode : collectTypeCompatibleNodes(targetType)) { - if (GraphHelper.isPathPossible(fromNode, toNode)) { - return true; - } - } - return false; - } - - /** - * Converts a list of values to their converted form, as indicated by the specified targetTypes. - * - * @param args The list with value to convert. - * @param targetTypes The output types the specified values should be converted into. - * @param useOriginalValueWhenIncompatible Indicates whether an exception should be thrown for inconvertible values or that the original - * value should be used instead. Basically change mode to "convert what you can". - * @return Array containing converted values where convertible or the original value otherwise. - * @throws IncompatibleTypeException Thrown when unable to convert and not use the original value. - */ - @NotNull - public static Object[] convert(final Object[] args, final Class[] targetTypes, boolean useOriginalValueWhenIncompatible) - throws IncompatibleTypeException { - if (args.length != targetTypes.length) { - throw new IllegalStateException("number of target types should match the number of arguments"); - } - final Object[] convertedValues = new Object[args.length]; - for (int i = 0; i < targetTypes.length; i++) { - try { - convertedValues[i] = convert(args[i], targetTypes[i]); - } catch (IncompatibleTypeException e) { - if (useOriginalValueWhenIncompatible) { - // simply take over the original value and keep converting where possible - convertedValues[i] = args[i]; - } else { - throw e; - } - } - } - return convertedValues; - } - - /** - * Converts a single value into a target output datatype. Only input/output pairs should be passed in here according to the possible - * conversions as determined by {@link #collectRegisteredCompatibleTargetTypes(Class)}.
- *
- * First checks if the input and output types aren't the same. Then the conversions are checked for and done in the following order: - *
    - *
  1. conversion to String (value.toString())
  2. - *
- * - * @param fromValue The value to convert. - * @param targetType The target data type the value should be converted into. - * @return The converted value according the specified target data type. - * @throws IncompatibleTypeException Thrown by the various convert() methods used. - */ - @SuppressWarnings("unchecked") - @Nullable - public static T convert(@Nullable final Object fromValue, final Class targetType) - throws IncompatibleTypeException { - if (fromValue == null) { - return null; - } else if (targetType.isAssignableFrom(fromValue.getClass())) { - return convertWithoutConversionGraph(fromValue, targetType); - } else { - checkForAndRegisterEnumConverter(targetType); - checkForAndRegisterToStringConverter(fromValue.getClass()); - return convertWithConversionGraph(fromValue, targetType); - } - } - - @NotNull - private static T convertWithoutConversionGraph(final Object fromValue, final Class targetType) { - if (valueConverters.containsKey(fromValue.getClass())) { - Map, ValueFunction> fromConverters = valueConverters.get(fromValue.getClass()); - if (fromConverters.containsKey(targetType)) { - try { - return trustedCast(fromConverters.get(targetType).convertValue(fromValue)); - } catch (IncompatibleTypeException e) { - // ignore - } - } - } - return trustedCast(fromValue); - } - - @SuppressWarnings("unchecked") - @NotNull - private static T convertWithConversionGraph(final Object fromValue, final Class targetType) { - final Node> fromNode = converterGraph.get(fromValue.getClass()); - - final List incompatibleTypeExceptions = new ArrayList<>(); - - if (fromNode != null) { - for (Node> toNode : collectTypeCompatibleNodes(targetType)) { - for (List>> conversionPathAscending : GraphHelper.findAllPathsAscending(fromNode, toNode)) { - try { - Object evolvingValueToConvert = fromValue; - for (Node> nodeInConversionPath : conversionPathAscending) { - Class currentFromType = evolvingValueToConvert.getClass(); - Class currentToType = nodeInConversionPath.getType(); - evolvingValueToConvert = valueConverters.get(currentFromType).get(currentToType).convertValue(evolvingValueToConvert); - } - return (T) evolvingValueToConvert; - } catch (IncompatibleTypeException e) { - incompatibleTypeExceptions.add(e); - // keep trying conversion paths... - } - } - } - } - - // conversion paths exhausted. - throw new IncompatibleTypeException(fromValue, fromValue.getClass(), targetType, incompatibleTypeExceptions); - } - - - @SuppressWarnings("unchecked") - private static > void checkForAndRegisterEnumConverter(Class targetType) { - if (Enum.class.isAssignableFrom(targetType)) { - if (!valueConverters.get(String.class).containsKey(targetType)) { - registerValueConverter(StringConverters.produceStringToEnumConverter((Class) targetType)); - } - } - } - - private static void checkForAndRegisterToStringConverter(Class fromType) { - if (!valueConverters.containsKey(fromType) || !valueConverters.get(fromType).containsKey(String.class)) { - registerValueConverter(StringConverters.produceTypeToStringConverter(fromType)); - } - } - - static Set>> collectTypeCompatibleNodes(Class targetType) { - final Set>> typeCompatibleNodes = new HashSet<>(); - for (Map.Entry, Node>> converterNodeEntry : converterGraph.entrySet()) { - if (targetType.isAssignableFrom(converterNodeEntry.getKey())) { - typeCompatibleNodes.add(converterNodeEntry.getValue()); - } - } - return typeCompatibleNodes; - } - - /** - * Returns whether a {@link Class} is a primitive number. - * - * @param targetType The class to check whether it's a number. - * @return Whether specified class is a primitive number. - */ - @SuppressWarnings("WeakerAccess") - public static boolean isPrimitiveNumber(final Class targetType) { - return PRIMITIVE_NUMBER_TYPES.contains(targetType); - } -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/valueconverter/ValueFunction.java b/src/main/java/org/bbottema/javareflection/valueconverter/ValueFunction.java deleted file mode 100644 index 031dff0..0000000 --- a/src/main/java/org/bbottema/javareflection/valueconverter/ValueFunction.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.bbottema.javareflection.valueconverter; - -import lombok.Getter; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import lombok.ToString; -import org.bbottema.javareflection.util.Function; - -/** - * Can be used to provide optional user converters. User converters also act as intermediate converters, ie. if a user converter can go to - * int, double is automatically supported as well as common conversion. - */ -public interface ValueFunction { - @NonNull Class getFromType(); - @NonNull Class getTargetType(); - @NonNull T convertValue(@NonNull F value) throws IncompatibleTypeException; - - /** - * Helper class to quickly define a {@link ValueFunction} from a {@link Function}. - */ - @Getter - @RequiredArgsConstructor - @ToString(onlyExplicitlyIncluded = true) - class ValueFunctionImpl implements ValueFunction { - @NonNull @ToString.Include protected final Class fromType; - @NonNull @ToString.Include protected final Class targetType; - @NonNull private final Function converter; - @NonNull @Override - public final T convertValue(@NonNull F value) throws IncompatibleTypeException { - return converter.apply(value); - } - } -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/valueconverter/converters/BooleanConverters.java b/src/main/java/org/bbottema/javareflection/valueconverter/converters/BooleanConverters.java deleted file mode 100644 index 6a6fdf4..0000000 --- a/src/main/java/org/bbottema/javareflection/valueconverter/converters/BooleanConverters.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.bbottema.javareflection.valueconverter.converters; - -import lombok.experimental.UtilityClass; -import org.bbottema.javareflection.util.Function; -import org.bbottema.javareflection.util.Function.Functions; -import org.bbottema.javareflection.valueconverter.ValueFunction; -import org.bbottema.javareflection.valueconverter.ValueFunction.ValueFunctionImpl; - -import java.util.ArrayList; -import java.util.Collection; - -/** - * Attempts to convert a Boolean to the target datatype. - *

- * Conversions are as follows: - *

    - *
  1. Boolean (or boolean): value
  2. - *
  3. String: value.toString()
  4. - *
  5. Number (or primitive number): 0 or 1
  6. - *
- */ -@UtilityClass -public final class BooleanConverters { - - public static final Collection> BOOLEAN_CONVERTERS = produceBooleanConverters(); - - private static Collection> produceBooleanConverters() { - ArrayList> converters = new ArrayList<>(); - converters.add(new ValueFunctionImpl<>(boolean.class, Boolean.class, Functions.identity())); - converters.add(new ValueFunctionImpl<>(Boolean.class, boolean.class, Functions.identity())); - converters.add(new ValueFunctionImpl<>(boolean.class, boolean.class, Functions.identity())); - converters.add(new ValueFunctionImpl<>(Boolean.class, Boolean.class, Functions.identity())); - - converters.add(new ValueFunctionImpl<>(Boolean.class, String.class, Functions.simpleToString())); - converters.add(new ValueFunctionImpl<>(Boolean.class, Number.class, new BooleanToNumberFunction())); - return converters; - } - - private static class BooleanToNumberFunction implements Function { - @Override - public Number apply(Boolean value) { - return value ? 1 : 0; - } - } -} diff --git a/src/main/java/org/bbottema/javareflection/valueconverter/converters/CharacterConverters.java b/src/main/java/org/bbottema/javareflection/valueconverter/converters/CharacterConverters.java deleted file mode 100644 index 08da7b0..0000000 --- a/src/main/java/org/bbottema/javareflection/valueconverter/converters/CharacterConverters.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.bbottema.javareflection.valueconverter.converters; - -import lombok.experimental.UtilityClass; -import org.bbottema.javareflection.util.Function; -import org.bbottema.javareflection.util.Function.Functions; -import org.bbottema.javareflection.valueconverter.ValueFunction; -import org.bbottema.javareflection.valueconverter.ValueFunction.ValueFunctionImpl; - -import java.util.ArrayList; -import java.util.Collection; - -/** - * Attempts to convert a Character to the target datatype. - *

- * Conversions are as follows: - *

    - *
  1. String: value.toString()
  2. - *
  3. Character (or primitive character): value
  4. - *
  5. Number (or primitive number): Deferred to ({@link Character#getNumericValue(char)}) or cast to {@code int} if not in number - * 0-9 range.
  6. - *
  7. Boolean (or boolean): !value.equals('0')
  8. - *
- */ -@UtilityClass -public final class CharacterConverters { - - public static final Collection> CHARACTER_CONVERTERS = produceCharacterConverters(); - - private static Collection> produceCharacterConverters() { - ArrayList> converters = new ArrayList<>(); - converters.add(new ValueFunctionImpl<>(char.class, Character.class, Functions.identity())); - converters.add(new ValueFunctionImpl<>(Character.class, char.class, Functions.identity())); - converters.add(new ValueFunctionImpl<>(char.class, char.class, Functions.identity())); - converters.add(new ValueFunctionImpl<>(Character.class, Character.class, Functions.identity())); - converters.add(new ValueFunctionImpl<>(Character.class, String.class, Functions.simpleToString())); - converters.add(new ValueFunctionImpl<>(Character.class, Number.class, new CharacterToNumberFunction())); - converters.add(new ValueFunctionImpl<>(Character.class, Boolean.class, new CharacterToBooleanFunction())); - return converters; - } - - private static class CharacterToNumberFunction implements Function { - @Override - public Number apply(Character value) { - int numericValue = Character.getNumericValue(value); - return numericValue == -1 || numericValue > 9 - ? (int) value - : numericValue; - } - } - - private static class CharacterToBooleanFunction implements Function { - @Override - public Boolean apply(Character value) { - return !value.equals('0'); - } - } -} diff --git a/src/main/java/org/bbottema/javareflection/valueconverter/converters/FileConverters.java b/src/main/java/org/bbottema/javareflection/valueconverter/converters/FileConverters.java deleted file mode 100644 index 02f8855..0000000 --- a/src/main/java/org/bbottema/javareflection/valueconverter/converters/FileConverters.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.bbottema.javareflection.valueconverter.converters; - -import lombok.experimental.UtilityClass; -import org.bbottema.javareflection.util.Function; -import org.bbottema.javareflection.util.Function.Functions; -import org.bbottema.javareflection.valueconverter.ValueFunction; -import org.bbottema.javareflection.valueconverter.ValueFunction.ValueFunctionImpl; -import org.jetbrains.annotations.Nullable; - -import jakarta.activation.DataSource; -import jakarta.activation.FileDataSource; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.Collection; - -@Nullable -@UtilityClass -public final class FileConverters { - - public static final Collection> FILE_CONVERTERS = produceFileConverters(); - - private static Collection> produceFileConverters() { - ArrayList> converters = new ArrayList<>(); - converters.add(new ValueFunctionImpl<>(File.class, File.class, Functions.identity())); - converters.add(new ValueFunctionImpl<>(File.class, DataSource.class, new FileToDataSourceFunction())); - converters.add(new ValueFunctionImpl<>(File.class, byte[].class, new FileToByteArrayFunction())); - converters.add(new ValueFunctionImpl<>(File.class, InputStream.class, new FileToInputStreamFunction())); - return converters; - } - - private static class FileToDataSourceFunction implements Function { - @Override - public DataSource apply(File file) { - return new FileDataSource(file); - } - } - - private static class FileToByteArrayFunction implements Function { - @Override - public byte[] apply(File file) { - try { - return Files.readAllBytes(file.toPath()); - } catch (IOException e) { - throw new RuntimeException("Was unable to read file content", e); - } - } - } - - private static class FileToInputStreamFunction implements Function { - @Override - public InputStream apply(File file) { - try { - return new FileInputStream(file); - } catch (FileNotFoundException e) { - throw new AssertionError("File found, but also not found? Is this the real life...", e); - } - } - } -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/valueconverter/converters/NumberConverters.java b/src/main/java/org/bbottema/javareflection/valueconverter/converters/NumberConverters.java deleted file mode 100644 index 4da7d86..0000000 --- a/src/main/java/org/bbottema/javareflection/valueconverter/converters/NumberConverters.java +++ /dev/null @@ -1,132 +0,0 @@ -package org.bbottema.javareflection.valueconverter.converters; - -import lombok.experimental.UtilityClass; -import org.bbottema.javareflection.util.Function; -import org.bbottema.javareflection.util.Function.Functions; -import org.bbottema.javareflection.valueconverter.ValueFunction; -import org.bbottema.javareflection.valueconverter.ValueFunction.ValueFunctionImpl; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; - -import static org.bbottema.javareflection.util.MiscUtil.newArrayList; - -/** - * Generates converters for all numbers to all other numbers, by virtue of Number's own interface that forces all subclasses to implement basic - * conversions to common other Number classes. - *

- * Attempts to convert a {@link Number} to the target datatype. - *

- * NOTE: precision may be lost when converting from a wide number to a narrower number (say float to integer). These - * conversions are done by simply calling {@link Number#intValue()} and {@link Number#floatValue()} etc. - *

- * Conversions are as follows: - *

    - *
  1. String: value.toString()
  2. - *
  3. Integer: value.intValue()
  4. - *
  5. Boolean: value.intValue() > 0
  6. - *
  7. Float: value.floatValue()
  8. - *
  9. Double: value.doubleValue()
  10. - *
  11. Long: value.longValue()
  12. - *
  13. Byte: value.byteValue()
  14. - *
  15. Short: value.shortValue()
  16. - *
  17. Character: Character.forDigit(value, 10) ({@link Character#forDigit(int, int)})
  18. - *
- */ -@UtilityClass -public class NumberConverters { - - private static final List> CONVERTABLE_NUMBER_FROM_CLASSES_JDK7 = newArrayList(Number.class, AtomicInteger.class, AtomicLong.class, BigDecimal.class, BigInteger.class, byte.class, Byte.class, double.class, Double.class, float.class, Float.class, int.class, Integer.class, long.class, Long.class, short.class, Short.class); - private static final Map, Function> CONVERTERS_BY_TARGET_TYPE = new HashMap, Function>() {{ - put(Number.class, new NumberDoubleFunction()); - put(Integer.class, new NumberIntegerFunction()); - put(int.class, new NumberIntegerFunction()); - put(Boolean.class, new NumberBooleanFunction()); - put(boolean.class, new NumberBooleanFunction()); - put(Float.class, new NumberFloatFunction()); - put(float.class, new NumberFloatFunction()); - put(Double.class, new NumberDoubleFunction()); - put(double.class, new NumberDoubleFunction()); - put(Long.class, new NumberLongFunction()); - put(long.class, new NumberLongFunction()); - put(Byte.class, new NumberByteFunction()); - put(byte.class, new NumberByteFunction()); - put(Short.class, new NumberShortFunction()); - put(short.class, new NumberShortFunction()); - put(Character.class, new NumberCharacterFunction()); - put(char.class, new NumberCharacterFunction()); - put(String.class, Functions.simpleToString()); - }}; - - public static final Collection> NUMBER_CONVERTERS = produceNumberConverters(); - - @SuppressWarnings("unchecked") - private static Collection> produceNumberConverters() { - ArrayList> valueFunctions = new ArrayList<>(); - for (Class numberFromClass : CONVERTABLE_NUMBER_FROM_CLASSES_JDK7) { - for (Map.Entry, Function> targetClassConverter : CONVERTERS_BY_TARGET_TYPE.entrySet()) { - Class targetClass = targetClassConverter.getKey(); - Function converter = (numberFromClass == targetClass || targetClass.isAssignableFrom(numberFromClass)) - ? Functions.identity() - : targetClassConverter.getValue(); - valueFunctions.add(new ValueFunctionImpl(numberFromClass, targetClass, converter)); - } - } - return valueFunctions; - } - - public static class NumberIntegerFunction implements Function { - public Integer apply(Number value) { - return value.intValue(); - } - } - - public static class NumberBooleanFunction implements Function { - public Boolean apply(Number value) { - return value.intValue() > 0; - } - } - - public static class NumberFloatFunction implements Function { - public Float apply(Number value) { - return value.floatValue(); - } - } - - public static class NumberDoubleFunction implements Function { - public Double apply(Number value) { - return value.doubleValue(); - } - } - - public static class NumberLongFunction implements Function { - public Long apply(Number value) { - return value.longValue(); - } - } - - public static class NumberByteFunction implements Function { - public Byte apply(Number value) { - return value.byteValue(); - } - } - - public static class NumberShortFunction implements Function { - public Short apply(Number value) { - return value.shortValue(); - } - } - - public static class NumberCharacterFunction implements Function { - public Character apply(Number value) { - return Character.forDigit(value.intValue(), 10); - } - } -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/valueconverter/converters/StringConverters.java b/src/main/java/org/bbottema/javareflection/valueconverter/converters/StringConverters.java deleted file mode 100644 index 3b93d5a..0000000 --- a/src/main/java/org/bbottema/javareflection/valueconverter/converters/StringConverters.java +++ /dev/null @@ -1,259 +0,0 @@ -package org.bbottema.javareflection.valueconverter.converters; - -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import lombok.experimental.UtilityClass; -import org.bbottema.javareflection.util.Function; -import org.bbottema.javareflection.util.Function.Functions; -import org.bbottema.javareflection.util.commonslang25.NumberUtils; -import org.bbottema.javareflection.valueconverter.IncompatibleTypeException; -import org.bbottema.javareflection.valueconverter.ValueFunction; -import org.bbottema.javareflection.valueconverter.ValueFunction.ValueFunctionImpl; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; - -import java.io.File; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.UUID; - -import static org.slf4j.LoggerFactory.getLogger; - -/** - * Conversions are as follows: - * FIXME add non-Number conversions - *
    - *
  1. Integer (or primitive int): Integer.parseInt(value)
  2. - *
  3. Character: value.getCharAt(0)
  4. - *
  5. Boolean: value equals {@code "true"} or {@code "1"}
  6. - *
  7. Number: new BigDecimal(value) (simply attempt the widest number type)
  8. - *
  9. Byte (or primitive byte): Byte.parseByte(value)
  10. - *
  11. Short (or primitive short): Short.parseShort(value)
  12. - *
  13. Long (or primitive long): Long.parseLong(value)
  14. - *
  15. Float (or primitive float): Float.parseFloat(value)
  16. - *
  17. Double (or primitive double): Double.parseDouble(value)
  18. - *
  19. BigInteger: BigInteger.valueOf(Long.parseLong(value))
  20. - *
  21. BigDecimal: new BigDecimal(value)
  22. - *
  23. UUID: UUID.fromString(value)
  24. - *
- */ -@Nullable -@UtilityClass -public final class StringConverters { - - public static final Collection> STRING_CONVERTERS = produceStringConverters(); - - private static Collection> produceStringConverters() { - ArrayList> converters = new ArrayList<>(); - converters.add(new ValueFunctionImpl<>(String.class, String.class, Functions.identity())); - converters.add(new ValueFunctionImpl<>(String.class, Character.class, new StringToCharacterFunction())); - converters.add(new ValueFunctionImpl<>(String.class, Boolean.class, new StringToBooleanFunction())); - converters.add(new ValueFunctionImpl<>(String.class, Number.class, new StringToNumberFunction())); - converters.add(new ValueFunctionImpl<>(String.class, Byte.class, new StringToByteFunction())); - converters.add(new ValueFunctionImpl<>(String.class, Short.class, new StringToShortFunction())); - converters.add(new ValueFunctionImpl<>(String.class, Long.class, new StringToLongFunction())); - converters.add(new ValueFunctionImpl<>(String.class, Float.class, new StringToFloatFunction())); - converters.add(new ValueFunctionImpl<>(String.class, Double.class, new StringToDoubleFunction())); - converters.add(new ValueFunctionImpl<>(String.class, BigInteger.class, new StringToBigIntegerFunction())); - converters.add(new ValueFunctionImpl<>(String.class, BigDecimal.class, new StringToBigDecimalFunction())); - converters.add(new ValueFunctionImpl<>(String.class, File.class, new StringToFileFunction())); - converters.add(new ValueFunctionImpl<>(String.class, UUID.class, new StringToUUIDFunction())); - converters.add(new ValueFunctionImpl<>(String.class, Date.class, new StringToDateFunction())); - return converters; - } - - /** - * Creates a converter to convert a String to an Enum instance, by mapping to the enum's name using - * {@link Enum#valueOf(Class, String)}. - */ - public static > ValueFunction produceStringToEnumConverter(Class targetEnumClass) { - return new ValueFunctionImpl<>(String.class, targetEnumClass, new StringToEnumFunction<>(targetEnumClass)); - } - - public static ValueFunction produceTypeToStringConverter(Class fromType) { - return new ValueFunctionImpl<>(fromType, String.class, Functions.simpleToString()); - } - - @RequiredArgsConstructor - private static class StringToEnumFunction> implements Function { - @NonNull - private final Class targetEnumClass; - - @Override - public T apply(String value) { - // /CLOVER:OFF - try { - // /CLOVER:ON - return Enum.valueOf(targetEnumClass, value); - // /CLOVER:OFF - } catch (final IllegalArgumentException | SecurityException e) { - throw new IncompatibleTypeException(value, String.class, targetEnumClass, e); - } - // /CLOVER:ON - } - } - - private static class StringToCharacterFunction implements Function { - @Override - public Character apply(String value) { - if (value.length() == 1) { - return value.charAt(0); - } - throw new IncompatibleTypeException(value, String.class, Character.class); - } - } - - private static class StringToBooleanFunction implements Function { - @Override - public Boolean apply(String value) { - if (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("0")) { - return false; - } else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("1")) { - return true; - } - throw new IncompatibleTypeException(value, String.class, Boolean.class); - } - } - - private static class StringToNumberFunction implements Function { - @Override - public Number apply(String value) { - if (NumberUtils.isNumber(value)) { - return new BigDecimal(value); - } - throw new IncompatibleTypeException(value, String.class, Number.class); - } - } - - private static class StringToByteFunction implements Function { - @Override - public Byte apply(String value) { - if (NumberUtils.isNumber(value)) { - try { - return Byte.parseByte(value); - } catch (NumberFormatException e) { - throw new IncompatibleTypeException(value, String.class, Byte.class, e); - } - } - throw new IncompatibleTypeException(value, String.class, Byte.class); - } - } - - private static class StringToShortFunction implements Function { - @Override - public Short apply(String value) { - if (NumberUtils.isNumber(value)) { - try { - return Short.parseShort(value); - } catch (NumberFormatException e) { - throw new IncompatibleTypeException(value, String.class, Short.class, e); - } - } - throw new IncompatibleTypeException(value, String.class, Short.class); - } - } - - private static class StringToLongFunction implements Function { - @Override - public Long apply(String value) { - if (NumberUtils.isNumber(value)) { - return Long.parseLong(value); - } - throw new IncompatibleTypeException(value, String.class, Long.class); - } - } - - private static class StringToFloatFunction implements Function { - @Override - public Float apply(String value) { - if (NumberUtils.isNumber(value)) { - return Float.parseFloat(value); - } - throw new IncompatibleTypeException(value, String.class, Float.class); - } - } - - private static class StringToDoubleFunction implements Function { - @Override - public Double apply(String value) { - if (NumberUtils.isNumber(value)) { - return Double.parseDouble(value); - } - throw new IncompatibleTypeException(value, String.class, Double.class); - } - } - - private static class StringToBigIntegerFunction implements Function { - @Override - public BigInteger apply(String value) { - if (NumberUtils.isNumber(value)) { - return BigInteger.valueOf(Long.parseLong(value)); - } - throw new IncompatibleTypeException(value, String.class, BigInteger.class); - } - } - - private static class StringToBigDecimalFunction implements Function { - @Override - public BigDecimal apply(String value) { - if (NumberUtils.isNumber(value)) { - return new BigDecimal(value); - } - throw new IncompatibleTypeException(value, String.class, BigDecimal.class); - } - } - - private static class StringToFileFunction implements Function { - - private static final Logger LOGGER = getLogger(StringToBigDecimalFunction.class); - - @Override - public File apply(String value) { - File file = new File(value); - if (!file.exists()) { - LOGGER.debug("file not found for [" + value + "]"); - throw new IncompatibleTypeException(value, String.class, File.class); - } - return file; - } - } - - private static class StringToUUIDFunction implements Function { - @Override - public UUID apply(String value) { - try { - return UUID.fromString(value); - } catch (IllegalArgumentException e) { - throw new IncompatibleTypeException(value, String.class, UUID.class); - } - } - } - - static class StringToDateFunction implements Function { - - private static ThreadLocal DATETIME_FORMAT_SIMPLE = - new ThreadLocal() { public DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd"); } }; - private static ThreadLocal DATETIME_FORMAT_ISO8601 = - new ThreadLocal() { public DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm"); } }; - private IllegalArgumentException INCOMPATIBLE_EXCEPTION = new IllegalArgumentException("not compatible with yyyy-MM-dd or yyyy-MM-dd HH:mm"); - - @Override - public Date apply(String value) { - try { - return DATETIME_FORMAT_ISO8601.get().parse(value); - } catch (IllegalArgumentException | ParseException e1) { - try { - return DATETIME_FORMAT_SIMPLE.get().parse(value); - } catch (IllegalArgumentException | ParseException e2) { - throw new IncompatibleTypeException(value, String.class, Date.class, INCOMPATIBLE_EXCEPTION); - } - } - } - } -} \ No newline at end of file diff --git a/src/main/java/org/bbottema/javareflection/valueconverter/converters/UUIDConverters.java b/src/main/java/org/bbottema/javareflection/valueconverter/converters/UUIDConverters.java deleted file mode 100644 index d9b436a..0000000 --- a/src/main/java/org/bbottema/javareflection/valueconverter/converters/UUIDConverters.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.bbottema.javareflection.valueconverter.converters; - -import lombok.experimental.UtilityClass; -import org.bbottema.javareflection.util.Function; -import org.bbottema.javareflection.util.Function.Functions; -import org.bbottema.javareflection.valueconverter.ValueFunction; -import org.bbottema.javareflection.valueconverter.ValueFunction.ValueFunctionImpl; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.UUID; - -@Nullable -@UtilityClass -public final class UUIDConverters { - - public static final Collection> UUID_CONVERTERS = produceUUIDConverters(); - - private static Collection> produceUUIDConverters() { - ArrayList> converters = new ArrayList<>(); - converters.add(new ValueFunctionImpl<>(UUID.class, UUID.class, Functions.identity())); - converters.add(new ValueFunctionImpl<>(UUID.class, String.class, new UUIDToStringFunction())); - return converters; - } - - private static class UUIDToStringFunction implements Function { - @Override - public String apply(UUID value) { - return value.toString(); - } - } -} \ No newline at end of file diff --git a/src/test/java/org/bbottema/javareflection/BeanUtilsTest.java b/src/test/java/org/bbottema/javareflection/BeanUtilsTest.java deleted file mode 100644 index d7a4e8c..0000000 --- a/src/test/java/org/bbottema/javareflection/BeanUtilsTest.java +++ /dev/null @@ -1,476 +0,0 @@ -package org.bbottema.javareflection; - -import org.bbottema.javareflection.BeanUtils.BeanRestriction; -import org.bbottema.javareflection.BeanUtils.Visibility; -import org.bbottema.javareflection.model.FieldWrapper; -import org.jetbrains.annotations.Nullable; -import org.junit.jupiter.api.Test; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; - -import static java.util.EnumSet.allOf; -import static java.util.EnumSet.of; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - -@SuppressWarnings("javadoc") -public class BeanUtilsTest { - - @Test - public void testCollectFieldsInheritanceAndOnlyGetters() { - final Map, List> fields = BeanUtils.collectFields(FieldsTestClass.class, FieldsTestClassGrandparent.class, - of(Visibility.PROTECTED), of(BeanRestriction.YES_GETTER, BeanRestriction.NO_SETTER)); - assertThat(fields.keySet()).containsExactlyInAnyOrder( - FieldsTestClass.class, FieldsTestClassParent.class, FieldsTestClassGrandparent.class); - - List fieldWrappers = fields.get(FieldsTestClass.class); - assertThat(fieldWrappers).hasSize(1); - assertThat(fieldWrappers.get(0).getField().getName()).isEqualTo("field5"); - assertThat(fieldWrappers.get(0).getSetter()).isNull(); - assertThat(fieldWrappers.get(0).getGetter()).isNotNull(); - fieldWrappers = fields.get(FieldsTestClassParent.class); - assertThat(fieldWrappers).hasSize(2); - assertThat(fieldWrappers.get(0).getField().getName()).isEqualTo("field4"); - assertThat(fieldWrappers.get(0).getSetter()).isNull(); - assertThat(fieldWrappers.get(0).getGetter()).isNotNull(); - assertThat(fieldWrappers.get(1).getField().getName()).isEqualTo("fieldA"); - assertThat(fieldWrappers.get(1).getSetter()).isNull(); - assertThat(fieldWrappers.get(1).getGetter()).isNotNull(); - fieldWrappers = fields.get(FieldsTestClassGrandparent.class); - assertThat(fieldWrappers).hasSize(2); - assertThat(fieldWrappers.get(0).getField().getName()).isEqualTo("field1"); - assertThat(fieldWrappers.get(0).getSetter()).isNull(); - assertThat(fieldWrappers.get(0).getGetter()).isNotNull(); - assertThat(fieldWrappers.get(1).getField().getName()).isEqualTo("fieldA"); - assertThat(fieldWrappers.get(1).getSetter()).isNull(); - assertThat(fieldWrappers.get(1).getGetter()).isNotNull(); - } - - @Test - public void testIsBeanMethodVariousScenarios() throws NoSuchMethodException { - Method notABeanSetter = FieldsTestClass.class.getDeclaredMethod("notABeanSetter"); - assertThat(BeanUtils.isBeanMethod(notABeanSetter, FieldsTestClassGrandparent.class, allOf(Visibility.class))).isFalse(); - assertThat(BeanUtils.isBeanMethod(notABeanSetter, FieldsTestClassGrandparent.class, of(Visibility.PROTECTED))).isFalse(); - assertThat(BeanUtils.isBeanMethod(notABeanSetter, FieldsTestClassGrandparent.class, of(Visibility.DEFAULT))).isFalse(); - assertThat(BeanUtils.isBeanMethod(notABeanSetter, FieldsTestClassGrandparent.class, of(Visibility.PRIVATE))).isFalse(); - assertThat(BeanUtils.isBeanMethod(notABeanSetter, FieldsTestClassGrandparent.class, of(Visibility.PUBLIC))).isFalse(); - - Method notABeanGetter = FieldsTestClass.class.getDeclaredMethod("notABeanGetter"); - assertThat(BeanUtils.isBeanMethod(notABeanGetter, FieldsTestClassGrandparent.class, allOf(Visibility.class))).isFalse(); - assertThat(BeanUtils.isBeanMethod(notABeanGetter, FieldsTestClassGrandparent.class, of(Visibility.PROTECTED))).isFalse(); - assertThat(BeanUtils.isBeanMethod(notABeanGetter, FieldsTestClassGrandparent.class, of(Visibility.DEFAULT))).isFalse(); - assertThat(BeanUtils.isBeanMethod(notABeanGetter, FieldsTestClassGrandparent.class, of(Visibility.PRIVATE))).isFalse(); - assertThat(BeanUtils.isBeanMethod(notABeanGetter, FieldsTestClassGrandparent.class, of(Visibility.PUBLIC))).isFalse(); - - Method getField5 = FieldsTestClass.class.getDeclaredMethod("getField5"); - assertThat(BeanUtils.isBeanMethod(getField5, FieldsTestClassGrandparent.class, allOf(Visibility.class))).isTrue(); - assertThat(BeanUtils.isBeanMethod(getField5, FieldsTestClassGrandparent.class, of(Visibility.PROTECTED))).isTrue(); - assertThat(BeanUtils.isBeanMethod(getField5, FieldsTestClassGrandparent.class, of(Visibility.DEFAULT))).isFalse(); - assertThat(BeanUtils.isBeanMethod(getField5, FieldsTestClassGrandparent.class, of(Visibility.PRIVATE))).isFalse(); - assertThat(BeanUtils.isBeanMethod(getField5, FieldsTestClassGrandparent.class, of(Visibility.PUBLIC))).isFalse(); - - Method setField2 = FieldsTestClassGrandparent.class.getDeclaredMethod("setField2", Object.class); - assertThat(BeanUtils.isBeanMethod(setField2, FieldsTestClassGrandparent.class, allOf(Visibility.class))).isTrue(); - assertThat(BeanUtils.isBeanMethod(setField2, FieldsTestClassGrandparent.class, of(Visibility.PROTECTED))).isTrue(); - assertThat(BeanUtils.isBeanMethod(setField2, FieldsTestClassGrandparent.class, of(Visibility.DEFAULT))).isFalse(); - assertThat(BeanUtils.isBeanMethod(setField2, FieldsTestClassGrandparent.class, of(Visibility.PRIVATE))).isFalse(); - assertThat(BeanUtils.isBeanMethod(setField2, FieldsTestClassGrandparent.class, of(Visibility.PUBLIC))).isFalse(); - } - - @SuppressWarnings({"unused", "WeakerAccess"}) - public class FieldsTestClassGrandparent { - protected Object field1; // yes - protected Object field2; // no, has setter - protected Object fieldA; // yes - - public Object getField1() { - return field1; - } - - public Object getField2() { - return field2; - } - - public void setField2(final Object field2) { - this.field2 = field2; - } - - public Object getFieldA() { - return fieldA; - } - } - - @SuppressWarnings({"unused", "WeakerAccess"}) - public class FieldsTestClassParent extends FieldsTestClassGrandparent { - protected Object field2; // no, parent has setter - protected Object field3; // no, no getter - protected Object field4; // yes - protected Object fieldA; // yes - - @Override - public Object getField2() { - return field2; - } - - public Object getField4() { - return field4; - } - - @Override - public Object getFieldA() { - return fieldA; - } - } - - @SuppressWarnings({"unused", "WeakerAccess"}) - public class FieldsTestClass extends FieldsTestClassParent { - protected Object field5; // yes - public Object field6; // no, not protected - - public Object getField5() { - return field5; - } - - public void notABeanSetter() { } - public String notABeanGetter() { return "moo"; } - } - - @Test - public void testCollectFieldsSimpleButOnlySetter() { - final Map, List> fields = BeanUtils.collectFields(FieldsTestClassOnlySetter.class, - FieldsTestClassOnlySetter.class, allOf(Visibility.class), of(BeanRestriction.YES_SETTER)); - assertThat(fields.keySet()).containsExactly(FieldsTestClassOnlySetter.class); - final List fieldWrappers = fields.get(FieldsTestClassOnlySetter.class); - assertThat(fieldWrappers).hasSize(1); - assertThat(fieldWrappers.get(0).getField().getName()).isEqualTo("field1"); - assertThat(fieldWrappers.get(0).getGetter()).as("field1").isNull(); - assertThat(fieldWrappers.get(0).getSetter()).as("field1").isNotNull(); - } - - @SuppressWarnings({"unused", "WeakerAccess"}) - public class FieldsTestClassOnlySetter { - protected Object field1; // yes - - public void setField1(final Object field1) { - this.field1 = field1; - } - } - - @Test - public void testMeetVisibilityRequirements() - throws SecurityException, NoSuchFieldException { - Field field = FieldModifiers.class.getDeclaredField("_private"); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.PRIVATE))).isTrue(); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.PUBLIC))).isFalse(); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.DEFAULT))).isFalse(); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.PROTECTED))).isFalse(); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.PRIVATE, Visibility.PUBLIC))).isTrue(); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.PROTECTED, Visibility.DEFAULT))).isFalse(); - - field = FieldModifiers.class.getDeclaredField("_protected"); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.PRIVATE))).isFalse(); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.PUBLIC))).isFalse(); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.DEFAULT))).isFalse(); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.PROTECTED))).isTrue(); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.PRIVATE, Visibility.PUBLIC))).isFalse(); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.PROTECTED, Visibility.DEFAULT))).isTrue(); - - field = FieldModifiers.class.getDeclaredField("_public"); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.PRIVATE))).isFalse(); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.PUBLIC))).isTrue(); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.DEFAULT))).isFalse(); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.PROTECTED))).isFalse(); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.PRIVATE, Visibility.PUBLIC))).isTrue(); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.PROTECTED, Visibility.DEFAULT))).isFalse(); - - field = FieldModifiers.class.getDeclaredField("_default"); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.PRIVATE))).isFalse(); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.PUBLIC))).isFalse(); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.DEFAULT))).isTrue(); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.PROTECTED))).isFalse(); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.PRIVATE, Visibility.PUBLIC))).isFalse(); - assertThat(BeanUtils.meetsVisibilityRequirements(field, of(Visibility.PROTECTED, Visibility.DEFAULT))).isTrue(); - } - - @SuppressWarnings({"unused", "WeakerAccess"}) - private static class FieldModifiers { - Object _default; - private Object _private; - protected Object _protected; - public Object _public; - } - - @Test - public void testResolveBeanProperty() - throws SecurityException, NoSuchFieldException { - Field field = BeanFields.class.getDeclaredField("withGetter"); - assertThat(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_GETTER))).isNull(); - assertNotNullProperty(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_SETTER)), true, false); - assertNotNullProperty(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.YES_GETTER)), true, false); - assertThat(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.YES_SETTER))).isNull(); - assertThat(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_GETTER, BeanRestriction.NO_SETTER))).isNull(); - assertNotNullProperty(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.YES_GETTER, BeanRestriction.NO_SETTER)), - true, false); - assertThat(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_GETTER, BeanRestriction.YES_SETTER))).isNull(); - assertThat(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.YES_GETTER, BeanRestriction.YES_SETTER))).isNull(); - - field = BeanFields.class.getDeclaredField("withGetterAndSetter"); - assertThat(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_GETTER))).isNull(); - assertThat(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_SETTER))).isNull(); - assertNotNullProperty(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.YES_GETTER)), true, true); - assertNotNullProperty(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.YES_SETTER)), true, true); - assertThat(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_GETTER, BeanRestriction.NO_SETTER))).isNull(); - assertThat(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.YES_GETTER, BeanRestriction.NO_SETTER))).isNull(); - assertThat(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_GETTER, BeanRestriction.YES_SETTER))).isNull(); - assertNotNullProperty(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.YES_GETTER, BeanRestriction.YES_SETTER)), - true, true); - - field = BeanFields.class.getDeclaredField("withSetter"); - assertNotNullProperty(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_GETTER)), false, true); - assertThat(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_SETTER))).isNull(); - assertThat(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.YES_GETTER))).isNull(); - assertNotNullProperty(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.YES_SETTER)), false, true); - assertThat(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_GETTER, BeanRestriction.NO_SETTER))).isNull(); - assertThat(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.YES_GETTER, BeanRestriction.NO_SETTER))).isNull(); - assertNotNullProperty(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_GETTER, BeanRestriction.YES_SETTER)), - false, true); - assertThat(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.YES_GETTER, BeanRestriction.YES_SETTER))).isNull(); - - field = BeanFields.class.getDeclaredField("withNone"); - assertNotNullProperty(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_GETTER)), false, false); - assertNotNullProperty(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_SETTER)), false, false); - assertThat(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.YES_GETTER))).isNull(); - assertThat(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.YES_SETTER))).isNull(); - assertNotNullProperty(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_GETTER, BeanRestriction.NO_SETTER)), - false, false); - assertThat(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.YES_GETTER, BeanRestriction.NO_SETTER))).isNull(); - assertThat(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_GETTER, BeanRestriction.YES_SETTER))).isNull(); - assertThat(BeanUtils.resolveBeanProperty(field, of(BeanRestriction.YES_GETTER, BeanRestriction.YES_SETTER))).isNull(); - } - - @Test - public void testResolvePrimitiveBooleanProperty() - throws SecurityException, NoSuchFieldException { - final Field field = BeanFields.class.getDeclaredField("primitiveBoolean"); - final FieldWrapper resolvedBeanProperty = BeanUtils.resolveBeanProperty(field, EnumSet.noneOf(BeanRestriction.class)); - assertNotNullProperty(resolvedBeanProperty, true, true); - } - - private void assertNotNullProperty(final FieldWrapper resolvedBeanProperty, final boolean hasGetter, final boolean hasSetter) { - assertThat(resolvedBeanProperty).isNotNull(); - assertThat(resolvedBeanProperty.getGetter() != null).isEqualTo(hasGetter); - assertThat(resolvedBeanProperty.getSetter() != null).isEqualTo(hasSetter); - } - - @Test - public void testResolveBeanPropertyExceptions() - throws SecurityException, NoSuchFieldException { - Field field = BeanFields.class.getDeclaredField("withGetter"); - try { - BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_GETTER, BeanRestriction.YES_GETTER)); - fail("IllegalArgumentException expected"); - } catch (final IllegalArgumentException e) { - // ok - } - try { - BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_SETTER, BeanRestriction.YES_SETTER)); - fail("IllegalArgumentException expected"); - } catch (final IllegalArgumentException e) { - // ok - } - field = BeanFields.class.getDeclaredField("withGetterAndSetter"); - try { - BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_GETTER, BeanRestriction.YES_GETTER)); - fail("IllegalArgumentException expected"); - } catch (final IllegalArgumentException e) { - // ok - } - try { - BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_SETTER, BeanRestriction.YES_SETTER)); - fail("IllegalArgumentException expected"); - } catch (final IllegalArgumentException e) { - // ok - } - field = BeanFields.class.getDeclaredField("withSetter"); - try { - BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_GETTER, BeanRestriction.YES_GETTER)); - fail("IllegalArgumentException expected"); - } catch (final IllegalArgumentException e) { - // ok - } - try { - BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_SETTER, BeanRestriction.YES_SETTER)); - fail("IllegalArgumentException expected"); - } catch (final IllegalArgumentException e) { - // ok - } - field = BeanFields.class.getDeclaredField("withNone"); - try { - BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_GETTER, BeanRestriction.YES_GETTER)); - fail("IllegalArgumentException expected"); - } catch (final IllegalArgumentException e) { - // ok - } - try { - BeanUtils.resolveBeanProperty(field, of(BeanRestriction.NO_SETTER, BeanRestriction.YES_SETTER)); - fail("IllegalArgumentException expected"); - } catch (final IllegalArgumentException e) { - // ok - } - } - - @Test - public void testInvokeBeanSetter_SimpleSuccess() { - BeanFields subject = new BeanFields(); - - assertThat(BeanUtils.invokeBeanSetter(subject, "withSetter", null)).isEqualTo(null); - assertThat(BeanUtils.invokeBeanSetter(subject, "withSetter", 123)).isEqualTo(123); - assertThat(BeanUtils.invokeBeanSetter(subject, "withGetterAndSetter", true)).isEqualTo(true); - assertThat(BeanUtils.invokeBeanSetter(subject, "primitiveBoolean", true)).isEqualTo(true); - - assertThat(subject.withSetter).isEqualTo(123); - assertThat(subject.getWithGetterAndSetter()).isEqualTo(true); - assertThat(subject.isPrimitiveBoolean()).isTrue(); - } - - @Test - public void testInvokeBeanSetter_WithConversionSuccess() { - BeanFields subject = new BeanFields(); - - assertThat(BeanUtils.invokeBeanSetter(subject, "primitiveBoolean", "true")).isEqualTo(true); - assertThat(subject.isPrimitiveBoolean()).isTrue(); - assertThat(BeanUtils.invokeBeanSetter(subject, "primitiveBoolean", "false")).isEqualTo(false); - assertThat(subject.isPrimitiveBoolean()).isFalse(); - } - - @Test - public void testInvokeBeanSetter_NoSetterForField() { - BeanFields subject = new BeanFields(); - - try { - BeanUtils.invokeBeanSetter(subject, "withGetter", 123); - fail("expected exception"); - } catch (RuntimeException e) { - assertThat(e.getCause()).isInstanceOf(NoSuchMethodException.class); - assertThat(e.getCause().getMessage()).isEqualTo("Bean setter for withGetter"); - } - } - - @Test - public void testInvokeBeanSetter_NoSetterForFieldWithCorrectType() { - BeanFields subject = new BeanFields(); - - try { - BeanUtils.invokeBeanSetter(subject, "primitiveBoolean", new Thread()); - fail("expected exception"); - } catch (RuntimeException e) { - assertThat(e.getCause()).isInstanceOf(NoSuchMethodException.class); - assertThat(e.getCause().getMessage()).contains("error: unable to convert value"); - } - - try { - BeanUtils.invokeBeanSetter(subject, "primitiveBoolean", null); - fail("expected exception"); - } catch (RuntimeException e) { - assertThat(e).isInstanceOf(IllegalArgumentException.class); - } - } - - @Test - public void testInvokeBeanGetter_SimpleSuccess() { - BeanFields subject = new BeanFields(); - subject.setWithGetterAndSetter(true); - subject.setPrimitiveBoolean(true); - - assertThat(BeanUtils.invokeBeanGetter(subject, "withGetterAndSetter")).isEqualTo(true); - assertThat(BeanUtils.invokeBeanGetter(subject, "primitiveBoolean")).isEqualTo(true); - } - - @Test - public void testInvokeBeanGetter_NoGetterForField() { - BeanFields subject = new BeanFields(); - subject.setWithSetter(true); - - try { - BeanUtils.invokeBeanGetter(subject, "withSetter"); - fail("expected exception"); - } catch (RuntimeException e) { - assertThat(e.getCause()).isInstanceOf(NoSuchMethodException.class); - assertThat(e.getCause().getMessage()).contains("Bean getter for withSetter"); - } - } - - @SuppressWarnings("unused") - private static class BeanFields { - private Object withGetter; - private Object withGetterAndSetter; - private Object withSetter; - private Object withNone; - private boolean primitiveBoolean; - - public Object getWithGetterAndSetter() { - return withGetterAndSetter; - } - - public void setWithGetterAndSetter(final Object withGetterAndSetter) { - this.withGetterAndSetter = withGetterAndSetter; - } - - public Object getWithGetter() { - return withGetter; - } - - public void setWithSetter(@Nullable final Object withSetter) { - this.withSetter = withSetter; - } - - public boolean isPrimitiveBoolean() { - return primitiveBoolean; - } - - public void setPrimitiveBoolean(final boolean primitiveBoolean) { - this.primitiveBoolean = primitiveBoolean; - } - } - - @Test - public void testMethodIsBeanlike() { - for (Method method : ValidBeanlikeMethods.class.getMethods()) { - assertThat(BeanUtils.methodIsBeanlike(method)).describedAs("method IS a beanlike: %s", method).isTrue(); - } - for (Method method : InvalidBeanlikeMethods.class.getMethods()) { - assertThat(BeanUtils.methodIsBeanlike(method)).describedAs("method IS NOT beanlike: %s", method).isFalse(); - } - } - - @SuppressWarnings("unused") - public interface ValidBeanlikeMethods { - // valid beanlike setters - void setBooleanPrimitive(boolean b); - void setBooleanBoxed(Boolean b); - void setObject(Object b); - void setInteger(Integer b); - // valid beanlike getters - boolean isBooleanPrimitive(); - Boolean getBooleanBoxed(); - Object getObject(); - Integer getInteger(); - } - - @SuppressWarnings("unused") - public interface InvalidBeanlikeMethods { - // invalid beanlike setters - boolean setBooleanPrimitive(boolean b); - Object setBooleanBoxed(Boolean b); - void setBooleanBoxed(Boolean b, Boolean b2); - void setObject(); - // invalid beanlike getters - Boolean isBooleanPrimitive(); - Boolean getBooleanBoxed(boolean b); - void getObject(); - } -} \ No newline at end of file diff --git a/src/test/java/org/bbottema/javareflection/ClassUtilsPowermockTest.java b/src/test/java/org/bbottema/javareflection/ClassUtilsPowermockTest.java deleted file mode 100644 index b146961..0000000 --- a/src/test/java/org/bbottema/javareflection/ClassUtilsPowermockTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.bbottema.javareflection; - -import org.assertj.core.api.Assertions; -import org.bbottema.javareflection.ClassUtils.ConstructorFactory; -import org.bbottema.javareflection.testmodel.A; -import org.bbottema.javareflection.valueconverter.ValueConversionHelper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.MockedStatic; -import org.mockito.Mockito; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -public class ClassUtilsPowermockTest { - - @BeforeEach - public void resetStaticCaches() { - ValueConversionHelper.resetDefaultConverters(); - } - - @Test - public void testNewInstanceHappyFlow() { - assertThat(ClassUtils.newInstanceSimple(Object.class).getClass()).isEqualTo(Object.class); - } - - @Test - public void testNewInstanceSimple() throws Exception { - testExceptionHandling(new SecurityException("moo"), "unable to invoke parameterless constructor; security problem", false); - testExceptionHandling(new InstantiationException("moo"), "unable to complete instantiation of object", false); - testExceptionHandling(new IllegalAccessException("moo"), "unable to access parameterless constructor", false); - testExceptionHandling(new InvocationTargetException(null, "moo"), "unable to invoke parameterless constructor", false); - testExceptionHandling(new NoSuchMethodException("moo"), "unable to find parameterless constructor (not public?)", true); - } - - private void testExceptionHandling(Throwable exceptionThatShouldBeHandled, String expectedExceptionMessage, boolean onGetConstructor) throws InvocationTargetException, InstantiationException, IllegalAccessException { - try (MockedStatic mockedStatic = Mockito.mockStatic(ConstructorFactory.class)) { - if (onGetConstructor) { - mockedStatic.when(() -> ConstructorFactory.obtainConstructor(A.class)).thenThrow(exceptionThatShouldBeHandled); - } else { - Constructor constructorMock = Mockito.mock(Constructor.class); - mockedStatic.when(() -> ConstructorFactory.obtainConstructor(A.class)).thenReturn(constructorMock); - Mockito.when(constructorMock.newInstance()).thenThrow(exceptionThatShouldBeHandled); - } - - Exception exception = assertThrows(RuntimeException.class, () -> ClassUtils.newInstanceSimple(A.class)); - Assertions.assertThat(exception.getMessage()).contains(expectedExceptionMessage); - Assertions.assertThat(exception).hasCauseExactlyInstanceOf(exceptionThatShouldBeHandled.getClass()); - } - } - - @Test - public void testLocateClass_CacheShouldShortcutLookup() throws Exception { - try (MockedStatic mockedStatic = Mockito.mockStatic(ClassUtils.class)) { - mockedStatic.when(() -> ClassUtils.locateClass("Integer", false, null)).thenCallRealMethod().thenCallRealMethod(); - mockedStatic.when(() -> ClassUtils.locateClass("Integer", "java.lang", null)).thenReturn(Byte.class).thenReturn(Double.class); - - Class resultFromLookup = ClassUtils.locateClass("Integer", false, null); - Class resultFromCache = ClassUtils.locateClass("Integer", false, null); - - assertThat(resultFromLookup).isSameAs(Byte.class); - assertThat(resultFromCache).isSameAs(resultFromLookup); - } - } -} \ No newline at end of file diff --git a/src/test/java/org/bbottema/javareflection/ClassUtilsTest.java b/src/test/java/org/bbottema/javareflection/ClassUtilsTest.java deleted file mode 100644 index 556e59b..0000000 --- a/src/test/java/org/bbottema/javareflection/ClassUtilsTest.java +++ /dev/null @@ -1,168 +0,0 @@ -package org.bbottema.javareflection; - -import org.bbottema.javareflection.testmodel.A; -import org.bbottema.javareflection.testmodel.C; -import org.bbottema.javareflection.testmodel.Meta; -import org.bbottema.javareflection.testmodel.Moo; -import org.bbottema.javareflection.testmodel.Pear; -import org.bbottema.javareflection.testmodel.Shmoo; -import org.bbottema.javareflection.util.MetaAnnotationExtractor; -import org.bbottema.javareflection.valueconverter.ValueConversionHelper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.Socket; -import java.util.Collection; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import static java.util.EnumSet.of; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.bbottema.javareflection.model.MethodModifier.MATCH_ANY; -import static org.bbottema.javareflection.model.MethodModifier.PUBLIC; - -public class ClassUtilsTest { - - @BeforeEach - public void resetStaticCaches() { - ValueConversionHelper.resetDefaultConverters(); - } - - @Test - public void testLocateClass() { - assertThat(ClassUtils.locateClass("Locale", false, null)).isEqualTo(Locale.class); - assertThat(ClassUtils.locateClass("Math", false, null)).isEqualTo(Math.class); - assertThat(ClassUtils.locateClass("Mathh", false, null)).isNull(); - assertThat(ClassUtils.locateClass("ClassUtils", false, null)).isNull(); - assertThat(ClassUtils.locateClass("ClassUtils", true, null)).isEqualTo(ClassUtils.class); - assertThat(ClassUtils.locateClass("Socket", false, null)).isNull(); - assertThat(ClassUtils.locateClass("Socket", true, null)).isEqualTo(Socket.class); - } - - @Test - public void testNewInstanceHappyFlow() { - assertThat(ClassUtils.newInstanceSimple(Object.class).getClass()).isEqualTo(Object.class); - } - - @Test - public void testSolveField() - throws NoSuchFieldException { - assertThat(ClassUtils.solveField(new C(new Pear()), "numberB")).isEqualTo(C.class.getField("numberB")); - assertThat(ClassUtils.solveField(C.class, "numberB_static")).isEqualTo(C.class.getField("numberB_static")); - assertThat(ClassUtils.solveField(C.class, "number_privateC")).isEqualTo(C.class.getDeclaredField("number_privateC")); - } - - @Test - public void testSolveFieldValue() { - assertThat(ClassUtils.solveFieldValue(Integer.class, "MAX_VALUE")).isEqualTo(Integer.MAX_VALUE); - final C instance = new C(new Pear()); - assertThat(ClassUtils.solveFieldValue(instance, "numberC")).isNull(); - instance.updateNumberC(100); - assertThat(ClassUtils.solveFieldValue(instance, "numberC")).isEqualTo(100); - instance.updateNumber_privateC(1234); - assertThat(ClassUtils.solveFieldValue(instance, "number_privateC")).isEqualTo(1234); - } - - @Test - public void testAssignToField() - throws IllegalAccessException, NoSuchFieldException { - assertThat(ClassUtils.assignToField(new C(new Pear()), "numberB", 50)).isEqualTo(50); - assertThat(ClassUtils.assignToField(new C(new Pear()), "numberB", "50")).isEqualTo(50); - assertThat(ClassUtils.assignToField(new C(new Pear()), "numberB", 50d)).isEqualTo(50); - try { - ClassUtils.assignToField(new C(new Pear()), "numberB", new Pear()); - fail("IllegalAccessException expected due to incompatible types"); - } catch (NoSuchFieldException e) { - assertThat(e.getMessage()).contains("unable to convert value"); - } - try { - ClassUtils.assignToField(new C(new Pear()), "number_privateB", new Pear()); - fail("IllegalAccessException expected due to incompatible types"); - } catch (NoSuchFieldException e) { - assertThat(e.getMessage()).contains("unable to convert value"); - } - } - - @Test - public void testCollectPropertyNames() { - assertThat(ClassUtils.collectPropertyNames(new C(new Pear()))) - .containsExactlyInAnyOrder("numberA", "numberB", "numberB_static", "numberC"); - } - - @Test - public void testCollectPublicMethodNames() { - final Object subject = new Object(); - Collection objectProperties = ClassUtils.collectMethodNames(subject.getClass(), Object.class, of(PUBLIC)); - final C subject1 = new C(new Pear()); - Collection cProperties = ClassUtils.collectMethodNames(subject1.getClass(), Object.class, of(PUBLIC)); - assertThat(objectProperties).isNotEmpty(); - assertThat(cProperties).hasSize(objectProperties.size() + 4); - cProperties.removeAll(objectProperties); - assertThat(cProperties).containsExactlyInAnyOrder("foo", "bar", "updateNumberC", "updateNumber_privateC"); - } - - @Test - public void testCollectAllMethodNames() throws IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { - Collection objectProperties = ClassUtils.collectMethodNames(Object.class, Object.class, MATCH_ANY); - Collection cProperties = ClassUtils.collectMethodNames(C.class, Object.class, MATCH_ANY); - assertThat(objectProperties).isNotEmpty(); - assertThat(cProperties).hasSize(objectProperties.size() + 6); - cProperties.removeAll(objectProperties); - assertThat(cProperties).containsExactlyInAnyOrder("foo", "bar", "protectedMethod", "privateMethod", "updateNumberC", "updateNumber_privateC"); - MethodUtils.invokeCompatibleMethod(new C(new Pear()), C.class, "privateMethod"); - } - - @Test - public void testCollectMethods() { - List methodsOnC = ClassUtils.collectMethods(C.class, A.class, of(PUBLIC)); - // the second foo and bar occurrence come from the interface Foo that A implements (Foo extends Bar) - assertThat(methodsOnC).extracting("name").containsExactlyInAnyOrder("foo", "foo", "bar", "bar", "updateNumberC", "updateNumber_privateC"); - } - - @Test - public void testCollectMethodsByName() { - assertThat(ClassUtils.collectMethodsByName(C.class, C.class, MATCH_ANY, "foo")) - .extracting("name").containsExactlyInAnyOrder("foo"); - assertThat(ClassUtils.collectMethodsByName(C.class, Object.class, MATCH_ANY, "protectedMethod")) - .extracting("name").containsExactlyInAnyOrder("protectedMethod", "protectedMethod", "protectedMethod"); - assertThat(ClassUtils.collectMethodsByName(C.class, C.class, MATCH_ANY, "nonexistantMethod")).isEmpty(); - } - - @Test - public void testFindFirstMethodsByName() { - assertThat(ClassUtils.findFirstMethodByName(C.class, C.class, MATCH_ANY, "foo")) - .extracting("name").isEqualTo("foo"); - assertThat(ClassUtils.findFirstMethodByName(C.class, Object.class, MATCH_ANY, "protectedMethod")) - .extracting("name").isEqualTo("protectedMethod"); - } - - @Test - public void testHasMethodByName() { - assertThat(ClassUtils.hasMethodByName(C.class, C.class, MATCH_ANY, "foo")).isTrue(); - assertThat(ClassUtils.hasMethodByName(C.class, Object.class, MATCH_ANY, "protectedMethod")).isTrue(); - assertThat(ClassUtils.hasMethodByName(C.class, Object.class, MATCH_ANY, "lalala")).isFalse(); - } - - @Test - public void testCollectMethodsMappingToName() { - Map> methodsByNames = ClassUtils.collectMethodsMappingToName(Moo.class, Shmoo.class, MATCH_ANY); - - assertThat(methodsByNames).containsOnlyKeys("method1", "method2"); - - assertThat(methodsByNames.get("method1")) - .extracting(new MetaAnnotationExtractor<>(Meta.class)) - .extractingResultOf("value") - .containsExactlyInAnyOrder( - "Moo.method1-A", "Moo.method1-B", "Moo.method1-C", - "Shmoo.method1-A", "Shmoo.method1-B", "Shmoo.method1-C"); - - assertThat(methodsByNames.get("method2")) - .extracting(new MetaAnnotationExtractor<>(Meta.class)) - .extractingResultOf("value") - .containsExactlyInAnyOrder("Moo.method2-A", "Shmoo.method2-A"); - } -} \ No newline at end of file diff --git a/src/test/java/org/bbottema/javareflection/ExternalClassLoaderTest.java b/src/test/java/org/bbottema/javareflection/ExternalClassLoaderTest.java deleted file mode 100644 index 9731097..0000000 --- a/src/test/java/org/bbottema/javareflection/ExternalClassLoaderTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.bbottema.javareflection; - -import org.bbottema.javareflection.util.ExternalClassLoader; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class ExternalClassLoaderTest { - - @Test - public void testConstructor() { - new ExternalClassLoader(); - // ok, no exception - } - - @Test - public void testSettersGetters() { - ExternalClassLoader loader = new ExternalClassLoader(); - assertThat(loader.getBasepath()).isNull(); - loader.setBasepath("test"); - assertThat(loader.getBasepath()).isEqualTo("test"); - } -} \ No newline at end of file diff --git a/src/test/java/org/bbottema/javareflection/MethodUtilsTest.java b/src/test/java/org/bbottema/javareflection/MethodUtilsTest.java deleted file mode 100644 index 1e63fa1..0000000 --- a/src/test/java/org/bbottema/javareflection/MethodUtilsTest.java +++ /dev/null @@ -1,415 +0,0 @@ -package org.bbottema.javareflection; - -import org.bbottema.javareflection.model.InvokableObject; -import org.bbottema.javareflection.model.LookupMode; -import org.bbottema.javareflection.model.MethodModifier; -import org.bbottema.javareflection.model.MethodParameter; -import org.bbottema.javareflection.testmodel.A; -import org.bbottema.javareflection.testmodel.B; -import org.bbottema.javareflection.testmodel.C; -import org.bbottema.javareflection.testmodel.Foo; -import org.bbottema.javareflection.testmodel.Fruit; -import org.bbottema.javareflection.testmodel.Kraa; -import org.bbottema.javareflection.testmodel.Meta; -import org.bbottema.javareflection.testmodel.Moo; -import org.bbottema.javareflection.testmodel.Pear; -import org.bbottema.javareflection.testmodel.Skree; -import org.bbottema.javareflection.util.MetaAnnotationExtractor; -import org.bbottema.javareflection.valueconverter.ValueConversionHelper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.internal.util.collections.Iterables; -import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.lang.annotation.Annotation; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.util.AbstractMap.SimpleEntry; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Set; - -import static java.util.Arrays.asList; -import static java.util.EnumSet.allOf; -import static java.util.EnumSet.of; -import static java.util.Objects.requireNonNull; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.bbottema.javareflection.ClassUtils.collectMethodsByName; -import static org.bbottema.javareflection.MethodUtils.onlyMethod; -import static org.bbottema.javareflection.model.MethodModifier.MATCH_ANY; - -public class MethodUtilsTest { - - @BeforeEach - public void resetStaticCaches() { - ValueConversionHelper.resetDefaultConverters(); - } - - @Test - public void testFindCompatibleMethod() - throws NoSuchMethodException { - // find method through interface on superclass, using autoboxing, class casting and an auto convert - Set allButSmartLookup = new HashSet<>(LookupMode.FULL); - allButSmartLookup.remove(LookupMode.SMART_CONVERT); - - Set> m = MethodUtils.findCompatibleMethod(B.class, "foo", allButSmartLookup, double.class, Pear.class, String.class); - assertThat(m).hasSize(1); - assertThat(onlyMethod(m)).isEqualTo(Foo.class.getMethod("foo", Double.class, Fruit.class, char.class)); - Set> m2 = MethodUtils.findCompatibleMethod(B.class, "foo", allButSmartLookup, double.class, Pear.class, String.class); - assertThat(m2).hasSize(1); - assertThat(m2).isEqualTo(m); - // find the same method, but now the first implementation on C should be returned - m = MethodUtils.findCompatibleMethod(C.class, "foo", allButSmartLookup, double.class, Pear.class, String.class); - assertThat(m).hasSize(1); - assertThat(onlyMethod(m)).isEqualTo(C.class.getMethod("foo", Double.class, Fruit.class, char.class)); - // find a String method - m = MethodUtils.findCompatibleMethod(String.class, "concat", EnumSet.noneOf(LookupMode.class), String.class); - assertThat(m).hasSize(1); - assertThat(onlyMethod(m)).isEqualTo(String.class.getMethod("concat", String.class)); - // shouldn't be able to find the following methods - try { - MethodUtils.findCompatibleMethod(B.class, "foos", allButSmartLookup, double.class, Pear.class, String.class); - fail("NoSuchMethodException expected"); - } catch (NoSuchMethodException e) { - // OK - } - try { - MethodUtils.findCompatibleMethod(B.class, "foo", allButSmartLookup, double.class, String.class, String.class); - fail("NoSuchMethodException expected"); - } catch (NoSuchMethodException e) { - // OK - } - try { - MethodUtils.findCompatibleMethod(B.class, "foo", allButSmartLookup, double.class, Fruit.class, Math.class); - fail("NoSuchMethodException expected"); - } catch (NoSuchMethodException e) { - // OK - } - Set> result = MethodUtils.findCompatibleMethod(B.class, "foo", LookupMode.FULL, double.class, Fruit.class, Math.class); - assertThat(result).hasSize(1); - } - - @Test - public void testFindCompatibleConstructor() - throws NoSuchMethodException { - // find constructor on superclass, using autoboxing - Set> m = MethodUtils.findCompatibleConstructor(B.class, LookupMode.FULL, Fruit.class); - assertThat(m).isNotEmpty(); - assertThat(m.iterator().next().getMethod()).isEqualTo(B.class.getConstructor(Fruit.class)); - Set> m2 = MethodUtils.findCompatibleConstructor(B.class, LookupMode.FULL, Fruit.class); - assertThat(m2).isNotEmpty(); - assertThat(m2).isEqualTo(m); - // find constructor on superclass, using autoboxing and class casting - m = MethodUtils.findCompatibleConstructor(B.class, LookupMode.FULL, Pear.class); - assertThat(m).isNotEmpty(); - assertThat(m.iterator().next().getMethod()).isEqualTo(B.class.getConstructor(Fruit.class)); - // still find constructor on superclass - m = MethodUtils.findCompatibleConstructor(C.class, LookupMode.FULL, Fruit.class); - assertThat(m).isNotEmpty(); - assertThat(m.iterator().next().getMethod()).isEqualTo(C.class.getConstructor(Fruit.class)); - // still find constructor on subclass - m = MethodUtils.findCompatibleConstructor(C.class, LookupMode.FULL, Pear.class); - assertThat(m).isNotEmpty(); - assertThat(m.iterator().next().getMethod()).isEqualTo(C.class.getConstructor(Pear.class)); - // find a String constructor - m = MethodUtils.findCompatibleConstructor(String.class, EnumSet.noneOf(LookupMode.class), String.class); - assertThat(m).isNotEmpty(); - assertThat(m.iterator().next().getMethod()).isEqualTo(String.class.getConstructor(String.class)); - // shouldn't be able to find the following methods - try { - MethodUtils.findCompatibleConstructor(B.class, LookupMode.FULL, double.class); - fail("NoSuchMethodException expected"); - } catch (NoSuchMethodException e) { - // OK - } - try { - MethodUtils.findCompatibleConstructor(B.class, LookupMode.FULL, String.class); - fail("NoSuchMethodException expected"); - } catch (NoSuchMethodException e) { - // OK - } - } - - @Test - public void testInvokeCompatibleMethod() - throws NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { - MethodUtils.invokeCompatibleMethod(new C(new Pear()), B.class, "foo", 50d, new Pear(), "g"); - MethodUtils.invokeCompatibleMethod(new C(new Pear()), C.class, "foo", 50d, new Pear(), "g"); - MethodUtils.invokeCompatibleMethod("", String.class, "concat", String.class); - - // shouldn't be able to find the following methods - try { - MethodUtils.invokeCompatibleMethod(new C(new Pear()), C.class, "foos", 50d, new Pear(), "g"); - fail("NoSuchMethodException expected"); - } catch (NoSuchMethodException e) { - // OK - } - try { - MethodUtils.invokeCompatibleMethod(new C(new Pear()), C.class, "foo", 50d, "foobar", "g"); - fail("NoSuchMethodException expected"); - } catch (NoSuchMethodException e) { - // OK - } - try { - MethodUtils.invokeCompatibleMethod(new C(new Pear()), C.class, "foo", 50d, new Pear(), Calendar.getInstance()); - fail("NoSuchMethodException expected"); - } catch (NoSuchMethodException e) { - // OK - } - } - - @Test - public void testInvokeCompatibleConstructor() - throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { - try { - MethodUtils.invokeCompatibleConstructor(B.class, new Pear()); - } catch (InstantiationException e) { - // Ok - } - MethodUtils.invokeCompatibleConstructor(C.class, new Pear()); - MethodUtils.invokeCompatibleConstructor(String.class, "test string"); - MethodUtils.invokeCompatibleConstructor(String.class, 1234567); - // shouldn't be able to find the following methods - try { - MethodUtils.invokeCompatibleConstructor(B.class, 50d); - fail("NoSuchMethodException expected"); - } catch (NoSuchMethodException e) { - // OK - } - try { - MethodUtils.invokeCompatibleConstructor(B.class, "foobar"); - fail("NoSuchMethodException expected"); - } catch (NoSuchMethodException e) { - // OK - } - } - - @Test - public void testIsMethodCompatible_Simple() throws NoSuchMethodException { - Set lookupModes = EnumSet.noneOf(LookupMode.class); - - Method m = Math.class.getMethod("min", int.class, int.class); - assertThat(MethodUtils.isMethodCompatible(m, lookupModes, int.class, int.class)).isTrue(); - assertThat(MethodUtils.isMethodCompatible(m, lookupModes, int.class, Calendar.class)).isFalse(); - assertThat(MethodUtils.isMethodCompatible(m, lookupModes, int.class, A.class)).isFalse(); - - Method stringConcat = String.class.getMethod("concat", String.class); - assertThat(MethodUtils.isMethodCompatible(stringConcat, lookupModes, String.class)).isTrue(); - assertThat(MethodUtils.isMethodCompatible(stringConcat, lookupModes, Calendar.class)).isFalse(); - } - - @Test - public void testIsMethodCompatible_TestLookupModes() throws NoSuchMethodException { - Set noConversions = EnumSet.noneOf(LookupMode.class); - Set commonConversions = of(LookupMode.COMMON_CONVERT); - Set castConvert = of(LookupMode.CAST_TO_SUPER, LookupMode.CAST_TO_INTERFACE); - Set castThenCommonsConvert = of(LookupMode.CAST_TO_SUPER, LookupMode.COMMON_CONVERT); - Set smartConversions = of(LookupMode.SMART_CONVERT); - - Method m = Math.class.getMethod("min", int.class, int.class); - assertThat(MethodUtils.isMethodCompatible(m, noConversions, int.class, boolean.class)).isFalse(); - assertThat(MethodUtils.isMethodCompatible(m, commonConversions, int.class, boolean.class)).isTrue(); - - Method cFoo = C.class.getMethod("foo", Double.class, Fruit.class, char.class); - assertThat(MethodUtils.isMethodCompatible(cFoo, castConvert, Double.class, Pear.class, char.class)).isTrue(); - assertThat(MethodUtils.isMethodCompatible(cFoo, noConversions, double.class, Pear.class, String.class)).isFalse(); - assertThat(MethodUtils.isMethodCompatible(cFoo, commonConversions, double.class, Pear.class, String.class)).isFalse(); - assertThat(MethodUtils.isMethodCompatible(cFoo, castConvert, double.class, Pear.class, String.class)).isFalse(); - assertThat(MethodUtils.isMethodCompatible(cFoo, castThenCommonsConvert, double.class, Pear.class, String.class)).isTrue(); - - Method stringConcat = String.class.getMethod("concat", String.class); - assertThat(MethodUtils.isMethodCompatible(stringConcat, noConversions, Calendar.class)).isFalse(); - assertThat(MethodUtils.isMethodCompatible(stringConcat, noConversions, String.class)).isTrue(); - assertThat(MethodUtils.isMethodCompatible(stringConcat, commonConversions, String.class)).isTrue(); - assertThat(MethodUtils.isMethodCompatible(stringConcat, commonConversions, Calendar.class)).isFalse(); - assertThat(MethodUtils.isMethodCompatible(stringConcat, smartConversions, Calendar.class)).isTrue(); - } - - @SuppressWarnings("ConstantConditions") - @Test - public void testInvokeCompatibleMethod_VariousAccessLevels() throws IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { - final C c = new C(new Pear()); - assertThat(MethodUtils.invokeCompatibleMethod(c, A.class, "privateMethod")).isEqualTo("private 1"); - assertThat(MethodUtils.invokeCompatibleMethod(c, B.class, "protectedMethod")).isEqualTo("protected 2"); - assertThat(MethodUtils.invokeCompatibleMethod(c, C.class, "privateMethod")).isEqualTo("private 2"); - assertThat(MethodUtils.invokeCompatibleMethod(c, C.class, "protectedMethod")).isEqualTo("protected 2"); - - assertThat(((Number) MethodUtils.invokeCompatibleMethod(null, Math.class, "min", 1, true)).intValue()).isEqualTo(1); - assertThat(((Number) MethodUtils.invokeCompatibleMethod(null, Math.class, "min", 1, false)).intValue()).isEqualTo(0); - assertThat(((Number) MethodUtils.invokeCompatibleMethod(null, Math.class, "min", 0, true)).intValue()).isEqualTo(0); - assertThat(((Number) MethodUtils.invokeCompatibleMethod(null, Math.class, "min", "d", 1000)).intValue()).isEqualTo(100); // d -> 100 - } - - @Test - public void testFindMatchingMethodsParamArray() { - assertThat(MethodUtils.findMatchingMethods(Moo.class, Object.class, "method1", "Integer")) - .extracting(new MetaAnnotationExtractor<>(Meta.class)) - .extractingResultOf("value") - .containsExactlyInAnyOrder("Moo.method1-A", "Shmoo.method1-A"); - - assertThat(MethodUtils.findMatchingMethods(Moo.class, Object.class, "method1", "Object", "java.lang.Integer")) - .extracting(new MetaAnnotationExtractor<>(Meta.class)) - .extractingResultOf("value") - .containsExactlyInAnyOrder("Moo.method1-C"); - } - - @Test - public void testFindMatchingMethodsParamCollection() { - //noinspection ArraysAsListWithZeroOrOneArgument - assertThat(MethodUtils.findMatchingMethods(Moo.class, Object.class, "method1", asList("Integer"))) - .extracting(new MetaAnnotationExtractor<>(Meta.class)) - .extractingResultOf("value") - .containsExactlyInAnyOrder("Moo.method1-A", "Shmoo.method1-A"); - - assertThat(MethodUtils.findMatchingMethods(Moo.class, Object.class, "method1", asList("Object", "java.lang.Integer"))) - .extracting(new MetaAnnotationExtractor<>(Meta.class)) - .extractingResultOf("value") - .containsExactlyInAnyOrder("Moo.method1-C"); - } - - @Test - public void testZipParametersAndArguments_SignatureMatches() { - Method testMethod = ClassUtils.findFirstMethodByName(Kraa.class, Kraa.class, allOf(MethodModifier.class), "testMethod"); - LinkedHashMap result = MethodUtils.zipParametersAndArguments(testMethod, 5, new ArrayList(), new HashSet()); - - Annotation[] p1 = testMethod.getParameterAnnotations()[0]; - Annotation[] p2 = testMethod.getParameterAnnotations()[1]; - Annotation[] p3 = testMethod.getParameterAnnotations()[2]; - - // verify annotations, which means the result assertions also saw these annotations - assertThat(p1).isEmpty(); - assertThat(p2).extractingResultOf("annotationType").containsExactly(Nullable.class); - assertThat(p3).extractingResultOf("annotationType").containsExactly(Nonnull.class); - - ParameterizedTypeImpl parameterizedHashSet = ParameterizedTypeImpl.make(HashSet.class, new Type[]{Double.class}, null); - - assertThat(result).containsExactly( - new SimpleEntry<>(new MethodParameter(0, Integer.class, Integer.class, asList(p1)), 5), - new SimpleEntry<>(new MethodParameter(1, List.class, List.class, asList(p2)), new ArrayList()), - new SimpleEntry<>(new MethodParameter(2, HashSet.class, parameterizedHashSet, asList(p3)), new HashSet()) - ); - } - - @Test - public void testZipParametersAndArguments_SignatureDoesntMatch_OnLength() { - Method testMethod = ClassUtils.findFirstMethodByName(Kraa.class, Kraa.class, allOf(MethodModifier.class), "testMethod"); - LinkedHashMap result = MethodUtils.zipParametersAndArguments(testMethod, 5, "moo", new ArrayList(), new HashSet()); - - assertThat(result).isNull(); - } - - @Test - public void testZipParametersAndArguments_SignatureDoesntMatch_OnType() { - Method testMethod = ClassUtils.findFirstMethodByName(Kraa.class, Kraa.class, allOf(MethodModifier.class), "testMethod"); - LinkedHashMap result = MethodUtils.zipParametersAndArguments(testMethod, new Kraa(), new ArrayList(), new HashSet()); - - assertThat(result).isNull(); - } - - @Test - public void testZipParametersAndArguments_SignatureDoesntMatch_OnType_ButDontCheck() { - final Kraa kraa = new Kraa(); - Method testMethod = ClassUtils.findFirstMethodByName(Kraa.class, Kraa.class, allOf(MethodModifier.class), "testMethod"); - LinkedHashMap result = MethodUtils.zipParametersAndArguments(false, testMethod, kraa, new ArrayList(), new HashSet()); - - Annotation[] p1 = testMethod.getParameterAnnotations()[0]; - Annotation[] p2 = testMethod.getParameterAnnotations()[1]; - Annotation[] p3 = testMethod.getParameterAnnotations()[2]; - - ParameterizedTypeImpl parameterizedHashSet = ParameterizedTypeImpl.make(HashSet.class, new Type[]{Double.class}, null); - - assertThat(result).containsExactly( - new SimpleEntry<>(new MethodParameter(0, Integer.class, Integer.class, asList(p1)), kraa), - new SimpleEntry<>(new MethodParameter(1, List.class, List.class, asList(p2)), new ArrayList()), - new SimpleEntry<>(new MethodParameter(2, HashSet.class, parameterizedHashSet, asList(p3)), new HashSet()) - ); - } - - @Test - public void testZipParametersAndArguments_SignatureDoesntMatch_TooManyArguments_ButDontCheck() { - Method testMethod = ClassUtils.findFirstMethodByName(Kraa.class, Kraa.class, allOf(MethodModifier.class), "testMethod"); - LinkedHashMap result = MethodUtils.zipParametersAndArguments(false, testMethod, 5, "moo", new ArrayList(), new HashSet()); - - Annotation[] p1 = testMethod.getParameterAnnotations()[0]; - Annotation[] p2 = testMethod.getParameterAnnotations()[1]; - Annotation[] p3 = testMethod.getParameterAnnotations()[2]; - - ParameterizedTypeImpl parameterizedHashSet = ParameterizedTypeImpl.make(HashSet.class, new Type[]{Double.class}, null); - - assertThat(result).containsExactly( - new SimpleEntry<>(new MethodParameter(0, Integer.class, Integer.class, asList(p1)), 5), - new SimpleEntry<>(new MethodParameter(1, List.class, List.class, asList(p2)), "moo"), - new SimpleEntry<>(new MethodParameter(2, HashSet.class, parameterizedHashSet, asList(p3)), new ArrayList()) - ); - } - - @Test - public void testZipParametersAndArguments_SignatureDoesntMatch_TooFewArguments_ButDontCheck() { - Method testMethod = ClassUtils.findFirstMethodByName(Kraa.class, Kraa.class, allOf(MethodModifier.class), "testMethod"); - LinkedHashMap result = MethodUtils.zipParametersAndArguments(false, testMethod, new ArrayList(), new HashSet()); - - Annotation[] p1 = testMethod.getParameterAnnotations()[0]; - Annotation[] p2 = testMethod.getParameterAnnotations()[1]; - - assertThat(result).containsExactly( - new SimpleEntry<>(new MethodParameter(0, Integer.class, Integer.class, asList(p1)), new ArrayList()), - new SimpleEntry<>(new MethodParameter(1, List.class, List.class, asList(p2)), new HashSet()) - ); - } - - @Test - public void testMethodHasCollectionParameter() { - assertThat(MethodUtils.methodHasCollectionParameter(findSkreeMethod("methodWithArray"))).isTrue(); - assertThat(MethodUtils.methodHasCollectionParameter(findSkreeMethod("methodWithCollection1"))).isTrue(); - assertThat(MethodUtils.methodHasCollectionParameter(findSkreeMethod("methodWithCollection2"))).isTrue(); - assertThat(MethodUtils.methodHasCollectionParameter(findSkreeMethod("methodWithCollection3"))).isTrue(); - assertThat(MethodUtils.methodHasCollectionParameter(findSkreeMethod("methodWithCollection4"))).isTrue(); - assertThat(MethodUtils.methodHasCollectionParameter(findSkreeMethod("methodWithCollection5"))).isTrue(); - assertThat(MethodUtils.methodHasCollectionParameter(findSkreeMethod("methodWithoutCollection1"))).isFalse(); - assertThat(MethodUtils.methodHasCollectionParameter(findSkreeMethod("methodWithoutCollection2"))).isFalse(); - assertThat(MethodUtils.methodHasCollectionParameter(findSkreeMethod("methodWithoutCollection3"))).isFalse(); - } - - private Method findSkreeMethod(String methodName) { - return collectMethodsByName(Skree.class, Skree.class, MATCH_ANY, methodName).iterator().next(); - } - - @Test - public void testFirstParameterArgumentByAnnotation() { - final Method testMethod = requireNonNull(ClassUtils.findFirstMethodByName(Kraa.class, Kraa.class, MATCH_ANY, "testMethod")); - final int argOne = 2; - final List argTwo = null; - final HashSet argThree = new HashSet(); - final Object[] arguments = {argOne, argTwo, argThree}; - assertThat((Object) MethodUtils.firstParameterArgumentByAnnotation(testMethod, arguments, Nullable.class)).isSameAs(argTwo); - assertThat((Object) MethodUtils.firstParameterArgumentByAnnotation(testMethod, arguments, Nonnull.class)).isSameAs(argThree); - assertThat((Object) MethodUtils.firstParameterArgumentByAnnotation(testMethod, arguments, Meta.class)).isNull(); - } - - @Test - public void testFirstParameterIndexByAnnotation() { - final Method testMethod = requireNonNull(ClassUtils.findFirstMethodByName(Kraa.class, Kraa.class, MATCH_ANY, "testMethod")); - - assertThat(MethodUtils.firstParameterIndexByAnnotation(testMethod, Nullable.class)).isEqualTo(1); - assertThat(MethodUtils.firstParameterIndexByAnnotation(testMethod, Nonnull.class)).isEqualTo(2); - assertThat(MethodUtils.firstParameterIndexByAnnotation(testMethod, Meta.class)).isEqualTo(-1); - } - - @Test - public void testInvokeMethodSimple() { - Set> valueOf = MethodUtils.findSimpleCompatibleMethod(String.class, "valueOf", boolean.class); - Method valueOfBoolean = Iterables.firstOf(valueOf).getMethod(); - - assertThat(MethodUtils.invokeMethodSimple(valueOfBoolean, null, true)).isEqualTo("true"); - assertThat(MethodUtils.invokeMethodSimple(valueOfBoolean, null, false)).isEqualTo("false"); - } -} \ No newline at end of file diff --git a/src/test/java/org/bbottema/javareflection/ReflectionUtilsTest.java b/src/test/java/org/bbottema/javareflection/ReflectionUtilsTest.java deleted file mode 100644 index 52780aa..0000000 --- a/src/test/java/org/bbottema/javareflection/ReflectionUtilsTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.bbottema.javareflection; - -import org.bbottema.javareflection.testmodel.reflectionutils.Bob; -import org.bbottema.javareflection.testmodel.reflectionutils.Moo; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class ReflectionUtilsTest { - - private Moo fieldDefinedGeneric = new Moo() { - }; - - @Test - public void testFieldHasCorrectType() { - assertThat(fieldDefinedGeneric.responseType).isEqualTo(Boolean.class); - } - - @Test - public void testFieldHasCorrectType2() { - assertThat(((Bob) fieldDefinedGeneric).responseType).isEqualTo(Boolean.class); - } - - @Test - public void getFirstGenericClassType() { - assertThat(ReflectionUtils.findParameterType(TestClassInteger.class, TestGenericClass.class, 0)).isEqualTo(Integer.class); - assertThat(ReflectionUtils.findParameterType(TestClassNested.class, TestGenericClass.class, 0)).isEqualTo(OtherTestGenericClass.class); - assertThat(ReflectionUtils.findParameterType(TestSubClassMultipleTypesSpecified.class, TestClassMultipleTypes.class, 1)).isEqualTo(Boolean.class); - } - - public static class TestGenericClass { - } - - public static class OtherTestGenericClass { - } - - public static class TestClassInteger extends TestGenericClass { - } - - public static class TestClassNested extends TestGenericClass> { - } - - public static class TestClassMultipleTypes { - } - - public static class TestSubClassMultipleTypes extends TestClassMultipleTypes { - } - - public static class TestSubClassMultipleTypesSpecified extends TestSubClassMultipleTypes { - } -} \ No newline at end of file diff --git a/src/test/java/org/bbottema/javareflection/TypeUtilsTest.java b/src/test/java/org/bbottema/javareflection/TypeUtilsTest.java deleted file mode 100644 index 076491e..0000000 --- a/src/test/java/org/bbottema/javareflection/TypeUtilsTest.java +++ /dev/null @@ -1,126 +0,0 @@ -package org.bbottema.javareflection; - -import org.bbottema.javareflection.testmodel.AnnotationsHelper; -import org.bbottema.javareflection.testmodel.AnnotationsHelper.MethodAnnotation; -import org.bbottema.javareflection.testmodel.AnnotationsHelper.ParamAnnotation3; -import org.bbottema.javareflection.testmodel.Fruit; -import org.bbottema.javareflection.testmodel.Pear; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.junit.jupiter.api.Test; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.List; - -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.bbottema.javareflection.ClassUtils.collectMethodsByName; -import static org.bbottema.javareflection.model.MethodModifier.MATCH_ANY; -import static org.bbottema.javareflection.testmodel.AnnotationsHelper.ParamAnnotation1; -import static org.bbottema.javareflection.testmodel.AnnotationsHelper.ParamAnnotation2; - -public class TypeUtilsTest { - - @Test - public void testAutobox() { - assertThat(TypeUtils.autobox(boolean.class)).isEqualTo(Boolean.class); - assertThat(TypeUtils.autobox(char.class)).isEqualTo(Character.class); - assertThat(TypeUtils.autobox(byte.class)).isEqualTo(Byte.class); - assertThat(TypeUtils.autobox(short.class)).isEqualTo(Short.class); - assertThat(TypeUtils.autobox(int.class)).isEqualTo(Integer.class); - assertThat(TypeUtils.autobox(long.class)).isEqualTo(Long.class); - assertThat(TypeUtils.autobox(float.class)).isEqualTo(Float.class); - assertThat(TypeUtils.autobox(double.class)).isEqualTo(Double.class); - assertThat(TypeUtils.autobox(Boolean.class)).isEqualTo(boolean.class); - assertThat(TypeUtils.autobox(Character.class)).isEqualTo(char.class); - assertThat(TypeUtils.autobox(Byte.class)).isEqualTo(byte.class); - assertThat(TypeUtils.autobox(Short.class)).isEqualTo(short.class); - assertThat(TypeUtils.autobox(Integer.class)).isEqualTo(int.class); - assertThat(TypeUtils.autobox(Long.class)).isEqualTo(long.class); - assertThat(TypeUtils.autobox(Float.class)).isEqualTo(float.class); - assertThat(TypeUtils.autobox(Double.class)).isEqualTo(double.class); - assertThat(TypeUtils.autobox(Fruit.class)).isNull(); - } - - @Test - public void testCollectTypes() { - final Class[] expectedTypeList = new Class[]{Pear.class, String.class, null, Double.class}; - final Object[] objectList = new Object[]{new Pear(), "foo", null, 4d}; - assertThat(TypeUtils.collectTypes(objectList)).isEqualTo(expectedTypeList); - } - - @Test - public void testWidestNumberClass() { - byte b = 1; - short s = 1; - int i = 1; - long l = 1; - float f = 1; - double d = 1; - Byte B = 1; - Short S = 1; - Integer I = 1; - Long L = 1L; - Float F = 1f; - Double D = 1d; - assertThat(TypeUtils.widestNumberClass(s, b)).isEqualTo(Short.class); - assertThat(TypeUtils.widestNumberClass(b, B)).isEqualTo(Byte.class); - assertThat(TypeUtils.widestNumberClass(B, b)).isEqualTo(Byte.class); - assertThat(TypeUtils.widestNumberClass(b, s, i)).isEqualTo(Integer.class); - assertThat(TypeUtils.widestNumberClass(b, s, i, l, f, d)).isEqualTo(Double.class); - assertThat(TypeUtils.widestNumberClass(B, S, I, L, F, D)).isEqualTo(Double.class); - assertThat(TypeUtils.widestNumberClass(L)).isEqualTo(Long.class); - assertThat(TypeUtils.widestNumberClass(i, D)).isEqualTo(Double.class); - } - - @Test - public void testIsPackage() { - assertThat(TypeUtils.isPackage("java")).isTrue(); - assertThat(TypeUtils.isPackage("java.util")).isTrue(); - assertThat(TypeUtils.isPackage("org.bbottema.javareflection")).isTrue(); - assertThat(TypeUtils.isPackage("java.lang.reflect")).isTrue(); - assertThat(TypeUtils.isPackage("org.junit.jupiter.api")).isTrue(); - assertThat(TypeUtils.isPackage("donkey.cake")).isFalse(); - assertThat(TypeUtils.isPackage("org.bbottema")).isFalse(); - } - - @Test - public void testReplaceInArray() { - Integer[] initial = new Integer[]{1, 2, 3, 4}; - Integer[] second = TypeUtils.replaceInArray(initial, 2, 2); - assertThat(second).isEqualTo(initial); - assertThat(second).isEqualTo(new Integer[]{1, 2, 2, 4}); - } - - @Test - public void testContainsAnnotation() { - final Method annotatedMethod = findAnnotatedMethod(); - assertThat(annotatedMethod.getParameterAnnotations().length).isEqualTo(3); - - List methodAnnotations = asList(annotatedMethod.getAnnotations()); - assertThat(TypeUtils.containsAnnotation(methodAnnotations, Nullable.class)).isFalse(); - assertThat(TypeUtils.containsAnnotation(methodAnnotations, NotNull.class)).isFalse(); // retention Class - assertThat(TypeUtils.containsAnnotation(methodAnnotations, MethodAnnotation.class)).isTrue(); // retention Runtime - - List param1Annotations = asList(annotatedMethod.getParameterAnnotations()[0]); - assertThat(TypeUtils.containsAnnotation(param1Annotations, ParamAnnotation1.class)).isTrue(); - assertThat(TypeUtils.containsAnnotation(param1Annotations, ParamAnnotation2.class)).isFalse(); - assertThat(TypeUtils.containsAnnotation(param1Annotations, ParamAnnotation3.class)).isFalse(); - - List param2Annotations = asList(annotatedMethod.getParameterAnnotations()[1]); - assertThat(TypeUtils.containsAnnotation(param2Annotations, ParamAnnotation1.class)).isFalse(); - assertThat(TypeUtils.containsAnnotation(param2Annotations, ParamAnnotation2.class)).isTrue(); - assertThat(TypeUtils.containsAnnotation(param2Annotations, ParamAnnotation3.class)).isFalse(); - - - List param3Annotations = asList(annotatedMethod.getParameterAnnotations()[2]); - assertThat(TypeUtils.containsAnnotation(param3Annotations, ParamAnnotation1.class)).isTrue(); - assertThat(TypeUtils.containsAnnotation(param3Annotations, ParamAnnotation2.class)).isTrue(); - assertThat(TypeUtils.containsAnnotation(param3Annotations, ParamAnnotation3.class)).isFalse(); - } - - private Method findAnnotatedMethod() { - return collectMethodsByName(AnnotationsHelper.class, Object.class, MATCH_ANY, "methodWithAnnotations").iterator().next(); - } -} \ No newline at end of file diff --git a/src/test/java/org/bbottema/javareflection/testmodel/A.java b/src/test/java/org/bbottema/javareflection/testmodel/A.java deleted file mode 100644 index 6b56462..0000000 --- a/src/test/java/org/bbottema/javareflection/testmodel/A.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.bbottema.javareflection.testmodel; - -@SuppressWarnings({"unused", "SameReturnValue", "WeakerAccess"}) -public abstract class A implements Foo { - - public Integer numberA; - Integer number_privateA; - - public A(Fruit f) { - } - - abstract String protectedMethod(); - - @SuppressWarnings("unused") - private String privateMethod() { - return "private 1"; - } - - public void bar() {} -} \ No newline at end of file diff --git a/src/test/java/org/bbottema/javareflection/testmodel/AnnotationsHelper.java b/src/test/java/org/bbottema/javareflection/testmodel/AnnotationsHelper.java deleted file mode 100644 index 4252649..0000000 --- a/src/test/java/org/bbottema/javareflection/testmodel/AnnotationsHelper.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.bbottema.javareflection.testmodel; - -import org.jetbrains.annotations.NotNull; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -public class AnnotationsHelper { - - @MethodAnnotation - @NotNull - public Integer methodWithAnnotations(@ParamAnnotation1 int i, - @ParamAnnotation2 Object o, - @ParamAnnotation1 @ParamAnnotation2 Moo m) { - return null; - } - - @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MethodAnnotation { } - @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface ParamAnnotation1 { } - @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface ParamAnnotation2 { } - @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface ParamAnnotation3 { } - -} diff --git a/src/test/java/org/bbottema/javareflection/testmodel/B.java b/src/test/java/org/bbottema/javareflection/testmodel/B.java deleted file mode 100644 index 65f0e41..0000000 --- a/src/test/java/org/bbottema/javareflection/testmodel/B.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.bbottema.javareflection.testmodel; - -@SuppressWarnings({"unused", "WeakerAccess"}) -public abstract class B extends A { - - public Integer numberB; - Integer number_privateB; - - public static Integer numberB_static; - - public B(Fruit f) { - super(f); - } - - @Override - String protectedMethod() { - return "protected 1"; - } -} diff --git a/src/test/java/org/bbottema/javareflection/testmodel/Bar.java b/src/test/java/org/bbottema/javareflection/testmodel/Bar.java deleted file mode 100644 index d493c51..0000000 --- a/src/test/java/org/bbottema/javareflection/testmodel/Bar.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.bbottema.javareflection.testmodel; - -public interface Bar { - void bar(); -} diff --git a/src/test/java/org/bbottema/javareflection/testmodel/C.java b/src/test/java/org/bbottema/javareflection/testmodel/C.java deleted file mode 100644 index eeb2402..0000000 --- a/src/test/java/org/bbottema/javareflection/testmodel/C.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.bbottema.javareflection.testmodel; - -@SuppressWarnings({"unused", "SameReturnValue", "WeakerAccess"}) -public class C extends B { - public Integer numberC; - Integer number_privateC; - - public C(Fruit f) { - super(f); - } - - public C(Pear p) { - super(p); - } - - @Override - public String foo(Double value, Fruit fruit, char c) { - return String.format("%s-%s-%s", value, fruit.getClass().getSimpleName(), c); - } - - @Override - String protectedMethod() { - return "protected 2"; - } - - @SuppressWarnings("unused") - private String privateMethod() { - return "private 2"; - } - - // specficially don't use Bean setter convention here, to avoid breaking existing Bean-related tests - public void updateNumberC(Integer value) { - numberC = value; - } - - // specficially don't use Bean setter convention here, to avoid breaking existing Bean-related tests - public void updateNumber_privateC(Integer value) { - number_privateC = value; - } -} \ No newline at end of file diff --git a/src/test/java/org/bbottema/javareflection/testmodel/Foo.java b/src/test/java/org/bbottema/javareflection/testmodel/Foo.java deleted file mode 100644 index 8b31d8c..0000000 --- a/src/test/java/org/bbottema/javareflection/testmodel/Foo.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.bbottema.javareflection.testmodel; - -public interface Foo extends Bar { - String foo(Double value, Fruit fruit, char c); -} diff --git a/src/test/java/org/bbottema/javareflection/testmodel/Fruit.java b/src/test/java/org/bbottema/javareflection/testmodel/Fruit.java deleted file mode 100644 index 5a8ef0b..0000000 --- a/src/test/java/org/bbottema/javareflection/testmodel/Fruit.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.bbottema.javareflection.testmodel; - -public abstract class Fruit { -} \ No newline at end of file diff --git a/src/test/java/org/bbottema/javareflection/testmodel/Kraa.java b/src/test/java/org/bbottema/javareflection/testmodel/Kraa.java deleted file mode 100644 index 7893523..0000000 --- a/src/test/java/org/bbottema/javareflection/testmodel/Kraa.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.bbottema.javareflection.testmodel; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.HashSet; -import java.util.List; - -public class Kraa { - private void testMethod( - Integer argOne, - @Nullable - List argTwo, - @Nonnull - HashSet argThree) { - } -} diff --git a/src/test/java/org/bbottema/javareflection/testmodel/Meta.java b/src/test/java/org/bbottema/javareflection/testmodel/Meta.java deleted file mode 100644 index 9158d4e..0000000 --- a/src/test/java/org/bbottema/javareflection/testmodel/Meta.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.bbottema.javareflection.testmodel; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -@Retention(RetentionPolicy.RUNTIME) -public @interface Meta { - String value(); -} diff --git a/src/test/java/org/bbottema/javareflection/testmodel/Moo.java b/src/test/java/org/bbottema/javareflection/testmodel/Moo.java deleted file mode 100644 index bce9a22..0000000 --- a/src/test/java/org/bbottema/javareflection/testmodel/Moo.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.bbottema.javareflection.testmodel; - -public class Moo extends Shmoo { - @Override - @Meta("Moo.method1-A") - protected void method1(Integer i) {} - @Meta("Moo.method1-B") - protected void method1(Object o) {} - @Meta("Moo.method1-C") - protected void method1(Object o, Integer i) {} - - @Meta("Moo.method2-A") - protected void method2(Object i) {} -} diff --git a/src/test/java/org/bbottema/javareflection/testmodel/Pear.java b/src/test/java/org/bbottema/javareflection/testmodel/Pear.java deleted file mode 100644 index 53b5261..0000000 --- a/src/test/java/org/bbottema/javareflection/testmodel/Pear.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.bbottema.javareflection.testmodel; - - -@SuppressWarnings("WeakerAccess") -public class Pear extends Fruit { -} \ No newline at end of file diff --git a/src/test/java/org/bbottema/javareflection/testmodel/Shmoo.java b/src/test/java/org/bbottema/javareflection/testmodel/Shmoo.java deleted file mode 100644 index a2c25c5..0000000 --- a/src/test/java/org/bbottema/javareflection/testmodel/Shmoo.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.bbottema.javareflection.testmodel; - -import java.util.Calendar; - -public class Shmoo { - @Meta("Shmoo.method1-A") - protected void method1(Integer i) {} - @Meta("Shmoo.method1-B") - protected void method1(String i) {} - @Meta("Shmoo.method1-C") - protected void method1(Double d, Calendar c) {} - - @Meta("Shmoo.method2-A") - protected void method2(Integer i) {} -} diff --git a/src/test/java/org/bbottema/javareflection/testmodel/Skree.java b/src/test/java/org/bbottema/javareflection/testmodel/Skree.java deleted file mode 100644 index 2f5dfe7..0000000 --- a/src/test/java/org/bbottema/javareflection/testmodel/Skree.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.bbottema.javareflection.testmodel; - -import java.util.Calendar; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; - -public class Skree { - public void methodWithArray(int[] ints) { - // - } - - public void methodWithCollection1(Iterable ints) { - // - } - - public void methodWithCollection2(Collection doubles) { - // - } - - public void methodWithCollection3(List ints) { - // - } - - public void methodWithCollection4(HashMap ints) { - // - } - - public void methodWithCollection5(String[] strings, Collection calendars) { - // - } - - public void methodWithoutCollection1(String strings, Calendar calendars) { - // - } - - public void methodWithoutCollection2(String s) { - // - } - - public void methodWithoutCollection3() { - // - } -} diff --git a/src/test/java/org/bbottema/javareflection/testmodel/reflectionutils/Bob.java b/src/test/java/org/bbottema/javareflection/testmodel/reflectionutils/Bob.java deleted file mode 100644 index 72fe9dc..0000000 --- a/src/test/java/org/bbottema/javareflection/testmodel/reflectionutils/Bob.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.bbottema.javareflection.testmodel.reflectionutils; - -import org.bbottema.javareflection.ReflectionUtils; - -public class Bob extends Pleb { - public final Class responseType; - - public Bob() { - this.responseType = ReflectionUtils.findParameterType(getClass(), Bob.class, 0); - } -} \ No newline at end of file diff --git a/src/test/java/org/bbottema/javareflection/testmodel/reflectionutils/Moo.java b/src/test/java/org/bbottema/javareflection/testmodel/reflectionutils/Moo.java deleted file mode 100644 index 817861e..0000000 --- a/src/test/java/org/bbottema/javareflection/testmodel/reflectionutils/Moo.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.bbottema.javareflection.testmodel.reflectionutils; - -import org.bbottema.javareflection.ReflectionUtils; - -public class Moo extends Schmoo { - public final Class responseType; - - public Moo() { - this.responseType = ReflectionUtils.findParameterType(getClass(), Bob.class, 0); - } -}; \ No newline at end of file diff --git a/src/test/java/org/bbottema/javareflection/testmodel/reflectionutils/Pleb.java b/src/test/java/org/bbottema/javareflection/testmodel/reflectionutils/Pleb.java deleted file mode 100644 index b06a455..0000000 --- a/src/test/java/org/bbottema/javareflection/testmodel/reflectionutils/Pleb.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.bbottema.javareflection.testmodel.reflectionutils; - -public class Pleb { - - } \ No newline at end of file diff --git a/src/test/java/org/bbottema/javareflection/testmodel/reflectionutils/Poo.java b/src/test/java/org/bbottema/javareflection/testmodel/reflectionutils/Poo.java deleted file mode 100644 index 2090839..0000000 --- a/src/test/java/org/bbottema/javareflection/testmodel/reflectionutils/Poo.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.bbottema.javareflection.testmodel.reflectionutils; - -public class Poo extends Bob { - - } \ No newline at end of file diff --git a/src/test/java/org/bbottema/javareflection/testmodel/reflectionutils/Schmoo.java b/src/test/java/org/bbottema/javareflection/testmodel/reflectionutils/Schmoo.java deleted file mode 100644 index d6c5433..0000000 --- a/src/test/java/org/bbottema/javareflection/testmodel/reflectionutils/Schmoo.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.bbottema.javareflection.testmodel.reflectionutils; - -public class Schmoo extends Poo { - } \ No newline at end of file diff --git a/src/test/java/org/bbottema/javareflection/util/MetaAnnotationExtractor.java b/src/test/java/org/bbottema/javareflection/util/MetaAnnotationExtractor.java deleted file mode 100644 index 78c7459..0000000 --- a/src/test/java/org/bbottema/javareflection/util/MetaAnnotationExtractor.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.bbottema.javareflection.util; - -import org.assertj.core.api.iterable.Extractor; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; - -public class MetaAnnotationExtractor implements Extractor { - private final Class annotationClass; - - public MetaAnnotationExtractor(Class annotationClass) { - this.annotationClass = annotationClass; - } - - @Override - public Annotation extract(Method input) { - return input.getAnnotation(annotationClass); - } -} diff --git a/src/test/java/org/bbottema/javareflection/util/graph/DijkstraTest.java b/src/test/java/org/bbottema/javareflection/util/graph/DijkstraTest.java deleted file mode 100644 index 8b06f84..0000000 --- a/src/test/java/org/bbottema/javareflection/util/graph/DijkstraTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.bbottema.javareflection.util.graph; - -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; - -public class DijkstraTest { - - @Test - @SuppressWarnings("ArraysAsListWithZeroOrOneArgument") - public void testFindShortestPathToAllOtherNodes() { - // define nodes - Node> nodeA = new Node>(byte.class); - Node> nodeB = new Node>(Integer.class); - Node> nodeC = new Node>(String.class); - Node> nodeD = new Node>(double.class); - Node> nodeE = new Node>(Double.class); - Node> nodeF = new Node>(boolean.class); - // define edges - nodeA.getToNodes().put(nodeB, 10); - nodeA.getToNodes().put(nodeC, 15); - nodeB.getToNodes().put(nodeD, 12); - nodeB.getToNodes().put(nodeF, 15); - nodeC.getToNodes().put(nodeE, 10); - nodeD.getToNodes().put(nodeE, 2); - nodeD.getToNodes().put(nodeF, 1); - nodeF.getToNodes().put(nodeE, 5); - - @SuppressWarnings("UnnecessaryLocalVariable") - Node> startingPoint = nodeA; - Set>> destinationNodes = new HashSet<>(); - destinationNodes.add(nodeB); - destinationNodes.add(nodeC); - destinationNodes.add(nodeD); - destinationNodes.add(nodeE); - destinationNodes.add(nodeF); - - Dijkstra.findShortestPathToAllOtherNodes(startingPoint); - - Map, List>>> expectedShortestPaths = new HashMap<>(); - expectedShortestPaths.put(Integer.class, Arrays.asList(nodeA)); - expectedShortestPaths.put(String.class, Arrays.asList(nodeA)); - expectedShortestPaths.put(double.class, Arrays.asList(nodeA, nodeB)); - expectedShortestPaths.put(Double.class, Arrays.asList(nodeA, nodeB, nodeD)); - expectedShortestPaths.put(boolean.class, Arrays.asList(nodeA, nodeB, nodeD)); - - for (Node> node : destinationNodes) { - assertThat(expectedShortestPaths.get(node.getType())).isEqualTo(node.getLeastExpensivePath()); - } - } -} diff --git a/src/test/java/org/bbottema/javareflection/util/graph/GraphHelperTest.java b/src/test/java/org/bbottema/javareflection/util/graph/GraphHelperTest.java deleted file mode 100644 index e724748..0000000 --- a/src/test/java/org/bbottema/javareflection/util/graph/GraphHelperTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.bbottema.javareflection.util.graph; - -import org.junit.jupiter.api.Test; - -import java.util.Arrays; - -import static org.assertj.core.api.Assertions.assertThat; - -public class GraphHelperTest { - - @Test - public void testFindAllPathsAscending() { - Node nodeA = new Node<>("A"); - Node nodeB = new Node<>("B"); - Node nodeC = new Node<>("C"); - Node nodeD = new Node<>("D"); - Node nodeE = new Node<>("E"); - Node nodeF = new Node<>("F"); - Node nodeX = new Node<>("X"); - Node nodeY = new Node<>("Y"); - Node nodeZ = new Node<>("Z"); - // define edges from source - nodeA.getToNodes().put(nodeE, 10); - nodeA.getToNodes().put(nodeB, 10); - nodeA.getToNodes().put(nodeC, 15); - nodeB.getToNodes().put(nodeE, 10); - nodeC.getToNodes().put(nodeA, 10); // cyclic path - nodeC.getToNodes().put(nodeE, 10); // cyclic path - // define other edges - nodeD.getToNodes().put(nodeA, 15); // to A but not reachable - nodeF.getToNodes().put(nodeE, 10); - nodeF.getToNodes().put(nodeD, 10); // cyclic path - // define low cost edges - nodeA.getToNodes().put(nodeX, 1); - nodeX.getToNodes().put(nodeY, 1); - nodeY.getToNodes().put(nodeZ, 1); - nodeZ.getToNodes().put(nodeE, 1); - - assertThat(GraphHelper.findAllPathsAscending(nodeA, nodeD)).isEmpty(); - assertThat(GraphHelper.findAllPathsAscending(nodeC, nodeF)).isEmpty(); - assertThat(GraphHelper.findAllPathsAscending(nodeA, nodeE)).containsExactly( - Arrays.asList(nodeX, nodeY, nodeZ, nodeE), - Arrays.asList(nodeE), - Arrays.asList(nodeB, nodeE), - Arrays.asList(nodeC, nodeE) - ); - } - - @Test - public void testFindReachableNodes() { - // define nodes - Node> nodeA = new Node>(byte.class); - Node> nodeB = new Node>(Integer.class); - Node> nodeC = new Node>(String.class); - Node> nodeD = new Node>(double.class); - Node> nodeE = new Node>(Double.class); - Node> nodeF = new Node>(boolean.class); - // define edges from source - nodeA.getToNodes().put(nodeB, 10); - nodeA.getToNodes().put(nodeC, 15); - nodeB.getToNodes().put(nodeE, 10); - nodeC.getToNodes().put(nodeA, 10); // cyclic path - // define other edges - nodeD.getToNodes().put(nodeA, 15); // to A but not reachable - nodeF.getToNodes().put(nodeE, 10); - nodeF.getToNodes().put(nodeD, 10); // cyclic path - - assertThat(GraphHelper.findReachableNodes(nodeA)).containsExactlyInAnyOrder(nodeB, nodeC, nodeE); - } -} \ No newline at end of file diff --git a/src/test/java/org/bbottema/javareflection/valueconverter/ValueConversionHelperTest.java b/src/test/java/org/bbottema/javareflection/valueconverter/ValueConversionHelperTest.java deleted file mode 100644 index 2820216..0000000 --- a/src/test/java/org/bbottema/javareflection/valueconverter/ValueConversionHelperTest.java +++ /dev/null @@ -1,461 +0,0 @@ -package org.bbottema.javareflection.valueconverter; - -import org.bbottema.javareflection.util.Function; -import org.bbottema.javareflection.util.graph.Node; -import org.bbottema.javareflection.valueconverter.ValueFunction.ValueFunctionImpl; -import org.jetbrains.annotations.NotNull; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import sun.reflect.generics.reflectiveObjects.NotImplementedException; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.Calendar; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - -@SuppressWarnings({"WrapperTypeMayBePrimitive", "ConstantConditions"}) -public class ValueConversionHelperTest { - - private static final Set> COMMON_TYPES = new HashSet>(asList(String.class, Integer.class, int.class, Float.class, - float.class, Double.class, double.class, Long.class, long.class, Byte.class, byte.class, Short.class, short.class, - Boolean.class, boolean.class, Character.class, char.class)); - - @SuppressWarnings("unused") - enum TestEnum { - ONE, TWO, THREE - } - - @BeforeEach - public void clearRuntimeTypes() { - ValueConversionHelper.resetDefaultConverters(); - } - - @Test - public void testIsCommonType() { - // basic commons - assertThat(ValueConversionHelper.isCommonType(String.class)).isTrue(); - assertThat(ValueConversionHelper.isCommonType(Integer.class)).isTrue(); - assertThat(ValueConversionHelper.isCommonType(int.class)).isTrue(); - assertThat(ValueConversionHelper.isCommonType(Float.class)).isTrue(); - assertThat(ValueConversionHelper.isCommonType(float.class)).isTrue(); - assertThat(ValueConversionHelper.isCommonType(Double.class)).isTrue(); - assertThat(ValueConversionHelper.isCommonType(double.class)).isTrue(); - assertThat(ValueConversionHelper.isCommonType(Long.class)).isTrue(); - assertThat(ValueConversionHelper.isCommonType(long.class)).isTrue(); - assertThat(ValueConversionHelper.isCommonType(Byte.class)).isTrue(); - assertThat(ValueConversionHelper.isCommonType(byte.class)).isTrue(); - assertThat(ValueConversionHelper.isCommonType(Short.class)).isTrue(); - assertThat(ValueConversionHelper.isCommonType(short.class)).isTrue(); - // limited commons - assertThat(ValueConversionHelper.isCommonType(Boolean.class)).isTrue(); - assertThat(ValueConversionHelper.isCommonType(boolean.class)).isTrue(); - assertThat(ValueConversionHelper.isCommonType(Character.class)).isTrue(); - assertThat(ValueConversionHelper.isCommonType(char.class)).isTrue(); - // no commons - assertThat(ValueConversionHelper.isCommonType(Math.class)).isFalse(); - assertThat(ValueConversionHelper.isCommonType(ValueConversionHelper.class)).isFalse(); - assertThat(ValueConversionHelper.isCommonType(ValueConversionHelper.class)).isFalse(); - assertThat(ValueConversionHelper.isCommonType(Calendar.class)).isFalse(); - } - - @Test - public void testCollectCompatibleTypes() { - // test that all commons types are convertible to all common types - for (Class basicCommonType : COMMON_TYPES) { - assertThat(ValueConversionHelper.collectCompatibleTargetTypes(basicCommonType)).containsAll(COMMON_TYPES); - } - - Set> types = ValueConversionHelper.collectCompatibleTargetTypes(String.class); - assertThat(types).containsAll(COMMON_TYPES); - - types = ValueConversionHelper.collectCompatibleTargetTypes(boolean.class); - assertThat(types).containsAll(COMMON_TYPES); - - types = ValueConversionHelper.collectCompatibleTargetTypes(Character.class); - assertThat(types).containsAll(COMMON_TYPES); - - types = ValueConversionHelper.collectRegisteredCompatibleTargetTypes(Calendar.class); - assertThat(types).containsExactly(Calendar.class); - types = ValueConversionHelper.collectCompatibleTargetTypes(Calendar.class); - assertThat(types.size()).isGreaterThan(10); // number depends on order of junit execution (due to dynamic runtime type registration) - assertThat(types).contains(String.class); - } - - @Test - public void testConvertObjectArrayClassOfQArray() { - // empty list - Object[] emptyArray = ValueConversionHelper.convert(new Object[] {}, new Class[] {}, true); - assertThat(emptyArray).isNotNull(); - assertThat(emptyArray).isEmpty(); - // asymmetric list - try { - ValueConversionHelper.convert(new Object[] { 1, 2, 3 }, new Class[] { String.class }, true); - fail("should not accept array arguments of different lengths!"); - } catch (IllegalStateException e) { - // OK - } - // list with inconvertible items, throwing exception for inconvertible values - try { - Calendar calendar = Calendar.getInstance(); - ValueConversionHelper.convert(new Object[] { 1, "blah", calendar }, new Class[] { String.class, Integer.class, Float.class }, false); - fail("should not accept inconvertible values!"); - } catch (IncompatibleTypeException e) { - // OK - } - // list with inconvertible items, keeping original for inconvertible values - Calendar calendar = Calendar.getInstance(); - Object[] result = ValueConversionHelper.convert(new Object[] { 1, "blah", calendar, 0 }, new Class[] { String.class, Integer.class, - Float.class, Boolean.class }, true); - assertThat(result).isNotNull(); - assertThat(result.length).isEqualTo(4); - assertThat(result[0]).isEqualTo("1"); - assertThat(result[1]).isEqualTo("blah"); - assertThat(result[2]).isEqualTo(calendar); - assertThat(result[3] instanceof Boolean).isTrue(); - assertThat((Boolean) result[3]).isFalse(); - } - - @SuppressWarnings("ConstantConditions") - @Test - public void testConvertObjectClassOfQ() { - // test null value - assertThat(ValueConversionHelper.convert(null, Number.class)).isNull(); - // test integer -> number (allowed) - Integer integer = 50; - assertThat(ValueConversionHelper.convert(integer, Number.class)).isEqualTo(integer); - // test with exact same type (allowed, should return the original value) - Calendar calendar = Calendar.getInstance(); - assertThat(ValueConversionHelper.convert(calendar, Calendar.class)).isEqualTo(calendar); - // test number -> integer (not allowed) - Number number = 100.5f; - Object o = ValueConversionHelper.convert(number, Integer.class); - assertThat(o).isNotEqualTo(number); - assertThat(o).isEqualTo(100); - // test to string conversion - assertThat(ValueConversionHelper.convert("a value", String.class)).isEqualTo("a value"); - assertThat(ValueConversionHelper.convert(number, String.class)).isEqualTo("100.5"); - // test from string to anything else conversion - assertThat(ValueConversionHelper.convert("a value", String.class)).isEqualTo("a value"); - assertThat(ValueConversionHelper.convert("false", boolean.class)).isFalse(); - assertThat((Object) ValueConversionHelper.convert("33", float.class)).isEqualTo(33f); - // test from character - Character chara = '5'; - char charb = '8'; - assertThat(ValueConversionHelper.convert(chara, Number.class).intValue()).isEqualTo(5); - assertThat((Object) ValueConversionHelper.convert(charb, float.class)).isEqualTo(8f); - // test from boolean - Boolean boola = false; - boolean boolb = true; - assertThat(ValueConversionHelper.convert(boola, Number.class).intValue()).isEqualTo(0); - assertThat((Object) ValueConversionHelper.convert(boolb, float.class)).isEqualTo(1f); - assertThat(ValueConversionHelper.convert(boola, String.class)).isEqualTo("false"); - assertThat(ValueConversionHelper.convert(boolb, String.class)).isEqualTo("true"); - // test for incompatibility error - try { - ValueConversionHelper.convert(false, Calendar.class); - fail("should not be able to convert value"); - } catch (IncompatibleTypeException e) { - // OK - } - try { - ValueConversionHelper.convert(Calendar.getInstance(), Number.class); - fail("should not be able to convert value"); - } catch (IncompatibleTypeException e) { - // OK - } - } - - @Test - public void testConvertNumberClassOfQ() { - assertThat(ValueConversionHelper.convert(null, boolean.class)).isNull(); - assertThat(ValueConversionHelper.convert(0, boolean.class)).isFalse(); - assertThat(ValueConversionHelper.convert(1, boolean.class)).isTrue(); - assertThat(ValueConversionHelper.convert(50, boolean.class)).isTrue(); - assertThat((Object) ValueConversionHelper.convert(50, float.class)).isEqualTo(50f); - assertThat((Object) ValueConversionHelper.convert(50, double.class)).isEqualTo(50d); - assertThat((Object) ValueConversionHelper.convert(50, long.class)).isEqualTo(50L); - assertThat((Object) ValueConversionHelper.convert(50, Integer.class)).isEqualTo(50); - assertThat((Object) ValueConversionHelper.convert(50, byte.class)).isEqualTo((byte) 50); - assertThat((Object) ValueConversionHelper.convert(50, short.class)).isEqualTo((short) 50); - assertThat((Object) ValueConversionHelper.convert(5, char.class)).isEqualTo('5'); - assertThat(ValueConversionHelper.convert(50, String.class)).isEqualTo("50"); - - try { - ValueConversionHelper.convert(50, Calendar.class); - fail("should not be able to convert value"); - } catch (IncompatibleTypeException e) { - // OK - } - } - - @Test - public void testConvertBooleanClassOfQ() { - assertThat(ValueConversionHelper.convert(null, Calendar.class)).isNull(); - assertThat(ValueConversionHelper.convert(false, boolean.class)).isFalse(); - assertThat(ValueConversionHelper.convert(true, boolean.class)).isTrue(); - assertThat(ValueConversionHelper.convert(true, boolean.class)).isTrue(); - assertThat(ValueConversionHelper.convert(true, String.class)).isEqualTo("true"); - assertThat(ValueConversionHelper.convert(false, String.class)).isEqualTo("false"); - assertThat((Object) ValueConversionHelper.convert(true, Integer.class)).isEqualTo(1); - assertThat((Object) ValueConversionHelper.convert(true, Float.class)).isEqualTo(1f); - assertThat(ValueConversionHelper.convert(true, Number.class).intValue()).isEqualTo(1); - assertThat((Object) ValueConversionHelper.convert(false, double.class)).isEqualTo(0d); - assertThat((Object) ValueConversionHelper.convert(false, Character.class)).isEqualTo('0'); - assertThat((Object) ValueConversionHelper.convert(true, Character.class)).isEqualTo('1'); - - try { - ValueConversionHelper.convert(false, Calendar.class); - fail("should not be able to convert value"); - } catch (IncompatibleTypeException e) { - // OK - } - } - - @Test - public void testConvertCharacterClassOfQ() { - assertThat(ValueConversionHelper.convert(null, Object.class)).isNull(); - assertThat((Object) ValueConversionHelper.convert('5', char.class)).isEqualTo('5'); - assertThat(ValueConversionHelper.convert('h', String.class)).isEqualTo("h"); - assertThat(ValueConversionHelper.convert('1', Boolean.class)).isTrue(); - assertThat(ValueConversionHelper.convert('0', Boolean.class)).isFalse(); - assertThat(ValueConversionHelper.convert('h', Boolean.class)).isTrue(); - assertThat((Object) ValueConversionHelper.convert('9', Integer.class)).isEqualTo(9); - assertThat(ValueConversionHelper.convert('9', Number.class).intValue()).isEqualTo(9); - assertThat((Object) ValueConversionHelper.convert('9', Double.class)).isEqualTo(9d); - assertThat((Object) ValueConversionHelper.convert("d", Integer.class)).isEqualTo(100); - - try { - ValueConversionHelper.convert('5', Calendar.class); - fail("should not be able to convert value"); - } catch (IncompatibleTypeException e) { - // OK - } - } - - @Test - public void testConvertStringClassOfQ() { - assertThat((Object) ValueConversionHelper.convert("0", Integer.class)).isEqualTo(0); - assertThat(ValueConversionHelper.convert(null, Integer.class)).isNull(); - assertThat((Object) ValueConversionHelper.convert("10", Integer.class)).isEqualTo(10); - assertThat((Object) ValueConversionHelper.convert("0", Float.class)).isEqualTo(0f); - assertThat((Object) ValueConversionHelper.convert("10", Float.class)).isEqualTo(10f); - assertThat((Object) ValueConversionHelper.convert("0", double.class)).isEqualTo(0d); - assertThat((Object) ValueConversionHelper.convert("10", double.class)).isEqualTo(10d); - assertThat(ValueConversionHelper.convert("0", Number.class).intValue()).isEqualTo(0); - assertThat(ValueConversionHelper.convert("10", Number.class).intValue()).isEqualTo(10); - assertThat(ValueConversionHelper.convert("0", Boolean.class)).isFalse(); - assertThat(ValueConversionHelper.convert("1", Boolean.class)).isTrue(); - assertThat(ValueConversionHelper.convert("true", Boolean.class)).isTrue(); - assertThat(ValueConversionHelper.convert("false", Boolean.class)).isFalse(); - assertThat((Object) ValueConversionHelper.convert("h", char.class)).isEqualTo('h'); - assertThat(ValueConversionHelper.convert("h", String.class)).isEqualTo("h"); - assertThat(ValueConversionHelper.convert("ONE", TestEnum.class)).isEqualTo(TestEnum.ONE); - assertThat(ValueConversionHelper.convert("h", Boolean.class)).isTrue(); - final UUID uuid = UUID.randomUUID(); - assertThat(ValueConversionHelper.convert(uuid.toString(), UUID.class)).isEqualTo(uuid); - try { - ValueConversionHelper.convert("falsef", Boolean.class); - fail("should not be able to convert value"); - } catch (IncompatibleTypeException e) { - // OK - } - try { - ValueConversionHelper.convert("h", Calendar.class); - fail("should not be able to convert value"); - } catch (IncompatibleTypeException e) { - // OK - } - try { - ValueConversionHelper.convert("hello", Calendar.class); - fail("should not be able to convert value"); - } catch (IncompatibleTypeException e) { - // OK - } - try { - ValueConversionHelper.convert("", int.class); - fail("should not be able to convert value"); - } catch (IncompatibleTypeException e) { - // OK - } - try { - ValueConversionHelper.convert("", UUID.class); - fail("should not be able to convert value"); - } catch (IncompatibleTypeException e) { - // OK - } - try { - ValueConversionHelper.convert("s", UUID.class); - fail("should not be able to convert value"); - } catch (IncompatibleTypeException e) { - // OK - } - try { - ValueConversionHelper.convert("s-s-s-s-s", UUID.class); - fail("should not be able to convert value"); - } catch (IncompatibleTypeException e) { - // OK - } - } - - @Test - public void testConvertEnum() { - assertThat(ValueConversionHelper.convert(null, TestEnum.class)).isNull(); - assertThat(ValueConversionHelper.convert("ONE", TestEnum.class)).isEqualTo(TestEnum.ONE); - try { - ValueConversionHelper.convert("5", TestEnum.class); - fail("should not be able to convert value"); - } catch (IncompatibleTypeException e) { - // OK - } - } - - @SuppressWarnings("unchecked") - @Test - public void testConvertNumber() { - assertThat(ValueConversionHelper.convert(null, Integer.class)).isNull(); - assertThat((Object) ValueConversionHelper.convert("1", Integer.class)).isEqualTo(1); - assertThat((Object) ValueConversionHelper.convert("1", Float.class)).isEqualTo(1f); - assertThat((Object) ValueConversionHelper.convert("1", Double.class)).isEqualTo(1d); - assertThat((Object) ValueConversionHelper.convert("1", Byte.class)).isEqualTo((byte) 1); - assertThat(ValueConversionHelper.convert("1", Number.class).intValue()).isEqualTo(1); - assertThat((Object) ValueConversionHelper.convert("1", short.class)).isEqualTo((short) 1); - assertThat((Object) ValueConversionHelper.convert("1", long.class)).isEqualTo(1L); - assertThat(ValueConversionHelper.convert("1", BigDecimal.class)).isEqualTo(BigDecimal.valueOf(1)); - assertThat(ValueConversionHelper.convert("1", BigInteger.class)).isEqualTo(BigInteger.valueOf(1)); - try { - ValueConversionHelper.convert("", Integer.class); - fail("should not be able to convert value"); - } catch (IncompatibleTypeException e) { - // OK - } - try { - ValueConversionHelper.convert("1", (Class) (Object) Calendar.class); - fail("should not be able to convert value"); - } catch (IncompatibleTypeException e) { - // OK - } - try { - ValueConversionHelper.convert("1", CustomNumber.class); - fail("should not be able to convert value"); - } catch (IncompatibleTypeException e) { - // OK - } - } - - @SuppressWarnings("serial") - private static class CustomNumber extends Number { - - @Override - public int intValue() { - throw new NotImplementedException(); - } - - @Override - public long longValue() { - throw new NotImplementedException(); - } - - @Override - public float floatValue() { - throw new NotImplementedException(); - } - - @Override - public double doubleValue() { - throw new NotImplementedException(); - } - } - - @Test - public void testIsPrimitiveNumber() { - assertThat(ValueConversionHelper.isPrimitiveNumber(char.class)).isFalse(); - assertThat(ValueConversionHelper.isPrimitiveNumber(int.class)).isTrue(); - assertThat(ValueConversionHelper.isPrimitiveNumber(float.class)).isTrue(); - assertThat(ValueConversionHelper.isPrimitiveNumber(double.class)).isTrue(); - assertThat(ValueConversionHelper.isPrimitiveNumber(long.class)).isTrue(); - assertThat(ValueConversionHelper.isPrimitiveNumber(byte.class)).isTrue(); - assertThat(ValueConversionHelper.isPrimitiveNumber(short.class)).isTrue(); - assertThat(ValueConversionHelper.isPrimitiveNumber(boolean.class)).isFalse(); - assertThat(ValueConversionHelper.isPrimitiveNumber(Calendar.class)).isFalse(); - assertThat(ValueConversionHelper.isPrimitiveNumber(Boolean.class)).isFalse(); - assertThat(ValueConversionHelper.isPrimitiveNumber(Character.class)).isFalse(); - assertThat(ValueConversionHelper.isPrimitiveNumber(Integer.class)).isFalse(); - assertThat(ValueConversionHelper.isPrimitiveNumber(Number.class)).isFalse(); - } - - @Test - public void testCollectTypeCompatibleNodes() { - ValueConversionHelper.registerValueConverter(new DummyValueConverter(Fruit.class, Vehicle.class)); - ValueConversionHelper.registerValueConverter(new DummyValueConverter(Fruit.class, Car.class)); - ValueConversionHelper.registerValueConverter(new DummyValueConverter(Fruit.class, Leon.class)); - ValueConversionHelper.registerValueConverter(new DummyValueConverter(Vehicle.class, Fruit.class)); - ValueConversionHelper.registerValueConverter(new DummyValueConverter(Car.class, Apple.class)); - ValueConversionHelper.registerValueConverter(new DummyValueConverter(Leon.class, Elstar.class)); - - Set>> result = ValueConversionHelper.collectTypeCompatibleNodes(Car.class); - - assertThat(result).containsExactlyInAnyOrder( - new Node>(Car.class), - new Node>(Leon.class) - ); - } - - @Test - public void testConversionOverInstanceForSameType() { - assertThat(ValueConversionHelper.convert("moo", String.class)).isEqualTo("moo"); - - ValueConversionHelper.registerValueConverter(new ValueFunctionImpl<>(String.class, String.class, new Function() { - @Override - public String apply(String value) { - return format("--%s--", value); - } - })); - - assertThat(ValueConversionHelper.convert("moo", String.class)).isEqualTo("--moo--"); - } - - class Fruit{} - class Apple extends Fruit{} - class Elstar extends Apple{} - - class Vehicle{} - class Car extends Vehicle{} - class Leon extends Car{} - - @SuppressWarnings("unchecked") - public static class DummyValueConverter implements ValueFunction { - - private final Class fromType; - private final Class targetType; - - public DummyValueConverter(Class fromType, Class targetType) { - this.fromType = fromType; - this.targetType = targetType; - } - - @NotNull - @Override - public Class getFromType() { - return (Class) fromType; - } - - @NotNull - @Override - public Class getTargetType() { - return (Class) targetType; - } - - @NotNull - @Override - public Class convertValue(Object value) { - throw new AssertionError("This method should not be used"); - } - } -} \ No newline at end of file diff --git a/src/test/java/org/bbottema/javareflection/valueconverter/converters/StringToDateFunctionTest.java b/src/test/java/org/bbottema/javareflection/valueconverter/converters/StringToDateFunctionTest.java deleted file mode 100644 index 74d2d36..0000000 --- a/src/test/java/org/bbottema/javareflection/valueconverter/converters/StringToDateFunctionTest.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.bbottema.javareflection.valueconverter.converters; - -import org.junit.jupiter.api.Test; - -import java.util.GregorianCalendar; - -import static java.util.Calendar.MAY; -import static org.assertj.core.api.Assertions.assertThat; -import static org.bbottema.javareflection.valueconverter.converters.StringConverters.StringToDateFunction; - -public class StringToDateFunctionTest { - - @Test - public void testStringToDateConversion() { - final StringToDateFunction converter = new StringToDateFunction(); - assertThat(converter.apply("2011-5-4")).isEqualTo(new GregorianCalendar(2011, MAY, 4).getTime()); - assertThat(converter.apply("2011-5-14")).isEqualTo(new GregorianCalendar(2011, MAY, 14).getTime()); - assertThat(converter.apply("2011-05-4")).isEqualTo(new GregorianCalendar(2011, MAY, 4).getTime()); - assertThat(converter.apply("2011-05-14")).isEqualTo(new GregorianCalendar(2011, MAY, 14).getTime()); - assertThat(converter.apply("2011-5-4 05:10")).isEqualTo(new GregorianCalendar(2011, MAY, 4, 5, 10).getTime()); - assertThat(converter.apply("2011-5-14 05:10")).isEqualTo(new GregorianCalendar(2011, MAY, 14, 5, 10).getTime()); - assertThat(converter.apply("2011-05-4 05:10")).isEqualTo(new GregorianCalendar(2011, MAY, 4, 5, 10).getTime()); - assertThat(converter.apply("2011-05-14 05:10")).isEqualTo(new GregorianCalendar(2011, MAY, 14, 5, 10).getTime()); - assertThat(converter.apply("2011-5-4 5:4")).isEqualTo(new GregorianCalendar(2011, MAY, 4, 5, 4).getTime()); - assertThat(converter.apply("2011-5-14 5:4")).isEqualTo(new GregorianCalendar(2011, MAY, 14, 5, 4).getTime()); - assertThat(converter.apply("2011-05-4 5:4")).isEqualTo(new GregorianCalendar(2011, MAY, 4, 5, 4).getTime()); - assertThat(converter.apply("2011-05-14 5:4")).isEqualTo(new GregorianCalendar(2011, MAY, 14, 5, 4).getTime()); - } -} \ No newline at end of file