[LANG-961] org.apache.commons.lang3.reflect.FieldUtils.removeFinalModifier(Field) does not clean up after itself.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/lang/trunk@1562991 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Gary D. Gregory 2014-01-30 21:53:52 +00:00
parent 8c04a90ec5
commit 2eecc96a53
3 changed files with 94 additions and 90 deletions

View File

@ -22,6 +22,7 @@
<body> <body>
<release version="3.3" date="TBA" description="Bugfix and Feature release"> <release version="3.3" date="TBA" description="Bugfix and Feature release">
<action issue="LANG-961" type="update" dev="ggregory">org.apache.commons.lang3.reflect.FieldUtils.removeFinalModifier(Field) does not clean up after itself</action>
<action issue="LANG-958" type="update" dev="chas">FastDateParser javadoc incorrectly states that SimpleDateFormat is used internally</action> <action issue="LANG-958" type="update" dev="chas">FastDateParser javadoc incorrectly states that SimpleDateFormat is used internally</action>
<action issue="LANG-637" type="add" dev="djones">There should be a DifferenceBuilder with a ReflectionDifferenceBuilder implementation</action> <action issue="LANG-637" type="add" dev="djones">There should be a DifferenceBuilder with a ReflectionDifferenceBuilder implementation</action>
<action issue="LANG-954" type="fix" due-to="Michael Keppler" dev="sebb">uncaught PatternSyntaxException in FastDateFormat on Android</action> <action issue="LANG-954" type="fix" due-to="Michael Keppler" dev="sebb">uncaught PatternSyntaxException in FastDateFormat on Android</action>

View File

