/*
* Copyright 2008 Google Inc.
*
* 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.gwt.inject.rebind.util;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.inject.rebind.reflect.FieldLiteral;
import com.google.gwt.inject.rebind.reflect.MethodLiteral;
import com.google.inject.Inject;
import com.google.inject.TypeLiteral;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
/**
* This class can be used to collect a type's members, including those of its
* (recursive) superclasses and interfaces. The collector treats overrides
* correctly, i.e. it returns the method defined the closest to the provided
* type.
* <p>Note: The collector uses internal caching and can be called with the same
* arguments repeatedly without repeated performance costs.
* <p>This class is not thread-safe.
*/
public class MemberCollector {
public static final MethodFilter ALL_METHOD_FILTER =
new MethodFilter() {
public boolean accept(MethodLiteral<?, Method> method) {
return true;
}
};
// TODO(schmitt): Add constructor collector?
/**
* Filter used during the collection of methods to restrict the kind of
* collected methods.
* <p/>
* Note: The method filter influences override detection! If a method A
* overrides a method A*, without a filter only A would be collected. If the
* filter only accepts A* and not A, A will not be collected, and A* will be
* collected <b>despite being overridden</b> by A.
*/
public static interface MethodFilter {
boolean accept(MethodLiteral<?, Method> method);
}
/**
* Filter used during the collection of fields to restrict the kind of
* collected fields.
*/
public static interface FieldFilter {
boolean accept(FieldLiteral<?> field);
}
/**
* Comparator which detects methods that are override-equal.
*
* The comparator assumes that both classes have been investigated for java
* specification compliance.
*/
private static final Comparator<MethodLiteral<?, Method>> METHOD_COMPARATOR =
new Comparator<MethodLiteral<?, Method>>() {
public int compare(MethodLiteral<?, Method> m1, MethodLiteral<?, Method> m2) {
if (m1 == m2) {
return 0;
}
int nameCompare = m1.getName().compareTo(m2.getName());
if (nameCompare != 0) {
return nameCompare;
}
List<TypeLiteral<?>> parameters1 = m1.getParameterTypes();
List<TypeLiteral<?>> parameters2 = m2.getParameterTypes();
if (parameters1.size() != parameters2.size()) {
return parameters1.size() - parameters2.size();
}
for (int i = 0; i < parameters1.size(); i++) {
TypeLiteral<?> param1 = parameters1.get(i);
TypeLiteral<?> param2 = parameters2.get(i);
if (!param1.equals(param2)) {
return param1.toString().compareTo(param2.toString());
}
}
/* If either of the methods is private, it is either (a) in the
* superclass, and thus invisible to the subclass, or (b) in the
* subclass, which implies that the method must be private in the
* superclass as well.
*
* If either of the methods has default access and the classes are not in
* the same package then they are invisible to each other and thus not
* override-equivalent.
*/
if ((m1.isPrivate() || m2.isPrivate())
|| ((m1.isDefaultAccess() || m2.isDefaultAccess()) && !samePackage(m1, m2))) {
return m1.getRawDeclaringType().getCanonicalName().compareTo(
m2.getRawDeclaringType().getCanonicalName());
}
// Methods have same name, parameter types and compatible visibility
return 0;
}
private boolean samePackage(MethodLiteral<?, Method> m1, MethodLiteral<?, Method> m2) {
return m1.getRawDeclaringType().getPackage() == m2.getRawDeclaringType().getPackage();
}
};
/**
* Comparator which compares fields by their name.
*/
private static final Comparator<FieldLiteral<?>> FIELD_COMPARATOR =
new Comparator<FieldLiteral<?>>() {
public int compare(FieldLiteral<?> f1, FieldLiteral<?> f2) {
return f1.getName().compareTo(f2.getName());
}
};
/**
* Internal method cache: Type name -> Method Set
*/
private final Map<TypeLiteral<?>, Set<MethodLiteral<?, Method>>> methodMultiMap =
new LinkedHashMap<TypeLiteral<?>, Set<MethodLiteral<?, Method>>>();
/**
* Internal field cache: Type name -> Method Set
*/
private final Map<TypeLiteral<?>, Set<FieldLiteral<?>>> fieldMultiMap =
new LinkedHashMap<TypeLiteral<?>, Set<FieldLiteral<?>>>();
private final TreeLogger logger;
/**
* Method filter that this collector operates with.
*/
private MethodFilter methodFilter;
/**
* Field filter that this collector operates with.
*/
private FieldFilter fieldFilter;
/**
* Locking status. The collector is locked once it started to accumulate
* members. No filters can be set on the collector after it has been locked.
*/
private boolean locked;
@Inject
public MemberCollector(TreeLogger logger) {
this.logger = logger;
this.locked = false;
}
/**
* Sets this collector's method filter. This method can only be called
* before members are requested.
*
* @param filter new method filter for this collector
* @throws IllegalStateException if the filter is set after members have been
* requested
*/
public void setMethodFilter(MethodFilter filter) throws IllegalStateException {
assertNotLocked();
this.methodFilter = filter;
}
/**
* Sets this collector's field filter. This method can only be called before
* members are requested.
*
* @param filter new field filter for this collector
* @throws IllegalStateException if the filter is set after members have been
* requested
*/
public void setFieldFilter(FieldFilter filter) throws IllegalStateException {
assertNotLocked();
this.fieldFilter = filter;
}
private void assertNotLocked() {
if (locked) {
String msg = "A filter can only be set on this collector before members are requested!";
logger.log(TreeLogger.Type.ERROR, msg);
throw new IllegalStateException(msg);
}
}
/**
* Returns all methods in the provided type, including those of the type's
* (recursive) super classes and interfaces. Treats overloads correctly. If
* no method filter is set will return an empty set.
*
* @param typeLiteral type for which methods are collected
* @return all methods for the given type
*/
public Collection<MethodLiteral<?, Method>> getMethods(TypeLiteral<?> typeLiteral) {
collect(typeLiteral);
return Collections.unmodifiableCollection(methodMultiMap.get(typeLiteral));
}
/**
* Returns all fields in the provided type, including those of the type's
* (recursive) super classes. If no field filter is set will return an empty
* set.
*
* @param typeLiteral type for which fields are collected
* @return all fields for the given type
*/
public Collection<FieldLiteral<?>> getFields(TypeLiteral<?> typeLiteral) {
collect(typeLiteral);
return Collections.unmodifiableCollection(fieldMultiMap.get(typeLiteral));
}
private void collect(TypeLiteral<?> typeLiteral) {
locked = true;
if (methodMultiMap.containsKey(typeLiteral)) {
return;
}
// Type hasn't been collected yet.
Set<MethodLiteral<?, Method>> typeMethods =
new TreeSet<MethodLiteral<?, Method>>(METHOD_COMPARATOR);
Set<FieldLiteral<?>> typeFields = new LinkedHashSet<FieldLiteral<?>>();
accumulateMembers(typeLiteral, typeMethods, typeFields);
methodMultiMap.put(typeLiteral, typeMethods);
fieldMultiMap.put(typeLiteral, typeFields);
}
private void accumulateMembers(TypeLiteral<?> typeLiteral,
Set<MethodLiteral<?, Method>> methodAccu, Set<FieldLiteral<?>> fieldAccu) {
if (methodFilter != null) {
if (methodMultiMap.containsKey(typeLiteral)) {
for (MethodLiteral<?, Method> method : methodMultiMap.get(typeLiteral)) {
methodAccu.add(method);
}
} else {
for (MethodLiteral<?, Method> method : getTypeMethods(typeLiteral)) {
if (methodFilter.accept(method)) {
methodAccu.add(method);
logger.log(TreeLogger.TRACE, String.format("Found method: %s", method));
} else {
logger.log(TreeLogger.DEBUG, String.format("Ignoring method: %s", method));
}
}
}
}
if (fieldFilter != null) {
if (fieldMultiMap.containsKey(typeLiteral)) {
for (FieldLiteral<?> field : fieldMultiMap.get(typeLiteral)) {
fieldAccu.add(field);
}
} else {
for (FieldLiteral<?> field : getTypeFields(typeLiteral)) {
if (fieldFilter.accept(field)) {
fieldAccu.add(field);
logger.log(TreeLogger.TRACE, String.format("Found field: %s", field));
} else {
logger.log(TreeLogger.DEBUG, String.format("Ignoring field: %s", field));
}
}
}
}
for (Class<?> ancestor : typeLiteral.getRawType().getInterfaces()) {
accumulateMembers(typeLiteral.getSupertype(ancestor), methodAccu, fieldAccu);
}
Class<?> ancestor = typeLiteral.getRawType().getSuperclass();
if (ancestor != null) {
accumulateMembers(typeLiteral.getSupertype(ancestor), methodAccu, fieldAccu);
}
}
private <T> Iterable<MethodLiteral<T, Method>> getTypeMethods(TypeLiteral<T> typeLiteral) {
List<MethodLiteral<T, Method>> methods = new ArrayList<MethodLiteral<T, Method>>();
for (Method method : typeLiteral.getRawType().getDeclaredMethods()) {
methods.add(MethodLiteral.get(method, typeLiteral));
}
Collections.sort(methods, METHOD_COMPARATOR);
return methods;
}
private <T> Iterable<FieldLiteral<T>> getTypeFields(TypeLiteral<T> typeLiteral) {
List<FieldLiteral<T>> fields = new ArrayList<FieldLiteral<T>>();
for (Field field : typeLiteral.getRawType().getDeclaredFields()) {
fields.add(FieldLiteral.get(field, typeLiteral));
}
Collections.sort(fields, FIELD_COMPARATOR);
return fields;
}
}