Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 171 additions & 0 deletions src/main/java/org/apache/commons/lang3/builder/AbstractReflection.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/*
* 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.lang3.builder;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.util.function.Supplier;

import org.apache.commons.lang3.SystemProperties;

/**
* Abstracts reflection access for reflection-based classes in this package.
* <p>
* See {@link AbstractBuilder#setForceAccessible(boolean)} for details.
* </p>
*
* @since 3.21.0
* @see AbstractBuilder#setForceAccessible(boolean)
* @see AccessibleObject#setAccessible(boolean)
*/
public abstract class AbstractReflection {

/**
* Builds an instance of a subclass of {@link AbstractReflection}.
*
* @param <B> An AbstractBuilder subclass.
*/
public abstract static class AbstractBuilder<B extends AbstractBuilder<B>> implements Supplier<AbstractReflection> {

/**
* Whether the {@link AbstractReflection} subclass will call {@link AccessibleObject#setAccessible(boolean) AccessibleObject#setAccessible(true)} on
* inaccessible fields.
*/
private boolean forceAccessible = getForceAccessible();

/**
* Constructs a new instance for a subclass.
*/
AbstractBuilder() {
// Empty.
}

/**
* Returns {@code this} instance typed as its subclass.
*
* @return {@code this} instance typed as its subclass.
*/
@SuppressWarnings("unchecked")
protected B asThis() {
return (B) this;
}

/**
* Whether the {@link AbstractReflection} subclass will call {@link AccessibleObject#setAccessible(boolean) AccessibleObject#setAccessible(true)} on
* inaccessible fields.
* <p>
* In general, controls whether the instances built by this builder will force the accessible flag for reflection.
* </p>
* <p>
* Defaults to {@code getForceAccessible()}, which defaults to true for compatibility.
* </p>
* <p>
* This default is read from the system property {@code "AbstractReflection.forceAccessible"}, which defaults to true for compatibility.
* </p>
* <p>
* The parsing rules are as {@link Boolean#parseBoolean(String)}.
* </p>
* <p>
* See subclassses for specific behavior.
* </p>
*
* @param forceAccessible Whether to force accessibility by calling {@link AccessibleObject#setAccessible(boolean)
* AccessibleObject#setAccessible(true)}.
* @return {@code this} instance.
* @see AccessibleObject#setAccessible(boolean)
*/
public B setForceAccessible(final boolean forceAccessible) {
this.forceAccessible = forceAccessible;
return asThis();
}
}

/**
* Tests whether the system property {@code "AbstractReflection.forceAccessible"} is set to true.
* <p>
* The parsing rules are as {@link Boolean#parseBoolean(String)}.
* </p>
* <p>
* If the property is not set, return true.
* </p>
*
* @return whether the system property {@code "AbstractReflection.forceAccessible"} is set to true with true as the default.
* @see Boolean#parseBoolean(String)
*/
static boolean getForceAccessible() {
return SystemProperties.getBoolean(AbstractReflection.class, "forceAccessible", () -> true);
}

/**
* If {@code forceAccessible} flag is true, each field in the given array is made accessible by calling {@link AccessibleObject#setAccessible(boolean)
* AccessibleObject#setAccessible(true)} but <em>only</em> if a field is not already accessible.
*
* @param forceAccessible Whether to call {@link AccessibleObject#setAccessible(boolean)} if a field is not already accessible.
* @param fields The fields to set.
* @throws SecurityException Thrown if {@code forceAccessible} flag is true and the request is denied.
* @see AccessibleObject#setAccessible(boolean)
* @see SecurityManager#checkPermission
*/
static void setAccessible(final boolean forceAccessible, final Field... fields) {
if (forceAccessible) {
for (final Field field : fields) {
// Test to avoid the permission check if there is a security manager.
if (field != null && !field.isAccessible()) {
field.setAccessible(true);
}
}
}
}

/**
* Whether to call {@link AccessibleObject#setAccessible(boolean) AccessibleObject#setAccessible(true)} on inaccessible fields.
*/
private final boolean forceAccessible;

/**
* Constructs a new instance.
*
* @param <T> The type to build.
* @param builder The builder.
*/
<T extends AbstractBuilder<T>> AbstractReflection(final AbstractBuilder<T> builder) {
this.forceAccessible = builder.forceAccessible;
}

/**
* Tests whether fields should be made accessible with {@link AccessibleObject#setAccessible(boolean)}.
*
* @return whether fields should be made accessible with {@link AccessibleObject#setAccessible(boolean)}.
*/
protected boolean isForceAccessible() {
return forceAccessible;
}

/**
* If {@code forceAccessible} flag is true, each field in the given array is made accessible by calling {@link AccessibleObject#setAccessible(boolean)
* AccessibleObject#setAccessible(true)} but <em>only</em> if a field is not already accessible.
*
* @param fields The fields to set.
* @throws SecurityException Thrown if {@code forceAccessible} flag is true and the request is denied.
* @see AccessibleObject#setAccessible(boolean)
* @see SecurityManager#checkPermission
*/
void setAccessible(final Field... fields) {
setAccessible(isForceAccessible(), fields);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*/
package org.apache.commons.lang3.builder;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
Expand Down Expand Up @@ -94,7 +93,35 @@
* @see HashCodeBuilder
* @since 1.0
*/
public class CompareToBuilder implements Builder<Integer> {
public class CompareToBuilder extends AbstractReflection implements Builder<Integer> {

/**
* Builds instances of CompareToBuilder.
*/
public static class Builder extends AbstractBuilder<Builder> {

/**
* Constructs a new Builder instance.
*/
private Builder() {
// empty
}

@Override
public CompareToBuilder get() {
return new CompareToBuilder(this);
}

}

/**
* Constructs a new Builder.
*
* @return a new Builder.
*/
public static Builder builder() {
return new Builder();
}

/**
* Appends to {@code builder} the comparison of {@code lhs}
Expand All @@ -106,23 +133,27 @@ public class CompareToBuilder implements Builder<Integer> {
* @param builder {@link CompareToBuilder} to append to
* @param useTransients whether to compare transient fields
* @param excludeFields fields to exclude
* @param forceAccessible Whether to set fields' accessible flags
*/
private static void reflectionAppend(
final Object lhs,
final Object rhs,
final Class<?> clazz,
final CompareToBuilder builder,
final boolean useTransients,
final String[] excludeFields) {
final String[] excludeFields,
final boolean forceAccessible) {

final Field[] fields = clazz.getDeclaredFields();
AccessibleObject.setAccessible(fields, true);
setAccessible(forceAccessible, fields);
for (int i = 0; i < fields.length && builder.comparison == 0; i++) {
final Field field = fields[i];
if (!ArrayUtils.contains(excludeFields, field.getName())
&& !field.getName().contains("$")
final String name = field.getName();
if (!ArrayUtils.contains(excludeFields, name)
&& !name.contains("$")
&& (useTransients || !Modifier.isTransient(field.getModifiers()))
&& !Modifier.isStatic(field.getModifiers())) {
&& !Modifier.isStatic(field.getModifiers())
&& field.isAccessible()) {
// IllegalAccessException can't happen. Would get a Security exception instead.
// Throw a runtime exception in case the impossible happens.
builder.append(Reflection.getUnchecked(field, lhs), Reflection.getUnchecked(field, rhs));
Expand Down Expand Up @@ -230,22 +261,20 @@ public static int reflectionCompare(
final boolean compareTransients,
final Class<?> reflectUpToClass,
final String... excludeFields) {

if (lhs == rhs) {
return 0;
}
Objects.requireNonNull(lhs, "lhs");
Objects.requireNonNull(rhs, "rhs");

Class<?> lhsClazz = lhs.getClass();
if (!lhsClazz.isInstance(rhs)) {
throw new ClassCastException();
}
final CompareToBuilder compareToBuilder = new CompareToBuilder();
reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields);
reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields, AbstractReflection.getForceAccessible());
while (lhsClazz.getSuperclass() != null && lhsClazz != reflectUpToClass) {
lhsClazz = lhsClazz.getSuperclass();
reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields);
reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields, AbstractReflection.getForceAccessible());
}
return compareToBuilder.toComparison();
}
Expand Down Expand Up @@ -329,9 +358,14 @@ public static int reflectionCompare(final Object lhs, final Object rhs, final St
* {@link #toComparison} to get the result.</p>
*/
public CompareToBuilder() {
super(builder());
comparison = 0;
}

private CompareToBuilder(final Builder builder) {
super(builder);
}

/**
* Appends to the {@code builder} the comparison of
* two {@code booleans}s.
Expand Down
Loading
Loading