001// Generated by delombok at Mon Jan 02 16:27:27 UTC 2023
002package io.cui.tools.reflect;
003
004import java.lang.reflect.Field;
005import java.util.Optional;
006import io.cui.tools.lang.SecuritySupport;
007import io.cui.tools.logging.CuiLogger;
008import io.cui.tools.string.MoreStrings;
009import lombok.NonNull;
010// owolff: The warning is "Reflection should not be used to
011// increase accessibility of classes, methods, or fields "
012// What is actually the use-case of this type, therefore there
013// is nothing we can do
014/**
015 * Wrapper around a {@link Field} that handles implicitly the accessible flag for access.
016 *
017 * @author Oliver Wolff
018 */
019@SuppressWarnings("java:S3011")
020public class FieldWrapper {
021    private static final CuiLogger log = new CuiLogger(FieldWrapper.class);
022    @NonNull
023    private final Field field;
024    private final boolean initialAccessible;
025
026    /**
027     * @param field must not be null
028     */
029    public FieldWrapper(Field field) {
030        this.field = field;
031        initialAccessible = field.isAccessible();
032    }
033
034    /**
035     * Reads from the field determined by {@link #getField()}. It implicitly sets and resets the
036     * {@link Field#isAccessible()} flag.
037     *
038     * @param object to be read from
039     * @return an {@link Optional} on the given field value if applicable. May return
040     *         {@link Optional#empty()} for cases where:
041     *         <ul>
042     *         <li>Field value is {@code null}</li>
043     *         <li>Given Object is {@code null}</li>
044     *         <li>Given Object is improper type</li>
045     *         <li>an {@link IllegalAccessException} occurred while accessing</li>
046     *         </ul>
047     */
048    public Optional<Object> readValue(Object object) {
049        log.trace("Reading from field \'{}\' with accessibleFlag=\'{}\' ", field, initialAccessible);
050        if (null == object) {
051            log.trace("No Object given, returning Optional#empty()");
052            return Optional.empty();
053        }
054        synchronized (field) {
055            if (!initialAccessible) {
056                log.trace("Explicitly setting accessible flag");
057                SecuritySupport.setAccessible(field, true);
058            }
059            try {
060                return Optional.ofNullable(field.get(object));
061            } catch (IllegalArgumentException | IllegalAccessException e) {
062                log.warn(e, "Reading from field \'{}\' with accessible=\'{}\' and parameter =\'{}\' could not complete", field, initialAccessible, object);
063                return Optional.empty();
064            } finally {
065                if (!initialAccessible) {
066                    log.trace("Resetting accessible flag");
067                    SecuritySupport.setAccessible(field, false);
068                }
069            }
070        }
071    }
072
073    /**
074     * Reads the value from the field in the given object. It implicitly sets and resets the
075     * {@link Field#isAccessible()} flag.
076     *
077     * @param fieldName to be read
078     * @param object to be read from
079     *
080     * @return the field value. {@link Optional#empty()} if the field cannot be read.
081     */
082    public static final Optional<Object> readValue(final String fieldName, final Object object) {
083        final Optional<FieldWrapper> fieldProvider = from(object.getClass(), fieldName);
084        log.trace("FieldWrapper: {}", fieldProvider);
085        if (fieldProvider.isPresent()) {
086            FieldWrapper fieldWrapper = fieldProvider.get();
087            return fieldWrapper.readValue(object);
088        }
089        return Optional.empty();
090    }
091
092    /**
093     * Writes to the field determined by {@link #getField()}. It implicitly sets and resets the
094     * {@link Field#isAccessible()} flag.
095     *
096     * @param object to be written to, must not be null
097     * @param value to be written, may be null
098     *
099     * @throws NullPointerException in case object is {@code null}
100     * @throws IllegalArgumentException in case the value is not applicable to the field
101     * @throws IllegalStateException wrapping an {@link IllegalAccessException}
102     */
103    public void writeValue(@NonNull Object object, Object value) {
104        if (object == null) {
105            throw new java.lang.NullPointerException("object is marked non-null but is null");
106        }
107        log.trace("Writing to field \'{}\' with accessibleFlag=\'{}\' ", field, initialAccessible);
108        synchronized (field) {
109            if (!initialAccessible) {
110                log.trace("Explicitly setting accessible flag");
111                SecuritySupport.setAccessible(field, true);
112            }
113            try {
114                field.set(object, value);
115            } catch (IllegalAccessException e) {
116                String message = MoreStrings.lenientFormat("Writing to field \'{}\' with accessible=\'{}\' and parameter =\'{}\' could not complete", field, initialAccessible, object);
117                throw new IllegalStateException(message, e);
118            } finally {
119                if (!initialAccessible) {
120                    log.trace("Resetting accessible flag");
121                    SecuritySupport.setAccessible(field, false);
122                }
123            }
124        }
125    }
126
127    /**
128     * Factory Method for creating an {@link FieldWrapper} instance
129     *
130     * @param type must not be null
131     * @param fieldName must not be null
132     * @return a {@link FieldWrapper} if the {@link Field} can be determined,
133     *         {@link Optional#empty()} otherwise
134     */
135    public static final Optional<FieldWrapper> from(final Class<?> type, final String fieldName) {
136        Optional<Field> loaded = MoreReflection.accessField(type, fieldName);
137        if (loaded.isPresent()) {
138            return Optional.of(new FieldWrapper(loaded.get()));
139        } else {
140            return Optional.empty();
141        }
142    }
143
144    @NonNull
145    @java.lang.SuppressWarnings("all")
146    @lombok.Generated
147    public Field getField() {
148        return this.field;
149    }
150}