diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..8a1fa2a
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,23 @@
+name: Build
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+
+jobs:
+ maven:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Temurin JDK 21
+ uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: '21'
+ cache: maven
+
+ - name: Build with Maven
+ run: mvn -B -Dmaven.repo.local=.m2 clean verify
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..c2c3d45
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,30 @@
+# Repository Guidelines
+
+## Project Structure & Module Organization
+- `src/main/java/org/azeckoski/reflectutils/` hosts the runtime code; key subpackages include `converters`, `transcoders` (Jackson-backed JSON/XML support), `annotations`, and `interfaces`.
+- Caching and core helpers live alongside the public APIs; caches prefer `WeakHashMap`/`ConcurrentHashMap` implementations—new utilities should follow this pattern.
+- Tests under `src/test/java` mirror the production layout; reuse fixtures in `classes` rather than inventing bespoke beans.
+- Build outputs land in `target/`; always run `mvn clean` before committing to avoid stray artifacts.
+
+## Build, Test, and Development Commands
+- `mvn clean verify` compiles against JDK 21, runs the JUnit 5 suite, and produces coverage via JaCoCo.
+- `mvn -DskipTests package` is handy for packaging when tests are covered elsewhere.
+- `mvn -P release deploy` signs and stages artifacts to OSSRH; ensure credentials exist in `~/.m2/settings.xml`.
+
+## Coding Style & Naming Conventions
+- Use 4-space indentation with braces on the same line; prefer explicit types over `var` unless readability improves.
+- Embrace modern Java 21 features (records, pattern matching) when they simplify code, but keep APIs source-compatibility friendly.
+- New subpackages should have concise, descriptive names (e.g., `cache`, `transcoders.jackson`).
+- Source files are UTF-8; group imports by domain and keep static imports separate.
+
+## Testing Guidelines
+- Tests are JUnit 5 (`@Test`), using `org.junit.jupiter.api.Assertions` statics.
+- For JSON/XML scenarios, assert using decoded `Map` representations rather than string equality.
+- Favor focused unit tests over giant integration fixtures; extend or reuse helpers in `classes` and `annotations`.
+- Run `mvn clean verify` before opening a PR; capture any CI-specific flakes in the PR description.
+
+## Commit & Pull Request Guidelines
+- Write imperative, scoped commit messages (e.g., `Replace beanutils cache with WeakHashMap`).
+- Reference GitHub issues in branch names or PR titles (e.g., `modernize/jdk21-cache`).
+- Document behavior or API changes in the PR body; include sample payloads when serializer output changes.
+- Keep `target/` and other build outputs out of commits; prefer smaller PRs that are easy to review.
diff --git a/README.md b/README.md
index 31ec2f2..9994936 100644
--- a/README.md
+++ b/README.md
@@ -1,97 +1,45 @@
# reflectutils
-A set of reflection utilities and miscellaneous utilities related to working with classes and their fields with no dependencies which is compatible with java 1.5 and generics (and the higher versions of Java as well).
-# Features:
-These are built to be compatible with Apache Commons BeanUtils (http://commons.apache.org/proper/commons-beanutils/) and the nesting structure works the same, refer to the apache BeanUtils? project docs for details. Support for Apache DynaClass? / DynaBean? is included. Current users of beanutils should be able to drop in these utilities and gain the functionality with minimal code changes.
-
-Handles field operations for properties (getters and setters), partial properties (only getter or only setter), and fields. This is configurable to use the fields only, properties only, or the hybrid approach (default). This improves upon the BeanUtils? limitation of handling only properties or the Google utilities limitation of handling only fields.
-
-Getting and setting fields supports simple, nested, indexed, and mapped values
-
-**Simple:** Get/set a field in a bean (or map), Example: "title", "id"
-**Nested:** Get/set a field in a bean which is contained in another bean, Example: "someBean.title", "someBean.id"
-**Indexed:** Get/set a list/array item by index in a bean, Example: "myList1?", "anArray2?"
-**Mapped:** Get/set a map entry by key in a bean, Example: "myMap(key)", "someMap(thing)"
-
-Includes support for dealing with annotations and working with fields which have annotations on them. Methods for finding fields with an annotation and finding all annotations in a class or on a fields are included.
-
-Includes support for deep cloning, deep copying, and populating objects using auto-conversion. Also includes support for fuzzy copies where object data can be copied from one object to another without the objects being the same type.
-
-Also includes an extendable conversion system for converting between java types. This system also handles conversions between arrays, maps, collections, enums, and scalars and improves upon the apache system by handling more types and handling object holders. Support for construction of any class and a set of utilities for determining what types of objects you are working with are also included. A method for executing a specific constructor can be used if more control is needed.
-
-Includes transcoders (encoder/decoder) for conversion of class data to and from JSON and XML. The transcoders are clean and simple and work with any type of object. They will default to converting the incoming data into maps of simple java objects but these can be converted to the correct objects using the reflection utilities if desired.
-
-The utilities cache reflection data for high performance operation but uses weak/soft caching to avoid holding open ClassLoaders? and causing the caches to exist in memory permanently. The ability to override the caching mechanism with your own is supported.
-
-The utilities are modular and are meant to be extendable and overridable. All methods are protected or public so that the various utility classes can be easily overridden if needed.
-
-Sample code:
-Examples operate on the class at the bottom (TestEntity). There are more samples in the javadocs.
-
- Getting a value from an object field
- TestEntity thing = new TestEntity();
- Object value = ReflectUtils.getInstance().getFieldValue(thing, "entityId");
- // value will be "33"
- Setting a value on an object field
- TestEntity thing = new TestEntity();
- ReflectUtils.getInstance().setFieldValue(thing, "entityId", 33);
- // value of thing.getEntityId() will be "33", value is autoconverted into the right type
- Setting a nested value on an object field
- Object thing = new HashMap(); // using a hashmap for simplicity here, could easily be nested POJOs
- ReflectUtils.getInstance().setFieldValue(thing, "person.contactInfo.name", "aaronz");
- // the value of the name field which is on the object in the contactInfo field which is on the object in the person field on the thing object is set to "aaronz"
- Constructing classes
- List l = ReflectUtils.getInstance().constructClass(List.class);
- Class> clazz = TestEntity.class;
- Object o = ReflectUtils.getInstance().constructClass(clazz);
- // o will be an instance of TestEntity
- TestEntity?.class (comes directly from the test cases)
-
- public class TestEntity {
- private Long id = new Long(3);
- private String entityId = "33";
- @TestAnnote
- private String extra = null;
- private Boolean bool = null;
- private String[] sArray = {"1","2"};
-
- public String getPrefix() {
- return "crud";
- }
- public String createEntity(Object entity) {
- return "1";
- }
- @TestAnnote
- public String getEntityId() {
- return entityId;
- }
- public void setEntityId(String entityId) {
- this.entityId = entityId;
- }
- public Long getId() {
- return id;
- }
- public void setId(Long id) {
- this.id = id;
- }
- @TestAnnoteField1
- public String getExtra() {
- return extra;
- }
- @TestAnnoteField2("TEST")
- public void setExtra(String extra) {
- this.extra = extra;
- }
- public String[] getSArray() {
- return sArray;
- }
- public void setSArray(String[] array) {
- sArray = array;
- }
- public Boolean getBool() {
- return bool;
- }
- public void setBool(Boolean bool) {
- this.bool = bool;
- }
- }
+Modern reflection utilities for Java 21+, providing convenient field/property access, deep cloning, conversion helpers, and lightweight JSON/XML transcoders powered by Jackson. The library is dependency-light, thread-safe, and designed to integrate with contemporary JVM applications.
+
+## Highlights
+- Unified field access: read/write simple, nested, indexed, or mapped properties using `FieldUtils` and `ReflectUtils`.
+- Pluggable caching: metadata caches default to `WeakHashMap` to avoid classloader leaks while keeping lookups fast.
+- Type conversion: `ConversionUtils` covers primitives, collections, maps, enums, arrays, and common I/O types.
+- Jackson-based transcoders: encode/decode arbitrary objects to JSON or XML via Jackson `ObjectMapper`/`XmlMapper`.
+- Extensible annotations: helpers detect custom annotations (e.g., `@ReflectTransient`) when building metadata.
+
+## Requirements
+- JDK 21 or newer
+- Maven 3.9+
+
+## Build & Test
+```bash
+mvn clean verify
+```
+This runs the JUnit 5 suite and generates a JaCoCo coverage report in `target/site/jacoco`.
+
+For a quicker iteration cycle:
+```bash
+mvn -DskipTests package
+```
+
+## Quick Start
+```java
+ReflectUtils reflect = ReflectUtils.getInstance();
+TestBean bean = new TestBean();
+reflect.setFieldValue(bean, "address.city", "Lisbon");
+String city = (String) reflect.getFieldValue(bean, "address.city");
+
+JSONTranscoder transcoder = new JSONTranscoder(true, true, true);
+Map meta = Map.of("source", "demo");
+String json = transcoder.encode(bean, "bean", meta);
+Map decoded = transcoder.decode(json);
+```
+
+## Contributing
+- Follow the guidelines in `AGENTS.md` for style, testing, and PR expectations.
+- Submit issues or PRs via GitHub; keep changes focused and well-tested (`mvn clean verify`).
+
+## License
+Apache License 2.0. See `LICENSE` for details.
diff --git a/pom.xml b/pom.xml
index b2a27ed..3519ea5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,356 +1,291 @@
-
+4.0.0
+
org.azeckoskireflectutils
+ 0.10.0-SNAPSHOTjar
- 0.9.21-SNAPSHOT
+
Reflection Utilities
- http://code.google.com/p/reflectutils/
+
+ Modern reflection helpers for Java 21+, including conversion utilities and lightweight transcoders.
+
+ https://github.com/azeckoski/reflectutils2006
- A set of reflection utilities and miscellaneous utilities related to
- working with classes and their fields with no dependencies which is compatible
- with java 1.5 and generics. The package was originally created by Aaron Zeckoski
- for the Sakai Project and Generic DAO project but was repackaged to make it
- distributable by request. It is used in the RSF framework
- (http://www2.caret.cam.ac.uk/rsfwiki/) and the EntityBroker among other things.
- Note about the BeanUtils provided dependency: BeanUtils is not required if you are
- not using it in your project. Note about the Hibernate provided dependency:
- Hibernate is not required if you are not using it in your project.
-
- 1.8.0
- UTF-8
-
+
- Apache License 2
- http://www.apache.org/licenses/LICENSE-2.0
- For source code
+ Apache License, Version 2.0
+ https://www.apache.org/licenses/LICENSE-2.0
+ repoCreative Commons Attribution 3.0
- http://creativecommons.org/licenses/by-sa/3.0/
- For documentation and other materials
+ https://creativecommons.org/licenses/by/3.0/
+ repo
+
aaronz@vt.eduAaron Zeckoskiazeckoski@vt.edu
- http://tinyurl.com/azprofileZeckoski
- http://zeckoski.org/
+ https://zeckoski.org/
- Project ManagerArchitectDeveloper
- 0
+ UTC
+
+
+ https://github.com/azeckoski/reflectutils
+ scm:git:https://github.com/azeckoski/reflectutils.git
+ scm:git:git@github.com:azeckoski/reflectutils.git
+ HEAD
+
+
ossrh
- https://oss.sonatype.org/content/repositories/snapshots
+ https://s01.oss.sonatype.org/content/repositories/snapshots/ossrh
- https://oss.sonatype.org/service/local/staging/deploy/maven2/
+ https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/
-
- local
- Local Test
- file:///tmp/reflectutils/site
-
-
-
- https://github.com/azeckoski/reflectutils
- scm:git:git@github.com:azeckoski/reflectutils.git
- scm:git:git@github.com:azeckoski/reflectutils.git
- HEAD
-
+
+ UTF-8
+ UTF-8
+ 21
+ true
+ 3.12.1
+ 3.2.5
+ 3.2.5
+ 3.6.0
+ 3.3.0
+ 3.3.0
+ 3.5.0
+ 0.8.12
+ 5.11.0
+
+
+
+
+
+ org.junit
+ junit-bom
+ ${junit.jupiter.version}
+ pom
+ import
+
+
+
+
-
- commons-beanutils
- commons-beanutils
- ${beanutils.version}
- provided
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.17.1
+
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-xml
+ 2.17.1
+
- junit
- junit
- 3.8.1
+ org.junit.jupiter
+ junit-jupitertest
-
-
-
-
- release
-
-
-
- org.apache.maven.plugins
- maven-source-plugin
-
-
- attach-sources
-
- jar-no-fork
-
-
-
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
-
-
- attach-javadocs
-
- jar
-
-
-
-
-
- org.apache.maven.plugins
- maven-gpg-plugin
-
-
- sign-artifacts
- verify
-
- sign
-
-
-
-
-
- org.sonatype.plugins
- nexus-staging-maven-plugin
- true
-
- ossrh
- https://oss.sonatype.org/
- true
-
-
-
-
-
-
+
-
-
-
- ${basedir}
-
- *.txt
- src/main/**/*.java
- src/main/**/*.html
- src/main/**/*.xml
- src/main/**/*.properties
- src/test/**/*.java
-
-
-
-
org.apache.maven.plugins
- maven-source-plugin
- 2.2.1
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
- 2.9.1
-
- ${basedir}/src/main/java/overview.html
-
- http://java.sun.com/j2se/1.5.0/docs/api/
- http://commons.apache.org/beanutils/apidocs/
-
- true
- true
- -Xdoclint:none
-
-
-
- org.apache.maven.plugins
- maven-release-plugin
- 2.5
-
- true
- false
- release
- deploy
-
-
-
- org.sonatype.plugins
- nexus-staging-maven-plugin
- 1.6.3
-
-
- org.apache.maven.plugins
- maven-gpg-plugin
- 1.5
-
-
- org.codehaus.mojo
- cobertura-maven-plugin
- 2.0
+ maven-compiler-plugin
+ ${maven.compiler.plugin.version}org.apache.maven.pluginsmaven-surefire-plugin
- 2.18.1
+ ${maven.surefire.plugin.version}org.apache.maven.plugins
- maven-pmd-plugin
- 3.4
+ maven-failsafe-plugin
+ ${maven.failsafe.plugin.version}org.apache.maven.plugins
- maven-changelog-plugin
- 2.3
+ maven-javadoc-plugin
+ ${maven.javadoc.plugin.version}org.apache.maven.plugins
- maven-jxr-plugin
- 2.5
-
-
- org.codehaus.mojo
- jdepend-maven-plugin
- 2.0
+ maven-source-plugin
+ ${maven.source.plugin.version}org.apache.maven.plugins
- maven-project-info-reports-plugin
- 2.8
-
-
- org.codehaus.mojo
- taglist-maven-plugin
- 2.4
+ maven-jar-plugin
+ ${maven.jar.plugin.version}
-
+
org.apache.maven.plugins
- maven-surefire-plugin
- 2.18.1
-
-
- **/Test*.java
-
-
- **/*Test.java
-
-
+ maven-enforcer-plugin
+ ${maven.enforcer.plugin.version}
+
+
+ enforce-java
+
+ enforce
+
+
+
+
+ [21,)
+
+
+
+
+
+
-
-
-
-
-
-
+ org.apache.maven.pluginsmaven-compiler-plugin
- 1.5
- 1.5
+ ${maven.compiler.release}
+ ${maven.compiler.parameters}
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
-
-
- org.codehaus.mojo
- cobertura-maven-plugin
-
-
-
org.apache.maven.plugins
- maven-pmd-plugin
+ maven-jar-plugin
- 1.5
-
- /rulesets/java/basic.xml
- /rulesets/java/codesize.xml
- /rulesets/java/design.xml
- /rulesets/java/finalizers.xml
- /rulesets/java/imports.xml
- /rulesets/java/logging-java.xml
- /rulesets/java/migrating.xml
- /rulesets/java/strings.xml
-
- /rulesets/java/unusedcode.xml
-
- xml
- true
- utf-8
- 100
+
+
+ true
+
+
+
-
org.apache.maven.plugins
- maven-changelog-plugin
+ maven-surefire-plugin
- range
- 90
+ false
+
-
- org.codehaus.mojo
- taglist-maven-plugin
+ org.apache.maven.plugins
+ maven-failsafe-plugin
-
- TODO
- FIXME
- @deprecated
-
+ false
+
-
- org.codehaus.mojo
- jdepend-maven-plugin
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco.version}
+
+
+
+ prepare-agent
+
+
+
+ report
+ verify
+
+ report
+
+
+
+
-
org.apache.maven.plugins
- maven-jxr-plugin
+ maven-javadoc-plugin
- true
- apidocs
+ false
+ ${project.build.sourceEncoding}
+ none
+
+ https://docs.oracle.com/en/java/javase/21/docs/api/
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
-
+
+
+
+
+ release
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ 3.2.4
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+ org.sonatype.plugins
+ nexus-staging-maven-plugin
+ 1.6.13
+ true
+
+ ossrh
+ https://s01.oss.sonatype.org/
+ true
+
+
+
+
+
+
diff --git a/readme.txt b/readme.txt
deleted file mode 100644
index 27081ba..0000000
--- a/readme.txt
+++ /dev/null
@@ -1,61 +0,0 @@
-These are the reflection utilities for java 1.5 written by Aaron Zeckoski (aaronz@vt.edu)
-
-Building this project and putting it in your repository (maven2: http://maven.apache.org/):
-Maven2: run "mvn clean install" from the root source directory of this project
-
-See the javadocs and source code (included in the jar and online) and the project website.
-http://code.google.com/p/reflectutils/
-
-A set of reflection utilities and miscellaneous utilities related to working with classes and their fields with no dependencies which is compatible with java 1.5 and generics.
-
-Features:
-These are built to be compatible with Apache Commons BeanUtils and the nesting structure works the same, refer to the apache BeanUtils? project docs for details. Support for Apache DynaClass? / DynaBean? is included. Current users of beanutils should be able to drop in these utilities and gain the functionality with minimal code changes.
-
-Handles field operations for properties (getters and setters), partial properties (only getter or only setter), and fields. This is configurable to use the fields only, properties only, or the hybrid approach (default). This improves upon the BeanUtils? limitation of handling only properties or the Google utilities limitation of handling only fields.
-
-Getting and setting fields supports simple, nested, indexed, and mapped values
-
- * Simple: Get/set a field in a bean (or map), Example: "title", "id"
- * Nested: Get/set a field in a bean which is contained in another bean, Example: "someBean.title", "someBean.id"
- * Indexed: Get/set a list/array item by index in a bean, Example: "myList1?", "anArray2?"
- * Mapped: Get/set a map entry by key in a bean, Example: "myMap(key)", "someMap(thing)"
-
-Includes support for dealing with annotations and working with fields which have annotations on them. Methods for finding fields with an annotation and finding all annotations in a class or on a fields are included.
-
-Includes support for deep cloning, deep copying, and populating objects using auto-conversion. Also includes support for fuzzy copies where object data can be copied from one object to another without the objects being the same type.
-
-Also includes an extendable conversion system for converting between java types. This system also handles conversions between arrays, maps, collections, enums, and scalars and improves upon the apache system by handling more types and handling object holders. Support for construction of any class and a set of utilities for determining what types of objects you are working with are also included. A method for executing a specific constructor can be used if more control if needed.
-
-Includes transcoders (encoder/decoder) for conversion of class data to and from JSON and XML. The transcoders are clean and simple and work with any type of object. They will default to converting the incoming data into maps of simple java objects but these can be converted to the correct objects using the reflection utilities if desired.
-
-The utilities cache reflection data for high performance operation but uses weak/soft caching to avoid holding open ClassLoaders? and causing the caches to exist in memory permanently. The ability to override the caching mechanism with your own is supported.
-
-The utilities are modular and are meant to be extendable and overridable. All methods are protected or public so that the various utility classes can be easily overridden if needed.
-Sample code:
-
-Examples operate on the class at the bottom (TestEntity?). There are more samples in the guide.
-
- * Getting a value from an object field
-
- TestEntity thing = new TestEntity();
- Object value = ReflectUtils.getInstance().getFieldValue(thing, "entityId");
- // value will be "33"
-
- * Setting a value on an object field
-
- TestEntity thing = new TestEntity();
- ReflectUtils.getInstance().setFieldValue(thing, "entityId", 33);
- // value of thing.getEntityId() will be "33", value is autoconverted into the right type
-
- * Setting a nested value on an object field
-
- Object thing = new HashMap(); // using a hashmap for simplicity here, could easily be nested POJOs
- ReflectUtils.getInstance().setFieldValue(thing, "person.contactInfo.name", "aaronz");
- // the value of the name field which is on the object in the contactInfo field which is on the object in the person field on the thing object is set to "aaronz"
-
- * Constructing classes
-
- List l = ReflectUtils.getInstance().constructClass(List.class);
- Class> clazz = TestEntity.class;
- Object o = ReflectUtils.getInstance().constructClass(clazz);
- // o will be an instance of TestEntity
diff --git a/src/main/java/org/azeckoski/reflectutils/ClassData.java b/src/main/java/org/azeckoski/reflectutils/ClassData.java
index dc8fcf1..a52d454 100644
--- a/src/main/java/org/azeckoski/reflectutils/ClassData.java
+++ b/src/main/java/org/azeckoski/reflectutils/ClassData.java
@@ -16,8 +16,10 @@
import java.io.Serializable;
import java.lang.annotation.Annotation;
+import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
+import java.lang.reflect.InaccessibleObjectException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
@@ -135,20 +137,8 @@ protected void getAllThings(final Class> type, final List fList, final
for (final Field field : fields) {
if (field != null) {
int modifiers = field.getModifiers();
- if (Modifier.isPublic(modifiers)) {
+ if (Modifier.isPublic(modifiers) || trySetAccessible(field)) {
fList.add(field);
- } else {
- try {
- AccessController.doPrivileged(new PrivilegedAction() {
- public T run() {
- field.setAccessible(true);
- return null;
- }
- });
- fList.add(field);
- } catch (SecurityException e) {
- // oh well, this does not get added then
- }
}
}
}
@@ -156,20 +146,8 @@ public T run() {
for (final Method method : methods) {
if (method != null) {
int modifiers = method.getModifiers();
- if (Modifier.isPublic(modifiers)) {
+ if (Modifier.isPublic(modifiers) || trySetAccessible(method)) {
mList.add(method);
- } else {
- try {
- AccessController.doPrivileged(new PrivilegedAction() {
- public T run() {
- method.setAccessible(true);
- return null;
- }
- });
- mList.add(method);
- } catch (SecurityException e) {
- // oh well, this does not get added then
- }
}
}
}
@@ -178,20 +156,8 @@ public T run() {
// need to avoid the Object constructor
if (!Object.class.equals(type) && constructor != null) {
int modifiers = constructor.getModifiers();
- if (Modifier.isPublic(modifiers)) {
+ if (Modifier.isPublic(modifiers) || trySetAccessible(constructor)) {
cList.add((Constructor)constructor);
- } else {
- try {
- AccessController.doPrivileged(new PrivilegedAction() {
- public T run() {
- constructor.setAccessible(true);
- return null;
- }
- });
- cList.add((Constructor)constructor);
- } catch (SecurityException e) {
- // oh well, this does not get added then
- }
}
}
}
@@ -210,6 +176,23 @@ public T run() {
}
}
+ private boolean trySetAccessible(final AccessibleObject object) {
+ try {
+ return AccessController.doPrivileged(new PrivilegedAction() {
+ public Boolean run() {
+ try {
+ object.setAccessible(true);
+ return Boolean.TRUE;
+ } catch (InaccessibleObjectException ex) {
+ return Boolean.FALSE;
+ }
+ }
+ });
+ } catch (SecurityException e) {
+ return false;
+ }
+ }
+
/**
* @return the type of the class this data represents
*/
@@ -289,4 +272,3 @@ public static final String getModifierPrefix(int modifier) {
}
}
-
diff --git a/src/main/java/org/azeckoski/reflectutils/ClassDataCacher.java b/src/main/java/org/azeckoski/reflectutils/ClassDataCacher.java
index 8a9eed8..b8b38f0 100644
--- a/src/main/java/org/azeckoski/reflectutils/ClassDataCacher.java
+++ b/src/main/java/org/azeckoski/reflectutils/ClassDataCacher.java
@@ -15,11 +15,10 @@
package org.azeckoski.reflectutils;
import org.azeckoski.reflectutils.ClassFields.FieldFindMode;
-import org.azeckoski.reflectutils.refmap.ReferenceMap;
-import org.azeckoski.reflectutils.refmap.ReferenceType;
-
import java.lang.ref.SoftReference;
+import java.util.Collections;
import java.util.Map;
+import java.util.WeakHashMap;
/**
* Class which provides access to the analysis objects and the cached reflection data
@@ -117,9 +116,7 @@ public boolean isIncludeClassField() {
@SuppressWarnings("unchecked")
protected Map, ClassFields> getReflectionCache() {
if (reflectionCache == null) {
- // internally we are using the ReferenceMap (from the Guice codebase)
- // modeled after the groovy reflection caching (weak -> soft)
- reflectionCache = new ReferenceMap,ClassFields>(ReferenceType.WEAK, ReferenceType.SOFT);
+ reflectionCache = Collections.synchronizedMap(new WeakHashMap, ClassFields>());
}
return reflectionCache;
}
@@ -134,7 +131,9 @@ protected Map, ClassFields> getReflectionCache() {
@SuppressWarnings("unchecked")
public void setReflectionCache(Map, ClassFields> reflectionCache) {
if (reflectionCache != null) {
- this.reflectionCache.clear();
+ if (this.reflectionCache != null) {
+ this.reflectionCache.clear();
+ }
this.reflectionCache = reflectionCache;
} else {
getReflectionCache();
diff --git a/src/main/java/org/azeckoski/reflectutils/ClassFields.java b/src/main/java/org/azeckoski/reflectutils/ClassFields.java
index 4ef737b..3269906 100644
--- a/src/main/java/org/azeckoski/reflectutils/ClassFields.java
+++ b/src/main/java/org/azeckoski/reflectutils/ClassFields.java
@@ -18,8 +18,6 @@
import org.azeckoski.reflectutils.annotations.ReflectIncludeStaticFields;
import org.azeckoski.reflectutils.annotations.ReflectTransientClassFields;
import org.azeckoski.reflectutils.exceptions.FieldnameNotFoundException;
-import org.azeckoski.reflectutils.map.ArrayOrderedMap;
-import org.azeckoski.reflectutils.map.OrderedMap;
import java.beans.*;
import java.lang.annotation.Annotation;
@@ -188,7 +186,7 @@ public List getFieldNames() {
*/
public List getFieldNames(FieldsFilter filter) {
List names = new ArrayList();
- for (Entry entry : namesToProperties.getEntries()) {
+ for (Entry entry : namesToProperties.entrySet()) {
String name = entry.getKey();
ClassProperty cp = entry.getValue();
if ( isFieldInFilter(cp, filter) ) {
@@ -212,9 +210,8 @@ public Map> getFieldTypes() {
* @return the map of fieldName -> field type
*/
public Map> getFieldTypes(FieldsFilter filter) {
- OrderedMap> fieldTypes = new ArrayOrderedMap>();
- fieldTypes.setName(getStoredClass().getName());
- for (Entry entry : namesToProperties.getEntries()) {
+ Map> fieldTypes = new LinkedHashMap>();
+ for (Entry entry : namesToProperties.entrySet()) {
String name = entry.getKey();
ClassProperty cp = entry.getValue();
if ( isFieldInFilter(cp, filter) ) {
@@ -475,7 +472,7 @@ public ClassData getClassData() {
private final Set transientFieldNames = new HashSet();
// WARNING: all these things can hold open a ClassLoader
private final ClassData classData;
- private final OrderedMap namesToProperties; // this contains all properties data (includes partials)
+ private final Map namesToProperties; // this contains all properties data (includes partials)
// PUBLIC constructors
@@ -508,7 +505,7 @@ public ClassFields(Class fieldClass, Method[] getterMethods, Method[] setterM
classData = new ClassData(fieldClass);
fieldFindMode = FieldFindMode.HYBRID;
// set the properties
- namesToProperties = new ArrayOrderedMap(getterMethods.length
+ namesToProperties = new LinkedHashMap(getterMethods.length
+ publicFields.length);
for (int i = 0; i < getterMethods.length; i++) {
if (getterMethods[i] != null && setterMethods[i] != null) {
@@ -544,7 +541,7 @@ public ClassFields(Class fieldClass, PropertyDescriptor[] descriptors, Field[
}
classData = new ClassData(fieldClass);
fieldFindMode = FieldFindMode.HYBRID;
- namesToProperties = new ArrayOrderedMap(descriptors.length + publicFields.length);
+ namesToProperties = new LinkedHashMap(descriptors.length + publicFields.length);
populateProperties(descriptors);
populateFields(publicFields);
populateAnnotationsFields();
@@ -611,7 +608,7 @@ public ClassFields(Class fieldClass, FieldFindMode findMode, boolean useIntro
classData = new ClassData(fieldClass);
fieldFindMode = findMode;
includeClassField = includeClassFields;
- namesToProperties = new ArrayOrderedMap();
+ namesToProperties = new LinkedHashMap();
// check for reflect annotations on the class
List annotations = classData.getAnnotations();
@@ -892,7 +889,7 @@ private String checkPropertyMethods(Method getter, Method setter) {
@SuppressWarnings("SameParameterValue")
private List findProperties(boolean includePartial) {
// find the property methods from all public methods
- Map propertyMap = new ArrayOrderedMap();
+ Map propertyMap = new LinkedHashMap();
for (Method method : classData.getPublicMethods()) {
Class>[] paramTypes = method.getParameterTypes();
String name = method.getName();
diff --git a/src/main/java/org/azeckoski/reflectutils/ClassProperty.java b/src/main/java/org/azeckoski/reflectutils/ClassProperty.java
index 453fadd..35148b6 100644
--- a/src/main/java/org/azeckoski/reflectutils/ClassProperty.java
+++ b/src/main/java/org/azeckoski/reflectutils/ClassProperty.java
@@ -20,12 +20,11 @@
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.azeckoski.reflectutils.annotations.ReflectTransient;
-import org.azeckoski.reflectutils.map.ArrayOrderedMap;
-import org.azeckoski.reflectutils.map.OrderedMap;
/**
@@ -56,11 +55,11 @@ public class ClassProperty {
protected boolean indexed = false;
protected boolean arrayed = false;
- private OrderedMap, Annotation> propertyAnnotations; // contains all annotations on this property
+ private Map, Annotation> propertyAnnotations; // contains all annotations on this property
protected void addAnnotation(Annotation annotation) {
if (annotation != null) {
if (propertyAnnotations == null) {
- propertyAnnotations = new ArrayOrderedMap, Annotation>();
+ propertyAnnotations = new LinkedHashMap, Annotation>();
}
Class extends Annotation> c = annotation.annotationType();
if (! propertyAnnotations.containsKey(c)) {
diff --git a/src/main/java/org/azeckoski/reflectutils/ConstructorUtils.java b/src/main/java/org/azeckoski/reflectutils/ConstructorUtils.java
index 8d51b0a..5c8e061 100644
--- a/src/main/java/org/azeckoski/reflectutils/ConstructorUtils.java
+++ b/src/main/java/org/azeckoski/reflectutils/ConstructorUtils.java
@@ -34,6 +34,8 @@
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -41,9 +43,6 @@
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
-import java.util.Vector;
-
-import org.azeckoski.reflectutils.map.ArrayOrderedMap;
/**
* Class which provides methods for dealing with class constructors,
@@ -332,7 +331,7 @@ public static boolean isClassList(Class> type) {
/**
* @param type any class
- * @return true if this class is a collection (e.g. {@link Collection}, {@link HashSet}, {@link Vector})
+ * @return true if this class is a collection (e.g. {@link Collection}, {@link HashSet}, {@link ArrayList})
*/
public static boolean isClassCollection(Class> type) {
checkNull(type);
@@ -440,14 +439,13 @@ public static Class getClassFromInterface(Class type) {
} else if (SortedMap.class.isAssignableFrom(type)) {
toType = (Class) TreeMap.class;
} else if ( isClassList(type) ) {
- // we use the thread safe version of list by default
- toType = (Class) Vector.class;
+ toType = (Class) ArrayList.class;
} else if (Set.class.isAssignableFrom(type)) {
- toType = (Class) HashSet.class;
+ toType = (Class) LinkedHashSet.class;
} else if ( isClassMap(type) ) {
- toType = (Class) ArrayOrderedMap.class;
+ toType = (Class) LinkedHashMap.class;
} else if ( isClassCollection(type) ) {
- toType = (Class) Vector.class;
+ toType = (Class) ArrayList.class;
// Serializable should stay at the end
} else if (Serializable.class.isAssignableFrom(type)) {
// if it is serializable then it is probably a string right?
@@ -640,10 +638,19 @@ public T constructClass(Class type) {
}
}
if (newC == null) {
+ Throwable fallbackFailure = null;
try {
- // this should work 99% of the time
- newC = (T) type.newInstance();
- } catch (Exception e) {
+ Constructor declaredConstructor = type.getDeclaredConstructor();
+ if (!declaredConstructor.canAccess(null)) {
+ declaredConstructor.setAccessible(true);
+ }
+ newC = declaredConstructor.newInstance();
+ } catch (NoSuchMethodException ignored) {
+ // continue to constructor iteration below
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
+ fallbackFailure = e;
+ }
+ if (newC == null) {
// now we will try to use the various constructors by giving them null values to construct the object
List> constructors = null;
if ( ConstructorUtils.isClassBean(type) ) {
@@ -661,24 +668,24 @@ public T constructClass(Class type) {
for (Constructor constructor : constructors) {
Object[] params = new Object[ constructor.getParameterTypes().length ];
try {
- newC = (T) constructor.newInstance(params);
+ if (!constructor.canAccess(null)) {
+ constructor.setAccessible(true);
+ }
+ newC = constructor.newInstance(params);
break;
- } catch (IllegalArgumentException e1) {
- // oh well
- } catch (InstantiationException e1) {
- // tough cookies
- } catch (IllegalAccessException e1) {
- // them's the breaks
- } catch (InvocationTargetException e1) {
- // life's tough
- } catch (ExceptionInInitializerError e1) {
- // meh
+ } catch (IllegalArgumentException | InstantiationException | IllegalAccessException |
+ InvocationTargetException | ExceptionInInitializerError e1) {
+ // ignore and continue
+ if (fallbackFailure == null) {
+ fallbackFailure = e1;
+ }
}
- // ignore any exceptions and keep trying
}
if (newC == null) {
- // all attempts failed
- throw new IllegalArgumentException("Could not construct object for class (" + type.getName() + ") using newInstance or using any of the constructors: " + e.getMessage(), e);
+ if (fallbackFailure == null) {
+ fallbackFailure = new IllegalArgumentException("No accessible constructors found for class " + type.getName());
+ }
+ throw new IllegalArgumentException("Could not construct object for class (" + type.getName() + ")", fallbackFailure);
}
}
}
diff --git a/src/main/java/org/azeckoski/reflectutils/ConversionUtils.java b/src/main/java/org/azeckoski/reflectutils/ConversionUtils.java
index 6103d79..c9ec15e 100644
--- a/src/main/java/org/azeckoski/reflectutils/ConversionUtils.java
+++ b/src/main/java/org/azeckoski/reflectutils/ConversionUtils.java
@@ -23,9 +23,9 @@
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.Vector;
import java.util.Map.Entry;
import org.azeckoski.reflectutils.converters.ArrayConverter;
@@ -56,8 +56,8 @@
import org.azeckoski.reflectutils.converters.api.Converter;
import org.azeckoski.reflectutils.converters.api.InterfaceConverter;
import org.azeckoski.reflectutils.converters.api.VariableConverter;
-import org.azeckoski.reflectutils.refmap.ReferenceMap;
-import org.azeckoski.reflectutils.refmap.ReferenceType;
+
+import java.util.concurrent.ConcurrentHashMap;
/**
* Class which provides methods for converting between object types,
@@ -116,7 +116,7 @@ protected Map, Converter>> getConverters() {
*/
protected void loadDefaultConverters() {
if (converters == null) {
- converters = new ReferenceMap, Converter>>(ReferenceType.WEAK, ReferenceType.STRONG);
+ converters = new ConcurrentHashMap, Converter>>();
} else {
converters.clear();
}
@@ -183,7 +183,7 @@ protected List getVariableConverters() {
protected void loadDefaultVariableConverters() {
if (variableConverters == null) {
- variableConverters = new Vector();
+ variableConverters = new ArrayList();
} else {
clearVariableConverters();
}
diff --git a/src/main/java/org/azeckoski/reflectutils/DeepUtils.java b/src/main/java/org/azeckoski/reflectutils/DeepUtils.java
index 739610e..1fdf195 100644
--- a/src/main/java/org/azeckoski/reflectutils/DeepUtils.java
+++ b/src/main/java/org/azeckoski/reflectutils/DeepUtils.java
@@ -23,13 +23,12 @@
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
+import java.util.LinkedHashMap;
import org.azeckoski.reflectutils.ClassFields.FieldsFilter;
-import org.azeckoski.reflectutils.beanutils.FieldAdapter;
import org.azeckoski.reflectutils.exceptions.FieldGetValueException;
import org.azeckoski.reflectutils.exceptions.FieldSetValueException;
import org.azeckoski.reflectutils.exceptions.FieldnameNotFoundException;
-import org.azeckoski.reflectutils.map.ArrayOrderedMap;
/**
* Class which provides methods for handling deep operations:
@@ -67,10 +66,6 @@ protected FieldUtils getFieldUtils() {
return FieldUtils.getInstance();
}
- protected FieldAdapter getFieldAdapter() {
- return getFieldUtils().getFieldAdapter();
- }
-
/**
* Deep clone an object and all the values in it into a brand new object of the same type,
* this will traverse the bean and will make new objects for all non-null values contained in the object
@@ -311,7 +306,7 @@ protected Object internalDeepClone(Object bean, CopyDestination dest, int maxDep
if ( ConstructorUtils.isClassBean(componentType)
&& CopyDestination.MAP.equals(dest)) {
// special array component type for arrays of objects when map converting
- componentType = ArrayOrderedMap.class;
+ componentType = LinkedHashMap.class;
}
copy = ArrayUtils.create(componentType, length);
// now copy the stuff into it
@@ -347,40 +342,11 @@ protected Object internalDeepClone(Object bean, CopyDestination dest, int maxDep
continue;
}
}
- } else if (getFieldAdapter().isAdaptableClass(beanClass)) {
- // special handling for dynabeans
- if (CopyDestination.MAP.equals(dest)) {
- copy = new ArrayOrderedMap();
- } else {
- copy = getFieldAdapter().newInstance(bean); // make new dynabean
- }
- List propertyNames = getFieldAdapter().getPropertyNames(bean);
- for (String name : propertyNames) {
- if ( fieldNamesToSkip != null
- && fieldNamesToSkip.contains(name) ) {
- continue; // skip to next
- }
- try {
- Object value = getFieldAdapter().getSimpleValue(bean, name);
- if (value == null && ignoreNulls) {
- continue;
- }
- if (CopyDestination.MAP.equals(dest)) {
- ((Map) copy).put(name,
- internalDeepClone(value, dest, maxDepth, null, currentDepth, ignoreNulls, ignoreTransient) );
- } else {
- getFieldUtils().setFieldValue(copy, name,
- internalDeepClone(value, dest, maxDepth, null, currentDepth, ignoreNulls, ignoreTransient) );
- }
- } catch (IllegalArgumentException e) {
- // this is ok, field might not be readable so we will not clone it, continue on
- }
- }
} else {
// regular javabean
FieldsFilter filter = FieldsFilter.COMPLETE;
if (CopyDestination.MAP.equals(dest)) {
- copy = new ArrayOrderedMap();
+ copy = new LinkedHashMap();
// maps should pick up all readable fields
if (ignoreTransient) {
filter = FieldsFilter.SERIALIZABLE;
@@ -523,27 +489,6 @@ protected void internalDeepCopy(Object orig, Object dest, int maxDepth, Set propertyNames = getFieldAdapter().getPropertyNames(orig);
- for (String name : propertyNames) {
- if ( fieldNamesToSkip != null
- && fieldNamesToSkip.contains(name) ) {
- continue; // skip to next
- }
- try {
- Object value = getFieldAdapter().getSimpleValue(orig, name);
- if (ignoreNulls && value == null) {
- // don't copy this null over the existing value
- } else {
- getFieldUtils().setFieldValue(dest, name,
- internalDeepClone(value, CopyDestination.ORIGINAL, maxDepth, null, currentDepth, ignoreNulls, false));
- }
- } catch (FieldnameNotFoundException e) {
- // it is ok for the objects to not be the same
- } catch (IllegalArgumentException e) {
- // it is ok for the objects to not be the same
- }
- }
} else {
// regular javabean
Map values = getFieldUtils().getFieldValues(orig, FieldsFilter.READABLE, false);
diff --git a/src/main/java/org/azeckoski/reflectutils/FieldUtils.java b/src/main/java/org/azeckoski/reflectutils/FieldUtils.java
index 4457580..f4cf04e 100644
--- a/src/main/java/org/azeckoski/reflectutils/FieldUtils.java
+++ b/src/main/java/org/azeckoski/reflectutils/FieldUtils.java
@@ -18,19 +18,17 @@
import org.azeckoski.reflectutils.ClassProperty.IndexedProperty;
import org.azeckoski.reflectutils.ClassProperty.MappedProperty;
import org.azeckoski.reflectutils.beanutils.DefaultResolver;
-import org.azeckoski.reflectutils.beanutils.FieldAdapter;
-import org.azeckoski.reflectutils.beanutils.FieldAdapterManager;
import org.azeckoski.reflectutils.beanutils.Resolver;
import org.azeckoski.reflectutils.exceptions.FieldGetValueException;
import org.azeckoski.reflectutils.exceptions.FieldSetValueException;
import org.azeckoski.reflectutils.exceptions.FieldnameNotFoundException;
-import org.azeckoski.reflectutils.map.ArrayOrderedMap;
import java.lang.ref.SoftReference;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -66,8 +64,6 @@ protected FieldUtils() {
public FieldUtils(Resolver resolver) {
setResolver(resolver);
- fieldAdapterManager = new FieldAdapterManager();
-
FieldUtils.setInstance(this);
}
@@ -98,18 +94,6 @@ protected Resolver getResolver() {
}
return nameResolver;
}
-
-
- protected FieldAdapterManager fieldAdapterManager;
- /**
- * INTERNAL USAGE
- * @return the field adapter being used by this set of field utils
- */
- public FieldAdapter getFieldAdapter() {
- return fieldAdapterManager.getFieldAdapter();
- }
-
-
/**
* Analyze a class and produce an object which contains information about it and its fields
* @param
@@ -253,9 +237,7 @@ public Class> getFieldType(Object obj, String name) {
String targetName = getResolver().getProperty(name); // simple name of target field
// get the type
Class> fieldType;
- if (fieldAdapterManager.isAdaptableObject(obj)) {
- fieldType = fieldAdapterManager.getFieldAdapter().getFieldType(obj, targetName);
- } else if ( ConstructorUtils.isClassObjectHolder(obj.getClass())
+ if ( ConstructorUtils.isClassObjectHolder(obj.getClass())
|| Object.class.equals(obj.getClass()) ) {
// special handling for the holder types, needed because attempting to analyze a map or other container will cause a failure
fieldType = Object.class;
@@ -350,26 +332,22 @@ public Map getFieldValues(Object obj, FieldsFilter filter, boole
if (obj == null) {
throw new IllegalArgumentException("obj cannot be null");
}
- Map values = new ArrayOrderedMap();
+ Map values = new LinkedHashMap();
if (includeClassField) {
// add as the first field
values.put(ClassFields.FIELD_CLASS, obj.getClass());
}
- if (fieldAdapterManager.isAdaptableObject(obj)) {
- values.putAll( fieldAdapterManager.getFieldAdapter().getFieldValues(obj, filter) );
- } else {
- Map> types = getFieldTypes(obj.getClass(), filter);
- if (FieldsFilter.WRITEABLE.equals(filter)) {
- types.clear();
- }
- for (String name : types.keySet()) {
- try {
- Object o = getFieldValue(obj, name);
- values.put(name, o);
- } catch (RuntimeException e) {
- // failed to get the value so we will skip this one
- continue;
- }
+ Map> types = getFieldTypes(obj.getClass(), filter);
+ if (FieldsFilter.WRITEABLE.equals(filter)) {
+ types.clear();
+ }
+ for (String name : types.keySet()) {
+ try {
+ Object o = getFieldValue(obj, name);
+ values.put(name, o);
+ } catch (RuntimeException e) {
+ // failed to get the value so we will skip this one
+ continue;
}
}
return values;
@@ -588,7 +566,7 @@ protected Holder unpackNestedName(final String fullName, final Object object, bo
Class> type = getFieldType(obj, next);
if (Object.class.equals(type)) {
// indeterminate type so we will make a map
- type = ArrayOrderedMap.class;
+ type = LinkedHashMap.class;
}
nestedBean = getConstructorUtils().constructClass(type);
setFieldValue(obj, next, nestedBean, false); // need to put this new object into the parent
@@ -629,29 +607,24 @@ protected Object getSimpleValue(Object obj, String name) {
}
Object value;
- // Handle DynaBean instances specially
- if (fieldAdapterManager.isAdaptableObject(obj)) {
- value = fieldAdapterManager.getFieldAdapter().getSimpleValue(obj, name);
- } else {
- // normal bean
- ClassFields> cf = analyzeObject(obj);
+ // normal bean
+ ClassFields> cf = analyzeObject(obj);
+ try {
+ // use the class property
+ ClassProperty cp = cf.getClassProperty(name);
+ value = findFieldValue(obj, cp);
+ } catch (FieldnameNotFoundException fnfe) {
+ // could not find this as a standard field so handle as internal lookup
+ ClassData> cd = cf.getClassData();
+ Field field = getFieldIfPossible(cd, name);
+ if (field == null) {
+ throw new FieldnameNotFoundException("Could not find field with name ("+name+") on object (" + obj + ") after extended look into non-visible fields", fnfe);
+ }
try {
- // use the class property
- ClassProperty cp = cf.getClassProperty(name);
- value = findFieldValue(obj, cp);
- } catch (FieldnameNotFoundException fnfe) {
- // could not find this as a standard field so handle as internal lookup
- ClassData> cd = cf.getClassData();
- Field field = getFieldIfPossible(cd, name);
- if (field == null) {
- throw new FieldnameNotFoundException("Could not find field with name ("+name+") on object (" + obj + ") after extended look into non-visible fields", fnfe);
- }
- try {
- value = field.get(obj);
- } catch (Exception e) {
- // catching the general exception is correct here, translate the exception
- throw new FieldGetValueException("Field get failure getting value for field ("+name+") from non-visible field in object: " + obj, name, obj, e);
- }
+ value = field.get(obj);
+ } catch (Exception e) {
+ // catching the general exception is correct here, translate the exception
+ throw new FieldGetValueException("Field get failure getting value for field ("+name+") from non-visible field in object: " + obj, name, obj, e);
}
}
return value;
@@ -694,68 +667,55 @@ protected Object getIndexedValue(Object obj, String name) {
}
boolean indexedProperty = false;
- // Handle DynaBean instances specially
- if (fieldAdapterManager.isAdaptableObject(obj)) {
- value = fieldAdapterManager.getFieldAdapter().getIndexedValue(obj, name, index);
+ boolean isArray = false;
+ Object indexedObject = null;
+
+ if (obj.getClass().isArray()) {
+ indexedObject = obj;
+ isArray = true;
+ } else if (List.class.isAssignableFrom(obj.getClass())) {
+ indexedObject = obj;
} else {
- boolean isArray = false;
- Object indexedObject = null;
- if (obj.getClass().isArray()) {
- indexedObject = obj;
- isArray = true;
- } else if (List.class.isAssignableFrom(obj.getClass())) {
- indexedObject = obj;
- } else {
- // normal bean
- ClassFields cf = analyzeObject(obj);
- ClassProperty cp = cf.getClassProperty(name);
- if (! cp.isIndexed()) {
- throw new IllegalArgumentException("This field ("+name+") is not an indexed field");
- }
- isArray = cp.isArray();
- // try to get the indexed getter and use that first
- if (cp instanceof IndexedProperty) {
- indexedProperty = true;
- IndexedProperty icp = (IndexedProperty) cp;
- try {
- Method getter = icp.getIndexGetter();
- //noinspection RedundantArrayCreation
- value = getter.invoke(obj, new Object[] {index});
- } catch (Exception e) {
- // catching the general exception is correct here, translate the exception
- throw new FieldGetValueException("Indexed getter method failure getting indexed ("
- +index+") value for name ("+cp.getFieldName()+") from: " + obj, cp.getFieldName(), obj, e);
- }
- } else {
- indexedObject = findFieldValue(obj, cp);
+ ClassFields> cf = analyzeObject(obj);
+ ClassProperty cp = cf.getClassProperty(name);
+ if (!cp.isIndexed()) {
+ throw new IllegalArgumentException("This field (" + name + ") is not an indexed field");
+ }
+ isArray = cp.isArray();
+ if (cp instanceof IndexedProperty) {
+ indexedProperty = true;
+ IndexedProperty icp = (IndexedProperty) cp;
+ try {
+ Method getter = icp.getIndexGetter();
+ value = getter.invoke(obj, index);
+ } catch (Exception e) {
+ throw new FieldGetValueException("Indexed getter method failure getting indexed (" + index + ") value for name (" + cp.getFieldName() + ") from: " + obj, cp.getFieldName(), obj, e);
}
}
if (!indexedProperty) {
- // now get the indexed value if possible
- if (indexedObject != null) {
- if (isArray) {
- // this is an array
- try {
- // get value from array
- value = Array.get(indexedObject, index);
- } catch (ArrayIndexOutOfBoundsException e) {
- throw new IllegalArgumentException("Index ("+index+") is out of bounds ("+Array.getLength(indexedObject)+") for the array: " + indexedObject, e);
- }
- } else {
- // this better be a list
- if (! List.class.isAssignableFrom(indexedObject.getClass())) {
- throw new IllegalArgumentException("Field (" + name + ") does not appear to be indexed (not an array or a list)");
- } else {
- // get value from list
- try {
- value = ((List)indexedObject).get(index);
- } catch (IndexOutOfBoundsException e) {
- throw new IllegalArgumentException("Index ("+index+") is out of bounds ("+((List)indexedObject).size()+") for the list: " + indexedObject, e);
- }
- }
- }
- } else {
- throw new IllegalArgumentException("Indexed object is null, cannot retrieve index ("+index+") value from field ("+name+")");
+ indexedObject = findFieldValue(obj, cp);
+ }
+ }
+
+ if (!indexedProperty) {
+ if (indexedObject == null) {
+ throw new IllegalArgumentException("Indexed object is null, cannot retrieve index (" + index + ") value from field (" + name + ")");
+ }
+ if (isArray) {
+ try {
+ value = Array.get(indexedObject, index);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new IllegalArgumentException("Index (" + index + ") is out of bounds (" + Array.getLength(indexedObject) + ") for the array: " + indexedObject, e);
+ }
+ } else {
+ if (!List.class.isAssignableFrom(indexedObject.getClass())) {
+ throw new IllegalArgumentException("Field (" + name + ") does not appear to be indexed (not an array or a list)");
+ }
+ List> list = (List>) indexedObject;
+ try {
+ value = list.get(index);
+ } catch (IndexOutOfBoundsException e) {
+ throw new IllegalArgumentException("Index (" + index + ") is out of bounds (" + list.size() + ") for the list: " + indexedObject, e);
}
}
}
@@ -797,52 +757,47 @@ protected Object getMappedValue(Object obj, String name) {
}
boolean mappedProperty = false;
- // Handle DynaBean instances specially
- if (fieldAdapterManager.isAdaptableObject(obj)) {
- value = fieldAdapterManager.getFieldAdapter().getMappedValue(obj, name, key);
+ Map map = null;
+ if (Map.class.isAssignableFrom(obj.getClass())) {
+ map = (Map) obj;
} else {
- Map map = null;
- if (Map.class.isAssignableFrom(obj.getClass())) {
- map = (Map) obj;
- } else {
- // normal bean
- ClassFields cf = analyzeObject(obj);
- ClassProperty cp = cf.getClassProperty(name);
- if (! cp.isMapped()) {
- throw new IllegalArgumentException("This field ("+name+") is not an mapped field");
+ // normal bean
+ ClassFields cf = analyzeObject(obj);
+ ClassProperty cp = cf.getClassProperty(name);
+ if (! cp.isMapped()) {
+ throw new IllegalArgumentException("This field ("+name+") is not an mapped field");
+ }
+ // try to get the mapped getter and use that first
+ if (cp instanceof MappedProperty) {
+ mappedProperty = true;
+ MappedProperty mcp = (MappedProperty) cp;
+ try {
+ Method getter = mcp.getMapGetter();
+ //noinspection RedundantArrayCreation
+ value = getter.invoke(obj, new Object[] {key});
+ } catch (Exception e) {
+ // catching the general exception is correct here, translate the exception
+ throw new FieldGetValueException("Mapped getter method failure getting mapped ("
+ +key+") value for name ("+cp.getFieldName()+") from: " + obj, cp.getFieldName(), obj, e);
}
- // try to get the mapped getter and use that first
- if (cp instanceof MappedProperty) {
- mappedProperty = true;
- MappedProperty mcp = (MappedProperty) cp;
- try {
- Method getter = mcp.getMapGetter();
- //noinspection RedundantArrayCreation
- value = getter.invoke(obj, new Object[] {key});
- } catch (Exception e) {
- // catching the general exception is correct here, translate the exception
- throw new FieldGetValueException("Mapped getter method failure getting mapped ("
- +key+") value for name ("+cp.getFieldName()+") from: " + obj, cp.getFieldName(), obj, e);
- }
- } else {
- Object o = findFieldValue(obj, cp);
- if (! Map.class.isAssignableFrom(o.getClass())) {
- throw new IllegalArgumentException("Field (" + name + ") does not appear to be a map (not instance of Map)");
- }
- map = (Map) o;
+ } else {
+ Object o = findFieldValue(obj, cp);
+ if (! Map.class.isAssignableFrom(o.getClass())) {
+ throw new IllegalArgumentException("Field (" + name + ") does not appear to be a map (not instance of Map)");
}
+ map = (Map) o;
}
- // get the value from the map
- if (!mappedProperty) {
- if (map != null) {
- try {
- value = map.get(key);
- } catch (Exception e) {
- throw new IllegalArgumentException("Key ("+key+") is invalid ("+map.size()+") for the map: " + map, e);
- }
- } else {
- throw new IllegalArgumentException("Mapped object is null, cannot retrieve key ("+key+") value from field ("+name+")");
+ }
+ // get the value from the map
+ if (!mappedProperty) {
+ if (map != null) {
+ try {
+ value = map.get(key);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Key ("+key+") is invalid ("+map.size()+") for the map: " + map, e);
}
+ } else {
+ throw new IllegalArgumentException("Mapped object is null, cannot retrieve key ("+key+") value from field ("+name+")");
}
}
return value;
@@ -928,29 +883,24 @@ protected void setSimpleValue(Object obj, String name, Object value) {
throw new IllegalArgumentException("field name cannot be null or blank");
}
- // Handle DynaBean instances specially
- if (fieldAdapterManager.isAdaptableObject(obj)) {
- fieldAdapterManager.getFieldAdapter().setSimpleValue(obj, name, value);
- } else {
- // normal bean
- ClassFields> cf = analyzeObject(obj);
+ // normal bean
+ ClassFields> cf = analyzeObject(obj);
+ try {
+ ClassProperty cp = cf.getClassProperty(name);
+ assignFieldValue(obj, cp, value);
+ } catch (FieldnameNotFoundException fnfe) {
+ // could not find this as a standard field so handle as internal lookup
+ ClassData> cd = cf.getClassData();
+ Field field = getFieldIfPossible(cd, name);
+ if (field == null) {
+ throw new FieldnameNotFoundException("Could not find field with name ("+name+") on object (" + obj + ") after extended look into non-visible fields", fnfe);
+ }
try {
- ClassProperty cp = cf.getClassProperty(name);
- assignFieldValue(obj, cp, value);
- } catch (FieldnameNotFoundException fnfe) {
- // could not find this as a standard field so handle as internal lookup
- ClassData> cd = cf.getClassData();
- Field field = getFieldIfPossible(cd, name);
- if (field == null) {
- throw new FieldnameNotFoundException("Could not find field with name ("+name+") on object (" + obj + ") after extended look into non-visible fields", fnfe);
- }
- try {
- value = getConversionUtils().convert(value, field.getType());
- field.set(obj, value);
- } catch (Exception e) {
- // catching the general exception is correct here, translate the exception
- throw new FieldSetValueException("Field set failure setting value ("+value+") for field ("+name+") from non-visible field in object: " + obj, name, obj, e);
- }
+ value = getConversionUtils().convert(value, field.getType());
+ field.set(obj, value);
+ } catch (Exception e) {
+ // catching the general exception is correct here, translate the exception
+ throw new FieldSetValueException("Field set failure setting value ("+value+") for field ("+name+") from non-visible field in object: " + obj, name, obj, e);
}
}
}
@@ -988,111 +938,87 @@ protected void setIndexedValue(Object obj, String name, Object value) {
}
boolean indexedProperty = false;
- // Handle DynaBean instances specially
- if (fieldAdapterManager.isAdaptableObject(obj)) {
- fieldAdapterManager.getFieldAdapter().setIndexedValue(obj, name, index, value);
+ boolean isArray = false;
+ Object indexedObject = null;
+
+ if (ConstructorUtils.isClassArray(obj.getClass())) {
+ indexedObject = obj;
+ isArray = true;
+ } else if (ConstructorUtils.isClassList(obj.getClass())) {
+ indexedObject = obj;
} else {
- boolean isArray = false;
- Object indexedObject = null;
- if ( ConstructorUtils.isClassArray(obj.getClass()) ) {
- indexedObject = obj;
- isArray = true;
- } else if ( ConstructorUtils.isClassList(obj.getClass()) ) {
- indexedObject = obj;
- } else {
- // normal bean
- ClassFields cf = analyzeObject(obj);
- ClassProperty cp = cf.getClassProperty(name);
- if (! cp.isIndexed()) {
- throw new IllegalArgumentException("This field ("+name+") is not an indexed field");
+ ClassFields> cf = analyzeObject(obj);
+ ClassProperty cp = cf.getClassProperty(name);
+ if (!cp.isIndexed()) {
+ throw new IllegalArgumentException("This field (" + name + ") is not an indexed field");
+ }
+ isArray = cp.isArray();
+ if (cp instanceof IndexedProperty) {
+ indexedProperty = true;
+ IndexedProperty icp = (IndexedProperty) cp;
+ try {
+ Method setter = icp.getIndexSetter();
+ setter.invoke(obj, index, value);
+ } catch (Exception e) {
+ throw new FieldSetValueException("Indexed setter method failure setting indexed (" + index + ") value for name (" + cp.getFieldName() + ") on: " + obj, cp.getFieldName(), obj, e);
}
- isArray = cp.isArray();
- // try to get the indexed setter and use that first
- if (cp instanceof IndexedProperty) {
- indexedProperty = true;
- IndexedProperty icp = (IndexedProperty) cp;
+ }
+ if (!indexedProperty) {
+ indexedObject = findFieldValue(obj, cp);
+ if (indexedObject == null) {
try {
- Method setter = icp.getIndexSetter();
- //noinspection RedundantArrayCreation
- setter.invoke(obj, new Object[] {index, value});
- } catch (Exception e) {
- // catching the general exception is correct here, translate the exception
- throw new FieldSetValueException("Indexed setter method failure setting indexed ("
- +index+") value for name ("+cp.getFieldName()+") on: " + obj, cp.getFieldName(), obj, e);
- }
- } else {
- // get the field value out and work with it directly
- indexedObject = findFieldValue(obj, cp);
- if (indexedObject == null) {
- // handle nulls by creating if possible
- try {
- if (isArray) {
- // create the array if it is null
- Class> type = value.getClass();
- indexedObject = ArrayUtils.create(type, index+1);
- } else { // List
- // create the list if it is null, back-fill it, and assign it back to the object
- Class> type = cp.getType();
- if (type.isInterface()) {
- indexedObject = new ArrayList(index+1);
- } else {
- indexedObject = type.newInstance();
- }
+ if (isArray) {
+ Class> type = value.getClass();
+ indexedObject = ArrayUtils.create(type, index + 1);
+ } else {
+ Class> type = cp.getType();
+ if (type.isInterface()) {
+ indexedObject = new ArrayList<>(index + 1);
+ } else {
+ indexedObject = getConstructorUtils().constructClass(type);
}
- setSimpleValue(obj, name, indexedObject);
- } catch (Exception e) {
- throw new IllegalArgumentException("Indexed object is null, attempt to create list failed, cannot set value for index ("+index+") on field ("+name+")", e);
}
+ setSimpleValue(obj, name, indexedObject);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Indexed object is null, attempt to create list failed, cannot set value for index (" + index + ") on field (" + name + ")", e);
}
}
}
- if (!indexedProperty) {
- // set the indexed value
- if (isArray) {
- // this is an array
- try {
- // set the value on the array
- int length = ArrayUtils.size((Object[])indexedObject);
- if (index >= length) {
- // automatically expand the array
- indexedObject = ArrayUtils.resize((Object[])indexedObject, index+1);
- setSimpleValue(obj, name, indexedObject); // need to put the array back into the object
- }
- // convert this value to the type for the array
- Class> componentType = ArrayUtils.type((Object[])indexedObject);
- Object convert = ReflectUtils.getInstance().convert(value, componentType);
- Array.set(indexedObject, index, convert);
- } catch (Exception e) {
- throw new IllegalArgumentException("Failed to set index ("+index+") for array of size ("+Array.getLength(indexedObject)+") to value: " + value, e);
+ }
+
+ if (!indexedProperty) {
+ if (indexedObject == null) {
+ throw new IllegalArgumentException("Indexed object is null, cannot set value for index (" + index + ") on field (" + name + ")");
+ }
+ if (isArray) {
+ try {
+ int length = ArrayUtils.size((Object[]) indexedObject);
+ if (index >= length) {
+ indexedObject = ArrayUtils.resize((Object[]) indexedObject, index + 1);
+ setSimpleValue(obj, name, indexedObject);
}
- } else {
- // this better be a list
- if (indexedObject == null
- || ! List.class.isAssignableFrom(indexedObject.getClass())) {
- throw new IllegalArgumentException("Field (" + name + ") does not appear to be indexed (not an array or a list): "
- + (indexedObject == null ? "NULL" : indexedObject.getClass()) );
+ Class> componentType = ArrayUtils.type((Object[]) indexedObject);
+ Object convert = ReflectUtils.getInstance().convert(value, componentType);
+ Array.set(indexedObject, index, convert);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Failed to set index (" + index + ") for array of size (" + Array.getLength(indexedObject) + ") to value: " + value, e);
+ }
+ } else {
+ if (!List.class.isAssignableFrom(indexedObject.getClass())) {
+ throw new IllegalArgumentException("Field (" + name + ") does not appear to be indexed (not an array or a list): " + indexedObject.getClass());
+ }
+ List
-
-
\ No newline at end of file
diff --git a/src/main/java/org/azeckoski/reflectutils/refmap/FinalizableReference.java b/src/main/java/org/azeckoski/reflectutils/refmap/FinalizableReference.java
deleted file mode 100644
index d4ef1e5..0000000
--- a/src/main/java/org/azeckoski/reflectutils/refmap/FinalizableReference.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2007 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.azeckoski.reflectutils.refmap;
-
-/**
- * Implemented by references that have code to run after garbage collection of
- * their referents.
- *
- * @see FinalizableReferenceQueue
- * @author Bob Lee
- */
-public interface FinalizableReference {
-
- /**
- * Invoked on a background thread after the referent has been garbage
- * collected.
- */
- void finalizeReferent();
-}
diff --git a/src/main/java/org/azeckoski/reflectutils/refmap/FinalizableReferenceQueue.java b/src/main/java/org/azeckoski/reflectutils/refmap/FinalizableReferenceQueue.java
deleted file mode 100644
index 01b566f..0000000
--- a/src/main/java/org/azeckoski/reflectutils/refmap/FinalizableReferenceQueue.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Copyright (C) 2007 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.azeckoski.reflectutils.refmap;
-
-import org.azeckoski.reflectutils.LifecycleManager;
-
-import java.lang.ref.ReferenceQueue;
-import java.lang.reflect.Method;
-import java.lang.reflect.InvocationTargetException;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.net.URISyntaxException;
-import java.io.IOException;
-import java.io.FileNotFoundException;
-
-/**
- * A reference queue with an associated background thread that dequeues
- * references and invokes {@link FinalizableReference#finalizeReferent()} on
- * them.
- *
- *
Keep a strong reference to this object until all of the associated
- * referents have been finalized. If this object is garbage collected earlier,
- * the backing thread will not invoke {@code finalizeReferent()} on the
- * remaining references.
- *
- * @author Bob Lee
- */
-public class FinalizableReferenceQueue {
-
- /*
- * The Finalizer thread keeps a phantom reference to this object. When the
- * client (ReferenceMap, for example) no longer has a strong reference to
- * this object, the garbage collector will reclaim it and enqueue the
- * phantom reference. The enqueued reference will trigger the Finalizer to
- * stop.
- *
- * If this library is loaded in the system class loader,
- * FinalizableReferenceQueue can load Finalizer directly with no problems.
- *
- * If this library is loaded in an application class loader, it's important
- * that Finalizer not have a strong reference back to the class loader.
- * Otherwise, you could have a graph like this:
- *
- * Finalizer Thread
- * runs instance of -> Finalizer.class
- * loaded by -> Application class loader
- * which loaded -> ReferenceMap.class
- * which has a static -> FinalizableReferenceQueue instance
- *
- * Even if no other references to classes from the application class loader
- * remain, the Finalizer thread keeps an indirect strong reference to the
- * queue in ReferenceMap, which keeps the Finalizer running, and as a result,
- * the application class loader can never be reclaimed.
- *
- * This means that dynamically loaded web applications and OSGi bundles can't
- * be unloaded.
- *
- * If the library is loaded in an application class loader, we try to break
- * the cycle by loading Finalizer in its own independent class loader:
- *
- * System class loader
- * -> Application class loader
- * -> ReferenceMap
- * -> FinalizableReferenceQueue
- * -> etc.
- * -> Decoupled class loader
- * -> Finalizer
- *
- * Now, Finalizer no longer keeps an indirect strong reference to the
- * static FinalizableReferenceQueue field in ReferenceMap. The application
- * class loader can be reclaimed at which point the Finalizer thread will
- * stop and its decoupled class loader can also be reclaimed.
- *
- * If any of this fails along the way, we fall back to loading Finalizer
- * directly in the application class loader.
- */
-
- private static final Logger logger
- = Logger.getLogger(FinalizableReferenceQueue.class.getName());
-
- private static final String FINALIZER_CLASS_NAME
- = "org.azeckoski.reflectutils.refmap.Finalizer";
-
- /** Reference to Finalizer.startFinalizer(). */
- private static final Method startFinalizer;
- static {
- Class> finalizer;
-
- // If the LifeCycleManager has been activated, then load the Finalizer in the current ClassLoader
- // so that the Finalizer thread can register to receive shutdown notification
- if (LifecycleManager.isActive()) {
- finalizer = loadFinalizer(new DirectLoader());
- } else {
- finalizer = loadFinalizer(
- new SystemLoader(), new DecoupledLoader(), new DirectLoader());
- }
-
- startFinalizer = getStartFinalizer(finalizer);
- }
-
- /**
- * The actual reference queue that our background thread will poll.
- */
- final ReferenceQueue queue;
-
- /**
- * Constructs a new queue.
- */
- @SuppressWarnings("unchecked")
- public FinalizableReferenceQueue() {
- // We could start the finalizer lazily, but I'd rather it blow up early.
- try {
- this.queue = (ReferenceQueue) startFinalizer.invoke(null,
- FinalizableReference.class, this, LifecycleManager.isActive());
- } catch (IllegalAccessException e) {
- // Finalizer.startFinalizer() is public.
- throw new AssertionError(e);
- } catch (InvocationTargetException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Iterates through the given loaders until it finds one that can load
- * Finalizer.
- *
- * @return Finalizer.class
- */
- private static Class> loadFinalizer(FinalizerLoader... loaders) {
- for (FinalizerLoader loader : loaders) {
- Class> finalizer = loader.loadFinalizer();
- if (finalizer != null) {
- return finalizer;
- }
- }
-
- throw new AssertionError();
- }
-
- /**
- * Loads Finalizer.class.
- */
- interface FinalizerLoader {
-
- /**
- * Returns Finalizer.class or null if this loader shouldn't or can't load
- * it.
- *
- * @throws SecurityException if we don't have the appropriate priveleges
- */
- Class> loadFinalizer();
- }
-
- /**
- * Tries to load Finalizer from the system class loader. If Finalizer is
- * in the system class path, we needn't create a separate loader.
- */
- static class SystemLoader implements FinalizerLoader {
- public Class> loadFinalizer() {
- ClassLoader systemLoader;
- try {
- systemLoader = ClassLoader.getSystemClassLoader();
- } catch (SecurityException e) {
- logger.info("Not allowed to access system class loader.");
- return null;
- }
- if (systemLoader != null) {
- try {
- return systemLoader.loadClass(FINALIZER_CLASS_NAME);
- } catch (ClassNotFoundException e) {
- // Ignore. Finalizer is simply in a child class loader.
- return null;
- }
- } else {
- return null;
- }
- }
- }
-
- /**
- * Try to load Finalizer in its own class loader. If Finalizer's thread
- * had a direct reference to our class loader (which could be that of
- * a dynamically loaded web application or OSGi bundle), it would prevent
- * our class loader from getting garbage collected.
- */
- static class DecoupledLoader implements FinalizerLoader {
-
- private static final String LOADING_ERROR = "Could not load Finalizer in"
- + " its own class loader. Loading Finalizer in the current class loader"
- + " instead. As a result, you will not be able to garbage collect this"
- + " class loader. To support reclaiming this class loader, either"
- + " resolve the underlying issue, or move Google Collections to your"
- + " system class path.";
-
- public Class> loadFinalizer() {
- try {
- /*
- * We use URLClassLoader because it's the only concrete class loader
- * implementation in the JDK. If we used our own ClassLoader subclass,
- * Finalizer would indirectly reference this class loader:
- *
- * Finalizer.class ->
- * CustomClassLoader ->
- * CustomClassLoader.class ->
- * This class loader
- *
- * System class loader will (and must) be the parent.
- */
- ClassLoader finalizerLoader = newLoader(getBaseUrl());
- return finalizerLoader.loadClass(FINALIZER_CLASS_NAME);
- } catch (Exception e) {
- logger.log(Level.WARNING, LOADING_ERROR, e);
- return null;
- }
- }
-
- /**
- * Gets URL for base of path containing Finalizer.class.
- */
- URL getBaseUrl() throws IOException, URISyntaxException {
- // Find URL pointing to Finalizer.class file.
- String finalizerPath = FINALIZER_CLASS_NAME.replace('.', '/') + ".class";
- URL finalizerUrl = getClass().getClassLoader().getResource(finalizerPath);
- if (finalizerUrl == null) {
- throw new FileNotFoundException(finalizerPath);
- }
-
- // Find URL pointing to base of class path.
- String urlString = finalizerUrl.toString();
- if (!urlString.endsWith(finalizerPath)) {
- throw new IOException("Unsupported path style: " + urlString);
- }
- urlString = urlString.substring(0,
- urlString.length() - finalizerPath.length());
- return new URL(urlString);
- }
-
- /** Creates a class loader with the given base URL as its classpath. */
- URLClassLoader newLoader(URL base) {
- return new URLClassLoader(new URL[] { base });
- }
- }
-
- /**
- * Loads Finalizer directly using the current class loader. We won't be
- * able to garbage collect this class loader, but at least the world
- * doesn't end.
- */
- static class DirectLoader implements FinalizerLoader {
- public Class> loadFinalizer() {
- try {
- return Class.forName(FINALIZER_CLASS_NAME);
- } catch (ClassNotFoundException e) {
- throw new AssertionError(e);
- }
- }
- }
-
- /**
- * Looks up Finalizer.startFinalizer().
- */
- static Method getStartFinalizer(Class> finalizer) {
- try {
- return finalizer.getMethod("startFinalizer", Class.class, Object.class, boolean.class);
- } catch (NoSuchMethodException e) {
- throw new AssertionError(e);
- }
- }
-}
-
diff --git a/src/main/java/org/azeckoski/reflectutils/refmap/FinalizableSoftReference.java b/src/main/java/org/azeckoski/reflectutils/refmap/FinalizableSoftReference.java
deleted file mode 100644
index 64f39f2..0000000
--- a/src/main/java/org/azeckoski/reflectutils/refmap/FinalizableSoftReference.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2007 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.azeckoski.reflectutils.refmap;
-
-import java.lang.ref.SoftReference;
-
-/**
- * Soft reference with a {@code finalizeReferent()} method which a background
- * thread invokes after the garbage collector reclaims the referent. This is a
- * simpler alternative to using a {@link java.lang.ref.ReferenceQueue}.
- *
- * @author Bob Lee
- */
-public abstract class FinalizableSoftReference extends SoftReference
- implements FinalizableReference {
-
- /**
- * Consructs a new finalizable soft reference.
- *
- * @param referent to softly reference
- * @param queue that should finalize the referent
- */
- protected FinalizableSoftReference(T referent,
- FinalizableReferenceQueue queue) {
- super(referent, queue.queue);
- }
-}
diff --git a/src/main/java/org/azeckoski/reflectutils/refmap/FinalizableWeakReference.java b/src/main/java/org/azeckoski/reflectutils/refmap/FinalizableWeakReference.java
deleted file mode 100644
index ca792a3..0000000
--- a/src/main/java/org/azeckoski/reflectutils/refmap/FinalizableWeakReference.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2007 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.azeckoski.reflectutils.refmap;
-
-import java.lang.ref.WeakReference;
-
-/**
- * Weak reference with a {@code finalizeReferent()} method which a background
- * thread invokes after the garbage collector reclaims the referent. This is a
- * simpler alternative to using a {@link java.lang.ref.ReferenceQueue}.
- *
- * @author Bob Lee
- */
-public abstract class FinalizableWeakReference extends WeakReference
- implements FinalizableReference {
-
- /**
- * Consructs a new finalizable weak reference.
- *
- * @param referent to weakly reference
- * @param queue that should finalize the referent
- */
- protected FinalizableWeakReference(T referent,
- FinalizableReferenceQueue queue) {
- super(referent, queue.queue);
- }
-}
diff --git a/src/main/java/org/azeckoski/reflectutils/refmap/Finalizer.java b/src/main/java/org/azeckoski/reflectutils/refmap/Finalizer.java
deleted file mode 100644
index 7727c9a..0000000
--- a/src/main/java/org/azeckoski/reflectutils/refmap/Finalizer.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2008 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.azeckoski.reflectutils.refmap;
-
-import org.azeckoski.reflectutils.Lifecycle;
-import org.azeckoski.reflectutils.LifecycleManager;
-
-import java.util.logging.Logger;
-import java.util.logging.Level;
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.Reference;
-import java.lang.ref.WeakReference;
-import java.lang.ref.PhantomReference;
-import java.lang.reflect.Method;
-
-/**
- * Thread that finalizes referents. All references should implement
- * {@code com.google.common.base.FinalizableReference}.
- *
- *
While this class is public, we consider it to be *internal* and not part
- * of our published API. It is public so we can access it reflectively across
- * class loaders in secure environments.
- *
- *
This class can't depend on other Google Collections code. If we were
- * to load this class in the same class loader as the rest of
- * Google Collections, this thread would keep an indirect strong reference
- * to the class loader and prevent it from being garbage collected. This
- * poses a problem for environments where you want to throw away the class
- * loader. For example, dynamically reloading a web application or unloading
- * an OSGi bundle.
- *
- *
{@code com.google.common.base.FinalizableReferenceQueue} loads this class
- * in its own class loader. That way, this class doesn't prevent the main
- * class loader from getting garbage collected, and this class can detect when
- * the main class loader has been garbage collected and stop itself.
- */
-public class Finalizer extends Thread implements Lifecycle {
-
- private static final Logger logger
- = Logger.getLogger(Finalizer.class.getName());
-
- /** Name of FinalizableReference.class. */
- private static final String FINALIZABLE_REFERENCE
- = "org.azeckoski.reflectutils.refmap.FinalizableReference";
-
- /**
- * Starts the Finalizer thread. FinalizableReferenceQueue calls this method
- * reflectively.
- *
- * @param finalizableReferenceClass FinalizableReference.class
- * @param frq reference to instance of FinalizableReferenceQueue that started
- * this thread
- * @return ReferenceQueue which Finalizer will poll
- */
- public static ReferenceQueue startFinalizer(
- Class> finalizableReferenceClass, Object frq, boolean registerForLifecycle) {
- /*
- * We use FinalizableReference.class for two things:
- *
- * 1) To invoke FinalizableReference.finalizeReferent()
- *
- * 2) To detect when FinalizableReference's class loader has to be garbage
- * collected, at which point, Finalizer can stop running
- */
- if (!finalizableReferenceClass.getName().equals(FINALIZABLE_REFERENCE)) {
- throw new IllegalArgumentException(
- "Expected " + FINALIZABLE_REFERENCE + ".");
- }
-
- Finalizer finalizerThread = new Finalizer(finalizableReferenceClass, frq);
- finalizerThread.start();
-
- // If we are running under the LifeCycleManager, register the created Thread
- try {
- if (registerForLifecycle) {
- LifecycleManager.register(finalizerThread);
- }
- } catch (Exception e) {
- System.err.println("Unable to register for lifecycle events: " + e.getMessage());
- // Ignore
- }
-
- return finalizerThread.queue;
- }
-
- private final WeakReference> finalizableReferenceClassReference;
- private final PhantomReference frqReference;
- private final ReferenceQueue queue = new ReferenceQueue();
-
- /** Constructs a new finalizer thread. */
- private Finalizer(Class> finalizableReferenceClass, Object frq) {
- super(Finalizer.class.getName());
-
- this.finalizableReferenceClassReference
- = new WeakReference>(finalizableReferenceClass);
-
- // Keep track of the FRQ that started us so we know when to stop.
- this.frqReference = new PhantomReference(frq, queue);
-
- setDaemon(true);
-
- // TODO: Priority?
- }
-
- /**
- * Loops continuously, pulling references off the queue and cleaning them up.
- */
- @Override
- public void run() {
- try {
- while (true) {
- try {
- cleanUp(queue.remove());
- } catch (InterruptedException e) {
- // Thread has been interrupted, so do local cleanup
- frqReference.clear();
-
- // Ensure that anything that may be remaining on the queue is cleaned up
- Reference> reference = queue.poll();
- while (reference != null) {
- cleanUp(reference);
- reference = queue.poll();
- }
-
- // Shut down the current thread
- throw new ShutDown();
- }
- }
- } catch (ShutDown shutDown) { /* ignore */ }
- }
-
- /**
- * Cleans up a single reference. Catches and logs all throwables.
- */
- private void cleanUp(Reference> reference) throws ShutDown {
- Method finalizeReferentMethod = getFinalizeReferentMethod();
- do {
- /*
- * This is for the benefit of phantom references. Weak and soft
- * references will have already been cleared by this point.
- */
- reference.clear();
-
- if (reference == frqReference) {
- /*
- * The client no longer has a reference to the
- * FinalizableReferenceQueue. We can stop.
- */
- throw new ShutDown();
- }
-
- try {
- finalizeReferentMethod.invoke(reference);
- } catch (Throwable t) {
- logger.log(Level.SEVERE, "Error cleaning up after reference.", t);
- }
-
- /*
- * Loop as long as we have references available so as not to waste
- * CPU looking up the Method over and over again.
- */
- } while ((reference = queue.poll()) != null);
- }
-
- /**
- * Looks up FinalizableReference.finalizeReferent() method.
- */
- private Method getFinalizeReferentMethod() throws ShutDown {
- Class> finalizableReferenceClass
- = finalizableReferenceClassReference.get();
- if (finalizableReferenceClass == null) {
- /*
- * FinalizableReference's class loader was reclaimed. While there's a
- * chance that other finalizable references could be enqueued
- * subsequently (at which point the class loader would be resurrected
- * by virtue of us having a strong reference to it), we should pretty
- * much just shut down and make sure we don't keep it alive any longer
- * than necessary.
- */
- throw new ShutDown();
- }
- try {
- return finalizableReferenceClass.getMethod("finalizeReferent");
- } catch (NoSuchMethodException e) {
- throw new AssertionError(e);
- }
- }
-
- /**
- * Lifecycle method to destroy the finalizer thread
- */
- public void shutdown() {
- // Thread will shut itself down when interrupted
- this.interrupt();
- }
-
- /** Indicates that it's time to shut down the Finalizer. */
- private class ShutDown extends Exception {}
-}
diff --git a/src/main/java/org/azeckoski/reflectutils/refmap/ReferenceMap.java b/src/main/java/org/azeckoski/reflectutils/refmap/ReferenceMap.java
deleted file mode 100644
index 3973a1e..0000000
--- a/src/main/java/org/azeckoski/reflectutils/refmap/ReferenceMap.java
+++ /dev/null
@@ -1,648 +0,0 @@
-/**
- * Copyright (C) 2006 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.azeckoski.reflectutils.refmap;
-
-import static org.azeckoski.reflectutils.refmap.ReferenceType.STRONG;
-
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
-import java.lang.ref.Reference;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.WeakHashMap;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-/**
- * Concurrent hash map that wraps keys and/or values in soft or weak
- * references. Does not support null keys or values. Uses identity equality
- * for weak and soft keys.
- *
- *
The concurrent semantics of {@link ConcurrentHashMap} combined with the
- * fact that the garbage collector can asynchronously reclaim and clean up
- * after keys and values at any time can lead to some racy semantics. For
- * example, {@link #size()} returns an upper bound on the size, i.e. the actual
- * size may be smaller in cases where the key or value has been reclaimed but
- * the map entry has not been cleaned up yet.
- *
- *
Another example: If {@link #get(Object)} cannot find an existing entry
- * for a key, it will try to create one. This operation is not atomic. One
- * thread could {@link #put(Object, Object)} a value between the time another
- * thread running {@code get()} checks for an entry and decides to create one.
- * In this case, the newly created value will replace the put value in the
- * map. Also, two threads running {@code get()} concurrently can potentially
- * create duplicate values for a given key.
- *
- *
In other words, this class is great for caching but not atomicity.
- *
- *
To determine equality to a key, this implementation uses
- * {@link Object#equals} for strong references, and identity-based equality for
- * soft and weak references. In other words, for a map with weak or soft key
- * references, {@link #get} returns {@code null} when passed an object that
- * equals a map key, but isn't the same instance. This behavior is similar to
- * the way {@link IdentityHashMap} handles key lookups. However, to determine
- * value equality, as occurs when {@link #containsValue} is called, the
- * {@code ReferenceMap} always uses {@code equals}, regardless of the value
- * reference type.
- *
- *
Note: {@code new ReferenceMap(WEAK, STRONG)} is very nearly a
- * drop-in replacement for {@link WeakHashMap}, but improves upon this by using
- * only identity-based equality for keys. When possible, {@code ReferenceMap}
- * should be preferred over the JDK collection, for its concurrency and greater
- * flexibility.
- *
- * @author crazybob@google.com (Bob Lee)
- */
-@SuppressWarnings("unchecked")
-public class ReferenceMap implements Map, Serializable {
-
- private static final long serialVersionUID = 0;
-
- transient ConcurrentMap delegate;
-
- final ReferenceType keyReferenceType;
- final ReferenceType valueReferenceType;
-
- /**
- * Concurrent hash map that wraps keys and/or values based on specified
- * reference types.
- *
- * @param keyReferenceType key reference type
- * @param valueReferenceType value reference type
- */
- public ReferenceMap(ReferenceType keyReferenceType,
- ReferenceType valueReferenceType) {
- ensureNotNull(keyReferenceType, valueReferenceType);
-
- if (keyReferenceType == ReferenceType.PHANTOM
- || valueReferenceType == ReferenceType.PHANTOM) {
- throw new IllegalArgumentException("Phantom references not supported.");
- }
-
- this.delegate = new ConcurrentHashMap();
- this.keyReferenceType = keyReferenceType;
- this.valueReferenceType = valueReferenceType;
- }
-
- V internalGet(K key) {
- Object valueReference = delegate.get(makeKeyReferenceAware(key));
- return valueReference == null
- ? null
- : (V) dereferenceValue(valueReference);
- }
-
- public V get(final Object key) {
- ensureNotNull(key);
- return internalGet((K) key);
- }
-
- V execute(Strategy strategy, K key, V value) {
- ensureNotNull(key, value);
- Object keyReference = referenceKey(key);
- Object valueReference = strategy.execute(
- this,
- keyReference,
- referenceValue(keyReference, value)
- );
- return valueReference == null ? null
- : (V) dereferenceValue(valueReference);
- }
-
- public V put(K key, V value) {
- return execute(putStrategy(), key, value);
- }
-
- public V remove(Object key) {
- ensureNotNull(key);
- Object referenceAwareKey = makeKeyReferenceAware(key);
- Object valueReference = delegate.remove(referenceAwareKey);
- return valueReference == null ? null
- : (V) dereferenceValue(valueReference);
- }
-
- public int size() {
- return delegate.size();
- }
-
- public boolean isEmpty() {
- return delegate.isEmpty();
- }
-
- public boolean containsKey(Object key) {
- ensureNotNull(key);
- Object referenceAwareKey = makeKeyReferenceAware(key);
- return delegate.containsKey(referenceAwareKey);
- }
-
- public boolean containsValue(Object value) {
- ensureNotNull(value);
- for (Object valueReference : delegate.values()) {
- if (value.equals(dereferenceValue(valueReference))) {
- return true;
- }
- }
- return false;
- }
-
- public void putAll(Map extends K, ? extends V> t) {
- for (Map.Entry extends K, ? extends V> entry : t.entrySet()) {
- put(entry.getKey(), entry.getValue());
- }
- }
-
- public void clear() {
- delegate.clear();
- }
-
- /**
- * Returns an unmodifiable set view of the keys in this map. As this method
- * creates a defensive copy, the performance is O(n).
- */
- public Set keySet() {
- return Collections.unmodifiableSet(
- dereferenceKeySet(delegate.keySet()));
- }
-
- /**
- * Returns an unmodifiable set view of the values in this map. As this
- * method creates a defensive copy, the performance is O(n).
- */
- public Collection values() {
- return Collections.unmodifiableCollection(
- dereferenceValues(delegate.values()));
- }
-
- public V putIfAbsent(K key, V value) {
- // TODO (crazybob) if the value has been gc'ed but the entry hasn't been
- // cleaned up yet, this put will fail.
- return execute(putIfAbsentStrategy(), key, value);
- }
-
- public boolean remove(Object key, Object value) {
- ensureNotNull(key, value);
- Object referenceAwareKey = makeKeyReferenceAware(key);
- Object referenceAwareValue = makeValueReferenceAware(value);
- return delegate.remove(referenceAwareKey, referenceAwareValue);
- }
-
- public boolean replace(K key, V oldValue, V newValue) {
- ensureNotNull(key, oldValue, newValue);
- Object keyReference = referenceKey(key);
-
- Object referenceAwareOldValue = makeValueReferenceAware(oldValue);
- return delegate.replace(
- keyReference,
- referenceAwareOldValue,
- referenceValue(keyReference, newValue)
- );
- }
-
- public V replace(K key, V value) {
- // TODO (crazybob) if the value has been gc'ed but the entry hasn't been
- // cleaned up yet, this will succeed when it probably shouldn't.
- return execute(replaceStrategy(), key, value);
- }
-
- /**
- * Returns an unmodifiable set view of the entries in this map. As this
- * method creates a defensive copy, the performance is O(n).
- */
- public Set> entrySet() {
- Set> entrySet = new HashSet>();
- for (Map.Entry entry : delegate.entrySet()) {
- Map.Entry dereferenced = dereferenceEntry(entry);
- if (dereferenced != null) {
- entrySet.add(dereferenced);
- }
- }
- return Collections.unmodifiableSet(entrySet);
- }
-
- /**
- * Dereferences an entry. Returns null if the key or value has been gc'ed.
- */
- Entry dereferenceEntry(Map.Entry entry) {
- K key = dereferenceKey(entry.getKey());
- V value = dereferenceValue(entry.getValue());
- return (key == null || value == null)
- ? null
- : new Entry(key, value);
- }
-
- /**
- * Creates a reference for a key.
- */
- Object referenceKey(K key) {
- switch (keyReferenceType) {
- case STRONG: return key;
- case SOFT: return new SoftKeyReference(key);
- case WEAK: return new WeakKeyReference(key);
- default: throw new AssertionError();
- }
- }
-
- /**
- * Converts a reference to a key.
- */
- K dereferenceKey(Object o) {
- return (K) dereference(keyReferenceType, o);
- }
-
- /**
- * Converts a reference to a value.
- */
- V dereferenceValue(Object o) {
- return (V) dereference(valueReferenceType, o);
- }
-
- /**
- * Returns the refererent for reference given its reference type.
- */
- Object dereference(ReferenceType referenceType, Object reference) {
- return referenceType == STRONG ? reference : ((Reference) reference).get();
- }
-
- /**
- * Creates a reference for a value.
- */
- Object referenceValue(Object keyReference, Object value) {
- switch (valueReferenceType) {
- case STRONG: return value;
- case SOFT: return new SoftValueReference(keyReference, value);
- case WEAK: return new WeakValueReference(keyReference, value);
- default: throw new AssertionError();
- }
- }
-
- /**
- * Dereferences a set of key references.
- */
- Set dereferenceKeySet(Set keyReferences) {
- return keyReferenceType == STRONG
- ? keyReferences
- : dereferenceCollection(keyReferenceType, keyReferences, new HashSet());
- }
-
- /**
- * Dereferences a collection of value references.
- */
- Collection dereferenceValues(Collection valueReferences) {
- return valueReferenceType == STRONG
- ? valueReferences
- : dereferenceCollection(valueReferenceType, valueReferences,
- new ArrayList(valueReferences.size()));
- }
-
- /**
- * Wraps key so it can be compared to a referenced key for equality.
- */
- Object makeKeyReferenceAware(Object o) {
- return keyReferenceType == STRONG ? o : new KeyReferenceAwareWrapper(o);
- }
-
- /**
- * Wraps value so it can be compared to a referenced value for equality.
- */
- Object makeValueReferenceAware(Object o) {
- return valueReferenceType == STRONG ? o : new ReferenceAwareWrapper(o);
- }
-
- /**
- * Dereferences elements in {@code in} using
- * {@code referenceType} and puts them in {@code out}. Returns
- * {@code out}.
- */
- > T dereferenceCollection(
- ReferenceType referenceType, T in, T out) {
- for (Object reference : in) {
- out.add(dereference(referenceType, reference));
- }
- return out;
- }
-
- static int keyHashCode(Object key) {
- return System.identityHashCode(key);
- }
-
- /**
- * Tests weak and soft references for identity equality. Compares references
- * to other references and wrappers. If o is a reference, this returns true
- * if r == o or if r and o reference the same non null object. If o is a
- * wrapper, this returns true if r's referent is identical to the wrapped
- * object.
- */
- static boolean referenceEquals(Reference r, Object o) {
- // compare reference to reference.
- if (o instanceof InternalReference) {
- // are they the same reference? used in cleanup.
- if (o == r) {
- return true;
- }
-
- // do they reference identical values? used in conditional puts.
- Object referent = ((Reference) o).get();
- return referent != null && referent == r.get();
- }
-
- // is the wrapped object identical to the referent? used in lookups.
- return ((ReferenceAwareWrapper) o).unwrap() == r.get();
- }
-
- /**
- * Big hack. Used to compare keys and values to referenced keys and values
- * without creating more references.
- */
- static class ReferenceAwareWrapper {
-
- final Object wrapped;
-
- ReferenceAwareWrapper(Object wrapped) {
- this.wrapped = wrapped;
- }
-
- Object unwrap() {
- return wrapped;
- }
-
- public int hashCode() {
- return wrapped.hashCode();
- }
-
- public boolean equals(Object obj) {
- // defer to reference's equals() logic.
- return obj == null ? false : obj.equals(this);
- }
- }
-
- /**
- * Used for keys. Overrides hash code to use identity hash code.
- */
- static class KeyReferenceAwareWrapper extends ReferenceAwareWrapper {
-
- public KeyReferenceAwareWrapper(Object wrapped) {
- super(wrapped);
- }
-
- @Override public int hashCode() {
- return System.identityHashCode(wrapped);
- }
- @Override public boolean equals(Object arg0) {
- return super.equals(arg0);
- }
- }
- /**
- * Lazy initialization holder for finalizable reference queue.
- */
- private static class ReferenceQueue {
- private static final FinalizableReferenceQueue instance
- = new FinalizableReferenceQueue();
- }
-
- /*
- * Marker interface to differentiate external and internal references. Also
- * duplicates finalizeReferent() and Reference.get() for internal use.
- */
- private interface InternalReference {
- void finalizeReferent();
- Object get();
- }
-
- private class SoftKeyReference extends FinalizableSoftReference
- implements InternalReference {
- final int hashCode;
-
- SoftKeyReference(Object key) {
- super(key, ReferenceQueue.instance);
- hashCode = System.identityHashCode(key);
- }
- public void finalizeReferent() {
- delegate.remove(this);
- }
- @Override public int hashCode() {
- return hashCode;
- }
- @Override public boolean equals(Object object) {
- return referenceEquals(this, object);
- }
- }
-
- private class SoftValueReference extends FinalizableSoftReference
- implements InternalReference {
- final Object keyReference;
-
- SoftValueReference(Object keyReference, Object value) {
- super(value, ReferenceQueue.instance);
- this.keyReference = keyReference;
- }
- public void finalizeReferent() {
- delegate.remove(keyReference, this);
- }
- @Override public int hashCode() {
- // It's hard to define a useful hash code, so we're careful not to use it.
- throw new AssertionError("don't hash me");
- }
- @Override public boolean equals(Object obj) {
- return referenceEquals(this, obj);
- }
- }
-
- /*
- * WeakKeyReference/WeakValueReference are absolutely identical to
- * SoftKeyReference/SoftValueReference except for which classes they extend.
- */
-
- private class WeakKeyReference extends FinalizableWeakReference
- implements InternalReference {
- final int hashCode;
-
- WeakKeyReference(Object key) {
- super(key, ReferenceQueue.instance);
- hashCode = System.identityHashCode(key);
- }
- public void finalizeReferent() {
- delegate.remove(this);
- }
- @Override public int hashCode() {
- return hashCode;
- }
- @Override public boolean equals(Object object) {
- return referenceEquals(this, object);
- }
- }
-
- private class WeakValueReference extends FinalizableWeakReference
- implements InternalReference {
- final Object keyReference;
-
- WeakValueReference(Object keyReference, Object value) {
- super(value, ReferenceQueue.instance);
- this.keyReference = keyReference;
- }
- public void finalizeReferent() {
- delegate.remove(keyReference, this);
- }
- @Override public int hashCode() {
- // It's hard to define a useful hash code, so we're careful not to use it.
- throw new AssertionError("don't hash me");
- }
- @Override public boolean equals(Object obj) {
- return referenceEquals(this, obj);
- }
- }
-
- protected interface Strategy {
- public Object execute(ReferenceMap map, Object keyReference,
- Object valueReference);
- }
-
- protected Strategy putStrategy() {
- return PutStrategy.PUT;
- }
-
- protected Strategy putIfAbsentStrategy() {
- return PutStrategy.PUT_IF_ABSENT;
- }
-
- protected Strategy replaceStrategy() {
- return PutStrategy.REPLACE;
- }
-
- protected enum PutStrategy implements Strategy {
- PUT {
- public Object execute(ReferenceMap map, Object keyReference,
- Object valueReference) {
- return map.delegate.put(keyReference, valueReference);
- }
- },
-
- REPLACE {
- public Object execute(ReferenceMap map, Object keyReference,
- Object valueReference) {
- return map.delegate.replace(keyReference, valueReference);
- }
- },
-
- PUT_IF_ABSENT {
- public Object execute(ReferenceMap map, Object keyReference,
- Object valueReference) {
- return map.delegate.putIfAbsent(keyReference, valueReference);
- }
- };
- };
-
- private static PutStrategy defaultPutStrategy;
- public static void setDefaultPutStrategy(PutStrategy defaultPutStrategy) {
- ReferenceMap.defaultPutStrategy = defaultPutStrategy;
- }
- protected PutStrategy getPutStrategy() {
- return defaultPutStrategy;
- }
-
-
- class Entry implements Map.Entry {
-
- final K key;
- final V value;
-
- public Entry(K key, V value) {
- this.key = key;
- this.value = value;
- }
-
- public K getKey() {
- return this.key;
- }
-
- public V getValue() {
- return this.value;
- }
-
- public V setValue(V value) {
- return put(key, value);
- }
-
- public int hashCode() {
- return key.hashCode() * 31 + value.hashCode();
- }
-
- public boolean equals(Object o) {
- if (!(o instanceof ReferenceMap.Entry)) {
- return false;
- }
-
- Entry entry = (Entry) o;
- return key.equals(entry.key) && value.equals(entry.value);
- }
-
- public String toString() {
- return key + "=" + value;
- }
- }
-
- static void ensureNotNull(Object o) {
- if (o == null) {
- throw new NullPointerException();
- }
- }
-
- static void ensureNotNull(Object... array) {
- for (int i = 0; i < array.length; i++) {
- if (array[i] == null) {
- throw new NullPointerException("Argument #" + i + " is null.");
- }
- }
- }
-
- private void writeObject(ObjectOutputStream out) throws IOException {
- out.defaultWriteObject();
- out.writeInt(size());
- for (Map.Entry entry : delegate.entrySet()) {
- Object key = dereferenceKey(entry.getKey());
- Object value = dereferenceValue(entry.getValue());
-
- // don't persist gc'ed entries.
- if (key != null && value != null) {
- out.writeObject(key);
- out.writeObject(value);
- }
- }
- out.writeObject(null);
- }
-
- private void readObject(ObjectInputStream in) throws IOException,
- ClassNotFoundException {
- in.defaultReadObject();
- int size = in.readInt();
- this.delegate = new ConcurrentHashMap(size);
- while (true) {
- K key = (K) in.readObject();
- if (key == null) {
- break;
- }
- V value = (V) in.readObject();
- put(key, value);
- }
- }
-
-}
diff --git a/src/main/java/org/azeckoski/reflectutils/refmap/ReferenceType.java b/src/main/java/org/azeckoski/reflectutils/refmap/ReferenceType.java
deleted file mode 100644
index 59b59a9..0000000
--- a/src/main/java/org/azeckoski/reflectutils/refmap/ReferenceType.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * Copyright (C) 2006 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.azeckoski.reflectutils.refmap;
-
-/**
- * Reference type. Used to specify what type of reference to keep to a
- * referent.
- *
- * @see java.lang.ref.Reference
- * @author crazybob@google.com (Bob Lee)
- */
-public enum ReferenceType {
-
- /**
- * Prevents referent from being reclaimed by the garbage collector.
- */
- STRONG,
-
- /**
- * Referent reclaimed in an LRU fashion when the VM runs low on memory and
- * no strong references exist.
- *
- * @see java.lang.ref.SoftReference
- */
- SOFT,
-
- /**
- * Referent reclaimed when no strong or soft references exist.
- *
- * @see java.lang.ref.WeakReference
- */
- WEAK,
-
- /**
- * Similar to weak references except the garbage collector doesn't actually
- * reclaim the referent. More flexible alternative to finalization.
- *
- * @see java.lang.ref.PhantomReference
- */
- PHANTOM;
-}
diff --git a/src/main/java/org/azeckoski/reflectutils/refmap/package.html b/src/main/java/org/azeckoski/reflectutils/refmap/package.html
deleted file mode 100644
index 7c6ee8e..0000000
--- a/src/main/java/org/azeckoski/reflectutils/refmap/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-A map for holding soft/weak references to objects (key and values)
-
-
\ No newline at end of file
diff --git a/src/main/java/org/azeckoski/reflectutils/transcoders/HTMLTranscoder.java b/src/main/java/org/azeckoski/reflectutils/transcoders/HTMLTranscoder.java
deleted file mode 100644
index 96d78e6..0000000
--- a/src/main/java/org/azeckoski/reflectutils/transcoders/HTMLTranscoder.java
+++ /dev/null
@@ -1,378 +0,0 @@
-/**
- * $Id: HTMLTranscoder.java 81 2011-12-19 17:03:40Z azeckoski $
- * $URL: http://reflectutils.googlecode.com/svn/trunk/src/main/java/org/azeckoski/reflectutils/transcoders/HTMLTranscoder.java $
- * HTMLTranscoder.java - entity-broker - Sep 15, 2008 6:36:42 PM - azeckoski
- **************************************************************************
- * Copyright (c) 2008 Aaron Zeckoski
- * Licensed under the Apache License, Version 2.0
- *
- * A copy of the Apache License has been included in this
- * distribution and is available at: http://www.apache.org/licenses/LICENSE-2.0.txt
- *
- * Aaron Zeckoski (azeckoski @ gmail.com) (aaronz @ vt.edu) (aaron @ caret.cam.ac.uk)
- */
-
-package org.azeckoski.reflectutils.transcoders;
-
-import java.lang.reflect.Array;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import org.azeckoski.reflectutils.ArrayUtils;
-import org.azeckoski.reflectutils.ConstructorUtils;
-import org.azeckoski.reflectutils.ReflectUtils;
-import org.azeckoski.reflectutils.ClassFields.FieldsFilter;
-
-
-/**
- * Provides methods for encoding and decoding HTML
- * Note that the HTML parser is not supported
- *
- * @author Aaron Zeckoski (azeckoski @ gmail.com)
- */
-@SuppressWarnings({ "unchecked", "rawtypes" })
-public class HTMLTranscoder implements Transcoder {
-
- public String getHandledFormat() {
- return "html";
- }
-
- public String encode(Object object, String name, Map properties) {
- return encode(object, name, properties, this.maxLevel);
- }
-
- public String encode(Object object, String name, Map properties, int maxDepth) {
- String encoded = "";
- if (object != null) {
- if (name == null || "".equals(name)) {
- name = DATA_KEY;
- }
- }
- encoded = HTMLTranscoder.makeHTML(object, name, properties, this.humanOutput, this.includeNulls, this.includeClassField, maxDepth, this.encoders);
- return encoded;
- }
-
- public Map decode(String string) {
- throw new UnsupportedOperationException("Decoding from HTML is not supported");
- }
-
- /**
- * Default constructor:
- * See other constructors for options
- */
- public HTMLTranscoder() {}
-
- private List encoders = null;
- public void setEncoders(List encoders) {
- this.encoders = encoders;
- }
- public List getEncoders() {
- return encoders;
- }
- public void addEncoder(ObjectEncoder objectEncoder) {
- if (this.encoders == null) {
- this.encoders = new ArrayList();
- }
- this.encoders.add(objectEncoder);
- }
-
- private boolean humanOutput = true;
- private boolean includeNulls = true;
- private boolean includeClassField = false;
- /**
- * @param humanOutput if true then enable human readable output (includes indentation and line breaks)
- * @param includeNulls if true then create output tags for null values
- * @param includeClassField if true then include the value from the "getClass()" method as "class" when encoding beans and maps
- */
- public HTMLTranscoder(boolean humanOutput, boolean includeNulls, boolean includeClassField) {
- this.humanOutput = humanOutput;
- this.includeNulls = includeNulls;
- this.includeClassField = includeClassField;
- }
-
- private int maxLevel = 7;
- /**
- * @param maxLevel the number of objects to follow when traveling through the object,
- * 0 means only the fields in the initial object, default is 7
- */
- public void setMaxLevel(int maxLevel) {
- this.maxLevel = maxLevel;
- }
-
-
- public static final char SPACE = ' ';
- public static final char AMP = '&';
- /**
- * single quote (')
- */
- public static final char APOS = '\'';
- public static final char BANG = '!';
- public static final char EQ = '=';
- public static final char GT = '>';
- public static final char LT = '<';
- public static final char QUEST = '?';
- public static final char QUOT = '"';
- public static final char SLASH = '/';
- public static final char EOL = '\n';
-
-
- /**
- * Convert an object into a well-formed, element-normal HTML string.
- * @param object any object
- * @return the HTML string version of the object
- */
- public static String makeHTML(Object object) {
- return makeHTML(object, null, null, false, true, false, 7, null);
- }
-
- /**
- * Convert an object into a well-formed, element-normal HTML string.
- * @param object any object
- * @param tagName (optional) enclosing root tag
- * @param humanOutput true of human readable output
- * @param includeNulls true to include null values when generating tags
- * @param maxLevel TODO
- * @return the HTML string version of the object
- */
- public static String makeHTML(Object object, String tagName, Map properties, boolean humanOutput, boolean includeNulls, boolean includeClassField, int maxLevel, List encoders) {
- return "
");
- makeEOL(sb, humanOutput);
- } else {
- // must be a bean or map, make sure it is a map
- tagName = validate(tagName == null ? makeElementName(type) : tagName);
- // special handling for certain object types
- String special = TranscoderUtils.checkObjectSpecial(object);
- if (special != null) {
- if ("".equals(special)) {
- // skip this one entirely
- } else {
- // just use the value in special to represent this
- makeLevelSpaces(sb, level, humanOutput);
- String value = escapeForXML( special );
- sb.append("
");
- sb.append(tagName);
- sb.append("
");
- sb.append(value);
- sb.append("
");
- makeEOL(sb, humanOutput);
- }
- } else {
- // normal handling
- if ((maxLevel*2) <= level) {
- // if the max level was reached then stop
- sb.append("
");
- makeEOL(sb, humanOutput);
- }
- }
- }
- }
- return sb.toString();
- }
-
- protected static String makeElementName(Class> type) {
- String name = "element";
- if (type != null) {
- if (! Map.class.isAssignableFrom(type)) {
- name = type.getSimpleName();
- }
- }
- return name;
- }
-
- protected static void makeEOL(StringBuilder sb, boolean includeEOL) {
- if (includeEOL) {
- sb.append(EOL);
- }
- }
-
- protected static final String SPACES = " ";
- protected static void makeLevelSpaces(StringBuilder sb, int level, boolean includeEOL) {
- level++;
- if (includeEOL) {
- for (int i = 0; i < level; i++) {
- sb.append(SPACES);
- }
- }
- }
-
- /**
- * Escape a string for XML encoding: replace special characters with XML escapes:
- *
- * & (ampersand) is replaced by &
- * < (less than) is replaced by <
- * > (greater than) is replaced by >
- * " (double quote) is replaced by "
- *
- * @param string The string to be escaped.
- * @return The escaped string.
- */
- public static String escapeForXML(String string) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0, len = string.length(); i < len; i++) {
- char c = string.charAt(i);
- switch (c) {
- case AMP:
- sb.append("&");
- break;
- case LT:
- sb.append("<");
- break;
- case GT:
- sb.append(">");
- break;
- case QUOT:
- sb.append(""");
- break;
- default:
- sb.append(c);
- }
- }
- return sb.toString();
- }
-
- /**
- * Validates that a string contains no spaces and is non-null/non-empty
- * Throw an exception if the string contains whitespace.
- * Whitespace is not allowed in tagNames and attributes.
- * @param string any string
- * @throws IllegalArgumentException
- */
- public static String validate(String string) {
- if (string == null) {
- throw new IllegalArgumentException("string is NULL");
- }
- int i, length = string.length();
- if (length == 0) {
- throw new IllegalArgumentException("Empty string.");
- }
- for (i = 0; i < length; i += 1) {
- if (Character.isWhitespace(string.charAt(i))) {
- throw new IllegalArgumentException("'" + string + "' contains a space character.");
- }
- }
- return string;
- }
-}
diff --git a/src/main/java/org/azeckoski/reflectutils/transcoders/JSONTranscoder.java b/src/main/java/org/azeckoski/reflectutils/transcoders/JSONTranscoder.java
index 9b01a40..4a13131 100644
--- a/src/main/java/org/azeckoski/reflectutils/transcoders/JSONTranscoder.java
+++ b/src/main/java/org/azeckoski/reflectutils/transcoders/JSONTranscoder.java
@@ -1,655 +1,92 @@
-/**
- * $Id: JSONTranscoder.java 110 2013-05-03 13:30:13Z azeckoski $
- * $URL: http://reflectutils.googlecode.com/svn/trunk/src/main/java/org/azeckoski/reflectutils/transcoders/JSONTranscoder.java $
- * JSONTranscoder.java - entity-broker - Sep 16, 2008 3:19:29 PM - azeckoski
- **************************************************************************
- * Copyright (c) 2008 Aaron Zeckoski
- * Licensed under the Apache License, Version 2.0
- *
- * A copy of the Apache License has been included in this
- * distribution and is available at: http://www.apache.org/licenses/LICENSE-2.0.txt
- *
- * Aaron Zeckoski (azeckoski @ gmail.com) (aaronz @ vt.edu) (aaron @ caret.cam.ac.uk)
- */
-
package org.azeckoski.reflectutils.transcoders;
-import java.lang.reflect.Array;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.sql.Timestamp;
-import java.text.CharacterIterator;
-import java.text.StringCharacterIterator;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import org.azeckoski.reflectutils.ArrayUtils;
-import org.azeckoski.reflectutils.ConstructorUtils;
-import org.azeckoski.reflectutils.ConversionUtils;
-import org.azeckoski.reflectutils.ReflectUtils;
-import org.azeckoski.reflectutils.ClassFields.FieldsFilter;
-import org.azeckoski.reflectutils.map.ArrayOrderedMap;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
/**
- * Provides methods for encoding and decoding JSON
- *
- * @author Aaron Zeckoski (azeckoski @ gmail.com)
+ * Jackson-backed implementation for JSON encoding/decoding.
*/
-@SuppressWarnings({ "unchecked", "rawtypes" })
public class JSONTranscoder implements Transcoder {
- public String getHandledFormat() {
- return "json";
- }
-
- public String encode(Object object, String name, Map properties) {
- return encode(object, name, properties, this.maxLevel);
- }
-
- public String encode(Object object, String name, Map properties, int maxDepth) {
-// Object data = object;
-// String encoded = "";
-// if (object != null) {
-// Map mapData = ReflectUtils.getInstance().map(object, 10, null, false, true, Transcoder.DATA_KEY);
-// // for JSON we can get out the "data" field and convert that only
-// if (mapData.size() == 1 && mapData.containsKey(Transcoder.DATA_KEY)) {
-// data = mapData.get(Transcoder.DATA_KEY);
-// } else {
-// data = mapData;
-// }
-// }
- // allow the transcoder to deal with the data directly, no need to convert it to a map first
- String encoded = JSONTranscoder.makeJSON(object, properties, humanOutput, includeNulls, includeClassField, maxDepth, null);
- return encoded;
- }
-
- public Map decode(String string) {
- Map decoded = null;
- Object decode = new JsonReader().read(string);
- if (decode instanceof Map) {
- decoded = (Map) decode;
- } else {
- // for JSON if the result is not a map then simply put the result into a map
- decoded = new ArrayOrderedMap();
- decoded.put(Transcoder.DATA_KEY, decode);
- }
- return decoded;
- }
-
- /**
- * Default constructor:
- * See other constructors for options
- */
- public JSONTranscoder() {}
+ private final ObjectMapper mapper;
+ private final boolean includeClassField;
- private List encoders = null;
- public void setEncoders(List encoders) {
- this.encoders = encoders;
- }
- public List getEncoders() {
- return encoders;
- }
- public void addEncoder(ObjectEncoder objectEncoder) {
- if (this.encoders == null) {
- this.encoders = new ArrayList();
- }
- this.encoders.add(objectEncoder);
+ public JSONTranscoder() {
+ this(false, true, false);
}
- private boolean humanOutput = false;
- private boolean includeNulls = true;
- private boolean includeClassField = false;
- /**
- * @param humanOutput if true then enable human readable output (includes indentation and line breaks)
- * @param includeNulls if true then create output tags for null values
- * @param includeClassField if true then include the value from the "getClass()" method as "class" when encoding beans and maps
- */
public JSONTranscoder(boolean humanOutput, boolean includeNulls, boolean includeClassField) {
- this.humanOutput = humanOutput;
- this.includeNulls = includeNulls;
this.includeClassField = includeClassField;
- }
-
- private int maxLevel = 7;
- /**
- * @param maxLevel the number of objects to follow when traveling through the object,
- * 0 means only the fields in the initial object, default is 7
- */
- public void setMaxLevel(int maxLevel) {
- this.maxLevel = maxLevel;
- }
-
-
- // Encoder
-
- public static final char OBJ_BEG = '{';
- public static final char OBJ_END = '}';
- public static final char OBJ_SEP = ':';
- public static final char ARRAY_BEG = '[';
- public static final char ARRAY_END = ']';
- public static final char JSON_SEP = ',';
-
- // based on code from: http://www.json.org/java/org/json/XML.java
-
- public static final char SPACE = ' ';
- public static final char AMP = '&';
- /**
- * single quote (')
- */
- public static final char APOS = '\'';
- public static final char BANG = '!';
- public static final char BACK = '\\';
- public static final char EOL = '\n';
- public static final char EQ = '=';
- public static final char GT = '>';
- public static final char LT = '<';
- public static final char QUEST = '?';
- public static final char QUOT = '"';
- public static final char SLASH = '/';
-
- public static final String BOOLEAN_TRUE = "true";
- public static final String BOOLEAN_FALSE = "false";
- public static final String NULL = "null";
-
- /**
- * Convert an object into a well-formed, element-normal XML string.
- * @param object any object
- * @return the JSON string version of the object
- */
- public static String makeJSON(Object object) {
- return makeJSON(object, null, false, true, false, 10, null);
- }
-
- /**
- * Convert an object into a well-formed, element-normal XML string.
- * @param object any object
- * @param humanOutput true of human readable output
- * @param includeNulls true to include null values when generating tags
- * @param includeClassField if true then include the value from the "getClass()" method as "class" when encoding beans and maps
- * @param maxLevel maximum level to traverse the objects before stopping
- * @param encoders the external encoders to allow to process complex objects
- * @return the JSON string version of the object
- */
- public static String makeJSON(Object object, Map properties, boolean humanOutput, boolean includeNulls, boolean includeClassField, int maxLevel, List encoders) {
- return toJSON(object, 0, maxLevel, humanOutput, includeNulls, includeClassField, properties, encoders);
- }
-
- protected static String toJSON(Object object, int level, int maxLevel, boolean humanOutput, boolean includeNulls, boolean includeClassField, Map properties, List encoders) {
- StringBuilder sb = new StringBuilder();
-
- if (object == null) {
- if (includeNulls) {
- // nulls use the constant
- sb.append(NULL);
- }
- } else {
- Class> type = ConstructorUtils.getWrapper(object.getClass());
- if ( ConstructorUtils.isClassSimple(type) ) {
- // Simple (String, Number, etc.)
- if (Date.class.isAssignableFrom(type) || Timestamp.class.isAssignableFrom(type)) {
- // date
- Date d = (Date) object;
- sb.append(d.getTime());
- } else if (Number.class.isAssignableFrom(type)) {
- // number
- sb.append(object.toString());
- } else if (Boolean.class.isAssignableFrom(type)) {
- // boolean
- if ( ((Boolean)object).booleanValue() ) {
- sb.append(BOOLEAN_TRUE);
- } else {
- sb.append(BOOLEAN_FALSE);
- }
- } else {
- sb.append(QUOT);
- sb.append( escapeForJSON(object.toString()) );
- sb.append(QUOT);
- }
- } else if ( ConstructorUtils.isClassArray(type) ) {
- // ARRAY
- int length = ArrayUtils.size((Object[])object);
- sb.append(ARRAY_BEG);
- if (length > 0) {
- for (int i = 0; i < length; ++i) {
- if (i > 0) {
- sb.append(JSON_SEP);
- }
- makeEOL(sb, humanOutput);
- makeLevelSpaces(sb, level+1, humanOutput);
- sb.append( toJSON(Array.get(object, i), level+1, maxLevel, humanOutput, includeNulls, includeClassField, properties, encoders) );
- }
- makeEOL(sb, humanOutput);
- makeLevelSpaces(sb, level, humanOutput);
- }
- sb.append(ARRAY_END);
- } else if ( ConstructorUtils.isClassCollection(type) ) {
- // COLLECTION
- Collection collection = (Collection) object;
- sb.append(ARRAY_BEG);
- if (! collection.isEmpty()) {
- boolean first = true;
- for (Object element : collection) {
- if (first) {
- first = false;
- } else {
- sb.append(JSON_SEP);
- }
- makeEOL(sb, humanOutput);
- makeLevelSpaces(sb, level+1, humanOutput);
- sb.append( toJSON(element, level+1, maxLevel, humanOutput, includeNulls, includeClassField, properties, encoders) );
- }
- makeEOL(sb, humanOutput);
- makeLevelSpaces(sb, level, humanOutput);
- }
- sb.append(ARRAY_END);
- } else {
- // must be a bean or map, make sure it is a map
- // special handling for certain object types
- String special = TranscoderUtils.handleObjectEncoding(object, encoders);
- if (special != null) {
- if ("".equals(special)) {
- // skip this one entirely
- sb.append(NULL);
- } else {
- // just use the value in special to represent this
- sb.append(QUOT);
- sb.append( escapeForJSON(special) );
- sb.append(QUOT);
- }
- } else {
- // normal handling
- if (maxLevel <= level) {
- // if the max level was reached then stop
- sb.append(QUOT);
- sb.append( "MAX level reached (" );
- sb.append( level );
- sb.append( "):" );
- sb.append( escapeForJSON(object.toString()) );
- sb.append(QUOT);
- } else {
- Map map = null;
- if (Map.class.isAssignableFrom(type)) {
- map = (Map) object;
- } else {
- // reflect over objects
- map = ReflectUtils.getInstance().getObjectValues(object, FieldsFilter.SERIALIZABLE, includeClassField);
- }
- // add in the optional properties if it makes sense to do so
- if (level == 0 && properties != null && ! properties.isEmpty()) {
- map.putAll(properties);
- }
- sb.append(OBJ_BEG);
- boolean first = true;
- for (Entry entry : map.entrySet()) {
- if (entry.getKey() != null) {
- Object value = entry.getValue();
- if (value != null || includeNulls) {
- if (first) {
- first = false;
- } else {
- sb.append(JSON_SEP);
- }
- makeEOL(sb, humanOutput);
- makeLevelSpaces(sb, level+1, humanOutput);
- sb.append(QUOT);
- sb.append(entry.getKey());
- sb.append(QUOT);
- sb.append(OBJ_SEP);
- if (humanOutput) { sb.append(SPACE); }
- sb.append( toJSON(value, level+1, maxLevel, humanOutput, includeNulls, includeClassField, properties, encoders) );
- }
- }
- }
- makeEOL(sb, humanOutput);
- makeLevelSpaces(sb, level, humanOutput);
- sb.append(OBJ_END);
- }
- }
- }
+ this.mapper = new ObjectMapper();
+ if (humanOutput) {
+ this.mapper.enable(SerializationFeature.INDENT_OUTPUT);
}
- return sb.toString();
- }
-
- protected static void makeEOL(StringBuilder sb, boolean includeEOL) {
- if (includeEOL) {
- sb.append(EOL);
+ if (!includeNulls) {
+ this.mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
}
- protected static final String SPACES = " ";
- protected static void makeLevelSpaces(StringBuilder sb, int level, boolean includeEOL) {
- if (includeEOL) {
- for (int i = 0; i < level; i++) {
- sb.append(SPACES);
- }
- }
+ @Override
+ public String getHandledFormat() {
+ return "json";
}
- /**
- * Escape a string for JSON encoding
- * @param string any string
- * @return the escaped string
- */
- public static String escapeForJSON(String string) {
- StringBuilder sb = new StringBuilder();
- if (string != null) {
- for (int i = 0, len = string.length(); i < len; i++) {
- char c = string.charAt(i);
- switch (c) {
- case QUOT:
- sb.append("\\\"");
- break;
- case BACK:
- sb.append("\\\\");
- break;
- case SLASH:
- sb.append("\\/");
- break;
- case '\b':
- sb.append("\\b");
- break;
- case '\f':
- sb.append("\\f");
- break;
- case '\n':
- sb.append("\\n");
- break;
- case '\r':
- sb.append("\\r");
- break;
- case '\t':
- sb.append("\\t");
- break;
- default:
- if (Character.isISOControl(c)) {
- sb.append("\\u");
- int n = c;
- for (int j = 0; j < 4; ++j) {
- int digit = (n & 0xf000) >> 12;
- sb.append(hex[digit]);
- n <<= 4;
- }
- } else {
- sb.append(c);
- }
- }
- }
+ @Override
+ public String encode(Object object, String name, Map properties) {
+ Objects.requireNonNull(object, "object cannot be null");
+ try {
+ Object payload = buildPayload(object, name, properties);
+ return mapper.writeValueAsString(payload);
+ } catch (JsonProcessingException e) {
+ throw new IllegalArgumentException("Failed to encode object to JSON", e);
}
- return sb.toString();
}
+ private Object buildPayload(Object object, String name, Map properties) {
+ boolean hasName = name != null && !name.isEmpty();
+ boolean hasProperties = properties != null && !properties.isEmpty();
- // STATICS
-
- private static final Object MARK_OBJECT_END = new Object();
- private static final Object MARK_ARRAY_END = new Object();
- private static final Object MARK_COLON = new Object();
- private static final Object MARK_COMMA = new Object();
- private static final Object MARK_END_INPUT = new Object();
- public static final int FIRST = 0;
- public static final int CURRENT = 1;
- public static final int NEXT = 2;
-
- private static Map escapes = new HashMap();
- static {
- escapes.put(new Character('"'), new Character('"'));
- escapes.put(new Character('\\'), new Character('\\'));
- escapes.put(new Character('/'), new Character('/'));
- escapes.put(new Character('b'), new Character('\b'));
- escapes.put(new Character('f'), new Character('\f'));
- escapes.put(new Character('n'), new Character('\n'));
- escapes.put(new Character('r'), new Character('\r'));
- escapes.put(new Character('t'), new Character('\t'));
- }
-
- protected static char[] hex = "0123456789ABCDEF".toCharArray();
-
- /**
- * Create Java objects from JSON (note that only simple java objects, maps, and arrays will be returned)
- * Dates will come back in as UTC timecodes or possibly strings which you will need to parse manually
- * Numbers will come in as int if they are small, long if they are big, and BigInteger if they are huge,
- * floating point is handled similarly: float, double, BigDecimal
- * JSON arrays come back as a List always, similarly, any collection or array that was output will come back as a list
- * You can use the {@link ConversionUtils} to help with conversion if needed
- *
- * Derived from code at:
- * https://svn.sourceforge.net/svnroot/stringtree/trunk/src/delivery/java/org/stringtree/json/JSONWriter.java
- */
- public class JsonReader {
-
- private CharacterIterator it; // TODO crikey - thread safety
- private char c; // TODO crikey - thread safety
- private Object token; // TODO crikey - thread safety
- private StringBuffer buf = new StringBuffer(); // TODO crikey - thread safety
-
- private char next() {
- c = it.next();
- return c;
- }
-
- private void skipWhiteSpace() {
- while (Character.isWhitespace(c)) {
- next();
- }
- }
-
- public Object read(CharacterIterator it) {
- return read(it, NEXT);
+ if (!hasName && !hasProperties && !includeClassField) {
+ return object;
}
- public Object read(String string) {
- CharacterIterator ci = new StringCharacterIterator(string);
- return read(ci, FIRST);
+ Map envelope = new LinkedHashMap<>();
+ if (hasProperties) {
+ envelope.putAll(properties);
}
-
- /**
- * 3rd public input method, should mostly not be used directly
- * Called ONLY by the other 2 read methods ({@link #read(String)} and {@link #read(CharacterIterator)})
- * @param ci
- * @param start
- * @return the Object which represents the JSON string, null for invalid
- */
- public Object read(CharacterIterator ci, int start) {
- it = ci;
- switch (start) {
- case FIRST:
- c = it.first();
- break;
- case CURRENT:
- c = it.current();
- break;
- case NEXT:
- c = it.next();
- break;
- }
- Object o = read();
- if (MARK_END_INPUT.equals(o) || MARK_COLON.equals(o)) {
- o = null; // SPECIAL cases - empty or invalid input
- }
- return o;
- }
-
- private Object read() {
- skipWhiteSpace();
- char ch = c;
- next();
- switch (ch) {
- case '"': token = string(); break;
- case '[': token = array(); break;
- case ']': token = MARK_ARRAY_END; break;
- case ',': token = MARK_COMMA; break;
- case '{': token = object(); break;
- case '}': token = MARK_OBJECT_END; break;
- case ':': token = MARK_COLON; break;
- case 't':
- next(); next(); next(); // assumed r-u-e
- token = Boolean.TRUE;
- break;
- case'f':
- next(); next(); next(); next(); // assumed a-l-s-e
- token = Boolean.FALSE;
- break;
- case 'n':
- next(); next(); next(); // assumed u-l-l
- token = null;
- break;
- case StringCharacterIterator.DONE:
- token = MARK_END_INPUT;
- break;
- default:
- if (Character.isDigit(ch) || ch == '-') {
- // Push this back on so it's part of the number
- c = it.previous();
- token = number();
- }
- }
- // System.out.println("token: " + token); // enable this line to see the token stream
- return token;
- }
-
- private Object object() {
- Map ret = new ArrayOrderedMap();
- Object key = read();
- while (token != MARK_OBJECT_END && token != MARK_END_INPUT) {
- read(); // should be a colon
- if (token != MARK_OBJECT_END) {
- ret.put(key, read());
- if (read() == MARK_COMMA) {
- key = read();
- }
- }
- }
- if (MARK_END_INPUT.equals(token)) {
- ret = null; // did not end the object in a valid way
- }
- return ret;
- }
-
- private Object array() {
- List ret = new ArrayList();
- Object value = read();
- while (token != MARK_ARRAY_END && token != MARK_END_INPUT) {
- ret.add(value);
- if (read() == MARK_COMMA) {
- value = read();
- }
- }
- if (MARK_END_INPUT.equals(token)) {
- ret = null; // did not end the array in a valid way
- }
- return ret;
- }
-
- private Object number() {
- int length = 0;
- boolean isFloatingPoint = false;
- buf.setLength(0);
-
- if (c == '-') {
- add();
- }
- length += addDigits();
- if (c == '.') {
- add();
- length += addDigits();
- isFloatingPoint = true;
- }
- if (c == 'e' || c == 'E') {
- add();
- if (c == '+' || c == '-') {
- add();
- }
- addDigits();
- isFloatingPoint = true;
- }
-
- String s = buf.toString();
- // more friendly handling of numbers
- Object num = null;
- if (isFloatingPoint) {
- if (length < 10) {
- num = Float.valueOf(s);
- } else if (length < 17) {
- num = Double.valueOf(s);
- } else {
- num = new BigDecimal(s);
- }
- } else {
- if (length < 10) {
- num = Integer.valueOf(s);
- } else if (length < 19) {
- num = Long.valueOf(s);
- } else {
- num = new BigInteger(s);
- }
- }
- return num;
- }
-
- private int addDigits() {
- int ret;
- for (ret = 0; Character.isDigit(c); ++ret) {
- add();
- }
- return ret;
+ if (includeClassField && !(object instanceof Map)) {
+ envelope.put("class", object.getClass().getName());
}
+ String key = hasName ? name : DATA_KEY;
+ envelope.put(key, object);
+ return envelope;
+ }
- private Object string() {
- buf.setLength(0);
- while (c != '"' && c != StringCharacterIterator.DONE) {
- if (c == '\\') {
- next();
- if (c == 'u') {
- add(unicode());
- } else {
- Object value = escapes.get(new Character(c));
- if (value != null) {
- add(((Character) value).charValue());
- }
- }
- } else {
- add();
- }
+ @Override
+ public Map decode(String string) {
+ Objects.requireNonNull(string, "string cannot be null");
+ try {
+ JsonNode node = mapper.readTree(string);
+ if (node == null || node.isNull()) {
+ return new LinkedHashMap<>();
}
- // unterminated string will terminate automatically
- next();
- return buf.toString();
- }
-
- private void add(char cc) {
- buf.append(cc);
- next();
- }
-
- private void add() {
- add(c);
- }
-
- private char unicode() {
- int value = 0;
- for (int i = 0; i < 4; ++i) {
- switch (next()) {
- case '0': case '1': case '2': case '3': case '4':
- case '5': case '6': case '7': case '8': case '9':
- value = (value << 4) + c - '0';
- break;
- case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
- value = (value << 4) + (c - 'a') + 10;
- break;
- case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
- value = (value << 4) + (c - 'A') + 10;
- break;
- }
+ if (node.isObject()) {
+ return mapper.convertValue(node, new TypeReference