@ -26,8 +26,8 @@
import java.util.List; import java.util.List;
/** /**
* Utilities for working with {@link Field}s by reflection. Adapted and refactored from the dormant [reflect] Commons sandbox * Utilities for working with {@link Field}s by reflection. Adapted and refactored from the dormant [reflect] Commons
* component. * sandbox component.
* <p> * <p>
* The ability is provided to break the scoping restrictions coded by the programmer. This can allow fields to be * The ability is provided to break the scoping restrictions coded by the programmer. This can allow fields to be
* changed that shouldn't be. This facility should be used with care. * changed that shouldn't be. This facility should be used with care.
@ -40,7 +40,8 @@ public class FieldUtils {
/** /**
* {@link FieldUtils} instances should NOT be constructed in standard programming. * {@link FieldUtils} instances should NOT be constructed in standard programming.
* <p> * <p>
* This constructor is {@code public} to permit tools that require a JavaBean instance to operate.</p> * This constructor is {@code public} to permit tools that require a JavaBean instance to operate.
* </p>
*/ */
public FieldUtils() { public FieldUtils() {
super(); super();
@ -73,12 +74,12 @@ public static Field getField(final Class<?> cls, final String fieldName) {
* the field name to obtain * the field name to obtain
* @param forceAccess * @param forceAccess
* whether to break scope restrictions using the * whether to break scope restrictions using the
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
* {@code false} will only match {@code public} fields. * match {@code public} fields.
* @return the Field object * @return the Field object
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if the class is {@code null}, or the field name is blank or empty or is matched at * if the class is {@code null}, or the field name is blank or empty or is matched at multiple places
* multiple places in the inheritance hierarchy * in the inheritance hierarchy
*/ */
public static Field getField(final Class<?> cls, final String fieldName, final boolean forceAccess) { public static Field getField(final Class<?> cls, final String fieldName, final boolean forceAccess) {
Validate.isTrue(cls != null, "The class must not be null"); Validate.isTrue(cls != null, "The class must not be null");
@ -156,8 +157,8 @@ public static Field getDeclaredField(final Class<?> cls, final String fieldName)
* the field name to obtain * the field name to obtain
* @param forceAccess * @param forceAccess
* whether to break scope restrictions using the * whether to break scope restrictions using the
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
* {@code false} will only match {@code public} fields. * match {@code public} fields.
* @return the Field object * @return the Field object
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if the class is {@code null}, or the field name is blank or empty * if the class is {@code null}, or the field name is blank or empty
@ -265,8 +266,8 @@ public static Object readStaticField(final Field field, final boolean forceAcces
* the field name to obtain * the field name to obtain
* @return the value of the field * @return the value of the field
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if the class is {@code null}, or the field name is blank or empty, * if the class is {@code null}, or the field name is blank or empty, is not {@code static}, or could
* is not {@code static}, or could not be found * not be found
* @throws IllegalAccessException * @throws IllegalAccessException
* if the field is not accessible * if the field is not accessible
*/ */
@ -283,12 +284,12 @@ public static Object readStaticField(final Class<?> cls, final String fieldName)
* the field name to obtain * the field name to obtain
* @param forceAccess * @param forceAccess
* whether to break scope restrictions using the * whether to break scope restrictions using the
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
* {@code false} will only match {@code public} fields. * match {@code public} fields.
* @return the Field object * @return the Field object
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if the class is {@code null}, or the field name is blank or empty, * if the class is {@code null}, or the field name is blank or empty, is not {@code static}, or could
* is not {@code static}, or could not be found * not be found
* @throws IllegalAccessException * @throws IllegalAccessException
* if the field is not made accessible * if the field is not made accessible
*/ */
@ -300,8 +301,8 @@ public static Object readStaticField(final Class<?> cls, final String fieldName,
} }
/** /**
* Gets the value of a {@code static} {@link Field} by name. The field must be {@code public}. * Gets the value of a {@code static} {@link Field} by name. The field must be {@code public}. Only the specified
* Only the specified class will be considered. * class will be considered.
* *
* @param cls * @param cls
* the {@link Class} to reflect, must not be {@code null} * the {@link Class} to reflect, must not be {@code null}
@ -309,8 +310,8 @@ public static Object readStaticField(final Class<?> cls, final String fieldName,
* the field name to obtain * the field name to obtain
* @return the value of the field * @return the value of the field
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if the class is {@code null}, or the field name is blank or empty, * if the class is {@code null}, or the field name is blank or empty, is not {@code static}, or could
* is not {@code static}, or could not be found * not be found
* @throws IllegalAccessException * @throws IllegalAccessException
* if the field is not accessible * if the field is not accessible
*/ */
@ -319,8 +320,7 @@ public static Object readDeclaredStaticField(final Class<?> cls, final String fi
} }
/** /**
* Gets the value of a {@code static} {@link Field} by name. * Gets the value of a {@code static} {@link Field} by name. Only the specified class will be considered.
* Only the specified class will be considered.
* *
* @param cls * @param cls
* the {@link Class} to reflect, must not be {@code null} * the {@link Class} to reflect, must not be {@code null}
@ -328,12 +328,12 @@ public static Object readDeclaredStaticField(final Class<?> cls, final String fi
* the field name to obtain * the field name to obtain
* @param forceAccess * @param forceAccess
* whether to break scope restrictions using the * whether to break scope restrictions using the
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
* {@code false} will only match {@code public} fields. * match {@code public} fields.
* @return the Field object * @return the Field object
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if the class is {@code null}, or the field name is blank or empty, * if the class is {@code null}, or the field name is blank or empty, is not {@code static}, or could
* is not {@code static}, or could not be found * not be found
* @throws IllegalAccessException * @throws IllegalAccessException
* if the field is not made accessible * if the field is not made accessible
*/ */
@ -396,8 +396,7 @@ public static Object readField(final Field field, final Object target, final boo
* the field name to obtain * the field name to obtain
* @return the value of the field * @return the value of the field
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if the class is {@code null}, or * if the class is {@code null}, or the field name is blank or empty or could not be found
* the field name is blank or empty or could not be found
* @throws IllegalAccessException * @throws IllegalAccessException
* if the named field is not {@code public} * if the named field is not {@code public}
*/ */
@ -414,12 +413,11 @@ public static Object readField(final Object target, final String fieldName) thro
* the field name to obtain * the field name to obtain
* @param forceAccess * @param forceAccess
* whether to break scope restrictions using the * whether to break scope restrictions using the
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
* {@code false} will only match {@code public} fields. * match {@code public} fields.
* @return the field value * @return the field value
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if {@code target} is {@code null}, or * if {@code target} is {@code null}, or the field name is blank or empty or could not be found
* the field name is blank or empty or could not be found
* @throws IllegalAccessException * @throws IllegalAccessException
* if the named field is not made accessible * if the named field is not made accessible
*/ */
@ -427,7 +425,7 @@ public static Object readField(final Object target, final String fieldName, fina
Validate.isTrue(target != null, "target object must not be null"); Validate.isTrue(target != null, "target object must not be null");
final Class<?> cls = target.getClass(); final Class<?> cls = target.getClass();
final Field field = getField(cls, fieldName, forceAccess); final Field field = getField(cls, fieldName, forceAccess);
Validate.isTrue(field != null, "Cannot locate field %s on %s" , fieldName, cls); Validate.isTrue(field != null, "Cannot locate field %s on %s", fieldName, cls);
// already forced access above, don't repeat it here: // already forced access above, don't repeat it here:
return readField(field, target, false); return readField(field, target, false);
} }
@ -441,8 +439,7 @@ public static Object readField(final Object target, final String fieldName, fina
* the field name to obtain * the field name to obtain
* @return the value of the field * @return the value of the field
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if {@code target} is {@code null}, or * if {@code target} is {@code null}, or the field name is blank or empty or could not be found
* the field name is blank or empty or could not be found
* @throws IllegalAccessException * @throws IllegalAccessException
* if the named field is not {@code public} * if the named field is not {@code public}
*/ */
@ -459,12 +456,11 @@ public static Object readDeclaredField(final Object target, final String fieldNa
* the field name to obtain * the field name to obtain
* @param forceAccess * @param forceAccess
* whether to break scope restrictions using the * whether to break scope restrictions using the
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
* {@code false} will only match public fields. * match public fields.
* @return the Field object * @return the Field object
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if {@code target} is {@code null}, or * if {@code target} is {@code null}, or the field name is blank or empty or could not be found
* the field name is blank or empty or could not be found
* @throws IllegalAccessException * @throws IllegalAccessException
* if the field is not made accessible * if the field is not made accessible
*/ */
@ -472,7 +468,7 @@ public static Object readDeclaredField(final Object target, final String fieldNa
Validate.isTrue(target != null, "target object must not be null"); Validate.isTrue(target != null, "target object must not be null");
final Class<?> cls = target.getClass(); final Class<?> cls = target.getClass();
final Field field = getDeclaredField(cls, fieldName, forceAccess); final Field field = getDeclaredField(cls, fieldName, forceAccess);
Validate.isTrue(field != null, "Cannot locate declared field %s.%s" , cls, fieldName); Validate.isTrue(field != null, "Cannot locate declared field %s.%s", cls, fieldName);
// already forced access above, don't repeat it here: // already forced access above, don't repeat it here:
return readField(field, target, false); return readField(field, target, false);
} }
@ -485,8 +481,7 @@ public static Object readDeclaredField(final Object target, final String fieldNa
* @param value * @param value
* to set * to set
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if the field is {@code null} or not {@code static}, * if the field is {@code null} or not {@code static}, or {@code value} is not assignable
* or {@code value} is not assignable
* @throws IllegalAccessException * @throws IllegalAccessException
* if the field is not {@code public} or is {@code final} * if the field is not {@code public} or is {@code final}
*/ */
@ -503,18 +498,17 @@ public static void writeStaticField(final Field field, final Object value) throw
* to set * to set
* @param forceAccess * @param forceAccess
* whether to break scope restrictions using the * whether to break scope restrictions using the
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
* will only match {@code public} fields. * match {@code public} fields.
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if the field is {@code null} or not {@code static}, * if the field is {@code null} or not {@code static}, or {@code value} is not assignable
* or {@code value} is not assignable
* @throws IllegalAccessException * @throws IllegalAccessException
* if the field is not made accessible or is {@code final} * if the field is not made accessible or is {@code final}
*/ */
public static void writeStaticField(final Field field, final Object value, final boolean forceAccess) throws IllegalAccessException { public static void writeStaticField(final Field field, final Object value, final boolean forceAccess) throws IllegalAccessException {
Validate.isTrue(field != null, "The field must not be null"); Validate.isTrue(field != null, "The field must not be null");
Validate.isTrue(Modifier.isStatic(field.getModifiers()), "The field %s.%s is not static", Validate.isTrue(Modifier.isStatic(field.getModifiers()), "The field %s.%s is not static", field.getDeclaringClass().getName(),
field.getDeclaringClass().getName(), field.getName()); field.getName());
writeField(field, (Object) null, value, forceAccess); writeField(field, (Object) null, value, forceAccess);
} }
@ -528,10 +522,8 @@ public static void writeStaticField(final Field field, final Object value, final
* @param value * @param value
* to set * to set
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if {@code cls} is {@code null}, * if {@code cls} is {@code null}, the field name is blank or empty, the field cannot be located or is
* the field name is blank or empty, * not {@code static}, or {@code value} is not assignable
* the field cannot be located or is not {@code static}, or
* {@code value} is not assignable
* @throws IllegalAccessException * @throws IllegalAccessException
* if the field is not {@code public} or is {@code final} * if the field is not {@code public} or is {@code final}
*/ */
@ -550,13 +542,11 @@ public static void writeStaticField(final Class<?> cls, final String fieldName,
* to set * to set
* @param forceAccess * @param forceAccess
* whether to break scope restrictions using the * whether to break scope restrictions using the
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
* {@code false} will only match {@code public} fields. * match {@code public} fields.
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if {@code cls} is {@code null}, * if {@code cls} is {@code null}, the field name is blank or empty, the field cannot be located or is
* the field name is blank or empty, * not {@code static}, or {@code value} is not assignable
* the field cannot be located or is not {@code static}, or
* {@code value} is not assignable
* @throws IllegalAccessException * @throws IllegalAccessException
* if the field is not made accessible or is {@code final} * if the field is not made accessible or is {@code final}
*/ */
@ -578,10 +568,8 @@ public static void writeStaticField(final Class<?> cls, final String fieldName,
* @param value * @param value
* to set * to set
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if {@code cls} is {@code null}, * if {@code cls} is {@code null}, the field name is blank or empty, the field cannot be located or is
* the field name is blank or empty, * not {@code static}, or {@code value} is not assignable
* the field cannot be located or is not {@code static}, or
* {@code value} is not assignable
* @throws IllegalAccessException * @throws IllegalAccessException
* if the field is not {@code public} or is {@code final} * if the field is not {@code public} or is {@code final}
*/ */
@ -602,10 +590,8 @@ public static void writeDeclaredStaticField(final Class<?> cls, final String fie
* whether to break scope restrictions using the {@code AccessibleObject#setAccessible(boolean)} method. * whether to break scope restrictions using the {@code AccessibleObject#setAccessible(boolean)} method.
* {@code false} will only match {@code public} fields. * {@code false} will only match {@code public} fields.
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if {@code cls} is {@code null}, * if {@code cls} is {@code null}, the field name is blank or empty, the field cannot be located or is
* the field name is blank or empty, * not {@code static}, or {@code value} is not assignable
* the field cannot be located or is not {@code static}, or
* {@code value} is not assignable
* @throws IllegalAccessException * @throws IllegalAccessException
* if the field is not made accessible or is {@code final} * if the field is not made accessible or is {@code final}
*/ */
@ -627,8 +613,7 @@ public static void writeDeclaredStaticField(final Class<?> cls, final String fie
* @param value * @param value
* to set * to set
* @throws IllegalAccessException * @throws IllegalAccessException
* if the field or target is {@code null}, * if the field or target is {@code null}, the field is not accessible or is {@code final}, or
* the field is not accessible or is {@code final}, or
* {@code value} is not assignable * {@code value} is not assignable
*/ */
public static void writeField(final Field field, final Object target, final Object value) throws IllegalAccessException { public static void writeField(final Field field, final Object target, final Object value) throws IllegalAccessException {
@ -646,11 +631,10 @@ public static void writeField(final Field field, final Object target, final Obje
* to set * to set
* @param forceAccess * @param forceAccess
* whether to break scope restrictions using the * whether to break scope restrictions using the
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
* {@code false} will only match {@code public} fields. * match {@code public} fields.
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if the field is {@code null} or * if the field is {@code null} or {@code value} is not assignable
* {@code value} is not assignable
* @throws IllegalAccessException * @throws IllegalAccessException
* if the field is not made accessible or is {@code final} * if the field is not made accessible or is {@code final}
*/ */
@ -674,23 +658,44 @@ public static void writeField(final Field field, final Object target, final Obje
* if the field is {@code null} * if the field is {@code null}
*/ */
public static void removeFinalModifier(Field field) { public static void removeFinalModifier(Field field) {
removeFinalModifier(field, true);
}
/**
* Removes the final modifier from a {@link Field}.
*
* @param field
* to remove the final modifier
* @param forceAccess
* whether to break scope restrictions using the
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
* match {@code public} fields.
* @throws IllegalArgumentException
* if the field is {@code null}
*/
public static void removeFinalModifier(Field field, boolean forceAccess) {
Validate.isTrue(field != null, "The field must not be null"); Validate.isTrue(field != null, "The field must not be null");
try { try {
if (Modifier.isFinal(field.getModifiers())) { if (Modifier.isFinal(field.getModifiers())) {
// Do all JREs implement Field with a private ivar called "modifiers"? // Do all JREs implement Field with a private ivar called "modifiers"?
Field modifiersField = Field.class.getDeclaredField("modifiers"); Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true); final boolean doForceAccess = forceAccess && !modifiersField.isAccessible();
if (doForceAccess) {
modifiersField.setAccessible(true);
}
try { try {
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
} finally { } finally {
modifiersField.setAccessible(false); if (doForceAccess) {
modifiersField.setAccessible(false);
}
} }
} }
} catch (NoSuchFieldException ignored) { } catch (NoSuchFieldException ignored) {
// The field class contains always a modifiers field // The field class contains always a modifiers field
} catch (IllegalAccessException ignored) { } catch (IllegalAccessException ignored) {
// The modifiers field is made accessible // The modifiers field is made accessible
} }
} }
@ -704,9 +709,8 @@ public static void removeFinalModifier(Field field) {
* @param value * @param value
* to set * to set
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if {@code target} is {@code null}, * if {@code target} is {@code null}, {@code fieldName} is blank or empty or could not be found, or
* {@code fieldName} is blank or empty or could not be found, * {@code value} is not assignable
* or {@code value} is not assignable
* @throws IllegalAccessException * @throws IllegalAccessException
* if the field is not accessible * if the field is not accessible
*/ */
@ -725,12 +729,11 @@ public static void writeField(final Object target, final String fieldName, final
* to set * to set
* @param forceAccess * @param forceAccess
* whether to break scope restrictions using the * whether to break scope restrictions using the
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
* will only match {@code public} fields. * match {@code public} fields.
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if {@code target} is {@code null}, * if {@code target} is {@code null}, {@code fieldName} is blank or empty or could not be found, or
* {@code fieldName} is blank or empty or could not be found, * {@code value} is not assignable
* or {@code value} is not assignable
* @throws IllegalAccessException * @throws IllegalAccessException
* if the field is not made accessible * if the field is not made accessible
*/ */
@ -754,9 +757,8 @@ public static void writeField(final Object target, final String fieldName, final
* @param value * @param value
* to set * to set
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if {@code target} is {@code null}, * if {@code target} is {@code null}, {@code fieldName} is blank or empty or could not be found, or
* {@code fieldName} is blank or empty or could not be found, * {@code value} is not assignable
* or {@code value} is not assignable
* @throws IllegalAccessException * @throws IllegalAccessException
* if the field is not made accessible * if the field is not made accessible
*/ */
@ -775,12 +777,11 @@ public static void writeDeclaredField(final Object target, final String fieldNam
* to set * to set
* @param forceAccess * @param forceAccess
* whether to break scope restrictions using the * whether to break scope restrictions using the
* {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. * {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
* {@code false} will only match {@code public} fields. * match {@code public} fields.
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if {@code target} is {@code null}, * if {@code target} is {@code null}, {@code fieldName} is blank or empty or could not be found, or
* {@code fieldName} is blank or empty or could not be found, * {@code value} is not assignable
* or {@code value} is not assignable
* @throws IllegalAccessException * @throws IllegalAccessException
* if the field is not made accessible * if the field is not made accessible
*/ */

View File

@ -1256,8 +1256,10 @@ public void testAmbig() {
@Test @Test
public void testRemoveFinalModifier() throws Exception { public void testRemoveFinalModifier() throws Exception {
Field field = StaticContainer.class.getDeclaredField("IMMUTABLE_PRIVATE_2"); Field field = StaticContainer.class.getDeclaredField("IMMUTABLE_PRIVATE_2");
assertFalse(field.isAccessible());
assertTrue(Modifier.isFinal(field.getModifiers())); assertTrue(Modifier.isFinal(field.getModifiers()));
FieldUtils.removeFinalModifier(field); FieldUtils.removeFinalModifier(field);
assertFalse(Modifier.isFinal(field.getModifiers())); assertFalse(Modifier.isFinal(field.getModifiers()));
assertFalse(field.isAccessible());
} }
} }