blob: f8485c8570cee6d8e73269281e0a4617e7aa37c4 [file] [log] [blame]
/*
* Copyright 2016 The Bazel 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.idea.blaze.base.model.blaze.deepequalstester;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.junit.Assert;
import java.io.File;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public final class ReachabilityAnalysis {
/**
* Wrapper around a map from class to set of paths that lead to that path from the root object
*/
public static final class ReachableClasses {
Map<Class<? extends Serializable>, Set<List<String>>> map;
public ReachableClasses() {
map = Maps.newHashMap();
}
boolean alreadyFound(Class<? extends Serializable> clazz) {
return map.containsKey(clazz);
}
void addPath(Class<? extends Serializable> clazz, List<String> path) {
Set<List<String>> paths;
if (map.containsKey(clazz)) {
paths = map.get(clazz);
}
else {
paths = Sets.newHashSet();
map.put(clazz, paths);
}
paths.add(path);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
Set<? extends Entry<Class<? extends Serializable>, ? extends Set<? extends List<String>>>> entries = map
.entrySet();
for (Entry<Class<? extends Serializable>, ? extends Set<? extends List<String>>> entry : entries) {
sb.append(entry.getKey().toString());
sb.append("\n");
}
return sb.toString();
}
public Set<Class<? extends Serializable>> getClasses() {
return map.keySet();
}
public List<String> getExamplePathTo(Class<? extends Serializable> aClass) {
if (map.containsKey(aClass)) {
return map.get(aClass).iterator().next();
}
return Lists.newArrayList();
}
}
/**
* Find all of the classes reachable from a root object
*
* @param root object to start reachability calculation from
* @param declaredRootClass declared class of the root object
* @param currentPath field access path to get to root
* @param reachableClasses output: add classes reachable from root to this object
* @throws IllegalAccessException
* @throws ClassNotFoundException
*/
public static void computeReachableFromObject(Object root, Class<?> declaredRootClass,
List<String> currentPath, ReachableClasses reachableClasses)
throws IllegalAccessException, ClassNotFoundException {
final Class<?> concreteRootClass = DeepEqualsTesterUtil.getClass(declaredRootClass, root);
List<Field> allFields = DeepEqualsTesterUtil.getAllFields(concreteRootClass);
for (Field field : allFields) {
if (!Modifier.isStatic(field.getModifiers())) {
field.setAccessible(true);
final Object fieldObject;
if (root == null) {
fieldObject = null;
}
else {
fieldObject = field.get(root);
}
List<String> childPath = Lists.newArrayList();
childPath.addAll(currentPath);
childPath.add(field.toString());
addToReachableAndRecurse(fieldObject, field.getType(), field.getGenericType(),
childPath, reachableClasses);
}
}
}
/**
* Determine the action we should take based on the type of Object and then take it. In the
* normal object case, this results in a recursive call to
* {@link #computeReachableFromObject(Object, Class, List, ReachableClasses)}. In the case of
* Collections, we skip the Collection type and continue on with the type contained in the
* collection.
*/
private static void addToReachableAndRecurse(Object object, Class<?> declaredObjectClass,
Type genericType, List<String> currentPath, ReachableClasses reachableClasses)
throws ClassNotFoundException, IllegalAccessException {
Class<? extends Serializable> objectType = DeepEqualsTesterUtil
.getClass(declaredObjectClass, object);
// TODO(salguarnieri) modify if so all ignored classes are taken care of together
if (objectType.isPrimitive()) {
// ignore
}
else if (objectType.isEnum()) {
// assume enums do the right thing, ignore
}
else if (DeepEqualsTesterUtil.isSubclassOf(objectType, String.class)) {
// ignore
}
else if (DeepEqualsTesterUtil.isSubclassOf(objectType, File.class)) {
// ignore
}
else if (DeepEqualsTesterUtil.isSubclassOf(objectType, Collection.class) || DeepEqualsTesterUtil
.isSubclassOf(objectType, Map.class)) {
if (genericType instanceof ParameterizedType) {
ParameterizedType parameterType = (ParameterizedType)genericType;
Type[] actualTypeArguments = parameterType.getActualTypeArguments();
for (Type typeArgument : actualTypeArguments) {
if (typeArgument instanceof Class) {
List<String> childPath = Lists.newArrayList();
childPath.addAll(currentPath);
childPath.add("[[IN COLLECTION]]");
// this does not properly handle subtyping
addToReachableAndRecurse(null, (Class)typeArgument, null, childPath,
reachableClasses);
}
else {
Assert.fail("This case is not handled yet");
}
}
}
else {
Assert.fail("This case is not handled yet");
}
}
else if (objectType.isArray()) {
Class<?> typeInArray = objectType.getComponentType();
// This does not properly handle subtyping
List<String> childPath = Lists.newArrayList();
childPath.addAll(currentPath);
childPath.add("[[IN ARRAY]]");
addToReachableAndRecurse(null, typeInArray, null, childPath, reachableClasses);
}
else {
boolean doRecursion = !reachableClasses.alreadyFound(objectType);
reachableClasses.addPath(objectType, currentPath);
if (doRecursion) {
computeReachableFromObject(object, declaredObjectClass, currentPath, reachableClasses);
}
}
}
}