blob: 3a33788c6d7fded3b118c4f3f2b58e79dd62d5ff [file]
/*
* 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.Set;
import java.util.function.Supplier;
import org.apache.commons.lang3.SystemProperties;
import org.apache.commons.lang3.tuple.Pair;
/**
* 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);
}
static boolean isRegistered(final Object lhs, final Object rhs, final Set<Pair<IDKey, IDKey>> registry) {
final Pair<IDKey, IDKey> pair = toRegisterPair(lhs, rhs);
final Pair<IDKey, IDKey> swappedPair = Pair.of(pair.getRight(), pair.getLeft());
return registry != null && (registry.contains(pair) || registry.contains(swappedPair));
}
static void register(final Object lhs, final Object rhs, final Set<Pair<IDKey, IDKey>> registry) {
registry.add(toRegisterPair(lhs, rhs));
}
/**
* If {@code forceAccessible} flag is true, then the field 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 field The field to set.
* @return true if the field is accessible, false otherwise.
* @throws SecurityException Thrown if {@code forceAccessible} flag is true and the request is denied.
* @see AccessibleObject#setAccessible(boolean)
* @see SecurityManager#checkPermission
*/
static boolean setAccessible(final boolean forceAccessible, final Field field) {
return !field.isAccessible() && forceAccessible && setAccessibleTrue(field);
}
/**
* Sets the field as accessible by calling {@link AccessibleObject#setAccessible(boolean) AccessibleObject#setAccessible(true)} but <em>only</em> if a field
* is not already accessible.
*
* @param field The field to set, may be null.
* @return true if the field is accessible, false otherwise.
* @throws SecurityException Thrown if {@code forceAccessible} flag is true and the request is denied.
* @see AccessibleObject#setAccessible(boolean)
* @see SecurityManager#checkPermission
*/
private static boolean setAccessibleTrue(final Field field) {
if (field != null) {
// Test isAccessible() to avoid the permission check.
if (!field.isAccessible()) {
field.setAccessible(true);
}
return field.isAccessible();
}
return false;
}
/**
* Converters value pair into a register pair.
*
* @param lhs {@code this} object.
* @param rhs the other object.
* @return the pair.
*/
static Pair<IDKey, IDKey> toRegisterPair(final Object lhs, final Object rhs) {
return Pair.of(new IDKey(lhs), new IDKey(rhs));
}
static void unregister(final Object lhs, final Object rhs, final Set<Pair<IDKey, IDKey>> registry, final ThreadLocal<Set<Pair<IDKey, IDKey>>> registryTL) {
registry.remove(toRegisterPair(lhs, rhs));
if (registry.isEmpty()) {
registryTL.remove();
}
}
/**
* 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 field The fields to set.
* @throws SecurityException Thrown if {@code forceAccessible} flag is true and the request is denied.
* @return true if the field is accessible, false otherwise.
* @see AccessibleObject#setAccessible(boolean)
* @see SecurityManager#checkPermission
*/
boolean setAccessible(final Field field) {
return setAccessible(isForceAccessible(), field);
}
}