
com.addthis.codec.reflection.CodableFieldInfo Maven / Gradle / Ivy
/*
* Licensed 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 com.addthis.codec.reflection;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;
import com.addthis.codec.annotations.FieldConfig;
import com.addthis.codec.codables.Codable;
import com.google.common.annotations.Beta;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* information about a field in a class - expensive to get so runs and gets cached
*/
@Beta
public final class CodableFieldInfo {
private static final Logger log = LoggerFactory.getLogger(CodableFieldInfo.class);
public static final int ARRAY = 1 << 0;
public static final int CODABLE = 1 << 1;
public static final int COLLECTION = 1 << 2;
public static final int GENERIC = 1 << 3;
public static final int NATIVE = 1 << 4;
public static final int MAP = 1 << 5;
public static final int NUMBER = 1 << 6;
public static final int REQUIRED = 1 << 7;
public static final int READONLY = 1 << 8;
public static final int WRITEONLY = 1 << 9;
public static final int ENUM = 1 << 10;
@Nonnull private final Field field;
@Nonnull private final Class> typeOrComponentType;
private final int bits;
@Nullable private final FieldConfig fieldConfig;
@Nullable private final Type[] genTypes;
@Nullable private final boolean[] genArray;
public CodableFieldInfo(@Nonnull Field field) {
this.field = field;
field.setAccessible(true);
fieldConfig = field.getAnnotation(FieldConfig.class);
Class> type = field.getType();
boolean array = type.isArray();
if (array) {
typeOrComponentType = type.getComponentType();
this.bits = cacheFlags(CodableFieldInfo.ARRAY);
} else {
typeOrComponentType = type;
this.bits = cacheFlags(0);
}
// extract generics info
if (!Fields.isNative(typeOrComponentType)) {
genTypes = Fields.collectTypes(typeOrComponentType, field.getGenericType());
} else {
genTypes = null;
}
if (genTypes == null) {
genArray = null;
} else {
genArray = new boolean[genTypes.length];
mutateGenericTypes(genTypes, genArray);
}
}
private int cacheFlags(int externalBits) {
int partialBits = externalBits;
if (Codable.class.isAssignableFrom(typeOrComponentType)) {
partialBits |= CodableFieldInfo.CODABLE;
}
if (Collection.class.isAssignableFrom(typeOrComponentType)) {
partialBits |= CodableFieldInfo.COLLECTION;
}
if (Map.class.isAssignableFrom(typeOrComponentType)) {
partialBits |= CodableFieldInfo.MAP;
}
if (typeOrComponentType.isEnum()) {
partialBits |= CodableFieldInfo.ENUM;
}
if (Number.class.isAssignableFrom(typeOrComponentType)) {
partialBits |= CodableFieldInfo.NUMBER;
}
if (Fields.isNative(typeOrComponentType)) {
partialBits |= CodableFieldInfo.NATIVE;
}
if (fieldConfig != null) {
if (fieldConfig.readonly()) {
partialBits |= CodableFieldInfo.READONLY;
}
if (fieldConfig.writeonly()) {
partialBits |= CodableFieldInfo.WRITEONLY;
}
if (fieldConfig.codable()) {
partialBits |= CodableFieldInfo.CODABLE;
}
if (fieldConfig.required()) {
partialBits |= CodableFieldInfo.REQUIRED;
}
}
return partialBits;
}
@Nonnull public Field getField() {
return field;
}
public String getName() {
return field.getName();
}
@Nonnull public Class> getTypeOrComponentType() {
return typeOrComponentType;
}
@Nullable public Type[] getGenericTypes() {
return genTypes;
}
// interacts with the ill-defined generic support
private void mutateGenericTypes(@Nonnull final Type[] collectedTypes,
@Nonnull final boolean[] genericFlags) {
for (int i = 0; i < collectedTypes.length; i++) {
Type currentType = collectedTypes[i];
if (currentType instanceof GenericArrayType) {
genericFlags[i] = true;
collectedTypes[i] = ((GenericArrayType) currentType).getGenericComponentType();
} else if ((currentType instanceof Class) && ((Class) currentType).isArray()) {
genericFlags[i] = true;
collectedTypes[i] = ((Class) currentType).getComponentType();
} else {
genericFlags[i] = false;
}
}
}
public Object newInstance() throws Exception {
return typeOrComponentType.newInstance();
}
@Nullable public Class> getCollectionClass() {
return ((genTypes != null) && (genTypes.length == 1)) ? (Class>) genTypes[0] : null;
}
@Nullable public Class> getMapKeyClass() {
return ((genTypes != null) && (genTypes.length == 2)) ? (Class>) genTypes[0] : null;
}
@Nullable public Class> getMapValueClass() {
return ((genTypes != null) && (genTypes.length == 2)) ? (Class>) genTypes[1] : null;
}
public boolean isCollectionArray() {
return ((genArray != null) && (genArray.length == 1)) ? genArray[0] : false;
}
public boolean isMapKeyArray() {
return ((genArray != null) && (genArray.length == 2)) ? genArray[0] : false;
}
public boolean isMapValueArray() {
return ((genArray != null) && (genArray.length == 2)) ? genArray[1] : false;
}
public Object get(Object src) {
try {
return field.get(src);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
/**
* Sets the field value for the destination object if and only if it is non-null and passes the field's
* validation method (if any). If the value is null and the field is marked as required, then an exception
* is thrown, otherwise the null value is ignored and the current value (if any) is kept.
*/
public void setStrict(@Nonnull Object dst, @Nullable Object value) throws IllegalAccessException {
if (value == null) {
if (isRequired()) {
throw new RequiredFieldException("missing required field '" +
this.getName() + "' for " + dst, getName());
}
return;
}
field.set(dst, value);
}
public void set(@Nonnull Object dst, @Nullable Object value) {
if (value == null) {
if (isRequired() && (get(dst) == null)) {
throw new RequiredFieldException("missing required field '" +
this.getName() + "' for " + dst, getName());
}
return;
}
try {
field.set(dst, value);
} catch (RuntimeException ex) {
throw ex;
} catch (Exception ex) {
log.warn("error setting ({})({}) on ({}) in {}", value, value.getClass(), dst, toString());
throw new RuntimeException(ex);
}
}
public boolean isArray() {
return (bits & ARRAY) == ARRAY;
}
public boolean isCodable() {
return (bits & CODABLE) == CODABLE;
}
public boolean isCollection() {
return (bits & COLLECTION) == COLLECTION;
}
public boolean isGeneric() {
return (bits & GENERIC) == GENERIC;
}
public boolean isMap() {
return (bits & MAP) == MAP;
}
public boolean isEnum() {
return (bits & ENUM) == ENUM;
}
public boolean isNative() {
return (bits & NATIVE) == NATIVE;
}
public boolean isRequired() {
return (bits & REQUIRED) == REQUIRED;
}
public boolean isReadOnly() {
return (bits & READONLY) == READONLY;
}
public boolean isWriteOnly() {
return (bits & WRITEONLY) == WRITEONLY;
}
public String toString() {
return "[" + getName() + "," + typeOrComponentType + (isArray() ? "[]," : ",") + Integer.toString(bits, 2) + "]";
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy