| /* |
| * 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.place.rebind; |
| |
| 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 com.google.gwt.place.shared.PlaceHistoryMapperWithFactory; |
| import com.google.gwt.place.shared.PlaceTokenizer; |
| import com.google.gwt.place.shared.Prefix; |
| import com.google.gwt.place.shared.WithTokenizers; |
| |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.LinkedHashSet; |
| import java.util.Set; |
| import java.util.TreeMap; |
| |
| class PlaceHistoryGeneratorContext { |
| /** |
| * Creates a {@link PlaceHistoryGeneratorContext} for the given |
| * {@link PlaceHistoryMapper} sub-interface. |
| * |
| * @return a {@link PlaceHistoryGeneratorContext}, or <code>null</code> if the |
| * generator should not run (i.e. <code>interfaceName</code> is not an |
| * interface) |
| * @throws UnableToCompleteException if the type denoted by |
| * <code>interfaceName</code> cannot be found in |
| * <code>typeOracle</code> |
| */ |
| static PlaceHistoryGeneratorContext create(TreeLogger logger, |
| TypeOracle typeOracle, String interfaceName) |
| throws UnableToCompleteException { |
| JClassType stringType = requireType(typeOracle, String.class); |
| JClassType placeTokenizerType = requireType(typeOracle, |
| PlaceTokenizer.class); |
| JClassType placeHistoryMapperWithFactoryType = requireType(typeOracle, |
| PlaceHistoryMapperWithFactory.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) { |
| return null; |
| } |
| |
| factoryType = findFactoryType(placeHistoryMapperWithFactoryType, |
| interfaceType); |
| |
| String implName = interfaceType.getName().replace(".", "_") + "Impl"; |
| |
| return new PlaceHistoryGeneratorContext(logger, typeOracle, interfaceType, |
| factoryType, stringType, placeTokenizerType, |
| interfaceType.getPackage().getName(), implName); |
| } |
| |
| private static JClassType findFactoryType( |
| JClassType placeHistoryMapperWithFactoryType, JClassType interfaceType) { |
| JClassType superInterfaces[] = interfaceType.getImplementedInterfaces(); |
| |
| for (JClassType superInterface : superInterfaces) { |
| JParameterizedType parameterizedType = superInterface.isParameterized(); |
| if (parameterizedType != null |
| && parameterizedType.getBaseType().equals( |
| placeHistoryMapperWithFactoryType)) { |
| 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 TreeLogger logger; |
| final TypeOracle typeOracle; |
| final JClassType interfaceType; |
| final JClassType factoryType; |
| |
| final String implName; |
| |
| final String packageName; |
| |
| /** |
| * All tokenizers, either as a {@link JMethod} for factory getters or as a |
| * {@link JClassType} for types that must be GWT.create()d, by prefix. |
| */ |
| private HashMap<String, Object> tokenizers; |
| |
| /** |
| * All place types and the prefix of their associated tokenizer, ordered from |
| * most-derived to least-derived type (and falling back to the natural |
| * ordering of their names). |
| */ |
| private TreeMap<JClassType, String> placeTypes = new TreeMap<JClassType, String>( |
| new MostToLeastDerivedPlaceTypeComparator()); |
| |
| PlaceHistoryGeneratorContext(TreeLogger logger, TypeOracle typeOracle, |
| JClassType interfaceType, JClassType factoryType, JClassType stringType, |
| JClassType placeTokenizerType, String packageName, String implName) { |
| this.logger = logger; |
| this.typeOracle = typeOracle; |
| this.interfaceType = interfaceType; |
| this.factoryType = factoryType; |
| this.stringType = stringType; |
| this.placeTokenizerType = placeTokenizerType; |
| this.packageName = packageName; |
| this.implName = implName; |
| } |
| |
| public Set<JClassType> getPlaceTypes() throws UnableToCompleteException { |
| ensureInitialized(); |
| return placeTypes.keySet(); |
| } |
| |
| public String getPrefix(JClassType placeType) |
| throws UnableToCompleteException { |
| ensureInitialized(); |
| return placeTypes.get(placeType); |
| } |
| |
| public Set<String> getPrefixes() throws UnableToCompleteException { |
| ensureInitialized(); |
| return tokenizers.keySet(); |
| } |
| |
| public JMethod getTokenizerGetter(String prefix) |
| throws UnableToCompleteException { |
| ensureInitialized(); |
| Object tokenizerGetter = tokenizers.get(prefix); |
| if (tokenizerGetter instanceof JMethod) { |
| return (JMethod) tokenizerGetter; |
| } |
| return null; |
| } |
| |
| public JClassType getTokenizerType(String prefix) |
| throws UnableToCompleteException { |
| ensureInitialized(); |
| Object tokenizerType = tokenizers.get(prefix); |
| if (tokenizerType instanceof JClassType) { |
| return (JClassType) tokenizerType; |
| } |
| return null; |
| } |
| |
| void ensureInitialized() throws UnableToCompleteException { |
| if (tokenizers == null) { |
| assert placeTypes.isEmpty(); |
| tokenizers = new HashMap<String, Object>(); |
| initTokenizerGetters(); |
| initTokenizersWithoutGetters(); |
| } |
| } |
| |
| private void addPlaceTokenizer(Object tokenizerClassOrGetter, String prefix, |
| JClassType tokenizerType) throws UnableToCompleteException { |
| if (prefix.contains(":")) { |
| logger.log(TreeLogger.ERROR, String.format( |
| "Found place prefix \"%s\" containing separator char \":\", on %s", |
| prefix, getLogMessage(tokenizerClassOrGetter))); |
| throw new UnableToCompleteException(); |
| } |
| if (tokenizers.containsKey(prefix)) { |
| logger.log(TreeLogger.ERROR, String.format( |
| "Found duplicate place prefix \"%s\" on %s, already seen on %s", |
| prefix, getLogMessage(tokenizerClassOrGetter), |
| getLogMessage(tokenizers.get(prefix)))); |
| throw new UnableToCompleteException(); |
| } |
| JClassType placeType = getPlaceTypeForTokenizerType(tokenizerType); |
| if (placeTypes.containsKey(placeType)) { |
| logger.log( |
| TreeLogger.ERROR, |
| String.format( |
| "Found duplicate tokenizer's place type \"%s\" on %s, already seen on %s", |
| placeType.getQualifiedSourceName(), |
| getLogMessage(tokenizerClassOrGetter), |
| getLogMessage(tokenizers.get(placeTypes.get(placeType))))); |
| throw new UnableToCompleteException(); |
| } |
| tokenizers.put(prefix, tokenizerClassOrGetter); |
| placeTypes.put(placeType, prefix); |
| } |
| |
| private String getLogMessage(Object methodOrClass) { |
| if (methodOrClass instanceof JMethod) { |
| JMethod method = (JMethod) methodOrClass; |
| return method.getEnclosingType().getQualifiedSourceName() + "#" |
| + method.getName() + "()"; |
| } |
| JClassType classType = (JClassType) methodOrClass; |
| return classType.getQualifiedSourceName(); |
| } |
| |
| private JClassType getPlaceTypeForTokenizerType(JClassType tokenizerType) |
| throws UnableToCompleteException { |
| JClassType rtn = placeTypeForInterfaces(tokenizerType.getFlattenedSupertypeHierarchy()); |
| 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 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 = typeOracle.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 void initTokenizerGetters() throws UnableToCompleteException { |
| if (factoryType != null) { |
| |
| // TODO: include non-public methods that are nevertheless accessible |
| // to the interface (package-scoped); |
| // Add a isCallable(JClassType) method to JAbstractMethod? |
| for (JMethod method : factoryType.getInheritableMethods()) { |
| if (!method.isPublic()) { |
| continue; |
| } |
| if (method.getParameters().length > 0) { |
| continue; |
| } |
| |
| JClassType returnType = method.getReturnType().isClassOrInterface(); |
| |
| if (returnType == null) { |
| continue; |
| } |
| |
| if (!placeTokenizerType.isAssignableFrom(returnType)) { |
| continue; |
| } |
| |
| addPlaceTokenizer(method, getPrefixForTokenizerGetter(method), |
| method.getReturnType().isClassOrInterface()); |
| } |
| } |
| } |
| |
| private void initTokenizersWithoutGetters() throws UnableToCompleteException { |
| for (JClassType tokenizerType : getWithTokenizerEntries()) { |
| addPlaceTokenizer(tokenizerType, |
| getPrefixForTokenizerType(tokenizerType), tokenizerType); |
| } |
| } |
| |
| private JClassType placeTypeForInterfaces(Collection<? extends 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; |
| } |
| } |