com.rt.storage.api.client.util.ClassInfo Maven / Gradle / Ivy
package com.rt.storage.api.client.util;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Computes class information to determine data key name/value pairs associated with the class.
*
* Implementation is thread-safe.
*
* @since 1.0
* @author Yaniv Inbar
*/
public final class ClassInfo {
/** Class information cache, with case-sensitive field names. */
private static final ConcurrentMap, ClassInfo> CACHE =
new ConcurrentHashMap, ClassInfo>();
/** Class information cache, with case-insensitive fields names. */
private static final ConcurrentMap, ClassInfo> CACHE_IGNORE_CASE =
new ConcurrentHashMap, ClassInfo>();
/** Class. */
private final Class> clazz;
/** Whether field names are case sensitive. */
private final boolean ignoreCase;
/** Map from {@link FieldInfo#getName()} to the field information. */
private final IdentityHashMap nameToFieldInfoMap =
new IdentityHashMap();
/**
* Unmodifiable sorted (with any possible {@code null} member first) list (without duplicates) of
* {@link FieldInfo#getName()}.
*/
final List names;
/**
* Returns the class information for the given underlying class.
*
* @param underlyingClass underlying class or {@code null} for {@code null} result
* @return class information or {@code null} for {@code null} input
*/
public static ClassInfo of(Class> underlyingClass) {
return of(underlyingClass, false);
}
/**
* Returns the class information for the given underlying class.
*
* @param underlyingClass underlying class or {@code null} for {@code null} result
* @param ignoreCase whether field names are case sensitive
* @return class information or {@code null} for {@code null} input
* @since 1.10
*/
public static ClassInfo of(Class> underlyingClass, boolean ignoreCase) {
if (underlyingClass == null) {
return null;
}
final ConcurrentMap, ClassInfo> cache = ignoreCase ? CACHE_IGNORE_CASE : CACHE;
// Logic copied from ConcurrentMap.computeIfAbsent
ClassInfo v, newValue;
return ((v = cache.get(underlyingClass)) == null
&& (newValue = new ClassInfo(underlyingClass, ignoreCase)) != null
&& (v = cache.putIfAbsent(underlyingClass, newValue)) == null)
? newValue
: v;
}
/**
* Returns the underlying class.
*
* @since 1.4
*/
public Class> getUnderlyingClass() {
return clazz;
}
/**
* Returns whether field names are case sensitive.
*
* @since 1.10
*/
public final boolean getIgnoreCase() {
return ignoreCase;
}
/**
* Returns the information for the given {@link FieldInfo#getName()}.
*
* @param name {@link FieldInfo#getName()} or {@code null}
* @return field information or {@code null} for none
*/
public FieldInfo getFieldInfo(String name) {
if (name != null) {
if (ignoreCase) {
name = name.toLowerCase(Locale.US);
}
name = name.intern();
}
return nameToFieldInfoMap.get(name);
}
/**
* Returns the field for the given {@link FieldInfo#getName()}.
*
* @param name {@link FieldInfo#getName()} or {@code null}
* @return field or {@code null} for none
*/
public Field getField(String name) {
FieldInfo fieldInfo = getFieldInfo(name);
return fieldInfo == null ? null : fieldInfo.getField();
}
/**
* Returns the underlying class is an enum.
*
* @since 1.4
*/
public boolean isEnum() {
return clazz.isEnum();
}
/**
* Returns an unmodifiable sorted set (with any possible {@code null} member first) of {@link
* FieldInfo#getName() names}.
*/
public Collection getNames() {
return names;
}
private ClassInfo(Class> srcClass, boolean ignoreCase) {
clazz = srcClass;
this.ignoreCase = ignoreCase;
Preconditions.checkArgument(
!ignoreCase || !srcClass.isEnum(), "cannot ignore case on an enum: " + srcClass);
// name set has a special comparator to keep null first
TreeSet nameSet =
new TreeSet(
new Comparator() {
public int compare(String s0, String s1) {
return Objects.equal(s0, s1)
? 0
: s0 == null ? -1 : s1 == null ? 1 : s0.compareTo(s1);
}
});
// iterate over declared fields
for (Field field : srcClass.getDeclaredFields()) {
FieldInfo fieldInfo = FieldInfo.of(field);
if (fieldInfo == null) {
continue;
}
String fieldName = fieldInfo.getName();
if (ignoreCase) {
fieldName = fieldName.toLowerCase(Locale.US).intern();
}
FieldInfo conflictingFieldInfo = nameToFieldInfoMap.get(fieldName);
Preconditions.checkArgument(
conflictingFieldInfo == null,
"two fields have the same %sname <%s>: %s and %s",
ignoreCase ? "case-insensitive " : "",
fieldName,
field,
conflictingFieldInfo == null ? null : conflictingFieldInfo.getField());
nameToFieldInfoMap.put(fieldName, fieldInfo);
nameSet.add(fieldName);
}
// inherit from super class and add only fields not already in nameToFieldInfoMap
Class> superClass = srcClass.getSuperclass();
if (superClass != null) {
ClassInfo superClassInfo = ClassInfo.of(superClass, ignoreCase);
nameSet.addAll(superClassInfo.names);
for (Map.Entry e : superClassInfo.nameToFieldInfoMap.entrySet()) {
String name = e.getKey();
if (!nameToFieldInfoMap.containsKey(name)) {
nameToFieldInfoMap.put(name, e.getValue());
}
}
}
names =
nameSet.isEmpty()
? Collections.emptyList()
: Collections.unmodifiableList(new ArrayList(nameSet));
}
/**
* Returns an unmodifiable collection of the {@code FieldInfo}s for this class, without any
* guarantee of order.
*
* If you need sorted order, instead use {@link #getNames()} with {@link
* #getFieldInfo(String)}.
*
* @since 1.16
*/
public Collection getFieldInfos() {
return Collections.unmodifiableCollection(nameToFieldInfoMap.values());
}
}