Use diamonds.
diff --git a/src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java b/src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java
index a798409..7248794 100644
--- a/src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java
+++ b/src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java
@@ -1,529 +1,529 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * 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.apache.commons.beanutils2;
-
-
-import java.beans.IntrospectionException;
-import java.beans.PropertyDescriptor;
-import java.lang.ref.Reference;
-import java.lang.ref.SoftReference;
-import java.lang.ref.WeakReference;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-
-
-/**
- * A MappedPropertyDescriptor describes one mapped property.
- * Mapped properties are multivalued properties like indexed properties
- * but that are accessed with a String key instead of an index.
- * Such property values are typically stored in a Map collection.
- * For this class to work properly, a mapped value must have
- * getter and setter methods of the form
- * <p><code>get<strong>Property</strong>(String key)</code> and
- * <p><code>set<strong>Property</strong>(String key, Object value)</code>,
- * <p>where <code><strong>Property</strong></code> must be replaced
- * by the name of the property.
- * @see java.beans.PropertyDescriptor
- *
- */
-public class MappedPropertyDescriptor extends PropertyDescriptor {
- // ----------------------------------------------------- Instance Variables
-
- /**
- * The underlying data type of the property we are describing.
- */
- private Reference<Class<?>> mappedPropertyTypeRef;
-
- /**
- * The reader method for this property (if any).
- */
- private MappedMethodReference mappedReadMethodRef;
-
- /**
- * The writer method for this property (if any).
- */
- private MappedMethodReference mappedWriteMethodRef;
-
- /**
- * The parameter types array for the reader method signature.
- */
- private static final Class<?>[] STRING_CLASS_PARAMETER = new Class[]{String.class};
-
- // ----------------------------------------------------------- Constructors
-
- /**
- * Constructs a MappedPropertyDescriptor for a property that follows
- * the standard Java convention by having getFoo and setFoo
- * accessor methods, with the addition of a String parameter (the key).
- * Thus if the argument name is "fred", it will
- * assume that the writer method is "setFred" and the reader method
- * is "getFred". Note that the property name should start with a lower
- * case character, which will be capitalized in the method names.
- *
- * @param propertyName The programmatic name of the property.
- * @param beanClass The Class object for the target bean. For
- * example sun.beans.OurButton.class.
- *
- * @throws IntrospectionException if an exception occurs during
- * introspection.
- */
- public MappedPropertyDescriptor(final String propertyName, final Class<?> beanClass)
- throws IntrospectionException {
-
- super(propertyName, null, null);
-
- if (propertyName == null || propertyName.length() == 0) {
- throw new IntrospectionException("bad property name: " +
- propertyName + " on class: " + beanClass.getClass().getName());
- }
-
- setName(propertyName);
- final String base = capitalizePropertyName(propertyName);
-
- // Look for mapped read method and matching write method
- Method mappedReadMethod = null;
- Method mappedWriteMethod = null;
- try {
- try {
- mappedReadMethod = getMethod(beanClass, "get" + base,
- STRING_CLASS_PARAMETER);
- } catch (final IntrospectionException e) {
- mappedReadMethod = getMethod(beanClass, "is" + base,
- STRING_CLASS_PARAMETER);
- }
- final Class<?>[] params = { String.class, mappedReadMethod.getReturnType() };
- mappedWriteMethod = getMethod(beanClass, "set" + base, params);
- } catch (final IntrospectionException e) {
- /* Swallow IntrospectionException
- * TODO: Why?
- */
- }
-
- // If there's no read method, then look for just a write method
- if (mappedReadMethod == null) {
- mappedWriteMethod = getMethod(beanClass, "set" + base, 2);
- }
-
- if (mappedReadMethod == null && mappedWriteMethod == null) {
- throw new IntrospectionException("Property '" + propertyName +
- "' not found on " +
- beanClass.getName());
- }
- mappedReadMethodRef = new MappedMethodReference(mappedReadMethod);
- mappedWriteMethodRef = new MappedMethodReference(mappedWriteMethod);
-
- findMappedPropertyType();
- }
-
-
- /**
- * This constructor takes the name of a mapped property, and method
- * names for reading and writing the property.
- *
- * @param propertyName The programmatic name of the property.
- * @param beanClass The Class object for the target bean. For
- * example sun.beans.OurButton.class.
- * @param mappedGetterName The name of the method used for
- * reading one of the property values. May be null if the
- * property is write-only.
- * @param mappedSetterName The name of the method used for writing
- * one of the property values. May be null if the property is
- * read-only.
- *
- * @throws IntrospectionException if an exception occurs during
- * introspection.
- */
- public MappedPropertyDescriptor(final String propertyName, final Class<?> beanClass,
- final String mappedGetterName, final String mappedSetterName)
- throws IntrospectionException {
-
- super(propertyName, null, null);
-
- if (propertyName == null || propertyName.length() == 0) {
- throw new IntrospectionException("bad property name: " +
- propertyName);
- }
- setName(propertyName);
-
- // search the mapped get and set methods
- Method mappedReadMethod = null;
- Method mappedWriteMethod = null;
- mappedReadMethod =
- getMethod(beanClass, mappedGetterName, STRING_CLASS_PARAMETER);
-
- if (mappedReadMethod != null) {
- final Class<?>[] params = { String.class, mappedReadMethod.getReturnType() };
- mappedWriteMethod =
- getMethod(beanClass, mappedSetterName, params);
- } else {
- mappedWriteMethod =
- getMethod(beanClass, mappedSetterName, 2);
- }
- mappedReadMethodRef = new MappedMethodReference(mappedReadMethod);
- mappedWriteMethodRef = new MappedMethodReference(mappedWriteMethod);
-
- findMappedPropertyType();
- }
-
- /**
- * This constructor takes the name of a mapped property, and Method
- * objects for reading and writing the property.
- *
- * @param propertyName The programmatic name of the property.
- * @param mappedGetter The method used for reading one of
- * the property values. May be be null if the property
- * is write-only.
- * @param mappedSetter The method used for writing one the
- * property values. May be null if the property is read-only.
- *
- * @throws IntrospectionException if an exception occurs during
- * introspection.
- */
- public MappedPropertyDescriptor(final String propertyName,
- final Method mappedGetter, final Method mappedSetter)
- throws IntrospectionException {
-
- super(propertyName, mappedGetter, mappedSetter);
-
- if (propertyName == null || propertyName.length() == 0) {
- throw new IntrospectionException("bad property name: " +
- propertyName);
- }
-
- setName(propertyName);
- mappedReadMethodRef = new MappedMethodReference(mappedGetter);
- mappedWriteMethodRef = new MappedMethodReference(mappedSetter);
- findMappedPropertyType();
- }
-
- // -------------------------------------------------------- Public Methods
-
- /**
- * Gets the Class object for the property values.
- *
- * @return The Java type info for the property values. Note that
- * the "Class" object may describe a built-in Java type such as "int".
- * The result may be "null" if this is a mapped property that
- * does not support non-keyed access.
- * <p>
- * This is the type that will be returned by the mappedReadMethod.
- */
- public Class<?> getMappedPropertyType() {
- return mappedPropertyTypeRef.get();
- }
-
- /**
- * Gets the method that should be used to read one of the property value.
- *
- * @return The method that should be used to read the property value.
- * May return null if the property can't be read.
- */
- public Method getMappedReadMethod() {
- return mappedReadMethodRef.get();
- }
-
- /**
- * Sets the method that should be used to read one of the property value.
- *
- * @param mappedGetter The mapped getter method.
- * @throws IntrospectionException If an error occurs finding the
- * mapped property
- */
- public void setMappedReadMethod(final Method mappedGetter)
- throws IntrospectionException {
- mappedReadMethodRef = new MappedMethodReference(mappedGetter);
- findMappedPropertyType();
- }
-
- /**
- * Gets the method that should be used to write one of the property value.
- *
- * @return The method that should be used to write one of the property value.
- * May return null if the property can't be written.
- */
- public Method getMappedWriteMethod() {
- return mappedWriteMethodRef.get();
- }
-
- /**
- * Sets the method that should be used to write the property value.
- *
- * @param mappedSetter The mapped setter method.
- * @throws IntrospectionException If an error occurs finding the
- * mapped property
- */
- public void setMappedWriteMethod(final Method mappedSetter)
- throws IntrospectionException {
- mappedWriteMethodRef = new MappedMethodReference(mappedSetter);
- findMappedPropertyType();
- }
-
- // ------------------------------------------------------- Private Methods
-
- /**
- * Introspect our bean class to identify the corresponding getter
- * and setter methods.
- */
- private void findMappedPropertyType() throws IntrospectionException {
- try {
- final Method mappedReadMethod = getMappedReadMethod();
- final Method mappedWriteMethod = getMappedWriteMethod();
- Class<?> mappedPropertyType = null;
- if (mappedReadMethod != null) {
- if (mappedReadMethod.getParameterTypes().length != 1) {
- throw new IntrospectionException
- ("bad mapped read method arg count");
- }
- mappedPropertyType = mappedReadMethod.getReturnType();
- if (mappedPropertyType == Void.TYPE) {
- throw new IntrospectionException
- ("mapped read method " +
- mappedReadMethod.getName() + " returns void");
- }
- }
-
- if (mappedWriteMethod != null) {
- final Class<?>[] params = mappedWriteMethod.getParameterTypes();
- if (params.length != 2) {
- throw new IntrospectionException
- ("bad mapped write method arg count");
- }
- if (mappedPropertyType != null &&
- mappedPropertyType != params[1]) {
- throw new IntrospectionException
- ("type mismatch between mapped read and write methods");
- }
- mappedPropertyType = params[1];
- }
- mappedPropertyTypeRef = new SoftReference<Class<?>>(mappedPropertyType);
- } catch (final IntrospectionException ex) {
- throw ex;
- }
- }
-
-
- /**
- * Return a capitalized version of the specified property name.
- *
- * @param s The property name
- */
- private static String capitalizePropertyName(final String s) {
- if (s.length() == 0) {
- return s;
- }
-
- final char[] chars = s.toCharArray();
- chars[0] = Character.toUpperCase(chars[0]);
- return new String(chars);
- }
-
- /**
- * Find a method on a class with a specified number of parameters.
- */
- private static Method internalGetMethod(final Class<?> initial, final String methodName,
- final int parameterCount) {
- // For overridden methods we need to find the most derived version.
- // So we start with the given class and walk up the superclass chain.
- for (Class<?> clazz = initial; clazz != null; clazz = clazz.getSuperclass()) {
- final Method[] methods = clazz.getDeclaredMethods();
- for (final Method method : methods) {
- if (method == null) {
- continue;
- }
- // skip static methods.
- final int mods = method.getModifiers();
- if (!Modifier.isPublic(mods) ||
- Modifier.isStatic(mods)) {
- continue;
- }
- if (method.getName().equals(methodName) &&
- method.getParameterTypes().length == parameterCount) {
- return method;
- }
- }
- }
-
- // Now check any inherited interfaces. This is necessary both when
- // the argument class is itself an interface, and when the argument
- // class is an abstract class.
- final Class<?>[] interfaces = initial.getInterfaces();
- for (final Class<?> interface1 : interfaces) {
- final Method method = internalGetMethod(interface1, methodName, parameterCount);
- if (method != null) {
- return method;
- }
- }
-
- return null;
- }
-
- /**
- * Find a method on a class with a specified number of parameters.
- */
- private static Method getMethod(final Class<?> clazz, final String methodName, final int parameterCount)
- throws IntrospectionException {
- if (methodName == null) {
- return null;
- }
-
- final Method method = internalGetMethod(clazz, methodName, parameterCount);
- if (method != null) {
- return method;
- }
-
- // No Method found
- throw new IntrospectionException("No method \"" + methodName +
- "\" with " + parameterCount + " parameter(s)");
- }
-
- /**
- * Find a method on a class with a specified parameter list.
- */
- private static Method getMethod(final Class<?> clazz, final String methodName, final Class<?>[] parameterTypes)
- throws IntrospectionException {
- if (methodName == null) {
- return null;
- }
-
- final Method method = MethodUtils.getMatchingAccessibleMethod(clazz, methodName, parameterTypes);
- if (method != null) {
- return method;
- }
-
- final int parameterCount = parameterTypes == null ? 0 : parameterTypes.length;
-
- // No Method found
- throw new IntrospectionException("No method \"" + methodName +
- "\" with " + parameterCount + " parameter(s) of matching types.");
- }
-
- /**
- * Holds a {@link Method} in a {@link SoftReference} so that it
- * it doesn't prevent any ClassLoader being garbage collected, but
- * tries to re-create the method if the method reference has been
- * released.
- *
- * See https://issues.apache.org/jira/browse/BEANUTILS-291
- */
- private static class MappedMethodReference {
- private String className;
- private String methodName;
- private Reference<Method> methodRef;
- private Reference<Class<?>> classRef;
- private Reference<Class<?>> writeParamTypeRef0;
- private Reference<Class<?>> writeParamTypeRef1;
- private String[] writeParamClassNames;
- MappedMethodReference(final Method m) {
- if (m != null) {
- className = m.getDeclaringClass().getName();
- methodName = m.getName();
- methodRef = new SoftReference<>(m);
- classRef = new WeakReference<Class<?>>(m.getDeclaringClass());
- final Class<?>[] types = m.getParameterTypes();
- if (types.length == 2) {
- writeParamTypeRef0 = new WeakReference<Class<?>>(types[0]);
- writeParamTypeRef1 = new WeakReference<Class<?>>(types[1]);
- writeParamClassNames = new String[2];
- writeParamClassNames[0] = types[0].getName();
- writeParamClassNames[1] = types[1].getName();
- }
- }
- }
- private Method get() {
- if (methodRef == null) {
- return null;
- }
- Method m = methodRef.get();
- if (m == null) {
- Class<?> clazz = classRef.get();
- if (clazz == null) {
- clazz = reLoadClass();
- if (clazz != null) {
- classRef = new WeakReference<Class<?>>(clazz);
- }
- }
- if (clazz == null) {
- throw new RuntimeException("Method " + methodName + " for " +
- className + " could not be reconstructed - class reference has gone");
- }
- Class<?>[] paramTypes = null;
- if (writeParamClassNames != null) {
- paramTypes = new Class[2];
- paramTypes[0] = writeParamTypeRef0.get();
- if (paramTypes[0] == null) {
- paramTypes[0] = reLoadClass(writeParamClassNames[0]);
- if (paramTypes[0] != null) {
- writeParamTypeRef0 = new WeakReference<Class<?>>(paramTypes[0]);
- }
- }
- paramTypes[1] = writeParamTypeRef1.get();
- if (paramTypes[1] == null) {
- paramTypes[1] = reLoadClass(writeParamClassNames[1]);
- if (paramTypes[1] != null) {
- writeParamTypeRef1 = new WeakReference<Class<?>>(paramTypes[1]);
- }
- }
- } else {
- paramTypes = STRING_CLASS_PARAMETER;
- }
- try {
- m = clazz.getMethod(methodName, paramTypes);
- // Un-comment following line for testing
- // System.out.println("Recreated Method " + methodName + " for " + className);
- } catch (final NoSuchMethodException e) {
- throw new RuntimeException("Method " + methodName + " for " +
- className + " could not be reconstructed - method not found");
- }
- methodRef = new SoftReference<>(m);
- }
- return m;
- }
-
- /**
- * Try to re-load the class
- */
- private Class<?> reLoadClass() {
- return reLoadClass(className);
- }
-
- /**
- * Try to re-load the class
- */
- private Class<?> reLoadClass(final String name) {
-
- ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
-
- // Try the context class loader
- if (classLoader != null) {
- try {
- return classLoader.loadClass(name);
- } catch (final ClassNotFoundException e) {
- // ignore
- }
- }
-
- // Try this class's class loader
- classLoader = MappedPropertyDescriptor.class.getClassLoader();
- try {
- return classLoader.loadClass(name);
- } catch (final ClassNotFoundException e) {
- return null;
- }
- }
- }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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.apache.commons.beanutils2;
+
+
+import java.beans.IntrospectionException;
+import java.beans.PropertyDescriptor;
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+
+/**
+ * A MappedPropertyDescriptor describes one mapped property.
+ * Mapped properties are multivalued properties like indexed properties
+ * but that are accessed with a String key instead of an index.
+ * Such property values are typically stored in a Map collection.
+ * For this class to work properly, a mapped value must have
+ * getter and setter methods of the form
+ * <p><code>get<strong>Property</strong>(String key)</code> and
+ * <p><code>set<strong>Property</strong>(String key, Object value)</code>,
+ * <p>where <code><strong>Property</strong></code> must be replaced
+ * by the name of the property.
+ * @see java.beans.PropertyDescriptor
+ *
+ */
+public class MappedPropertyDescriptor extends PropertyDescriptor {
+ // ----------------------------------------------------- Instance Variables
+
+ /**
+ * The underlying data type of the property we are describing.
+ */
+ private Reference<Class<?>> mappedPropertyTypeRef;
+
+ /**
+ * The reader method for this property (if any).
+ */
+ private MappedMethodReference mappedReadMethodRef;
+
+ /**
+ * The writer method for this property (if any).
+ */
+ private MappedMethodReference mappedWriteMethodRef;
+
+ /**
+ * The parameter types array for the reader method signature.
+ */
+ private static final Class<?>[] STRING_CLASS_PARAMETER = new Class[]{String.class};
+
+ // ----------------------------------------------------------- Constructors
+
+ /**
+ * Constructs a MappedPropertyDescriptor for a property that follows
+ * the standard Java convention by having getFoo and setFoo
+ * accessor methods, with the addition of a String parameter (the key).
+ * Thus if the argument name is "fred", it will
+ * assume that the writer method is "setFred" and the reader method
+ * is "getFred". Note that the property name should start with a lower
+ * case character, which will be capitalized in the method names.
+ *
+ * @param propertyName The programmatic name of the property.
+ * @param beanClass The Class object for the target bean. For
+ * example sun.beans.OurButton.class.
+ *
+ * @throws IntrospectionException if an exception occurs during
+ * introspection.
+ */
+ public MappedPropertyDescriptor(final String propertyName, final Class<?> beanClass)
+ throws IntrospectionException {
+
+ super(propertyName, null, null);
+
+ if (propertyName == null || propertyName.length() == 0) {
+ throw new IntrospectionException("bad property name: " +
+ propertyName + " on class: " + beanClass.getClass().getName());
+ }
+
+ setName(propertyName);
+ final String base = capitalizePropertyName(propertyName);
+
+ // Look for mapped read method and matching write method
+ Method mappedReadMethod = null;
+ Method mappedWriteMethod = null;
+ try {
+ try {
+ mappedReadMethod = getMethod(beanClass, "get" + base,
+ STRING_CLASS_PARAMETER);
+ } catch (final IntrospectionException e) {
+ mappedReadMethod = getMethod(beanClass, "is" + base,
+ STRING_CLASS_PARAMETER);
+ }
+ final Class<?>[] params = { String.class, mappedReadMethod.getReturnType() };
+ mappedWriteMethod = getMethod(beanClass, "set" + base, params);
+ } catch (final IntrospectionException e) {
+ /* Swallow IntrospectionException
+ * TODO: Why?
+ */
+ }
+
+ // If there's no read method, then look for just a write method
+ if (mappedReadMethod == null) {
+ mappedWriteMethod = getMethod(beanClass, "set" + base, 2);
+ }
+
+ if (mappedReadMethod == null && mappedWriteMethod == null) {
+ throw new IntrospectionException("Property '" + propertyName +
+ "' not found on " +
+ beanClass.getName());
+ }
+ mappedReadMethodRef = new MappedMethodReference(mappedReadMethod);
+ mappedWriteMethodRef = new MappedMethodReference(mappedWriteMethod);
+
+ findMappedPropertyType();
+ }
+
+
+ /**
+ * This constructor takes the name of a mapped property, and method
+ * names for reading and writing the property.
+ *
+ * @param propertyName The programmatic name of the property.
+ * @param beanClass The Class object for the target bean. For
+ * example sun.beans.OurButton.class.
+ * @param mappedGetterName The name of the method used for
+ * reading one of the property values. May be null if the
+ * property is write-only.
+ * @param mappedSetterName The name of the method used for writing
+ * one of the property values. May be null if the property is
+ * read-only.
+ *
+ * @throws IntrospectionException if an exception occurs during
+ * introspection.
+ */
+ public MappedPropertyDescriptor(final String propertyName, final Class<?> beanClass,
+ final String mappedGetterName, final String mappedSetterName)
+ throws IntrospectionException {
+
+ super(propertyName, null, null);
+
+ if (propertyName == null || propertyName.length() == 0) {
+ throw new IntrospectionException("bad property name: " +
+ propertyName);
+ }
+ setName(propertyName);
+
+ // search the mapped get and set methods
+ Method mappedReadMethod = null;
+ Method mappedWriteMethod = null;
+ mappedReadMethod =
+ getMethod(beanClass, mappedGetterName, STRING_CLASS_PARAMETER);
+
+ if (mappedReadMethod != null) {
+ final Class<?>[] params = { String.class, mappedReadMethod.getReturnType() };
+ mappedWriteMethod =
+ getMethod(beanClass, mappedSetterName, params);
+ } else {
+ mappedWriteMethod =
+ getMethod(beanClass, mappedSetterName, 2);
+ }
+ mappedReadMethodRef = new MappedMethodReference(mappedReadMethod);
+ mappedWriteMethodRef = new MappedMethodReference(mappedWriteMethod);
+
+ findMappedPropertyType();
+ }
+
+ /**
+ * This constructor takes the name of a mapped property, and Method
+ * objects for reading and writing the property.
+ *
+ * @param propertyName The programmatic name of the property.
+ * @param mappedGetter The method used for reading one of
+ * the property values. May be be null if the property
+ * is write-only.
+ * @param mappedSetter The method used for writing one the
+ * property values. May be null if the property is read-only.
+ *
+ * @throws IntrospectionException if an exception occurs during
+ * introspection.
+ */
+ public MappedPropertyDescriptor(final String propertyName,
+ final Method mappedGetter, final Method mappedSetter)
+ throws IntrospectionException {
+
+ super(propertyName, mappedGetter, mappedSetter);
+
+ if (propertyName == null || propertyName.length() == 0) {
+ throw new IntrospectionException("bad property name: " +
+ propertyName);
+ }
+
+ setName(propertyName);
+ mappedReadMethodRef = new MappedMethodReference(mappedGetter);
+ mappedWriteMethodRef = new MappedMethodReference(mappedSetter);
+ findMappedPropertyType();
+ }
+
+ // -------------------------------------------------------- Public Methods
+
+ /**
+ * Gets the Class object for the property values.
+ *
+ * @return The Java type info for the property values. Note that
+ * the "Class" object may describe a built-in Java type such as "int".
+ * The result may be "null" if this is a mapped property that
+ * does not support non-keyed access.
+ * <p>
+ * This is the type that will be returned by the mappedReadMethod.
+ */
+ public Class<?> getMappedPropertyType() {
+ return mappedPropertyTypeRef.get();
+ }
+
+ /**
+ * Gets the method that should be used to read one of the property value.
+ *
+ * @return The method that should be used to read the property value.
+ * May return null if the property can't be read.
+ */
+ public Method getMappedReadMethod() {
+ return mappedReadMethodRef.get();
+ }
+
+ /**
+ * Sets the method that should be used to read one of the property value.
+ *
+ * @param mappedGetter The mapped getter method.
+ * @throws IntrospectionException If an error occurs finding the
+ * mapped property
+ */
+ public void setMappedReadMethod(final Method mappedGetter)
+ throws IntrospectionException {
+ mappedReadMethodRef = new MappedMethodReference(mappedGetter);
+ findMappedPropertyType();
+ }
+
+ /**
+ * Gets the method that should be used to write one of the property value.
+ *
+ * @return The method that should be used to write one of the property value.
+ * May return null if the property can't be written.
+ */
+ public Method getMappedWriteMethod() {
+ return mappedWriteMethodRef.get();
+ }
+
+ /**
+ * Sets the method that should be used to write the property value.
+ *
+ * @param mappedSetter The mapped setter method.
+ * @throws IntrospectionException If an error occurs finding the
+ * mapped property
+ */
+ public void setMappedWriteMethod(final Method mappedSetter)
+ throws IntrospectionException {
+ mappedWriteMethodRef = new MappedMethodReference(mappedSetter);
+ findMappedPropertyType();
+ }
+
+ // ------------------------------------------------------- Private Methods
+
+ /**
+ * Introspect our bean class to identify the corresponding getter
+ * and setter methods.
+ */
+ private void findMappedPropertyType() throws IntrospectionException {
+ try {
+ final Method mappedReadMethod = getMappedReadMethod();
+ final Method mappedWriteMethod = getMappedWriteMethod();
+ Class<?> mappedPropertyType = null;
+ if (mappedReadMethod != null) {
+ if (mappedReadMethod.getParameterTypes().length != 1) {
+ throw new IntrospectionException
+ ("bad mapped read method arg count");
+ }
+ mappedPropertyType = mappedReadMethod.getReturnType();
+ if (mappedPropertyType == Void.TYPE) {
+ throw new IntrospectionException
+ ("mapped read method " +
+ mappedReadMethod.getName() + " returns void");
+ }
+ }
+
+ if (mappedWriteMethod != null) {
+ final Class<?>[] params = mappedWriteMethod.getParameterTypes();
+ if (params.length != 2) {
+ throw new IntrospectionException
+ ("bad mapped write method arg count");
+ }
+ if (mappedPropertyType != null &&
+ mappedPropertyType != params[1]) {
+ throw new IntrospectionException
+ ("type mismatch between mapped read and write methods");
+ }
+ mappedPropertyType = params[1];
+ }
+ mappedPropertyTypeRef = new SoftReference<>(mappedPropertyType);
+ } catch (final IntrospectionException ex) {
+ throw ex;
+ }
+ }
+
+
+ /**
+ * Return a capitalized version of the specified property name.
+ *
+ * @param s The property name
+ */
+ private static String capitalizePropertyName(final String s) {
+ if (s.length() == 0) {
+ return s;
+ }
+
+ final char[] chars = s.toCharArray();
+ chars[0] = Character.toUpperCase(chars[0]);
+ return new String(chars);
+ }
+
+ /**
+ * Find a method on a class with a specified number of parameters.
+ */
+ private static Method internalGetMethod(final Class<?> initial, final String methodName,
+ final int parameterCount) {
+ // For overridden methods we need to find the most derived version.
+ // So we start with the given class and walk up the superclass chain.
+ for (Class<?> clazz = initial; clazz != null; clazz = clazz.getSuperclass()) {
+ final Method[] methods = clazz.getDeclaredMethods();
+ for (final Method method : methods) {
+ if (method == null) {
+ continue;
+ }
+ // skip static methods.
+ final int mods = method.getModifiers();
+ if (!Modifier.isPublic(mods) ||
+ Modifier.isStatic(mods)) {
+ continue;
+ }
+ if (method.getName().equals(methodName) &&
+ method.getParameterTypes().length == parameterCount) {
+ return method;
+ }
+ }
+ }
+
+ // Now check any inherited interfaces. This is necessary both when
+ // the argument class is itself an interface, and when the argument
+ // class is an abstract class.
+ final Class<?>[] interfaces = initial.getInterfaces();
+ for (final Class<?> interface1 : interfaces) {
+ final Method method = internalGetMethod(interface1, methodName, parameterCount);
+ if (method != null) {
+ return method;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Find a method on a class with a specified number of parameters.
+ */
+ private static Method getMethod(final Class<?> clazz, final String methodName, final int parameterCount)
+ throws IntrospectionException {
+ if (methodName == null) {
+ return null;
+ }
+
+ final Method method = internalGetMethod(clazz, methodName, parameterCount);
+ if (method != null) {
+ return method;
+ }
+
+ // No Method found
+ throw new IntrospectionException("No method \"" + methodName +
+ "\" with " + parameterCount + " parameter(s)");
+ }
+
+ /**
+ * Find a method on a class with a specified parameter list.
+ */
+ private static Method getMethod(final Class<?> clazz, final String methodName, final Class<?>[] parameterTypes)
+ throws IntrospectionException {
+ if (methodName == null) {
+ return null;
+ }
+
+ final Method method = MethodUtils.getMatchingAccessibleMethod(clazz, methodName, parameterTypes);
+ if (method != null) {
+ return method;
+ }
+
+ final int parameterCount = parameterTypes == null ? 0 : parameterTypes.length;
+
+ // No Method found
+ throw new IntrospectionException("No method \"" + methodName +
+ "\" with " + parameterCount + " parameter(s) of matching types.");
+ }
+
+ /**
+ * Holds a {@link Method} in a {@link SoftReference} so that it
+ * it doesn't prevent any ClassLoader being garbage collected, but
+ * tries to re-create the method if the method reference has been
+ * released.
+ *
+ * See https://issues.apache.org/jira/browse/BEANUTILS-291
+ */
+ private static class MappedMethodReference {
+ private String className;
+ private String methodName;
+ private Reference<Method> methodRef;
+ private Reference<Class<?>> classRef;
+ private Reference<Class<?>> writeParamTypeRef0;
+ private Reference<Class<?>> writeParamTypeRef1;
+ private String[] writeParamClassNames;
+ MappedMethodReference(final Method m) {
+ if (m != null) {
+ className = m.getDeclaringClass().getName();
+ methodName = m.getName();
+ methodRef = new SoftReference<>(m);
+ classRef = new WeakReference<>(m.getDeclaringClass());
+ final Class<?>[] types = m.getParameterTypes();
+ if (types.length == 2) {
+ writeParamTypeRef0 = new WeakReference<>(types[0]);
+ writeParamTypeRef1 = new WeakReference<>(types[1]);
+ writeParamClassNames = new String[2];
+ writeParamClassNames[0] = types[0].getName();
+ writeParamClassNames[1] = types[1].getName();
+ }
+ }
+ }
+ private Method get() {
+ if (methodRef == null) {
+ return null;
+ }
+ Method m = methodRef.get();
+ if (m == null) {
+ Class<?> clazz = classRef.get();
+ if (clazz == null) {
+ clazz = reLoadClass();
+ if (clazz != null) {
+ classRef = new WeakReference<>(clazz);
+ }
+ }
+ if (clazz == null) {
+ throw new RuntimeException("Method " + methodName + " for " +
+ className + " could not be reconstructed - class reference has gone");
+ }
+ Class<?>[] paramTypes = null;
+ if (writeParamClassNames != null) {
+ paramTypes = new Class[2];
+ paramTypes[0] = writeParamTypeRef0.get();
+ if (paramTypes[0] == null) {
+ paramTypes[0] = reLoadClass(writeParamClassNames[0]);
+ if (paramTypes[0] != null) {
+ writeParamTypeRef0 = new WeakReference<>(paramTypes[0]);
+ }
+ }
+ paramTypes[1] = writeParamTypeRef1.get();
+ if (paramTypes[1] == null) {
+ paramTypes[1] = reLoadClass(writeParamClassNames[1]);
+ if (paramTypes[1] != null) {
+ writeParamTypeRef1 = new WeakReference<>(paramTypes[1]);
+ }
+ }
+ } else {
+ paramTypes = STRING_CLASS_PARAMETER;
+ }
+ try {
+ m = clazz.getMethod(methodName, paramTypes);
+ // Un-comment following line for testing
+ // System.out.println("Recreated Method " + methodName + " for " + className);
+ } catch (final NoSuchMethodException e) {
+ throw new RuntimeException("Method " + methodName + " for " +
+ className + " could not be reconstructed - method not found");
+ }
+ methodRef = new SoftReference<>(m);
+ }
+ return m;
+ }
+
+ /**
+ * Try to re-load the class
+ */
+ private Class<?> reLoadClass() {
+ return reLoadClass(className);
+ }
+
+ /**
+ * Try to re-load the class
+ */
+ private Class<?> reLoadClass(final String name) {
+
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+
+ // Try the context class loader
+ if (classLoader != null) {
+ try {
+ return classLoader.loadClass(name);
+ } catch (final ClassNotFoundException e) {
+ // ignore
+ }
+ }
+
+ // Try this class's class loader
+ classLoader = MappedPropertyDescriptor.class.getClassLoader();
+ try {
+ return classLoader.loadClass(name);
+ } catch (final ClassNotFoundException e) {
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/beanutils2/WrapDynaClass.java b/src/main/java/org/apache/commons/beanutils2/WrapDynaClass.java
index 00f1008..2d298b4 100644
--- a/src/main/java/org/apache/commons/beanutils2/WrapDynaClass.java
+++ b/src/main/java/org/apache/commons/beanutils2/WrapDynaClass.java
@@ -58,7 +58,7 @@
*/
private WrapDynaClass(final Class<?> beanClass, final PropertyUtilsBean propUtils) {
- this.beanClassRef = new SoftReference<Class<?>>(beanClass);
+ this.beanClassRef = new SoftReference<>(beanClass);
this.beanClassName = beanClass.getName();
propertyUtilsBean = propUtils;
introspect();
diff --git a/src/test/java/org/apache/commons/beanutils2/BeanificationTestCase.java b/src/test/java/org/apache/commons/beanutils2/BeanificationTestCase.java
index 18ae331..a2381b3 100644
--- a/src/test/java/org/apache/commons/beanutils2/BeanificationTestCase.java
+++ b/src/test/java/org/apache/commons/beanutils2/BeanificationTestCase.java
@@ -1,544 +1,544 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * 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.apache.commons.beanutils2;
-
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.WeakReference;
-import java.util.Map;
-import java.util.WeakHashMap;
-
-import org.apache.commons.logging.LogFactory;
-
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-
-/**
- * <p>
- * Test Case for changes made during Beanutils Beanification
- * </p>
- *
- */
-
-public class BeanificationTestCase extends TestCase {
-
- // ---------------------------------------------------- Constants
-
- /** Maximum number of iterations before our test fails */
- public static final int MAX_GC_ITERATIONS = 50;
-
- // ---------------------------------------------------- Instance Variables
-
-
- // ---------------------------------------------------------- Constructors
-
-
- /**
- * Construct a new instance of this test case.
- *
- * @param name Name of the test case
- */
- public BeanificationTestCase(final String name) {
- super(name);
- }
-
-
- // -------------------------------------------------- Overall Test Methods
-
-
- /**
- * Set up instance variables required by this test case.
- */
- @Override
- public void setUp() {
-
- ConvertUtils.deregister();
-
- }
-
-
- /**
- * Return the tests included in this test suite.
- */
- public static Test suite() {
- return new TestSuite(BeanificationTestCase.class);
- }
-
-
- /**
- * Tear down instance variables required by this test case.
- */
- @Override
- public void tearDown() {
- // No action required
- }
-
-
- // ------------------------------------------------ Individual Test Methods
-
- /** Test of the methodology we'll use for some of the later tests */
- public void testMemoryTestMethodology() throws Exception {
- // test methodology
- // many thanks to Juozas Baliuka for suggesting this method
- ClassLoader loader = new ClassLoader(this.getClass().getClassLoader()) {};
- final WeakReference<ClassLoader> reference = new WeakReference<>(loader);
- @SuppressWarnings("unused")
- Class<?> myClass = loader.loadClass("org.apache.commons.beanutils2.BetaBean");
-
- assertNotNull("Weak reference released early", reference.get());
-
- // dereference class loader and class:
- loader = null;
- myClass = null;
-
- int iterations = 0;
- int bytz = 2;
- while(true) {
- System.gc();
- if(iterations++ > MAX_GC_ITERATIONS){
- fail("Max iterations reached before resource released.");
- }
- if( reference.get() == null ) {
- break;
-
- }
- // create garbage:
- @SuppressWarnings("unused")
- final
- byte[] b = new byte[bytz];
- bytz = bytz * 2;
- }
- }
-
- /** Tests whether classloaders and beans are released from memory by the map used by beanutils */
- public void testMemoryLeak2() throws Exception {
- // tests when the map used by beanutils has the right behaviour
-
- if (BeanUtilsTestCase.isPre14JVM()) {
- System.out.println("WARNING: CANNOT TEST MEMORY LEAK ON PRE1.4 JVM");
- return;
- }
-
- // many thanks to Juozas Baliuka for suggesting this methodology
- TestClassLoader loader = new TestClassLoader();
- final ReferenceQueue<Object> queue = new ReferenceQueue<>();
- final WeakReference<ClassLoader> loaderReference = new WeakReference<ClassLoader>(loader, queue);
- Integer test = new Integer(1);
-
- final WeakReference<Integer> testReference = new WeakReference<>(test, queue);
- //Map map = new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.HARD, true);
- final Map<Object, Object> map = new WeakHashMap<>();
- map.put(loader, test);
-
- assertEquals("In map", test, map.get(loader));
- assertNotNull("Weak reference released early (1)", loaderReference.get());
- assertNotNull("Weak reference released early (2)", testReference.get());
-
- // dereference strong references
- loader = null;
- test = null;
-
- int iterations = 0;
- int bytz = 2;
- while(true) {
- System.gc();
- if(iterations++ > MAX_GC_ITERATIONS){
- fail("Max iterations reached before resource released.");
- }
- map.isEmpty();
-
- if(
- loaderReference.get() == null &&
- testReference.get() == null) {
- break;
-
- }
- // create garbage:
- @SuppressWarnings("unused")
- final
- byte[] b = new byte[bytz];
- bytz = bytz * 2;
- }
- }
-
- /** Tests whether classloaders and beans are released from memory */
- public void testMemoryLeak() throws Exception {
- if (BeanUtilsTestCase.isPre14JVM()) {
- System.out.println("WARNING: CANNOT TEST MEMORY LEAK ON PRE1.4 JVM");
- return;
- }
-
- // many thanks to Juozas Baliuka for suggesting this methodology
- TestClassLoader loader = new TestClassLoader();
- final WeakReference<ClassLoader> loaderReference = new WeakReference<ClassLoader>(loader);
- BeanUtilsBean.getInstance();
-
- class GetBeanUtilsBeanThread extends Thread {
-
- BeanUtilsBean beanUtils;
- ConvertUtilsBean convertUtils;
- PropertyUtilsBean propertyUtils;
-
- GetBeanUtilsBeanThread() {}
-
- @Override
- public void run() {
- beanUtils = BeanUtilsBean.getInstance();
- convertUtils = ConvertUtilsBean.getInstance();
- propertyUtils = PropertyUtilsBean.getInstance();
- // XXX Log keeps a reference around!
- LogFactory.releaseAll();
- }
-
- @Override
- public String toString() {
- return "GetBeanUtilsBeanThread";
- }
- }
-
-
- GetBeanUtilsBeanThread thread = new GetBeanUtilsBeanThread();
- @SuppressWarnings("unused")
- final
- WeakReference<Thread> threadWeakReference = new WeakReference<Thread>(thread);
- thread.setContextClassLoader(loader);
-
- thread.start();
- thread.join();
-
- final WeakReference<BeanUtilsBean> beanUtilsReference = new WeakReference<>(thread.beanUtils);
- final WeakReference<PropertyUtilsBean> propertyUtilsReference = new WeakReference<>(thread.propertyUtils);
- final WeakReference<ConvertUtilsBean> convertUtilsReference = new WeakReference<>(thread.convertUtils);
-
- assertNotNull("Weak reference released early (1)", loaderReference.get());
- assertNotNull("Weak reference released early (2)", beanUtilsReference.get());
- assertNotNull("Weak reference released early (3)", propertyUtilsReference.get());
- assertNotNull("Weak reference released early (4)", convertUtilsReference.get());
-
- // dereference strong references
- loader = null;
- thread.setContextClassLoader(null);
- thread = null;
-
- int iterations = 0;
- int bytz = 2;
- while(true) {
- BeanUtilsBean.getInstance();
- System.gc();
- if(iterations++ > MAX_GC_ITERATIONS){
- fail("Max iterations reached before resource released.");
- }
-
- if(
- loaderReference.get() == null &&
- beanUtilsReference.get() == null &&
- propertyUtilsReference.get() == null &&
- convertUtilsReference.get() == null) {
- break;
-
- }
- // create garbage:
- @SuppressWarnings("unused")
- final
- byte[] b = new byte[bytz];
- bytz = bytz * 2;
- }
- }
-
- /**
- * Tests whether difference instances are loaded by different
- * context classloaders.
- */
- public void testGetByContextClassLoader() throws Exception {
-
- class GetBeanUtilsBeanThread extends Thread {
-
- private final Signal signal;
-
- GetBeanUtilsBeanThread(final Signal signal) {
- this.signal = signal;
- }
-
- @Override
- public void run() {
- signal.setSignal(2);
- signal.setBean(BeanUtilsBean.getInstance());
- signal.setConvertUtils(ConvertUtilsBean.getInstance());
- signal.setPropertyUtils(PropertyUtilsBean.getInstance());
- }
-
- @Override
- public String toString() {
- return "GetBeanUtilsBeanThread";
- }
- }
-
- final Signal signal = new Signal();
- signal.setSignal(1);
-
- final GetBeanUtilsBeanThread thread = new GetBeanUtilsBeanThread(signal);
- thread.setContextClassLoader(new TestClassLoader());
-
- thread.start();
- thread.join();
-
- assertEquals("Signal not set by test thread", 2, signal.getSignal());
- assertTrue(
- "Different BeanUtilsBean instances per context classloader",
- BeanUtilsBean.getInstance() != signal.getBean());
- assertTrue(
- "Different ConvertUtilsBean instances per context classloader",
- ConvertUtilsBean.getInstance() != signal.getConvertUtils());
- assertTrue(
- "Different PropertyUtilsBean instances per context classloader",
- PropertyUtilsBean.getInstance() != signal.getPropertyUtils());
- }
-
-
- /**
- * Tests whether difference instances are loaded by different
- * context classloaders.
- */
- public void testContextClassLoaderLocal() throws Exception {
-
- class CCLLTesterThread extends Thread {
-
- private final Signal signal;
- private final ContextClassLoaderLocal<Integer> ccll;
-
- CCLLTesterThread(final Signal signal, final ContextClassLoaderLocal<Integer> ccll) {
- this.signal = signal;
- this.ccll = ccll;
- }
-
- @Override
- public void run() {
- ccll.set(new Integer(1789));
- signal.setSignal(2);
- signal.setMarkerObject(ccll.get());
- }
-
- @Override
- public String toString() {
- return "CCLLTesterThread";
- }
- }
-
- final ContextClassLoaderLocal<Integer> ccll = new ContextClassLoaderLocal<>();
- ccll.set(new Integer(1776));
- assertEquals("Start thread sets value", new Integer(1776), ccll.get());
-
- final Signal signal = new Signal();
- signal.setSignal(1);
-
- final CCLLTesterThread thread = new CCLLTesterThread(signal, ccll);
- thread.setContextClassLoader(new TestClassLoader());
-
- thread.start();
- thread.join();
-
- assertEquals("Signal not set by test thread", 2, signal.getSignal());
- assertEquals("Second thread preserves value", new Integer(1776), ccll.get());
- assertEquals("Second thread gets value it set", new Integer(1789), signal.getMarkerObject());
- }
-
- /** Tests whether calls are independent for different classloaders */
- public void testContextClassloaderIndependence() throws Exception {
-
- class TestIndependenceThread extends Thread {
- private final Signal signal;
- private final PrimitiveBean bean;
-
- TestIndependenceThread(final Signal signal, final PrimitiveBean bean) {
- this.signal = signal;
- this.bean = bean;
- }
-
- @Override
- public void run() {
- try {
- signal.setSignal(3);
- ConvertUtils.register(new Converter() {
- @Override
- public <T> T convert(final Class<T> type, final Object value) {
- return ConvertUtils.primitiveToWrapper(type).cast(new Integer(9));
- }
- }, Integer.TYPE);
- BeanUtils.setProperty(bean, "int", new Integer(1));
- } catch (final Exception e) {
- e.printStackTrace();
- signal.setException(e);
- }
- }
-
- @Override
- public String toString() {
- return "TestIndependenceThread";
- }
- }
-
- final PrimitiveBean bean = new PrimitiveBean();
- BeanUtils.setProperty(bean, "int", new Integer(1));
- assertEquals("Wrong property value (1)", 1, bean.getInt());
-
- ConvertUtils.register(new Converter() {
- @Override
- public <T> T convert(final Class<T> type, final Object value) {
- return ConvertUtils.primitiveToWrapper(type).cast(new Integer(5));
- }
- }, Integer.TYPE);
- BeanUtils.setProperty(bean, "int", new Integer(1));
- assertEquals("Wrong property value(2)", 5, bean.getInt());
-
- final Signal signal = new Signal();
- signal.setSignal(1);
- final TestIndependenceThread thread = new TestIndependenceThread(signal, bean);
- thread.setContextClassLoader(new TestClassLoader());
-
- thread.start();
- thread.join();
-
- assertNull("Exception thrown by test thread:" + signal.getException(), signal.getException());
- assertEquals("Signal not set by test thread", 3, signal.getSignal());
- assertEquals("Wrong property value(3)", 9, bean.getInt());
-
- }
-
- /** Tests whether different threads can set beanutils instances correctly */
- public void testBeanUtilsBeanSetInstance() throws Exception {
-
- class SetInstanceTesterThread extends Thread {
-
- private final Signal signal;
- private final BeanUtilsBean bean;
-
- SetInstanceTesterThread(final Signal signal, final BeanUtilsBean bean) {
- this.signal = signal;
- this.bean = bean;
- }
-
- @Override
- public void run() {
- BeanUtilsBean.setInstance(bean);
- signal.setSignal(21);
- signal.setBean(BeanUtilsBean.getInstance());
- }
-
- @Override
- public String toString() {
- return "SetInstanceTesterThread";
- }
- }
-
- final Signal signal = new Signal();
- signal.setSignal(1);
-
- final BeanUtilsBean beanOne = new BeanUtilsBean();
- final BeanUtilsBean beanTwo = new BeanUtilsBean();
-
- final SetInstanceTesterThread thread = new SetInstanceTesterThread(signal, beanTwo);
- thread.setContextClassLoader(new TestClassLoader());
-
- BeanUtilsBean.setInstance(beanOne);
- assertEquals("Start thread gets right instance", beanOne, BeanUtilsBean.getInstance());
-
- thread.start();
- thread.join();
-
- assertEquals("Signal not set by test thread", 21, signal.getSignal());
- assertEquals("Second thread preserves value", beanOne, BeanUtilsBean.getInstance());
- assertEquals("Second thread gets value it set", beanTwo, signal.getBean());
- }
-
- /** Tests whether the unset method works*/
- public void testContextClassLoaderUnset() throws Exception {
- final BeanUtilsBean beanOne = new BeanUtilsBean();
- final ContextClassLoaderLocal<BeanUtilsBean> ccll = new ContextClassLoaderLocal<>();
- ccll.set(beanOne);
- assertEquals("Start thread gets right instance", beanOne, ccll.get());
- ccll.unset();
- assertTrue("Unset works", !beanOne.equals(ccll.get()));
- }
-
- // ---- Auxillary classes
-
- class TestClassLoader extends ClassLoader {
- @Override
- public String toString() {
- return "TestClassLoader";
- }
- }
-
- class Signal {
- private Exception e;
- private int signal = 0;
- private BeanUtilsBean bean;
- private PropertyUtilsBean propertyUtils;
- private ConvertUtilsBean convertUtils;
- private Object marker;
-
- public Exception getException() {
- return e;
- }
-
- public void setException(final Exception e) {
- this.e = e;
- }
-
- public int getSignal() {
- return signal;
- }
-
- public void setSignal(final int signal) {
- this.signal = signal;
- }
-
- public Object getMarkerObject() {
- return marker;
- }
-
- public void setMarkerObject(final Object marker) {
- this.marker = marker;
- }
-
- public BeanUtilsBean getBean() {
- return bean;
- }
-
- public void setBean(final BeanUtilsBean bean) {
- this.bean = bean;
- }
-
- public PropertyUtilsBean getPropertyUtils() {
- return propertyUtils;
- }
-
- public void setPropertyUtils(final PropertyUtilsBean propertyUtils) {
- this.propertyUtils = propertyUtils;
- }
-
- public ConvertUtilsBean getConvertUtils() {
- return convertUtils;
- }
-
- public void setConvertUtils(final ConvertUtilsBean convertUtils) {
- this.convertUtils = convertUtils;
- }
- }
-}
-
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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.apache.commons.beanutils2;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import org.apache.commons.logging.LogFactory;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * <p>
+ * Test Case for changes made during Beanutils Beanification
+ * </p>
+ *
+ */
+
+public class BeanificationTestCase extends TestCase {
+
+ // ---------------------------------------------------- Constants
+
+ /** Maximum number of iterations before our test fails */
+ public static final int MAX_GC_ITERATIONS = 50;
+
+ // ---------------------------------------------------- Instance Variables
+
+
+ // ---------------------------------------------------------- Constructors
+
+
+ /**
+ * Construct a new instance of this test case.
+ *
+ * @param name Name of the test case
+ */
+ public BeanificationTestCase(final String name) {
+ super(name);
+ }
+
+
+ // -------------------------------------------------- Overall Test Methods
+
+
+ /**
+ * Set up instance variables required by this test case.
+ */
+ @Override
+ public void setUp() {
+
+ ConvertUtils.deregister();
+
+ }
+
+
+ /**
+ * Return the tests included in this test suite.
+ */
+ public static Test suite() {
+ return new TestSuite(BeanificationTestCase.class);
+ }
+
+
+ /**
+ * Tear down instance variables required by this test case.
+ */
+ @Override
+ public void tearDown() {
+ // No action required
+ }
+
+
+ // ------------------------------------------------ Individual Test Methods
+
+ /** Test of the methodology we'll use for some of the later tests */
+ public void testMemoryTestMethodology() throws Exception {
+ // test methodology
+ // many thanks to Juozas Baliuka for suggesting this method
+ ClassLoader loader = new ClassLoader(this.getClass().getClassLoader()) {};
+ final WeakReference<ClassLoader> reference = new WeakReference<>(loader);
+ @SuppressWarnings("unused")
+ Class<?> myClass = loader.loadClass("org.apache.commons.beanutils2.BetaBean");
+
+ assertNotNull("Weak reference released early", reference.get());
+
+ // dereference class loader and class:
+ loader = null;
+ myClass = null;
+
+ int iterations = 0;
+ int bytz = 2;
+ while(true) {
+ System.gc();
+ if(iterations++ > MAX_GC_ITERATIONS){
+ fail("Max iterations reached before resource released.");
+ }
+ if( reference.get() == null ) {
+ break;
+
+ }
+ // create garbage:
+ @SuppressWarnings("unused")
+ final
+ byte[] b = new byte[bytz];
+ bytz = bytz * 2;
+ }
+ }
+
+ /** Tests whether classloaders and beans are released from memory by the map used by beanutils */
+ public void testMemoryLeak2() throws Exception {
+ // tests when the map used by beanutils has the right behaviour
+
+ if (BeanUtilsTestCase.isPre14JVM()) {
+ System.out.println("WARNING: CANNOT TEST MEMORY LEAK ON PRE1.4 JVM");
+ return;
+ }
+
+ // many thanks to Juozas Baliuka for suggesting this methodology
+ TestClassLoader loader = new TestClassLoader();
+ final ReferenceQueue<Object> queue = new ReferenceQueue<>();
+ final WeakReference<ClassLoader> loaderReference = new WeakReference<>(loader, queue);
+ Integer test = new Integer(1);
+
+ final WeakReference<Integer> testReference = new WeakReference<>(test, queue);
+ //Map map = new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.HARD, true);
+ final Map<Object, Object> map = new WeakHashMap<>();
+ map.put(loader, test);
+
+ assertEquals("In map", test, map.get(loader));
+ assertNotNull("Weak reference released early (1)", loaderReference.get());
+ assertNotNull("Weak reference released early (2)", testReference.get());
+
+ // dereference strong references
+ loader = null;
+ test = null;
+
+ int iterations = 0;
+ int bytz = 2;
+ while(true) {
+ System.gc();
+ if(iterations++ > MAX_GC_ITERATIONS){
+ fail("Max iterations reached before resource released.");
+ }
+ map.isEmpty();
+
+ if(
+ loaderReference.get() == null &&
+ testReference.get() == null) {
+ break;
+
+ }
+ // create garbage:
+ @SuppressWarnings("unused")
+ final
+ byte[] b = new byte[bytz];
+ bytz = bytz * 2;
+ }
+ }
+
+ /** Tests whether classloaders and beans are released from memory */
+ public void testMemoryLeak() throws Exception {
+ if (BeanUtilsTestCase.isPre14JVM()) {
+ System.out.println("WARNING: CANNOT TEST MEMORY LEAK ON PRE1.4 JVM");
+ return;
+ }
+
+ // many thanks to Juozas Baliuka for suggesting this methodology
+ TestClassLoader loader = new TestClassLoader();
+ final WeakReference<ClassLoader> loaderReference = new WeakReference<>(loader);
+ BeanUtilsBean.getInstance();
+
+ class GetBeanUtilsBeanThread extends Thread {
+
+ BeanUtilsBean beanUtils;
+ ConvertUtilsBean convertUtils;
+ PropertyUtilsBean propertyUtils;
+
+ GetBeanUtilsBeanThread() {}
+
+ @Override
+ public void run() {
+ beanUtils = BeanUtilsBean.getInstance();
+ convertUtils = ConvertUtilsBean.getInstance();
+ propertyUtils = PropertyUtilsBean.getInstance();
+ // XXX Log keeps a reference around!
+ LogFactory.releaseAll();
+ }
+
+ @Override
+ public String toString() {
+ return "GetBeanUtilsBeanThread";
+ }
+ }
+
+
+ GetBeanUtilsBeanThread thread = new GetBeanUtilsBeanThread();
+ @SuppressWarnings("unused")
+ final
+ WeakReference<Thread> threadWeakReference = new WeakReference<>(thread);
+ thread.setContextClassLoader(loader);
+
+ thread.start();
+ thread.join();
+
+ final WeakReference<BeanUtilsBean> beanUtilsReference = new WeakReference<>(thread.beanUtils);
+ final WeakReference<PropertyUtilsBean> propertyUtilsReference = new WeakReference<>(thread.propertyUtils);
+ final WeakReference<ConvertUtilsBean> convertUtilsReference = new WeakReference<>(thread.convertUtils);
+
+ assertNotNull("Weak reference released early (1)", loaderReference.get());
+ assertNotNull("Weak reference released early (2)", beanUtilsReference.get());
+ assertNotNull("Weak reference released early (3)", propertyUtilsReference.get());
+ assertNotNull("Weak reference released early (4)", convertUtilsReference.get());
+
+ // dereference strong references
+ loader = null;
+ thread.setContextClassLoader(null);
+ thread = null;
+
+ int iterations = 0;
+ int bytz = 2;
+ while(true) {
+ BeanUtilsBean.getInstance();
+ System.gc();
+ if(iterations++ > MAX_GC_ITERATIONS){
+ fail("Max iterations reached before resource released.");
+ }
+
+ if(
+ loaderReference.get() == null &&
+ beanUtilsReference.get() == null &&
+ propertyUtilsReference.get() == null &&
+ convertUtilsReference.get() == null) {
+ break;
+
+ }
+ // create garbage:
+ @SuppressWarnings("unused")
+ final
+ byte[] b = new byte[bytz];
+ bytz = bytz * 2;
+ }
+ }
+
+ /**
+ * Tests whether difference instances are loaded by different
+ * context classloaders.
+ */
+ public void testGetByContextClassLoader() throws Exception {
+
+ class GetBeanUtilsBeanThread extends Thread {
+
+ private final Signal signal;
+
+ GetBeanUtilsBeanThread(final Signal signal) {
+ this.signal = signal;
+ }
+
+ @Override
+ public void run() {
+ signal.setSignal(2);
+ signal.setBean(BeanUtilsBean.getInstance());
+ signal.setConvertUtils(ConvertUtilsBean.getInstance());
+ signal.setPropertyUtils(PropertyUtilsBean.getInstance());
+ }
+
+ @Override
+ public String toString() {
+ return "GetBeanUtilsBeanThread";
+ }
+ }
+
+ final Signal signal = new Signal();
+ signal.setSignal(1);
+
+ final GetBeanUtilsBeanThread thread = new GetBeanUtilsBeanThread(signal);
+ thread.setContextClassLoader(new TestClassLoader());
+
+ thread.start();
+ thread.join();
+
+ assertEquals("Signal not set by test thread", 2, signal.getSignal());
+ assertTrue(
+ "Different BeanUtilsBean instances per context classloader",
+ BeanUtilsBean.getInstance() != signal.getBean());
+ assertTrue(
+ "Different ConvertUtilsBean instances per context classloader",
+ ConvertUtilsBean.getInstance() != signal.getConvertUtils());
+ assertTrue(
+ "Different PropertyUtilsBean instances per context classloader",
+ PropertyUtilsBean.getInstance() != signal.getPropertyUtils());
+ }
+
+
+ /**
+ * Tests whether difference instances are loaded by different
+ * context classloaders.
+ */
+ public void testContextClassLoaderLocal() throws Exception {
+
+ class CCLLTesterThread extends Thread {
+
+ private final Signal signal;
+ private final ContextClassLoaderLocal<Integer> ccll;
+
+ CCLLTesterThread(final Signal signal, final ContextClassLoaderLocal<Integer> ccll) {
+ this.signal = signal;
+ this.ccll = ccll;
+ }
+
+ @Override
+ public void run() {
+ ccll.set(new Integer(1789));
+ signal.setSignal(2);
+ signal.setMarkerObject(ccll.get());
+ }
+
+ @Override
+ public String toString() {
+ return "CCLLTesterThread";
+ }
+ }
+
+ final ContextClassLoaderLocal<Integer> ccll = new ContextClassLoaderLocal<>();
+ ccll.set(new Integer(1776));
+ assertEquals("Start thread sets value", new Integer(1776), ccll.get());
+
+ final Signal signal = new Signal();
+ signal.setSignal(1);
+
+ final CCLLTesterThread thread = new CCLLTesterThread(signal, ccll);
+ thread.setContextClassLoader(new TestClassLoader());
+
+ thread.start();
+ thread.join();
+
+ assertEquals("Signal not set by test thread", 2, signal.getSignal());
+ assertEquals("Second thread preserves value", new Integer(1776), ccll.get());
+ assertEquals("Second thread gets value it set", new Integer(1789), signal.getMarkerObject());
+ }
+
+ /** Tests whether calls are independent for different classloaders */
+ public void testContextClassloaderIndependence() throws Exception {
+
+ class TestIndependenceThread extends Thread {
+ private final Signal signal;
+ private final PrimitiveBean bean;
+
+ TestIndependenceThread(final Signal signal, final PrimitiveBean bean) {
+ this.signal = signal;
+ this.bean = bean;
+ }
+
+ @Override
+ public void run() {
+ try {
+ signal.setSignal(3);
+ ConvertUtils.register(new Converter() {
+ @Override
+ public <T> T convert(final Class<T> type, final Object value) {
+ return ConvertUtils.primitiveToWrapper(type).cast(new Integer(9));
+ }
+ }, Integer.TYPE);
+ BeanUtils.setProperty(bean, "int", new Integer(1));
+ } catch (final Exception e) {
+ e.printStackTrace();
+ signal.setException(e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "TestIndependenceThread";
+ }
+ }
+
+ final PrimitiveBean bean = new PrimitiveBean();
+ BeanUtils.setProperty(bean, "int", new Integer(1));
+ assertEquals("Wrong property value (1)", 1, bean.getInt());
+
+ ConvertUtils.register(new Converter() {
+ @Override
+ public <T> T convert(final Class<T> type, final Object value) {
+ return ConvertUtils.primitiveToWrapper(type).cast(new Integer(5));
+ }
+ }, Integer.TYPE);
+ BeanUtils.setProperty(bean, "int", new Integer(1));
+ assertEquals("Wrong property value(2)", 5, bean.getInt());
+
+ final Signal signal = new Signal();
+ signal.setSignal(1);
+ final TestIndependenceThread thread = new TestIndependenceThread(signal, bean);
+ thread.setContextClassLoader(new TestClassLoader());
+
+ thread.start();
+ thread.join();
+
+ assertNull("Exception thrown by test thread:" + signal.getException(), signal.getException());
+ assertEquals("Signal not set by test thread", 3, signal.getSignal());
+ assertEquals("Wrong property value(3)", 9, bean.getInt());
+
+ }
+
+ /** Tests whether different threads can set beanutils instances correctly */
+ public void testBeanUtilsBeanSetInstance() throws Exception {
+
+ class SetInstanceTesterThread extends Thread {
+
+ private final Signal signal;
+ private final BeanUtilsBean bean;
+
+ SetInstanceTesterThread(final Signal signal, final BeanUtilsBean bean) {
+ this.signal = signal;
+ this.bean = bean;
+ }
+
+ @Override
+ public void run() {
+ BeanUtilsBean.setInstance(bean);
+ signal.setSignal(21);
+ signal.setBean(BeanUtilsBean.getInstance());
+ }
+
+ @Override
+ public String toString() {
+ return "SetInstanceTesterThread";
+ }
+ }
+
+ final Signal signal = new Signal();
+ signal.setSignal(1);
+
+ final BeanUtilsBean beanOne = new BeanUtilsBean();
+ final BeanUtilsBean beanTwo = new BeanUtilsBean();
+
+ final SetInstanceTesterThread thread = new SetInstanceTesterThread(signal, beanTwo);
+ thread.setContextClassLoader(new TestClassLoader());
+
+ BeanUtilsBean.setInstance(beanOne);
+ assertEquals("Start thread gets right instance", beanOne, BeanUtilsBean.getInstance());
+
+ thread.start();
+ thread.join();
+
+ assertEquals("Signal not set by test thread", 21, signal.getSignal());
+ assertEquals("Second thread preserves value", beanOne, BeanUtilsBean.getInstance());
+ assertEquals("Second thread gets value it set", beanTwo, signal.getBean());
+ }
+
+ /** Tests whether the unset method works*/
+ public void testContextClassLoaderUnset() throws Exception {
+ final BeanUtilsBean beanOne = new BeanUtilsBean();
+ final ContextClassLoaderLocal<BeanUtilsBean> ccll = new ContextClassLoaderLocal<>();
+ ccll.set(beanOne);
+ assertEquals("Start thread gets right instance", beanOne, ccll.get());
+ ccll.unset();
+ assertTrue("Unset works", !beanOne.equals(ccll.get()));
+ }
+
+ // ---- Auxillary classes
+
+ class TestClassLoader extends ClassLoader {
+ @Override
+ public String toString() {
+ return "TestClassLoader";
+ }
+ }
+
+ class Signal {
+ private Exception e;
+ private int signal = 0;
+ private BeanUtilsBean bean;
+ private PropertyUtilsBean propertyUtils;
+ private ConvertUtilsBean convertUtils;
+ private Object marker;
+
+ public Exception getException() {
+ return e;
+ }
+
+ public void setException(final Exception e) {
+ this.e = e;
+ }
+
+ public int getSignal() {
+ return signal;
+ }
+
+ public void setSignal(final int signal) {
+ this.signal = signal;
+ }
+
+ public Object getMarkerObject() {
+ return marker;
+ }
+
+ public void setMarkerObject(final Object marker) {
+ this.marker = marker;
+ }
+
+ public BeanUtilsBean getBean() {
+ return bean;
+ }
+
+ public void setBean(final BeanUtilsBean bean) {
+ this.bean = bean;
+ }
+
+ public PropertyUtilsBean getPropertyUtils() {
+ return propertyUtils;
+ }
+
+ public void setPropertyUtils(final PropertyUtilsBean propertyUtils) {
+ this.propertyUtils = propertyUtils;
+ }
+
+ public ConvertUtilsBean getConvertUtils() {
+ return convertUtils;
+ }
+
+ public void setConvertUtils(final ConvertUtilsBean convertUtils) {
+ this.convertUtils = convertUtils;
+ }
+ }
+}
+
diff --git a/src/test/java/org/apache/commons/beanutils2/converters/MemoryTestCase.java b/src/test/java/org/apache/commons/beanutils2/converters/MemoryTestCase.java
index dc6ffcc..15e8480 100644
--- a/src/test/java/org/apache/commons/beanutils2/converters/MemoryTestCase.java
+++ b/src/test/java/org/apache/commons/beanutils2/converters/MemoryTestCase.java
@@ -1,291 +1,291 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * 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.apache.commons.beanutils2.converters;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import java.lang.ref.WeakReference;
-
-import org.apache.commons.beanutils2.ConvertUtils;
-import org.apache.commons.beanutils2.Converter;
-import org.junit.Test;
-
-/**
- * This class provides a number of unit tests related to classloaders and
- * garbage collection, particularly in j2ee-like situations.
- *
- */
-public class MemoryTestCase {
-
- @Test
- public void testWeakReference() throws Exception {
- final ClassLoader origContextClassLoader = Thread.currentThread().getContextClassLoader();
- try {
- ClassReloader componentLoader = new ClassReloader(origContextClassLoader);
-
- Thread.currentThread().setContextClassLoader(componentLoader);
- Thread.currentThread().setContextClassLoader(origContextClassLoader);
-
- final WeakReference<ClassLoader> ref = new WeakReference<ClassLoader>(componentLoader);
- componentLoader = null;
-
- forceGarbageCollection(ref);
- assertNull(ref.get());
- } finally {
- // Restore context classloader that was present before this
- // test started. It is expected to be the same as the system
- // classloader, but we handle all cases here..
- Thread.currentThread().setContextClassLoader(origContextClassLoader);
-
- // and restore all the standard converters
- ConvertUtils.deregister();
- }
- }
-
- /**
- * Test whether registering a standard Converter instance while
- * a custom context classloader is set causes a memory leak.
- *
- * <p>This test emulates a j2ee container where BeanUtils has been
- * loaded from a "common" lib location that is shared across all
- * components running within the container. The "component" registers
- * a converter object, whose class was loaded from the "common" lib
- * location. The registered converter:
- * <ul>
- * <li>should not be visible to other components; and</li>
- * <li>should not prevent the component-specific classloader from being
- * garbage-collected when the container sets its reference to null.
- * </ul>
- *
- */
- @Test
- public void testComponentRegistersStandardConverter() throws Exception {
-
- final ClassLoader origContextClassLoader = Thread.currentThread().getContextClassLoader();
- try {
- // sanity check; who's paranoid?? :-)
- assertEquals(origContextClassLoader, ConvertUtils.class.getClassLoader());
-
- // create a custom classloader for a "component"
- // just like a container would.
- ClassLoader componentLoader1 = new ClassLoader() {};
- final ClassLoader componentLoader2 = new ClassLoader() {};
-
- final Converter origFloatConverter = ConvertUtils.lookup(Float.TYPE);
- final Converter floatConverter1 = new FloatConverter();
-
- // Emulate the container invoking a component #1, and the component
- // registering a custom converter instance whose class is
- // available via the "shared" classloader.
- Thread.currentThread().setContextClassLoader(componentLoader1);
- {
- // here we pretend we're running inside component #1
-
- // When we first do a ConvertUtils operation inside a custom
- // classloader, we get a completely fresh copy of the
- // ConvertUtilsBean, with all-new Converter objects in it..
- assertFalse(ConvertUtils.lookup(Float.TYPE) == origFloatConverter);
-
- // Now we register a custom converter (but of a standard class).
- // This should only affect code that runs with exactly the
- // same context classloader set.
- ConvertUtils.register(floatConverter1, Float.TYPE);
- assertTrue(ConvertUtils.lookup(Float.TYPE) == floatConverter1);
- }
- Thread.currentThread().setContextClassLoader(origContextClassLoader);
-
- // The converter visible outside any custom component should not
- // have been altered.
- assertTrue(ConvertUtils.lookup(Float.TYPE) == origFloatConverter);
-
- // Emulate the container invoking a component #2.
- Thread.currentThread().setContextClassLoader(componentLoader2);
- {
- // here we pretend we're running inside component #2
-
- // we should get a completely fresh ConvertUtilsBean, with
- // all-new Converter objects again.
- assertFalse(ConvertUtils.lookup(Float.TYPE) == origFloatConverter);
- assertFalse(ConvertUtils.lookup(Float.TYPE) == floatConverter1);
- }
- Thread.currentThread().setContextClassLoader(origContextClassLoader);
-
- // Emulate a container "undeploying" component #1. This should
- // make component loader available for garbage collection (we hope)
- final WeakReference<ClassLoader> weakRefToComponent1 = new WeakReference<>(componentLoader1);
- componentLoader1 = null;
-
- // force garbage collection and verify that the componentLoader
- // has been garbage-collected
- forceGarbageCollection(weakRefToComponent1);
- assertNull(
- "Component classloader did not release properly; memory leak present",
- weakRefToComponent1.get());
- } finally {
- // Restore context classloader that was present before this
- // test started, so that in case of a test failure we don't stuff
- // up later tests...
- Thread.currentThread().setContextClassLoader(origContextClassLoader);
-
- // and restore all the standard converters
- ConvertUtils.deregister();
- }
- }
-
- /**
- * Test whether registering a custom Converter subclass while
- * a custom context classloader is set causes a memory leak.
- *
- * <p>This test emulates a j2ee container where BeanUtils has been
- * loaded from a "common" lib location that is shared across all
- * components running within the container. The "component" registers
- * a converter object, whose class was loaded via the component-specific
- * classloader. The registered converter:
- * <ul>
- * <li>should not be visible to other components; and</li>
- * <li>should not prevent the component-specific classloader from being
- * garbage-collected when the container sets its reference to null.
- * </ul>
- *
- */
- @Test
- public void testComponentRegistersCustomConverter() throws Exception {
-
- final ClassLoader origContextClassLoader = Thread.currentThread().getContextClassLoader();
- try {
- // sanity check; who's paranoid?? :-)
- assertEquals(origContextClassLoader, ConvertUtils.class.getClassLoader());
-
- // create a custom classloader for a "component"
- // just like a container would.
- ClassReloader componentLoader = new ClassReloader(origContextClassLoader);
-
- // Load a custom Converter via component loader. This emulates what
- // would happen if a user wrote their own FloatConverter subclass
- // and deployed it via the component-specific classpath.
- Thread.currentThread().setContextClassLoader(componentLoader);
- {
- // Here we pretend we're running inside the component, and that
- // a class FloatConverter has been loaded from the component's
- // private classpath.
- final Class<?> newFloatConverterClass = componentLoader.reload(FloatConverter.class);
- Object newFloatConverter = newFloatConverterClass.newInstance();
- assertTrue(newFloatConverter.getClass().getClassLoader() == componentLoader);
-
- // verify that this new object does implement the Converter type
- // despite being loaded via a classloader different from the one
- // that loaded the Converter class.
- assertTrue(
- "Converter loader via child does not implement parent type",
- Converter.class.isInstance(newFloatConverter));
-
- // this converter registration will only apply to the
- // componentLoader classloader...
- ConvertUtils.register((Converter)newFloatConverter, Float.TYPE);
-
- // After registering a custom converter, lookup should return
- // it back to us. We'll try this lookup again with a different
- // context-classloader set, and shouldn't see it
- final Converter componentConverter = ConvertUtils.lookup(Float.TYPE);
- assertTrue(componentConverter.getClass().getClassLoader() == componentLoader);
-
- newFloatConverter = null;
- }
- Thread.currentThread().setContextClassLoader(origContextClassLoader);
-
- // Because the context classloader has been reset, we shouldn't
- // see the custom registered converter here...
- final Converter sharedConverter = ConvertUtils.lookup(Float.TYPE);
- assertFalse(sharedConverter.getClass().getClassLoader() == componentLoader);
-
- // and here we should see it again
- Thread.currentThread().setContextClassLoader(componentLoader);
- {
- final Converter componentConverter = ConvertUtils.lookup(Float.TYPE);
- assertTrue(componentConverter.getClass().getClassLoader() == componentLoader);
- }
- Thread.currentThread().setContextClassLoader(origContextClassLoader);
- // Emulate a container "undeploying" the component. This should
- // make component loader available for garbage collection (we hope)
- final WeakReference<ClassLoader> weakRefToComponent = new WeakReference<ClassLoader>(componentLoader);
- componentLoader = null;
-
- // force garbage collection and verify that the componentLoader
- // has been garbage-collected
- forceGarbageCollection(weakRefToComponent);
- assertNull(
- "Component classloader did not release properly; memory leak present",
- weakRefToComponent.get());
- } finally {
- // Restore context classloader that was present before this
- // test started. It is expected to be the same as the system
- // classloader, but we handle all cases here..
- Thread.currentThread().setContextClassLoader(origContextClassLoader);
-
- // and restore all the standard converters
- ConvertUtils.deregister();
- }
- }
-
- /**
- * Attempt to force garbage collection of the specified target.
- *
- * <p>Unfortunately there is no way to force a JVM to perform
- * garbage collection; all we can do is <i>hint</i> to it that
- * garbage-collection would be a good idea, and to consume
- * memory in order to trigger it.</p>
- *
- * <p>On return, target.get() will return null if the target has
- * been garbage collected.</p>
- *
- * <p>If target.get() still returns non-null after this method has returned,
- * then either there is some reference still being held to the target, or
- * else we were not able to trigger garbage collection; there is no way
- * to tell these scenarios apart.</p>
- */
- private void forceGarbageCollection(final WeakReference<?> target) {
- int bytes = 2;
-
- while(target.get() != null) {
- System.gc();
-
- // Create increasingly-large amounts of non-referenced memory
- // in order to persuade the JVM to collect it. We are hoping
- // here that the JVM is dumb enough to run a full gc pass over
- // all data (including the target) rather than simply collecting
- // this easily-reclaimable memory!
- try {
- @SuppressWarnings("unused")
- final
- byte[] b = new byte[bytes];
- bytes = bytes * 2;
- } catch(final OutOfMemoryError e) {
- // well that sure should have forced a garbage collection
- // run to occur!
- break;
- }
- }
-
- // and let's do one more just to clean up any garbage we might have
- // created on the last pass..
- System.gc();
- }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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.apache.commons.beanutils2.converters;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.ref.WeakReference;
+
+import org.apache.commons.beanutils2.ConvertUtils;
+import org.apache.commons.beanutils2.Converter;
+import org.junit.Test;
+
+/**
+ * This class provides a number of unit tests related to classloaders and
+ * garbage collection, particularly in j2ee-like situations.
+ *
+ */
+public class MemoryTestCase {
+
+ @Test
+ public void testWeakReference() throws Exception {
+ final ClassLoader origContextClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ ClassReloader componentLoader = new ClassReloader(origContextClassLoader);
+
+ Thread.currentThread().setContextClassLoader(componentLoader);
+ Thread.currentThread().setContextClassLoader(origContextClassLoader);
+
+ final WeakReference<ClassLoader> ref = new WeakReference<>(componentLoader);
+ componentLoader = null;
+
+ forceGarbageCollection(ref);
+ assertNull(ref.get());
+ } finally {
+ // Restore context classloader that was present before this
+ // test started. It is expected to be the same as the system
+ // classloader, but we handle all cases here..
+ Thread.currentThread().setContextClassLoader(origContextClassLoader);
+
+ // and restore all the standard converters
+ ConvertUtils.deregister();
+ }
+ }
+
+ /**
+ * Test whether registering a standard Converter instance while
+ * a custom context classloader is set causes a memory leak.
+ *
+ * <p>This test emulates a j2ee container where BeanUtils has been
+ * loaded from a "common" lib location that is shared across all
+ * components running within the container. The "component" registers
+ * a converter object, whose class was loaded from the "common" lib
+ * location. The registered converter:
+ * <ul>
+ * <li>should not be visible to other components; and</li>
+ * <li>should not prevent the component-specific classloader from being
+ * garbage-collected when the container sets its reference to null.
+ * </ul>
+ *
+ */
+ @Test
+ public void testComponentRegistersStandardConverter() throws Exception {
+
+ final ClassLoader origContextClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ // sanity check; who's paranoid?? :-)
+ assertEquals(origContextClassLoader, ConvertUtils.class.getClassLoader());
+
+ // create a custom classloader for a "component"
+ // just like a container would.
+ ClassLoader componentLoader1 = new ClassLoader() {};
+ final ClassLoader componentLoader2 = new ClassLoader() {};
+
+ final Converter origFloatConverter = ConvertUtils.lookup(Float.TYPE);
+ final Converter floatConverter1 = new FloatConverter();
+
+ // Emulate the container invoking a component #1, and the component
+ // registering a custom converter instance whose class is
+ // available via the "shared" classloader.
+ Thread.currentThread().setContextClassLoader(componentLoader1);
+ {
+ // here we pretend we're running inside component #1
+
+ // When we first do a ConvertUtils operation inside a custom
+ // classloader, we get a completely fresh copy of the
+ // ConvertUtilsBean, with all-new Converter objects in it..
+ assertFalse(ConvertUtils.lookup(Float.TYPE) == origFloatConverter);
+
+ // Now we register a custom converter (but of a standard class).
+ // This should only affect code that runs with exactly the
+ // same context classloader set.
+ ConvertUtils.register(floatConverter1, Float.TYPE);
+ assertTrue(ConvertUtils.lookup(Float.TYPE) == floatConverter1);
+ }
+ Thread.currentThread().setContextClassLoader(origContextClassLoader);
+
+ // The converter visible outside any custom component should not
+ // have been altered.
+ assertTrue(ConvertUtils.lookup(Float.TYPE) == origFloatConverter);
+
+ // Emulate the container invoking a component #2.
+ Thread.currentThread().setContextClassLoader(componentLoader2);
+ {
+ // here we pretend we're running inside component #2
+
+ // we should get a completely fresh ConvertUtilsBean, with
+ // all-new Converter objects again.
+ assertFalse(ConvertUtils.lookup(Float.TYPE) == origFloatConverter);
+ assertFalse(ConvertUtils.lookup(Float.TYPE) == floatConverter1);
+ }
+ Thread.currentThread().setContextClassLoader(origContextClassLoader);
+
+ // Emulate a container "undeploying" component #1. This should
+ // make component loader available for garbage collection (we hope)
+ final WeakReference<ClassLoader> weakRefToComponent1 = new WeakReference<>(componentLoader1);
+ componentLoader1 = null;
+
+ // force garbage collection and verify that the componentLoader
+ // has been garbage-collected
+ forceGarbageCollection(weakRefToComponent1);
+ assertNull(
+ "Component classloader did not release properly; memory leak present",
+ weakRefToComponent1.get());
+ } finally {
+ // Restore context classloader that was present before this
+ // test started, so that in case of a test failure we don't stuff
+ // up later tests...
+ Thread.currentThread().setContextClassLoader(origContextClassLoader);
+
+ // and restore all the standard converters
+ ConvertUtils.deregister();
+ }
+ }
+
+ /**
+ * Test whether registering a custom Converter subclass while
+ * a custom context classloader is set causes a memory leak.
+ *
+ * <p>This test emulates a j2ee container where BeanUtils has been
+ * loaded from a "common" lib location that is shared across all
+ * components running within the container. The "component" registers
+ * a converter object, whose class was loaded via the component-specific
+ * classloader. The registered converter:
+ * <ul>
+ * <li>should not be visible to other components; and</li>
+ * <li>should not prevent the component-specific classloader from being
+ * garbage-collected when the container sets its reference to null.
+ * </ul>
+ *
+ */
+ @Test
+ public void testComponentRegistersCustomConverter() throws Exception {
+
+ final ClassLoader origContextClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ // sanity check; who's paranoid?? :-)
+ assertEquals(origContextClassLoader, ConvertUtils.class.getClassLoader());
+
+ // create a custom classloader for a "component"
+ // just like a container would.
+ ClassReloader componentLoader = new ClassReloader(origContextClassLoader);
+
+ // Load a custom Converter via component loader. This emulates what
+ // would happen if a user wrote their own FloatConverter subclass
+ // and deployed it via the component-specific classpath.
+ Thread.currentThread().setContextClassLoader(componentLoader);
+ {
+ // Here we pretend we're running inside the component, and that
+ // a class FloatConverter has been loaded from the component's
+ // private classpath.
+ final Class<?> newFloatConverterClass = componentLoader.reload(FloatConverter.class);
+ Object newFloatConverter = newFloatConverterClass.newInstance();
+ assertTrue(newFloatConverter.getClass().getClassLoader() == componentLoader);
+
+ // verify that this new object does implement the Converter type
+ // despite being loaded via a classloader different from the one
+ // that loaded the Converter class.
+ assertTrue(
+ "Converter loader via child does not implement parent type",
+ Converter.class.isInstance(newFloatConverter));
+
+ // this converter registration will only apply to the
+ // componentLoader classloader...
+ ConvertUtils.register((Converter)newFloatConverter, Float.TYPE);
+
+ // After registering a custom converter, lookup should return
+ // it back to us. We'll try this lookup again with a different
+ // context-classloader set, and shouldn't see it
+ final Converter componentConverter = ConvertUtils.lookup(Float.TYPE);
+ assertTrue(componentConverter.getClass().getClassLoader() == componentLoader);
+
+ newFloatConverter = null;
+ }
+ Thread.currentThread().setContextClassLoader(origContextClassLoader);
+
+ // Because the context classloader has been reset, we shouldn't
+ // see the custom registered converter here...
+ final Converter sharedConverter = ConvertUtils.lookup(Float.TYPE);
+ assertFalse(sharedConverter.getClass().getClassLoader() == componentLoader);
+
+ // and here we should see it again
+ Thread.currentThread().setContextClassLoader(componentLoader);
+ {
+ final Converter componentConverter = ConvertUtils.lookup(Float.TYPE);
+ assertTrue(componentConverter.getClass().getClassLoader() == componentLoader);
+ }
+ Thread.currentThread().setContextClassLoader(origContextClassLoader);
+ // Emulate a container "undeploying" the component. This should
+ // make component loader available for garbage collection (we hope)
+ final WeakReference<ClassLoader> weakRefToComponent = new WeakReference<>(componentLoader);
+ componentLoader = null;
+
+ // force garbage collection and verify that the componentLoader
+ // has been garbage-collected
+ forceGarbageCollection(weakRefToComponent);
+ assertNull(
+ "Component classloader did not release properly; memory leak present",
+ weakRefToComponent.get());
+ } finally {
+ // Restore context classloader that was present before this
+ // test started. It is expected to be the same as the system
+ // classloader, but we handle all cases here..
+ Thread.currentThread().setContextClassLoader(origContextClassLoader);
+
+ // and restore all the standard converters
+ ConvertUtils.deregister();
+ }
+ }
+
+ /**
+ * Attempt to force garbage collection of the specified target.
+ *
+ * <p>Unfortunately there is no way to force a JVM to perform
+ * garbage collection; all we can do is <i>hint</i> to it that
+ * garbage-collection would be a good idea, and to consume
+ * memory in order to trigger it.</p>
+ *
+ * <p>On return, target.get() will return null if the target has
+ * been garbage collected.</p>
+ *
+ * <p>If target.get() still returns non-null after this method has returned,
+ * then either there is some reference still being held to the target, or
+ * else we were not able to trigger garbage collection; there is no way
+ * to tell these scenarios apart.</p>
+ */
+ private void forceGarbageCollection(final WeakReference<?> target) {
+ int bytes = 2;
+
+ while(target.get() != null) {
+ System.gc();
+
+ // Create increasingly-large amounts of non-referenced memory
+ // in order to persuade the JVM to collect it. We are hoping
+ // here that the JVM is dumb enough to run a full gc pass over
+ // all data (including the target) rather than simply collecting
+ // this easily-reclaimable memory!
+ try {
+ @SuppressWarnings("unused")
+ final
+ byte[] b = new byte[bytes];
+ bytes = bytes * 2;
+ } catch(final OutOfMemoryError e) {
+ // well that sure should have forced a garbage collection
+ // run to occur!
+ break;
+ }
+ }
+
+ // and let's do one more just to clean up any garbage we might have
+ // created on the last pass..
+ System.gc();
+ }
+}
diff --git a/src/test/java/org/apache/commons/beanutils2/locale/LocaleBeanificationTestCase.java b/src/test/java/org/apache/commons/beanutils2/locale/LocaleBeanificationTestCase.java
index adfc844..cdc9a26 100644
--- a/src/test/java/org/apache/commons/beanutils2/locale/LocaleBeanificationTestCase.java
+++ b/src/test/java/org/apache/commons/beanutils2/locale/LocaleBeanificationTestCase.java
@@ -1,580 +1,580 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * 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.apache.commons.beanutils2.locale;
-
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.WeakReference;
-import java.util.Locale;
-import java.util.Map;
-import java.util.WeakHashMap;
-
-import org.apache.commons.beanutils2.BeanUtilsBean;
-import org.apache.commons.beanutils2.BeanUtilsTestCase;
-import org.apache.commons.beanutils2.ContextClassLoaderLocal;
-import org.apache.commons.beanutils2.ConversionException;
-import org.apache.commons.beanutils2.ConvertUtils;
-import org.apache.commons.beanutils2.PrimitiveBean;
-import org.apache.commons.beanutils2.locale.converters.LongLocaleConverter;
-import org.apache.commons.logging.LogFactory;
-
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-
-/**
- * <p>
- * Test Case for changes made during LocaleBeanutils Beanification.
- * This is basically a cut-and-correct version of the beanutils beanifications tests.
- * </p>
- *
- */
-
-public class LocaleBeanificationTestCase extends TestCase {
-
- // ---------------------------------------------------- Constants
-
- /** Maximum number of iterations before our test fails */
- public static final int MAX_GC_ITERATIONS = 50;
-
- // ---------------------------------------------------- Instance Variables
-
-
- // ---------------------------------------------------------- Constructors
-
-
- /**
- * Construct a new instance of this test case.
- *
- * @param name Name of the test case
- */
- public LocaleBeanificationTestCase(final String name) {
- super(name);
- }
-
-
- // -------------------------------------------------- Overall Test Methods
-
-
- /**
- * Set up instance variables required by this test case.
- */
- @Override
- public void setUp() {
-
- LocaleConvertUtils.deregister();
-
- }
-
-
- /**
- * Return the tests included in this test suite.
- */
- public static Test suite() {
- return new TestSuite(LocaleBeanificationTestCase.class);
- }
-
-
- /**
- * Tear down instance variables required by this test case.
- */
- @Override
- public void tearDown() {
- // No action required
- }
-
-
- // ------------------------------------------------ Individual Test Methods
-
- /** Test of the methodology we'll use for some of the later tests */
- public void testMemoryTestMethodology() throws Exception {
- // test methodology
- // many thanks to Juozas Baliuka for suggesting this method
- ClassLoader loader = new ClassLoader(this.getClass().getClassLoader()) {};
- final WeakReference<ClassLoader> reference = new WeakReference<>(loader);
- Class<?> myClass = loader.loadClass("org.apache.commons.beanutils2.BetaBean");
-
- assertNotNull("Weak reference released early", reference.get());
-
- // dereference class loader and class:
- loader = null;
- myClass = null;
-
- int iterations = 0;
- int bytz = 2;
- while(true) {
- System.gc();
- if(iterations++ > MAX_GC_ITERATIONS){
- fail("Max iterations reached before resource released.");
- }
- if( reference.get() == null ) {
- break;
-
- }
- // create garbage:
- final byte[] b = new byte[bytz];
- bytz = bytz * 2;
- }
- }
-
- /** Tests whether classloaders and beans are released from memory by the map used by beanutils */
- public void testMemoryLeak2() throws Exception {
- // tests when the map used by beanutils has the right behaviour
-
- if (BeanUtilsTestCase.isPre14JVM()) {
- System.out.println("WARNING: CANNOT TEST MEMORY LEAK ON PRE1.4 JVM");
- return;
- }
-
- // many thanks to Juozas Baliuka for suggesting this methodology
- TestClassLoader loader = new TestClassLoader();
- final ReferenceQueue<Object> queue = new ReferenceQueue<>();
- final WeakReference<ClassLoader> loaderReference = new WeakReference<ClassLoader>(loader, queue);
- Integer test = new Integer(1);
-
- final WeakReference<Integer> testReference = new WeakReference<>(test, queue);
- //Map map = new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.HARD, true);
- final Map<TestClassLoader, Integer> map = new WeakHashMap<>();
- map.put(loader, test);
-
- assertEquals("In map", test, map.get(loader));
- assertNotNull("Weak reference released early (1)", loaderReference.get());
- assertNotNull("Weak reference released early (2)", testReference.get());
-
- // dereference strong references
- loader = null;
- test = null;
-
- int iterations = 0;
- int bytz = 2;
- while(true) {
- System.gc();
- if(iterations++ > MAX_GC_ITERATIONS){
- fail("Max iterations reached before resource released.");
- }
- map.isEmpty();
-
- if(
- loaderReference.get() == null &&
- testReference.get() == null) {
- break;
-
- }
- // create garbage:
- final byte[] b = new byte[bytz];
- bytz = bytz * 2;
- }
- }
-
- /** Tests whether classloaders and beans are released from memory */
- public void testMemoryLeak() throws Exception {
- if (BeanUtilsTestCase.isPre14JVM()) {
- System.out.println("WARNING: CANNOT TEST MEMORY LEAK ON PRE1.4 JVM");
- return;
- }
-
- // many thanks to Juozas Baliuka for suggesting this methodology
- TestClassLoader loader = new TestClassLoader();
- final WeakReference<TestClassLoader> loaderReference = new WeakReference<>(loader);
- LocaleBeanUtilsBean.getLocaleBeanUtilsInstance();
-
- class GetBeanUtilsBeanThread extends Thread {
-
- LocaleBeanUtilsBean beanUtils;
- LocaleConvertUtilsBean convertUtils;
-
- GetBeanUtilsBeanThread() {}
-
- @Override
- public void run() {
- beanUtils = LocaleBeanUtilsBean.getLocaleBeanUtilsInstance();
- convertUtils = LocaleConvertUtilsBean.getInstance();
- // XXX Log keeps a reference around!
- LogFactory.releaseAll();
- }
-
- @Override
- public String toString() {
- return "GetBeanUtilsBeanThread";
- }
- }
-
-
- GetBeanUtilsBeanThread thread = new GetBeanUtilsBeanThread();
- final WeakReference<GetBeanUtilsBeanThread> threadWeakReference = new WeakReference<>(thread);
- thread.setContextClassLoader(loader);
-
- thread.start();
- thread.join();
-
- final WeakReference<LocaleBeanUtilsBean> beanUtilsReference = new WeakReference<>(thread.beanUtils);
- final WeakReference<LocaleConvertUtilsBean> convertUtilsReference = new WeakReference<>(thread.convertUtils);
-
- assertNotNull("Weak reference released early (1)", loaderReference.get());
- assertNotNull("Weak reference released early (2)", beanUtilsReference.get());
- assertNotNull("Weak reference released early (4)", convertUtilsReference.get());
-
- // dereference strong references
- loader = null;
- thread.setContextClassLoader(null);
- thread = null;
-
- int iterations = 0;
- int bytz = 2;
- while(true) {
- LocaleBeanUtilsBean.getLocaleBeanUtilsInstance();
- System.gc();
- if(iterations++ > MAX_GC_ITERATIONS){
- fail("Max iterations reached before resource released.");
- }
-
- if(
- loaderReference.get() == null &&
- beanUtilsReference.get() == null &&
- convertUtilsReference.get() == null) {
- break;
-
- }
- // create garbage:
- final byte[] b = new byte[bytz];
- bytz = bytz * 2;
- }
- }
-
- /**
- * Tests whether difference instances are loaded by different
- * context classloaders.
- */
- public void testGetByContextClassLoader() throws Exception {
-
- class GetBeanUtilsBeanThread extends Thread {
-
- private final Signal signal;
-
- GetBeanUtilsBeanThread(final Signal signal) {
- this.signal = signal;
- }
-
- @Override
- public void run() {
- signal.setSignal(2);
- signal.setBean(LocaleBeanUtilsBean.getLocaleBeanUtilsInstance());
- signal.setConvertUtils(LocaleConvertUtilsBean.getInstance());
- }
-
- @Override
- public String toString() {
- return "GetBeanUtilsBeanThread";
- }
- }
-
- final Signal signal = new Signal();
- signal.setSignal(1);
-
- final GetBeanUtilsBeanThread thread = new GetBeanUtilsBeanThread(signal);
- thread.setContextClassLoader(new TestClassLoader());
-
- thread.start();
- thread.join();
-
- assertEquals("Signal not set by test thread", 2, signal.getSignal());
- assertTrue(
- "Different LocaleBeanUtilsBean instances per context classloader",
- BeanUtilsBean.getInstance() != signal.getBean());
- assertTrue(
- "Different LocaleConvertUtilsBean instances per context classloader",
- LocaleConvertUtilsBean.getInstance() != signal.getConvertUtils());
- }
-
-
- /**
- * Tests whether difference instances are loaded by different
- * context classloaders.
- */
- public void testContextClassLoaderLocal() throws Exception {
-
- class CCLLTesterThread extends Thread {
-
- private final Signal signal;
- private final ContextClassLoaderLocal<Integer> ccll;
-
- CCLLTesterThread(final Signal signal, final ContextClassLoaderLocal<Integer> ccll) {
- this.signal = signal;
- this.ccll = ccll;
- }
-
- @Override
- public void run() {
- ccll.set(new Integer(1789));
- signal.setSignal(2);
- signal.setMarkerObject(ccll.get());
- }
-
- @Override
- public String toString() {
- return "CCLLTesterThread";
- }
- }
-
- final ContextClassLoaderLocal<Integer> ccll = new ContextClassLoaderLocal<>();
- ccll.set(1776);
- assertEquals("Start thread sets value", new Integer(1776), ccll.get());
-
- final Signal signal = new Signal();
- signal.setSignal(1);
-
- final CCLLTesterThread thread = new CCLLTesterThread(signal, ccll);
- thread.setContextClassLoader(new TestClassLoader());
-
- thread.start();
- thread.join();
-
- assertEquals("Signal not set by test thread", 2, signal.getSignal());
- assertEquals("Second thread preserves value", new Integer(1776), ccll.get());
- assertEquals("Second thread gets value it set", new Integer(1789), signal.getMarkerObject());
- }
-
- /** Tests whether calls are independent for different classloaders */
- public void testContextClassloaderIndependence() throws Exception {
-
- class TestIndependenceThread extends Thread {
- private final Signal signal;
- private final PrimitiveBean bean;
-
- TestIndependenceThread(final Signal signal, final PrimitiveBean bean) {
- this.signal = signal;
- this.bean = bean;
- }
-
- @Override
- public void run() {
- try {
- signal.setSignal(3);
- LocaleConvertUtils.register(new LocaleConverter() {
- @Override
- public <T> T convert(final Class<T> type, final Object value) {
- return ConvertUtils.primitiveToWrapper(type).cast(9);
- }
- @Override
- public <T> T convert(final Class<T> type, final Object value, final String pattern) {
- return ConvertUtils.primitiveToWrapper(type).cast(9);
- }
- }, Integer.TYPE, Locale.getDefault());
- LocaleBeanUtils.setProperty(bean, "int", "1");
- } catch (final Exception e) {
- e.printStackTrace();
- signal.setException(e);
- }
- }
-
- @Override
- public String toString() {
- return "TestIndependenceThread";
- }
- }
-
- final PrimitiveBean bean = new PrimitiveBean();
- LocaleBeanUtils.setProperty(bean, "int", new Integer(1));
- assertEquals("Wrong property value (1)", 1, bean.getInt());
-
- LocaleConvertUtils.register(new LocaleConverter() {
- @Override
- public <T> T convert(final Class<T> type, final Object value) {
- return ConvertUtils.primitiveToWrapper(type).cast(5);
- }
- @Override
- public <T> T convert(final Class<T> type, final Object value, final String pattern) {
- return ConvertUtils.primitiveToWrapper(type).cast(5);
- }
- }, Integer.TYPE, Locale.getDefault());
- LocaleBeanUtils.setProperty(bean, "int", "1");
- assertEquals("Wrong property value(2)", 5, bean.getInt());
-
- final Signal signal = new Signal();
- signal.setSignal(1);
- final TestIndependenceThread thread = new TestIndependenceThread(signal, bean);
- thread.setContextClassLoader(new TestClassLoader());
-
- thread.start();
- thread.join();
-
- assertNull("Exception thrown by test thread:" + signal.getException(), signal.getException());
- assertEquals("Signal not set by test thread", 3, signal.getSignal());
- assertEquals("Wrong property value(3)", 9, bean.getInt());
-
- }
-
- /** Tests whether different threads can set beanutils instances correctly */
- public void testBeanUtilsBeanSetInstance() throws Exception {
-
- class SetInstanceTesterThread extends Thread {
-
- private final Signal signal;
- private final LocaleBeanUtilsBean bean;
-
- SetInstanceTesterThread(final Signal signal, final LocaleBeanUtilsBean bean) {
- this.signal = signal;
- this.bean = bean;
- }
-
- @Override
- public void run() {
- LocaleBeanUtilsBean.setInstance(bean);
- signal.setSignal(21);
- signal.setBean(LocaleBeanUtilsBean.getLocaleBeanUtilsInstance());
- }
-
- @Override
- public String toString() {
- return "SetInstanceTesterThread";
- }
- }
-
- final Signal signal = new Signal();
- signal.setSignal(1);
-
- final LocaleBeanUtilsBean beanOne = new LocaleBeanUtilsBean();
- final LocaleBeanUtilsBean beanTwo = new LocaleBeanUtilsBean();
-
- final SetInstanceTesterThread thread = new SetInstanceTesterThread(signal, beanTwo);
- thread.setContextClassLoader(new TestClassLoader());
-
- LocaleBeanUtilsBean.setInstance(beanOne);
- assertEquals("Start thread gets right instance", beanOne, LocaleBeanUtilsBean.getLocaleBeanUtilsInstance());
-
- thread.start();
- thread.join();
-
- assertEquals("Signal not set by test thread", 21, signal.getSignal());
- assertEquals("Second thread preserves value", beanOne, LocaleBeanUtilsBean.getLocaleBeanUtilsInstance());
- assertEquals("Second thread gets value it set", beanTwo, signal.getBean());
- }
-
- /** Tests whether the unset method works*/
- public void testContextClassLoaderUnset() throws Exception {
- final LocaleBeanUtilsBean beanOne = new LocaleBeanUtilsBean();
- final ContextClassLoaderLocal<LocaleBeanUtilsBean> ccll = new ContextClassLoaderLocal<>();
- ccll.set(beanOne);
- assertEquals("Start thread gets right instance", beanOne, ccll.get());
- ccll.unset();
- assertTrue("Unset works", !beanOne.equals(ccll.get()));
- }
-
- /**
- * Test registering a locale-aware converter with the standard ConvertUtils.
- */
- public void testLocaleAwareConverterInConvertUtils() throws Exception {
- try {
- // first use the default non-locale-aware converter
- try {
- final Long data = (Long) ConvertUtils.convert("777", Long.class);
- assertEquals("Standard format long converted ok", 777, data.longValue());
- }
- catch(final ConversionException ex) {
- fail("Unable to convert non-locale-aware number 777");
- }
-
- // now try default converter with special delimiters
- try {
- // This conversion will cause an error. But the default
- // Long converter is set up to return a default value of
- // zero on error.
- final Long data = (Long) ConvertUtils.convert("1.000.000", Long.class);
- assertEquals("Standard format behaved as expected", 0, data.longValue());
- }
- catch(final ConversionException ex) {
- fail("Unexpected exception from standard Long converter.");
- }
-
- // Now try using a locale-aware converter together with
- // locale-specific input string. Note that in the german locale,
- // large numbers can be split up into groups of three digits
- // using a dot character (and comma is the decimal-point indicator).
- try {
-
- final Locale germanLocale = Locale.GERMAN;
- final LongLocaleConverter longLocaleConverter = new LongLocaleConverter(germanLocale);
- ConvertUtils.register(longLocaleConverter, Long.class);
-
- final Long data = (Long) ConvertUtils.convert("1.000.000", Long.class);
- assertEquals("German-format long converted ok", 1000000, data.longValue());
- } catch(final ConversionException ex) {
- fail("Unable to convert german-format number");
- }
- } finally {
- ConvertUtils.deregister();
- }
- }
-
- // ---- Auxillary classes
-
- class TestClassLoader extends ClassLoader {
- @Override
- public String toString() {
- return "TestClassLoader";
- }
- }
-
- class Signal {
- private Exception e;
- private int signal = 0;
- private LocaleBeanUtilsBean bean;
- private LocaleConvertUtilsBean convertUtils;
- private Object marker;
-
- public Exception getException() {
- return e;
- }
-
- public void setException(final Exception e) {
- this.e = e;
- }
-
- public int getSignal() {
- return signal;
- }
-
- public void setSignal(final int signal) {
- this.signal = signal;
- }
-
- public Object getMarkerObject() {
- return marker;
- }
-
- public void setMarkerObject(final Object marker) {
- this.marker = marker;
- }
-
- public LocaleBeanUtilsBean getBean() {
- return bean;
- }
-
- public void setBean(final LocaleBeanUtilsBean bean) {
- this.bean = bean;
- }
-
- public LocaleConvertUtilsBean getConvertUtils() {
- return convertUtils;
- }
-
- public void setConvertUtils(final LocaleConvertUtilsBean convertUtils) {
- this.convertUtils = convertUtils;
- }
- }
-}
-
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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.apache.commons.beanutils2.locale;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.Locale;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import org.apache.commons.beanutils2.BeanUtilsBean;
+import org.apache.commons.beanutils2.BeanUtilsTestCase;
+import org.apache.commons.beanutils2.ContextClassLoaderLocal;
+import org.apache.commons.beanutils2.ConversionException;
+import org.apache.commons.beanutils2.ConvertUtils;
+import org.apache.commons.beanutils2.PrimitiveBean;
+import org.apache.commons.beanutils2.locale.converters.LongLocaleConverter;
+import org.apache.commons.logging.LogFactory;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * <p>
+ * Test Case for changes made during LocaleBeanutils Beanification.
+ * This is basically a cut-and-correct version of the beanutils beanifications tests.
+ * </p>
+ *
+ */
+
+public class LocaleBeanificationTestCase extends TestCase {
+
+ // ---------------------------------------------------- Constants
+
+ /** Maximum number of iterations before our test fails */
+ public static final int MAX_GC_ITERATIONS = 50;
+
+ // ---------------------------------------------------- Instance Variables
+
+
+ // ---------------------------------------------------------- Constructors
+
+
+ /**
+ * Construct a new instance of this test case.
+ *
+ * @param name Name of the test case
+ */
+ public LocaleBeanificationTestCase(final String name) {
+ super(name);
+ }
+
+
+ // -------------------------------------------------- Overall Test Methods
+
+
+ /**
+ * Set up instance variables required by this test case.
+ */
+ @Override
+ public void setUp() {
+
+ LocaleConvertUtils.deregister();
+
+ }
+
+
+ /**
+ * Return the tests included in this test suite.
+ */
+ public static Test suite() {
+ return new TestSuite(LocaleBeanificationTestCase.class);
+ }
+
+
+ /**
+ * Tear down instance variables required by this test case.
+ */
+ @Override
+ public void tearDown() {
+ // No action required
+ }
+
+
+ // ------------------------------------------------ Individual Test Methods
+
+ /** Test of the methodology we'll use for some of the later tests */
+ public void testMemoryTestMethodology() throws Exception {
+ // test methodology
+ // many thanks to Juozas Baliuka for suggesting this method
+ ClassLoader loader = new ClassLoader(this.getClass().getClassLoader()) {};
+ final WeakReference<ClassLoader> reference = new WeakReference<>(loader);
+ Class<?> myClass = loader.loadClass("org.apache.commons.beanutils2.BetaBean");
+
+ assertNotNull("Weak reference released early", reference.get());
+
+ // dereference class loader and class:
+ loader = null;
+ myClass = null;
+
+ int iterations = 0;
+ int bytz = 2;
+ while(true) {
+ System.gc();
+ if(iterations++ > MAX_GC_ITERATIONS){
+ fail("Max iterations reached before resource released.");
+ }
+ if( reference.get() == null ) {
+ break;
+
+ }
+ // create garbage:
+ final byte[] b = new byte[bytz];
+ bytz = bytz * 2;
+ }
+ }
+
+ /** Tests whether classloaders and beans are released from memory by the map used by beanutils */
+ public void testMemoryLeak2() throws Exception {
+ // tests when the map used by beanutils has the right behaviour
+
+ if (BeanUtilsTestCase.isPre14JVM()) {
+ System.out.println("WARNING: CANNOT TEST MEMORY LEAK ON PRE1.4 JVM");
+ return;
+ }
+
+ // many thanks to Juozas Baliuka for suggesting this methodology
+ TestClassLoader loader = new TestClassLoader();
+ final ReferenceQueue<Object> queue = new ReferenceQueue<>();
+ final WeakReference<ClassLoader> loaderReference = new WeakReference<>(loader, queue);
+ Integer test = new Integer(1);
+
+ final WeakReference<Integer> testReference = new WeakReference<>(test, queue);
+ //Map map = new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.HARD, true);
+ final Map<TestClassLoader, Integer> map = new WeakHashMap<>();
+ map.put(loader, test);
+
+ assertEquals("In map", test, map.get(loader));
+ assertNotNull("Weak reference released early (1)", loaderReference.get());
+ assertNotNull("Weak reference released early (2)", testReference.get());
+
+ // dereference strong references
+ loader = null;
+ test = null;
+
+ int iterations = 0;
+ int bytz = 2;
+ while(true) {
+ System.gc();
+ if(iterations++ > MAX_GC_ITERATIONS){
+ fail("Max iterations reached before resource released.");
+ }
+ map.isEmpty();
+
+ if(
+ loaderReference.get() == null &&
+ testReference.get() == null) {
+ break;
+
+ }
+ // create garbage:
+ final byte[] b = new byte[bytz];
+ bytz = bytz * 2;
+ }
+ }
+
+ /** Tests whether classloaders and beans are released from memory */
+ public void testMemoryLeak() throws Exception {
+ if (BeanUtilsTestCase.isPre14JVM()) {
+ System.out.println("WARNING: CANNOT TEST MEMORY LEAK ON PRE1.4 JVM");
+ return;
+ }
+
+ // many thanks to Juozas Baliuka for suggesting this methodology
+ TestClassLoader loader = new TestClassLoader();
+ final WeakReference<TestClassLoader> loaderReference = new WeakReference<>(loader);
+ LocaleBeanUtilsBean.getLocaleBeanUtilsInstance();
+
+ class GetBeanUtilsBeanThread extends Thread {
+
+ LocaleBeanUtilsBean beanUtils;
+ LocaleConvertUtilsBean convertUtils;
+
+ GetBeanUtilsBeanThread() {}
+
+ @Override
+ public void run() {
+ beanUtils = LocaleBeanUtilsBean.getLocaleBeanUtilsInstance();
+ convertUtils = LocaleConvertUtilsBean.getInstance();
+ // XXX Log keeps a reference around!
+ LogFactory.releaseAll();
+ }
+
+ @Override
+ public String toString() {
+ return "GetBeanUtilsBeanThread";
+ }
+ }
+
+
+ GetBeanUtilsBeanThread thread = new GetBeanUtilsBeanThread();
+ final WeakReference<GetBeanUtilsBeanThread> threadWeakReference = new WeakReference<>(thread);
+ thread.setContextClassLoader(loader);
+
+ thread.start();
+ thread.join();
+
+ final WeakReference<LocaleBeanUtilsBean> beanUtilsReference = new WeakReference<>(thread.beanUtils);
+ final WeakReference<LocaleConvertUtilsBean> convertUtilsReference = new WeakReference<>(thread.convertUtils);
+
+ assertNotNull("Weak reference released early (1)", loaderReference.get());
+ assertNotNull("Weak reference released early (2)", beanUtilsReference.get());
+ assertNotNull("Weak reference released early (4)", convertUtilsReference.get());
+
+ // dereference strong references
+ loader = null;
+ thread.setContextClassLoader(null);
+ thread = null;
+
+ int iterations = 0;
+ int bytz = 2;
+ while(true) {
+ LocaleBeanUtilsBean.getLocaleBeanUtilsInstance();
+ System.gc();
+ if(iterations++ > MAX_GC_ITERATIONS){
+ fail("Max iterations reached before resource released.");
+ }
+
+ if(
+ loaderReference.get() == null &&
+ beanUtilsReference.get() == null &&
+ convertUtilsReference.get() == null) {
+ break;
+
+ }
+ // create garbage:
+ final byte[] b = new byte[bytz];
+ bytz = bytz * 2;
+ }
+ }
+
+ /**
+ * Tests whether difference instances are loaded by different
+ * context classloaders.
+ */
+ public void testGetByContextClassLoader() throws Exception {
+
+ class GetBeanUtilsBeanThread extends Thread {
+
+ private final Signal signal;
+
+ GetBeanUtilsBeanThread(final Signal signal) {
+ this.signal = signal;
+ }
+
+ @Override
+ public void run() {
+ signal.setSignal(2);
+ signal.setBean(LocaleBeanUtilsBean.getLocaleBeanUtilsInstance());
+ signal.setConvertUtils(LocaleConvertUtilsBean.getInstance());
+ }
+
+ @Override
+ public String toString() {
+ return "GetBeanUtilsBeanThread";
+ }
+ }
+
+ final Signal signal = new Signal();
+ signal.setSignal(1);
+
+ final GetBeanUtilsBeanThread thread = new GetBeanUtilsBeanThread(signal);
+ thread.setContextClassLoader(new TestClassLoader());
+
+ thread.start();
+ thread.join();
+
+ assertEquals("Signal not set by test thread", 2, signal.getSignal());
+ assertTrue(
+ "Different LocaleBeanUtilsBean instances per context classloader",
+ BeanUtilsBean.getInstance() != signal.getBean());
+ assertTrue(
+ "Different LocaleConvertUtilsBean instances per context classloader",
+ LocaleConvertUtilsBean.getInstance() != signal.getConvertUtils());
+ }
+
+
+ /**
+ * Tests whether difference instances are loaded by different
+ * context classloaders.
+ */
+ public void testContextClassLoaderLocal() throws Exception {
+
+ class CCLLTesterThread extends Thread {
+
+ private final Signal signal;
+ private final ContextClassLoaderLocal<Integer> ccll;
+
+ CCLLTesterThread(final Signal signal, final ContextClassLoaderLocal<Integer> ccll) {
+ this.signal = signal;
+ this.ccll = ccll;
+ }
+
+ @Override
+ public void run() {
+ ccll.set(new Integer(1789));
+ signal.setSignal(2);
+ signal.setMarkerObject(ccll.get());
+ }
+
+ @Override
+ public String toString() {
+ return "CCLLTesterThread";
+ }
+ }
+
+ final ContextClassLoaderLocal<Integer> ccll = new ContextClassLoaderLocal<>();
+ ccll.set(1776);
+ assertEquals("Start thread sets value", new Integer(1776), ccll.get());
+
+ final Signal signal = new Signal();
+ signal.setSignal(1);
+
+ final CCLLTesterThread thread = new CCLLTesterThread(signal, ccll);
+ thread.setContextClassLoader(new TestClassLoader());
+
+ thread.start();
+ thread.join();
+
+ assertEquals("Signal not set by test thread", 2, signal.getSignal());
+ assertEquals("Second thread preserves value", new Integer(1776), ccll.get());
+ assertEquals("Second thread gets value it set", new Integer(1789), signal.getMarkerObject());
+ }
+
+ /** Tests whether calls are independent for different classloaders */
+ public void testContextClassloaderIndependence() throws Exception {
+
+ class TestIndependenceThread extends Thread {
+ private final Signal signal;
+ private final PrimitiveBean bean;
+
+ TestIndependenceThread(final Signal signal, final PrimitiveBean bean) {
+ this.signal = signal;
+ this.bean = bean;
+ }
+
+ @Override
+ public void run() {
+ try {
+ signal.setSignal(3);
+ LocaleConvertUtils.register(new LocaleConverter() {
+ @Override
+ public <T> T convert(final Class<T> type, final Object value) {
+ return ConvertUtils.primitiveToWrapper(type).cast(9);
+ }
+ @Override
+ public <T> T convert(final Class<T> type, final Object value, final String pattern) {
+ return ConvertUtils.primitiveToWrapper(type).cast(9);
+ }
+ }, Integer.TYPE, Locale.getDefault());
+ LocaleBeanUtils.setProperty(bean, "int", "1");
+ } catch (final Exception e) {
+ e.printStackTrace();
+ signal.setException(e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "TestIndependenceThread";
+ }
+ }
+
+ final PrimitiveBean bean = new PrimitiveBean();
+ LocaleBeanUtils.setProperty(bean, "int", new Integer(1));
+ assertEquals("Wrong property value (1)", 1, bean.getInt());
+
+ LocaleConvertUtils.register(new LocaleConverter() {
+ @Override
+ public <T> T convert(final Class<T> type, final Object value) {
+ return ConvertUtils.primitiveToWrapper(type).cast(5);
+ }
+ @Override
+ public <T> T convert(final Class<T> type, final Object value, final String pattern) {
+ return ConvertUtils.primitiveToWrapper(type).cast(5);
+ }
+ }, Integer.TYPE, Locale.getDefault());
+ LocaleBeanUtils.setProperty(bean, "int", "1");
+ assertEquals("Wrong property value(2)", 5, bean.getInt());
+
+ final Signal signal = new Signal();
+ signal.setSignal(1);
+ final TestIndependenceThread thread = new TestIndependenceThread(signal, bean);
+ thread.setContextClassLoader(new TestClassLoader());
+
+ thread.start();
+ thread.join();
+
+ assertNull("Exception thrown by test thread:" + signal.getException(), signal.getException());
+ assertEquals("Signal not set by test thread", 3, signal.getSignal());
+ assertEquals("Wrong property value(3)", 9, bean.getInt());
+
+ }
+
+ /** Tests whether different threads can set beanutils instances correctly */
+ public void testBeanUtilsBeanSetInstance() throws Exception {
+
+ class SetInstanceTesterThread extends Thread {
+
+ private final Signal signal;
+ private final LocaleBeanUtilsBean bean;
+
+ SetInstanceTesterThread(final Signal signal, final LocaleBeanUtilsBean bean) {
+ this.signal = signal;
+ this.bean = bean;
+ }
+
+ @Override
+ public void run() {
+ LocaleBeanUtilsBean.setInstance(bean);
+ signal.setSignal(21);
+ signal.setBean(LocaleBeanUtilsBean.getLocaleBeanUtilsInstance());
+ }
+
+ @Override
+ public String toString() {
+ return "SetInstanceTesterThread";
+ }
+ }
+
+ final Signal signal = new Signal();
+ signal.setSignal(1);
+
+ final LocaleBeanUtilsBean beanOne = new LocaleBeanUtilsBean();
+ final LocaleBeanUtilsBean beanTwo = new LocaleBeanUtilsBean();
+
+ final SetInstanceTesterThread thread = new SetInstanceTesterThread(signal, beanTwo);
+ thread.setContextClassLoader(new TestClassLoader());
+
+ LocaleBeanUtilsBean.setInstance(beanOne);
+ assertEquals("Start thread gets right instance", beanOne, LocaleBeanUtilsBean.getLocaleBeanUtilsInstance());
+
+ thread.start();
+ thread.join();
+
+ assertEquals("Signal not set by test thread", 21, signal.getSignal());
+ assertEquals("Second thread preserves value", beanOne, LocaleBeanUtilsBean.getLocaleBeanUtilsInstance());
+ assertEquals("Second thread gets value it set", beanTwo, signal.getBean());
+ }
+
+ /** Tests whether the unset method works*/
+ public void testContextClassLoaderUnset() throws Exception {
+ final LocaleBeanUtilsBean beanOne = new LocaleBeanUtilsBean();
+ final ContextClassLoaderLocal<LocaleBeanUtilsBean> ccll = new ContextClassLoaderLocal<>();
+ ccll.set(beanOne);
+ assertEquals("Start thread gets right instance", beanOne, ccll.get());
+ ccll.unset();
+ assertTrue("Unset works", !beanOne.equals(ccll.get()));
+ }
+
+ /**
+ * Test registering a locale-aware converter with the standard ConvertUtils.
+ */
+ public void testLocaleAwareConverterInConvertUtils() throws Exception {
+ try {
+ // first use the default non-locale-aware converter
+ try {
+ final Long data = (Long) ConvertUtils.convert("777", Long.class);
+ assertEquals("Standard format long converted ok", 777, data.longValue());
+ }
+ catch(final ConversionException ex) {
+ fail("Unable to convert non-locale-aware number 777");
+ }
+
+ // now try default converter with special delimiters
+ try {
+ // This conversion will cause an error. But the default
+ // Long converter is set up to return a default value of
+ // zero on error.
+ final Long data = (Long) ConvertUtils.convert("1.000.000", Long.class);
+ assertEquals("Standard format behaved as expected", 0, data.longValue());
+ }
+ catch(final ConversionException ex) {
+ fail("Unexpected exception from standard Long converter.");
+ }
+
+ // Now try using a locale-aware converter together with
+ // locale-specific input string. Note that in the german locale,
+ // large numbers can be split up into groups of three digits
+ // using a dot character (and comma is the decimal-point indicator).
+ try {
+
+ final Locale germanLocale = Locale.GERMAN;
+ final LongLocaleConverter longLocaleConverter = new LongLocaleConverter(germanLocale);
+ ConvertUtils.register(longLocaleConverter, Long.class);
+
+ final Long data = (Long) ConvertUtils.convert("1.000.000", Long.class);
+ assertEquals("German-format long converted ok", 1000000, data.longValue());
+ } catch(final ConversionException ex) {
+ fail("Unable to convert german-format number");
+ }
+ } finally {
+ ConvertUtils.deregister();
+ }
+ }
+
+ // ---- Auxillary classes
+
+ class TestClassLoader extends ClassLoader {
+ @Override
+ public String toString() {
+ return "TestClassLoader";
+ }
+ }
+
+ class Signal {
+ private Exception e;
+ private int signal = 0;
+ private LocaleBeanUtilsBean bean;
+ private LocaleConvertUtilsBean convertUtils;
+ private Object marker;
+
+ public Exception getException() {
+ return e;
+ }
+
+ public void setException(final Exception e) {
+ this.e = e;
+ }
+
+ public int getSignal() {
+ return signal;
+ }
+
+ public void setSignal(final int signal) {
+ this.signal = signal;
+ }
+
+ public Object getMarkerObject() {
+ return marker;
+ }
+
+ public void setMarkerObject(final Object marker) {
+ this.marker = marker;
+ }
+
+ public LocaleBeanUtilsBean getBean() {
+ return bean;
+ }
+
+ public void setBean(final LocaleBeanUtilsBean bean) {
+ this.bean = bean;
+ }
+
+ public LocaleConvertUtilsBean getConvertUtils() {
+ return convertUtils;
+ }
+
+ public void setConvertUtils(final LocaleConvertUtilsBean convertUtils) {
+ this.convertUtils = convertUtils;
+ }
+ }
+}
+