mirror of
https://github.com/google/nomulus.git
synced 2025-05-12 22:38:16 +02:00
mv com/google/domain/registry google/registry
This change renames directories in preparation for the great package rename. The repository is now in a broken state because the code itself hasn't been updated. However this should ensure that git correctly preserves history for each file.
This commit is contained in:
parent
a41677aea1
commit
5012893c1d
2396 changed files with 0 additions and 0 deletions
296
java/google/registry/model/ModelUtils.java
Normal file
296
java/google/registry/model/ModelUtils.java
Normal file
|
@ -0,0 +1,296 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.google.domain.registry.model;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Predicates.instanceOf;
|
||||
import static com.google.common.base.Predicates.isNull;
|
||||
import static com.google.common.base.Predicates.or;
|
||||
import static com.google.common.collect.Iterables.all;
|
||||
import static com.google.common.collect.Lists.newArrayList;
|
||||
import static com.google.common.collect.Maps.transformValues;
|
||||
import static com.google.common.collect.Sets.newLinkedHashSet;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Ordering;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.Ref;
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.Parent;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.AbstractList;
|
||||
import java.util.Collection;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/** A collection of static methods that deal with reflection on model classes. */
|
||||
public class ModelUtils {
|
||||
|
||||
/** Caches all instance fields on an object, including non-public and inherited fields. */
|
||||
private static final LoadingCache<Class<?>, ImmutableMap<String, Field>> ALL_FIELDS_CACHE =
|
||||
CacheBuilder.newBuilder().build(new CacheLoader<Class<?>, ImmutableMap<String, Field>>() {
|
||||
@Override
|
||||
public ImmutableMap<String, Field> load(Class<?> clazz) {
|
||||
Deque<Class<?>> hierarchy = new LinkedList<>();
|
||||
// Walk the hierarchy up to but not including ImmutableObject (to ignore hashCode).
|
||||
for (; clazz != ImmutableObject.class; clazz = clazz.getSuperclass()) {
|
||||
// Add to the front, so that shadowed fields show up later in the list.
|
||||
// This will mean that getFieldValues will show the most derived value.
|
||||
hierarchy.addFirst(clazz);
|
||||
}
|
||||
Map<String, Field> fields = new LinkedHashMap<>();
|
||||
for (Class<?> hierarchyClass : hierarchy) {
|
||||
Package pakkage = hierarchyClass.getPackage();
|
||||
// Don't use hierarchyClass.getFields() because it only picks up public fields.
|
||||
for (Field field : hierarchyClass.getDeclaredFields()) {
|
||||
if (Modifier.isStatic(field.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
// Strictly speaking this shouldn't be necessary since all of these fields
|
||||
// are already accessible to their FieldExposer, but it is more performant
|
||||
// to access fields if they are marked accessible this way because it skips
|
||||
// various security checks.
|
||||
checkNotNull(
|
||||
FIELD_EXPOSERS.get(pakkage),
|
||||
"No FieldExposer registered for %s", pakkage.getName())
|
||||
.setAccessible(field);
|
||||
fields.put(field.getName(), field);
|
||||
}
|
||||
}
|
||||
return ImmutableMap.copyOf(fields);
|
||||
}});
|
||||
|
||||
/** Per-package trampolines to expose package-private fields for reflection. */
|
||||
private static final Map<Package, AbstractFieldExposer> FIELD_EXPOSERS = Maps.uniqueIndex(
|
||||
FieldExposerRegistry.getFieldExposers(),
|
||||
new Function<AbstractFieldExposer, Package>() {
|
||||
@Override
|
||||
public Package apply(AbstractFieldExposer exposer) {
|
||||
return exposer.getClass().getPackage();
|
||||
}});
|
||||
|
||||
/** Lists all instance fields on an object, including non-public and inherited fields. */
|
||||
static Map<String, Field> getAllFields(Class<?> clazz) {
|
||||
return ALL_FIELDS_CACHE.getUnchecked(clazz);
|
||||
}
|
||||
|
||||
/** Return a string representing the persisted schema of a type or enum. */
|
||||
static String getSchema(Class<?> clazz) {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
Iterable<?> body;
|
||||
if (clazz.isEnum()) {
|
||||
stringBuilder.append("enum ");
|
||||
body = FluentIterable.from(asList(clazz.getEnumConstants()));
|
||||
} else {
|
||||
stringBuilder.append("class ");
|
||||
body = FluentIterable.from(getAllFields(clazz).values())
|
||||
.filter(new Predicate<Field>() {
|
||||
@Override
|
||||
public boolean apply(Field field) {
|
||||
return !field.isAnnotationPresent(Ignore.class);
|
||||
}})
|
||||
.transform(new Function<Field, Object>() {
|
||||
@Override
|
||||
public Object apply(Field field) {
|
||||
String annotation = field.isAnnotationPresent(Id.class)
|
||||
? "@Id "
|
||||
: field.isAnnotationPresent(Parent.class)
|
||||
? "@Parent "
|
||||
: "";
|
||||
String type = field.getType().isArray()
|
||||
? field.getType().getComponentType().getName() + "[]"
|
||||
: field.getGenericType().toString().replaceFirst("class ", "");
|
||||
return String.format("%s%s %s", annotation, type, field.getName());
|
||||
}});
|
||||
}
|
||||
return stringBuilder
|
||||
.append(clazz.getName()).append(" {\n ")
|
||||
.append(Joiner.on(";\n ").join(Ordering.usingToString().sortedCopy(body)))
|
||||
.append(";\n}")
|
||||
.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of Class objects of all persisted fields. This includes the parameterized
|
||||
* type(s) of any fields (if any).
|
||||
*/
|
||||
static Set<Class<?>> getPersistedFieldTypes(Class<?> clazz) {
|
||||
ImmutableSet.Builder<Class<?>> builder = new ImmutableSet.Builder<>();
|
||||
for (Field field : getAllFields(clazz).values()) {
|
||||
// Skip fields that aren't persisted to datastore.
|
||||
if (field.isAnnotationPresent(Ignore.class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the field's type is the same as the field's class object, then it's a non-parameterized
|
||||
// type, and thus we just add it directly. We also don't bother looking at the parameterized
|
||||
// types of Key and Ref objects, since they are just references to other objects and don't
|
||||
// actual embed themselves in the persisted object anyway.
|
||||
Class<?> fieldClazz = field.getType();
|
||||
Type fieldType = field.getGenericType();
|
||||
builder.add(fieldClazz);
|
||||
if (fieldType.equals(fieldClazz) || Ref.class.equals(clazz) || Key.class.equals(clazz)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the field is a parameterized type, then also add the parameterized field.
|
||||
if (fieldType instanceof ParameterizedType) {
|
||||
ParameterizedType parameterizedType = (ParameterizedType) fieldType;
|
||||
for (Type actualType : parameterizedType.getActualTypeArguments()) {
|
||||
if (actualType instanceof Class<?>) {
|
||||
builder.add((Class<?>) actualType);
|
||||
} else {
|
||||
// We intentionally ignore types that are parameterized on non-concrete types. In theory
|
||||
// we could have collections embedded within collections, but Objectify does not allow
|
||||
// that.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/** Retrieves a field value via reflection. */
|
||||
static Object getFieldValue(Object instance, Field field) {
|
||||
try {
|
||||
return Preconditions.checkNotNull(
|
||||
FIELD_EXPOSERS.get(field.getDeclaringClass().getPackage()),
|
||||
"No FieldExposer registered for %s", field.getDeclaringClass().getPackage().getName())
|
||||
.getFieldValue(instance, field);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets a field value via reflection. */
|
||||
static void setFieldValue(Object instance, Field field, Object value) {
|
||||
try {
|
||||
Preconditions.checkNotNull(
|
||||
FIELD_EXPOSERS.get(field.getDeclaringClass().getPackage()),
|
||||
"No FieldExposer registered for %s", field.getDeclaringClass().getPackage().getName())
|
||||
.setFieldValue(instance, field, value);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map from field names (including non-public and inherited fields) to values.
|
||||
* <p>
|
||||
* This turns arrays into {@link List} objects so that ImmutableObject can more easily use the
|
||||
* returned map in its implementation of {@link ImmutableObject#toString} and
|
||||
* {@link ImmutableObject#equals}, which work by comparing and printing these maps.
|
||||
*/
|
||||
static Map<String, Object> getFieldValues(Object instance) {
|
||||
// Don't make this ImmutableMap because field values can be null.
|
||||
Map<String, Object> values = new LinkedHashMap<>();
|
||||
for (Field field : getAllFields(instance.getClass()).values()) {
|
||||
Object value = getFieldValue(instance, field);
|
||||
if (value != null && value.getClass().isArray()) {
|
||||
// It's surprisingly difficult to convert arrays into lists if the array might be primitive.
|
||||
final Object arrayValue = value;
|
||||
value = new AbstractList<Object>() {
|
||||
@Override
|
||||
public Object get(int index) {
|
||||
return Array.get(arrayValue, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return Array.getLength(arrayValue);
|
||||
}};
|
||||
}
|
||||
values.put(field.getName(), value);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/** Functional helper for {@link #cloneEmptyToNull}. */
|
||||
private static final Function<Object, ?> CLONE_EMPTY_TO_NULL = new Function<Object, Object>() {
|
||||
@Override
|
||||
public Object apply(Object obj) {
|
||||
if (obj instanceof ImmutableSortedMap) {
|
||||
// ImmutableSortedMapTranslatorFactory handles empty for us. If the object is null, then
|
||||
// its on-save hook can't run.
|
||||
return obj;
|
||||
}
|
||||
if ("".equals(obj)
|
||||
|| (obj instanceof Collection && ((Collection<?>) obj).isEmpty())
|
||||
|| (obj instanceof Map && ((Map<?, ?>) obj).isEmpty())
|
||||
|| (obj != null && obj.getClass().isArray() && Array.getLength(obj) == 0)) {
|
||||
return null;
|
||||
}
|
||||
Predicate<Object> immutableObjectOrNull = or(isNull(), instanceOf(ImmutableObject.class));
|
||||
if ((obj instanceof Set || obj instanceof List)
|
||||
&& all((Iterable<?>) obj, immutableObjectOrNull)) {
|
||||
// Recurse into sets and lists, but only if they contain ImmutableObjects.
|
||||
FluentIterable<?> fluent = FluentIterable.from((Iterable<?>) obj).transform(this);
|
||||
return (obj instanceof List) ? newArrayList(fluent) : newLinkedHashSet(fluent);
|
||||
}
|
||||
if (obj instanceof Map && all(((Map<?, ?>) obj).values(), immutableObjectOrNull)) {
|
||||
// Recurse into maps with ImmutableObject values.
|
||||
return transformValues((Map<?, ?>) obj, this);
|
||||
}
|
||||
if (obj instanceof ImmutableObject) {
|
||||
// Recurse on the fields of an ImmutableObject.
|
||||
ImmutableObject copy = ImmutableObject.clone((ImmutableObject) obj);
|
||||
for (Field field : getAllFields(obj.getClass()).values()) {
|
||||
Object oldValue = getFieldValue(obj, field);
|
||||
Object newValue = apply(oldValue);
|
||||
if (!Objects.equals(oldValue, newValue)) {
|
||||
setFieldValue(copy, field, newValue);
|
||||
}
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
return obj;
|
||||
}};
|
||||
|
||||
/** Returns a clone of the object and sets empty collections, arrays, maps and strings to null. */
|
||||
@SuppressWarnings("unchecked")
|
||||
protected static <T extends ImmutableObject> T cloneEmptyToNull(T obj) {
|
||||
return (T) CLONE_EMPTY_TO_NULL.apply(obj);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static void resetCaches() {
|
||||
ALL_FIELDS_CACHE.invalidateAll();
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue