blob: 7428fdcf5383c9eff3266c24e1e1a28f963b2318 [file] [log] [blame]
/*
* Copyright 2010 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.app.rebind;
import com.google.gwt.app.place.PlaceHistoryHandlerWithFactory;
import com.google.gwt.app.place.PlaceTokenizer;
import com.google.gwt.app.place.Prefix;
import com.google.gwt.app.place.WithTokenizers;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
class PlaceHistoryGeneratorContext {
static PlaceHistoryGeneratorContext create(TreeLogger logger,
GeneratorContext generatorContext, String interfaceName)
throws UnableToCompleteException {
TypeOracle typeOracle = generatorContext.getTypeOracle();
JClassType stringType = requireType(typeOracle, String.class);
JClassType placeTokenizerType = requireType(typeOracle,
PlaceTokenizer.class);
JClassType placeHistoryHandlerWithFactoryType = requireType(typeOracle,
PlaceHistoryHandlerWithFactory.class);
JClassType factoryType;
JClassType interfaceType = typeOracle.findType(interfaceName);
if (interfaceType == null) {
logger.log(TreeLogger.ERROR, "Could not find requested typeName: "
+ interfaceName);
throw new UnableToCompleteException();
}
if (interfaceType.isInterface() == null) {
logger.log(TreeLogger.ERROR, interfaceType.getQualifiedSourceName()
+ " is not an interface.", null);
throw new UnableToCompleteException();
}
factoryType = findFactoryType(placeHistoryHandlerWithFactoryType,
interfaceType);
String implName = interfaceType.getName().replace(".", "_") + "Impl";
return new PlaceHistoryGeneratorContext(logger, generatorContext,
interfaceType, factoryType, stringType, placeTokenizerType,
placeHistoryHandlerWithFactoryType,
interfaceType.getPackage().getName(), implName);
}
private static JClassType findFactoryType(
JClassType placeHistoryHandlerWithFactoryType, JClassType interfaceType) {
JClassType superInterfaces[] = interfaceType.getImplementedInterfaces();
for (JClassType superInterface : superInterfaces) {
JParameterizedType parameterizedType = superInterface.isParameterized();
if (parameterizedType != null
&& parameterizedType.getBaseType().equals(
placeHistoryHandlerWithFactoryType)) {
return parameterizedType.getTypeArgs()[0];
}
}
return null;
}
private static JClassType requireType(TypeOracle typeOracle, Class<?> clazz) {
try {
return typeOracle.getType(clazz.getName());
} catch (NotFoundException e) {
throw new RuntimeException(e);
}
}
final JClassType stringType;
final JClassType placeTokenizerType;
final JClassType placeHistoryHandlerType;
final TreeLogger logger;
final GeneratorContext generatorContext;
final JClassType interfaceType;
final JClassType factoryType;
final String implName;
final String packageName;
/**
* All factory getters that can provide tokenizers, by prefix.
*/
private LinkedHashMap<String, JMethod> tokenizerGetters;
/**
* All tokenizer types that must be GWT.create()d, by prefix.
*/
private LinkedHashMap<String, JClassType> tokenizersWithNoGetters;
/**
* Cache of all tokenizer types, union of the entries in tokenizerGetters and
* tokenizersWithNoGetters.
*/
private LinkedHashMap<String, JClassType> tokenizerTypes;
PlaceHistoryGeneratorContext(TreeLogger logger,
GeneratorContext generatorContext, JClassType interfaceType,
JClassType factoryType, JClassType stringType,
JClassType placeTokenizerType, JClassType placeHistoryHandlerType,
String packageName, String implName) {
this.logger = logger;
this.generatorContext = generatorContext;
this.interfaceType = interfaceType;
this.factoryType = factoryType;
this.stringType = stringType;
this.placeTokenizerType = placeTokenizerType;
this.placeHistoryHandlerType = placeHistoryHandlerType;
this.packageName = packageName;
this.implName = implName;
}
public JClassType getPlaceType(String prefix)
throws UnableToCompleteException {
return getPlaceTypeForTokenizerType(getTokenizerType(prefix));
}
public Set<String> getPrefixes() throws UnableToCompleteException {
return getTokenizerTypes().keySet();
}
public JMethod getTokenizerGetter(String prefix)
throws UnableToCompleteException {
return getTokenizerGetters().get(prefix);
}
public JClassType getTokenizerType(String prefix)
throws UnableToCompleteException {
JMethod getter = getTokenizerGetters().get(prefix);
if (getter != null) {
return getter.getReturnType().isClassOrInterface();
}
return getTokenizersWihoutGetters().get(prefix);
}
public Map<String, JClassType> getTokenizerTypes()
throws UnableToCompleteException {
if (tokenizerTypes == null) {
tokenizerTypes = new LinkedHashMap<String, JClassType>();
for (Entry<String, JMethod> entry : getTokenizerGetters().entrySet()) {
tokenizerTypes.put(entry.getKey(),
entry.getValue().getReturnType().isClassOrInterface());
}
for (Entry<String, JClassType> entry : getTokenizersWihoutGetters().entrySet()) {
tokenizerTypes.put(entry.getKey(), entry.getValue());
}
}
return tokenizerTypes;
}
public boolean hasNonFactoryTokenizer() throws UnableToCompleteException {
return !getTokenizersWihoutGetters().isEmpty();
}
private JClassType getPlaceTypeForTokenizerType(JClassType tokenizerType)
throws UnableToCompleteException {
List<JClassType> implementedInterfaces = new ArrayList<JClassType>();
JClassType isInterface = tokenizerType.isInterface();
if (isInterface != null) {
implementedInterfaces.add(isInterface);
}
implementedInterfaces.addAll(Arrays.asList(tokenizerType.getImplementedInterfaces()));
JClassType rtn = placeTypeForInterfaces(implementedInterfaces);
if (rtn == null) {
logger.log(TreeLogger.ERROR, "Found no Place type for "
+ tokenizerType.getQualifiedSourceName());
throw new UnableToCompleteException();
}
return rtn;
}
private String getPrefixForTokenizerGetter(JMethod method)
throws UnableToCompleteException {
Prefix annotation = method.getAnnotation(Prefix.class);
if (annotation != null) {
return annotation.value();
}
JClassType returnType = method.getReturnType().isClassOrInterface();
return getPrefixForTokenizerType(returnType);
}
private String getPrefixForTokenizerType(JClassType returnType)
throws UnableToCompleteException {
Prefix annotation;
annotation = returnType.getAnnotation(Prefix.class);
if (annotation != null) {
return annotation.value();
}
return getPlaceTypeForTokenizerType(returnType).getName();
}
private Map<String, JMethod> getTokenizerGetters()
throws UnableToCompleteException {
if (factoryType == null) {
return Collections.emptyMap();
}
if (tokenizerGetters == null) {
tokenizerGetters = new LinkedHashMap<String, JMethod>();
/* Gets inherited methods, but not final ones */
JMethod[] overridableMethods = factoryType.getOverridableMethods();
/* So we pick up the finals here */
JMethod[] methods = factoryType.getMethods();
LinkedHashSet<JMethod> allMethods = new LinkedHashSet<JMethod>();
allMethods.addAll(Arrays.asList(overridableMethods));
for (JMethod method : methods) {
if (method.isPublic()) {
allMethods.add(method);
}
}
for (JMethod method : allMethods) {
if (method.getParameters().length > 0) {
continue;
}
JClassType returnType = method.getReturnType().isClassOrInterface();
if (returnType == null) {
continue;
}
if (!placeTokenizerType.isAssignableFrom(returnType)) {
continue;
}
String prefix = getPrefixForTokenizerGetter(method);
if (tokenizerGetters.containsKey(prefix)) {
logger.log(TreeLogger.ERROR, String.format(
"Found duplicate place prefix \"%s\" in factory type %s, "
+ "used by both %s and %s", prefix,
factoryType.getQualifiedSourceName(),
tokenizerGetters.get(prefix).getName(), method.getName()));
throw new UnableToCompleteException();
}
tokenizerGetters.put(prefix, method);
}
}
return tokenizerGetters;
}
private HashMap<String, JClassType> getTokenizersWihoutGetters()
throws UnableToCompleteException {
if (tokenizersWithNoGetters == null) {
tokenizersWithNoGetters = new LinkedHashMap<String, JClassType>();
for (JClassType tokenizerType : getWithTokenizerEntries()) {
tokenizersWithNoGetters.put(getPrefixForTokenizerType(tokenizerType),
tokenizerType);
}
}
return tokenizersWithNoGetters;
}
private Set<JClassType> getWithTokenizerEntries() {
WithTokenizers annotation = interfaceType.getAnnotation(WithTokenizers.class);
if (annotation == null) {
return Collections.emptySet();
}
LinkedHashSet<JClassType> rtn = new LinkedHashSet<JClassType>();
for (Class<? extends PlaceTokenizer<?>> tokenizerClass : annotation.value()) {
JClassType tokenizerType = generatorContext.getTypeOracle().findType(
tokenizerClass.getCanonicalName());
if (tokenizerType == null) {
logger.log(TreeLogger.ERROR, String.format(
"Error processing @%s, cannot find type %s",
WithTokenizers.class.getSimpleName(),
tokenizerClass.getCanonicalName()));
}
rtn.add(tokenizerType);
}
return rtn;
}
private JClassType placeTypeForInterfaces(Collection<JClassType> interfaces) {
JClassType rtn = null;
for (JClassType i : interfaces) {
JParameterizedType parameterizedType = i.isParameterized();
if (parameterizedType != null
&& placeTokenizerType.equals(parameterizedType.getBaseType())) {
rtn = parameterizedType.getTypeArgs()[0];
}
}
return rtn;
}
}