com.google.api.server.spi.MethodHierarchyReader Maven / Gradle / Ivy
Show all versions of endpoints-framework Show documentation
/*
* Copyright 2016 Google Inc. 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.api.server.spi;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ListMultimap;
import com.google.common.reflect.TypeToken;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;
/**
* Utility to take a class and create maps of methods usable for endpoints and related data.
*
* On the first call, parses and stores the full hierarchy information. The cached full
* hierarchy is then used for all subsequent calls. Returned iterables, maps, and lists are copied
* out without reference to the original full hierarchy information to allow the caller to keep just
* the needed portion of information and release the full hierarchy along with this class.
*
* @author Eric Orth
*/
public class MethodHierarchyReader {
private final Class> endpointClass;
// Map from method signatures to a list of method overrides for that signature (ordered
// subclass -> superclass).
private ListMultimap endpointMethods;
/**
* Constructs a {@code MethodHierarchyReader} for the given class type.
*
* @param endpointClass Must be a concrete type (not abstract or interface).
*/
public MethodHierarchyReader(Class> endpointClass) {
Preconditions.checkArgument(
!Modifier.isAbstract(endpointClass.getModifiers())
&& !Modifier.isInterface(endpointClass.getModifiers()),
"A concrete class is expected, but got %s.", endpointClass.getName());
this.endpointClass = endpointClass;
}
private void readMethodHierarchyIfNecessary() {
if (endpointMethods == null) {
ImmutableListMultimap.Builder builder =
ImmutableListMultimap.builder();
buildServiceMethods(builder, TypeToken.of(endpointClass));
endpointMethods = builder.build();
}
}
/**
* Returns the final leaf subclass version of a method.
*
* @param overrides A list of method overrides ordered subclass -> superclass.
*/
private EndpointMethod getLeafMethod(List overrides) {
// Because the list is ordered subclass -> superclass, index 0 will always contain the leaf
// subclass implementation.
return overrides.get(0);
}
/**
* Returns {@link ListMultimap#asMap multimap.asMap()}, with its type
* corrected from {@code Map>} to {@code Map>}.
*/
// Copied from com.google.common.collect.Multimaps. We can't use the actual method from
// that class as appengine build magic gives us an older version of guava that doesn't yet have
// this method.
// TODO: Switch to Multimaps.asMap() once it becomes available in appengine.
@SuppressWarnings("unchecked")
// safe by specification of ListMultimap.asMap()
private static Map> asMap(ListMultimap multimap) {
return (Map>) (Map) multimap.asMap();
}
/**
* Returns a collection of public service methods defined by the class and its super classes.
* Bridge methods are ignored. Only the final leaf subclass version of each method is included.
*/
public Iterable getLeafMethods() {
readMethodHierarchyIfNecessary();
ImmutableList.Builder builder = ImmutableList.builder();
for (List overrides : asMap(endpointMethods).values()) {
builder.add(getLeafMethod(overrides).getMethod());
}
return builder.build();
}
/**
* Returns a collection of public service methods defined by the class and its super classes.
* Bridge methods are ignored. Only the final leaf subclass version of each method is included.
* Methods are stored in the EndpointMethod container.
*/
public Iterable getLeafEndpointMethods() {
readMethodHierarchyIfNecessary();
ImmutableList.Builder builder = ImmutableList.builder();
for (List overrides : asMap(endpointMethods).values()) {
builder.add(getLeafMethod(overrides));
}
return builder.build();
}
/**
* Returns a collection of public service methods defined by the class and its super classes.
* Bridge methods are ignored. For each method, all valid method implementations are included,
* ordered subclass to superclass.
*/
public Iterable> getMethodOverrides() {
readMethodHierarchyIfNecessary();
ImmutableList.Builder> builder = ImmutableList.builder();
for (List overrides : asMap(endpointMethods).values()) {
ImmutableList.Builder methodBuilder = ImmutableList.builder();
for (EndpointMethod method : overrides) {
methodBuilder.add(method.getMethod());
}
builder.add(methodBuilder.build());
}
return builder.build();
}
/**
* Returns a collection of public service methods defined by the class and its super classes.
* Bridge methods are ignored. For each method, all valid method implementations are included,
* ordered subclass to superclass. Methods are stored in the EndpointMethod container.
*/
public Iterable> getEndpointOverrides() {
readMethodHierarchyIfNecessary();
return asMap(endpointMethods).values();
}
/**
* Returns a mapping of public service methods defined by the class and its super classes,
* keyed by method name. Bridge methods are ignored. Only the final leaf subclass version of
* each method is included.
*/
public Map getNameToLeafMethodMap() {
readMethodHierarchyIfNecessary();
ImmutableMap.Builder builder = ImmutableMap.builder();
for (List overrides : asMap(endpointMethods).values()) {
Method leafMethod = getLeafMethod(overrides).getMethod();
builder.put(leafMethod.getName(), leafMethod);
}
return builder.build();
}
/**
* Returns a mapping of public service methods defined by the class and its super classes,
* keyed by method name. Bridge methods are ignored. All valid method implementations are
* included, ordered subclass to superclass.
*/
public ListMultimap getNameToEndpointOverridesMap() {
readMethodHierarchyIfNecessary();
ImmutableListMultimap.Builder builder = ImmutableListMultimap.builder();
for (List overrides : asMap(endpointMethods).values()) {
builder.putAll(getLeafMethod(overrides).getMethod().getName(), overrides);
}
return builder.build();
}
/**
* Recursively builds a map from method_names to methods. Method types will be resolved as much
* as possible using {@code serviceType}.
*
* @param serviceType is the class object being inspected for service methods
*/
private void buildServiceMethods(
ImmutableListMultimap.Builder builder,
TypeToken> serviceType) {
for (TypeToken> typeToken : serviceType.getTypes().classes()) {
Class> serviceClass = typeToken.getRawType();
if (Object.class.equals(serviceClass)) {
return;
}
for (Method method : serviceClass.getDeclaredMethods()) {
if (!isServiceMethod(method)) {
continue;
}
EndpointMethod currentMethod = EndpointMethod.create(endpointClass, method, typeToken);
builder.put(currentMethod.getResolvedMethodSignature(), currentMethod);
}
}
}
@VisibleForTesting
static boolean isServiceMethod(Method method) {
int modifiers = method.getModifiers();
return Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers) && !method.isBridge();
}
}