| /* |
| * 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.user.rebind.rpc; |
| |
| import com.google.gwt.core.ext.GeneratorContext; |
| import com.google.gwt.core.ext.TreeLogger; |
| import com.google.gwt.core.ext.typeinfo.JArrayType; |
| import com.google.gwt.core.ext.typeinfo.JClassType; |
| import com.google.gwt.core.ext.typeinfo.JField; |
| import com.google.gwt.core.ext.typeinfo.JGenericType; |
| import com.google.gwt.core.ext.typeinfo.JParameterizedType; |
| import com.google.gwt.core.ext.typeinfo.JTypeParameter; |
| |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| |
| /** |
| * This class is used to compute type parameter exposure using a flow algorithm. |
| */ |
| class TypeParameterExposureComputer { |
| /** |
| * Helper class for type parameter flow information. |
| */ |
| class TypeParameterFlowInfo { |
| /** |
| * The class that declares this type parameter. |
| */ |
| private final JGenericType baseType; |
| |
| /** |
| * The keys are the set of type parameters that, if exposed, cause this type |
| * parameter to be exposed. The value for each key is the dimensionality |
| * that the exposure will cause. If the key is exposed as an array, then the |
| * dimensionality should be added to the dimensionality that the key is |
| * already exposed as. |
| */ |
| private final Map<TypeParameterFlowInfo, Integer> causesExposure = |
| new LinkedHashMap<TypeParameterFlowInfo, Integer>(); |
| |
| private int exposure = EXPOSURE_NONE; |
| |
| private final Map<TypeParameterFlowInfo, Boolean> isTransitivelyAffectedByCache = |
| new HashMap<TypeParameterFlowInfo, Boolean>(); |
| |
| /** |
| * Type parameters that need to be notified when my exposure changes. |
| */ |
| private final Set<TypeParameterFlowInfo> listeners = new LinkedHashSet<TypeParameterFlowInfo>(); |
| |
| private boolean mightNotBeExposed = true; |
| |
| /** |
| * Ordinal of this type parameter. |
| */ |
| private final int ordinal; |
| |
| private boolean visited; |
| |
| TypeParameterFlowInfo(JGenericType baseType, int ordinal) { |
| this.baseType = baseType; |
| this.ordinal = ordinal; |
| } |
| |
| public boolean checkDirectExposure() { |
| boolean didChange = false; |
| JClassType type = baseType; |
| while (type != null) { |
| // any problems should already have been captured by our caller, so we |
| // make a throw-away ProblemReport here. |
| if (SerializableTypeOracleBuilder.shouldConsiderFieldsForSerialization(type, typeFilter, |
| new ProblemReport())) { |
| JField[] fields = type.getFields(); |
| for (JField field : fields) { |
| if (!SerializableTypeOracleBuilder.shouldConsiderForSerialization(TreeLogger.NULL, |
| context, field)) { |
| continue; |
| } |
| |
| if (field.getType().getLeafType() == getTypeParameter()) { |
| /* |
| * If the type parameter is referenced explicitly or as the leaf |
| * type of an array, then it will be considered directly exposed. |
| */ |
| markExposedAsArray(0); |
| mightNotBeExposed = false; |
| didChange = true; |
| |
| JArrayType fieldTypeAsArray = field.getType().isArray(); |
| if (fieldTypeAsArray != null) { |
| markExposedAsArray(fieldTypeAsArray.getRank()); |
| } |
| } |
| } |
| } |
| |
| /* |
| * Counting on substitution to propagate the type parameter. |
| */ |
| type = type.getSuperclass(); |
| } |
| |
| return didChange; |
| } |
| |
| public Map<TypeParameterFlowInfo, Integer> getCausesExposure() { |
| return causesExposure; |
| } |
| |
| public int getExposure() { |
| return exposure; |
| } |
| |
| public Set<TypeParameterFlowInfo> getListeners() { |
| return listeners; |
| } |
| |
| /** |
| * Return whether it is possible for the parameter not to be exposed. For |
| * example, if a class has one subclass that uses the parameter and another |
| * that does not, then the parameter is exposed (exposure >= |
| * <code>EXPOSURE_DIRECT</code>) but this method will return |
| * <code>false</code>. |
| */ |
| public boolean getMightNotBeExposed() { |
| return mightNotBeExposed; |
| } |
| |
| /** |
| * Determine whether there is an infinite array exposure if this type |
| * parameter is used in an array type which is then passed as an actual type |
| * argument for the formal type parameter <code>other</code>. |
| */ |
| public boolean infiniteArrayExpansionPathBetween(TypeParameterFlowInfo other) { |
| Integer dimensionDelta = getCausesExposure().get(other); |
| if (dimensionDelta == null) { |
| return false; |
| } |
| return dimensionDelta > 0 && other.isTransitivelyAffectedBy(this); |
| } |
| |
| @Override |
| public String toString() { |
| return getTypeParameter().getName() + " in " + baseType.getName(); |
| } |
| |
| public boolean updateFlowInfo() { |
| boolean didChange = false; |
| if (!wasVisited()) { |
| didChange |= initializeExposure(); |
| markVisited(); |
| } |
| |
| for (Entry<TypeParameterFlowInfo, Integer> entry : getCausesExposure().entrySet()) { |
| TypeParameterFlowInfo info2 = entry.getKey(); |
| int dimensionDelta = entry.getValue(); |
| if (info2.getExposure() >= 0) { |
| if (!infiniteArrayExpansionPathBetween(info2)) { |
| didChange |= markExposedAsArray(dimensionDelta + info2.getExposure()); |
| } |
| } |
| } |
| |
| return didChange; |
| } |
| |
| void addListener(TypeParameterFlowInfo listener) { |
| listeners.add(listener); |
| } |
| |
| JTypeParameter getTypeParameter() { |
| return baseType.getTypeParameters()[ordinal]; |
| } |
| |
| boolean initializeExposure() { |
| computeIndirectExposureCauses(); |
| return checkDirectExposure(); |
| } |
| |
| boolean isTransitivelyAffectedBy(TypeParameterFlowInfo flowInfo) { |
| Boolean result = isTransitivelyAffectedByCache.get(flowInfo); |
| if (result != null) { |
| return result; |
| } |
| |
| HashSet<TypeParameterFlowInfo> affectedBy = new HashSet<TypeParameterFlowInfo>(); |
| Set<TypeParameterFlowInfo> affectedByWorklist = new LinkedHashSet<TypeParameterFlowInfo>(); |
| affectedByWorklist.add(this); |
| |
| result = false; |
| while (!affectedByWorklist.isEmpty()) { |
| TypeParameterFlowInfo currFlowInfo = affectedByWorklist.iterator().next(); |
| affectedByWorklist.remove(currFlowInfo); |
| |
| if (currFlowInfo == flowInfo) { |
| result = true; |
| break; |
| } |
| |
| if (affectedBy.add(currFlowInfo)) { |
| affectedByWorklist.addAll(currFlowInfo.getAffectedBy()); |
| } |
| } |
| |
| isTransitivelyAffectedByCache.put(flowInfo, result); |
| return result; |
| } |
| |
| boolean markExposedAsArray(int dim) { |
| if (exposure >= dim) { |
| return false; |
| } |
| |
| exposure = dim; |
| return true; |
| } |
| |
| void markVisited() { |
| visited = true; |
| } |
| |
| boolean wasVisited() { |
| return visited; |
| } |
| |
| private void computeIndirectExposureCauses() { |
| // TODO(spoon): this only needs to consider immediate subtypes, not all |
| // subtypes |
| JClassType[] subtypes = baseType.getSubtypes(); |
| for (JClassType subtype : subtypes) { |
| JGenericType isGeneric = subtype.isGenericType(); |
| if (isGeneric == null) { |
| // Only generic types can cause a type parameter to be exposed |
| continue; |
| } |
| |
| // any problems should already have been captured by our caller, so we |
| // make a throw-away ProblemReport here. |
| if (!SerializableTypeOracleBuilder.shouldConsiderFieldsForSerialization(subtype, |
| typeFilter, new ProblemReport())) { |
| continue; |
| } |
| |
| JParameterizedType asParameterizationOf = subtype.asParameterizationOf(baseType); |
| Set<JTypeParameter> paramsUsed = new LinkedHashSet<JTypeParameter>(); |
| SerializableTypeOracleBuilder.recordTypeParametersIn( |
| asParameterizationOf.getTypeArgs()[ordinal], paramsUsed); |
| |
| for (JTypeParameter paramUsed : paramsUsed) { |
| recordCausesExposure(isGeneric, paramUsed.getOrdinal(), 0); |
| } |
| } |
| |
| JClassType type = baseType; |
| while (type != null) { |
| if (SerializableTypeOracleBuilder.shouldConsiderFieldsForSerialization(type, typeFilter, |
| new ProblemReport())) { |
| JField[] fields = type.getFields(); |
| for (JField field : fields) { |
| if (!SerializableTypeOracleBuilder.shouldConsiderForSerialization(TreeLogger.NULL, |
| context, field)) { |
| continue; |
| } |
| |
| JParameterizedType isParameterized = field.getType().getLeafType().isParameterized(); |
| if (isParameterized == null) { |
| continue; |
| } |
| |
| JClassType[] typeArgs = isParameterized.getTypeArgs(); |
| for (int i = 0; i < typeArgs.length; ++i) { |
| if (referencesTypeParameter(typeArgs[i], getTypeParameter())) { |
| JGenericType genericFieldType = isParameterized.getBaseType(); |
| recordCausesExposure(genericFieldType, i, 0); |
| JArrayType typeArgIsArray = typeArgs[i].isArray(); |
| if (typeArgIsArray != null && typeArgIsArray.getLeafType() == getTypeParameter()) { |
| int dims = typeArgIsArray.getRank(); |
| recordCausesExposure(genericFieldType, i, dims); |
| } |
| } |
| } |
| } |
| } |
| |
| /* |
| * Counting on substitution to propagate the type parameter. |
| */ |
| type = type.getSuperclass(); |
| } |
| } |
| |
| private Collection<? extends TypeParameterFlowInfo> getAffectedBy() { |
| return causesExposure.keySet(); |
| } |
| |
| /** |
| * The same as |
| * {@link TypeParameterExposureComputer#getFlowInfo(JGenericType, int)}, |
| * except that it additionally adds <code>this</code> as a listener to the |
| * returned flow info. |
| */ |
| private TypeParameterFlowInfo getFlowInfo(JGenericType type, int index) { |
| TypeParameterFlowInfo flowInfo = TypeParameterExposureComputer.this.getFlowInfo(type, index); |
| flowInfo.addListener(this); |
| return flowInfo; |
| } |
| |
| private void recordCausesExposure(JGenericType type, int index, int level) { |
| assert (index < type.getTypeParameters().length); |
| TypeParameterFlowInfo flowInfo = getFlowInfo(type, index); |
| Integer oldLevel = causesExposure.get(flowInfo); |
| if (oldLevel == null || oldLevel < level) { |
| causesExposure.put(flowInfo, level); |
| } |
| } |
| |
| private boolean referencesTypeParameter(JClassType classType, JTypeParameter typeParameter) { |
| Set<JTypeParameter> typeParameters = new LinkedHashSet<JTypeParameter>(); |
| SerializableTypeOracleBuilder.recordTypeParametersIn(classType, typeParameters); |
| return typeParameters.contains(typeParameter); |
| } |
| } |
| |
| /** |
| * Type parameter is exposed. |
| */ |
| static final int EXPOSURE_DIRECT = 0; |
| |
| /** |
| * Type parameter is exposed as a bounded array. The value is the max bound of |
| * the exposure. |
| */ |
| static final int EXPOSURE_MIN_BOUNDED_ARRAY = EXPOSURE_DIRECT + 1; |
| |
| /** |
| * Type parameter is not exposed. |
| */ |
| static final int EXPOSURE_NONE = -1; |
| |
| private final GeneratorContext context; |
| |
| private TypeFilter typeFilter; |
| |
| private final Map<JTypeParameter, TypeParameterFlowInfo> typeParameterToFlowInfo = |
| new IdentityHashMap<JTypeParameter, TypeParameterFlowInfo>(); |
| |
| private final Set<TypeParameterFlowInfo> worklist = new LinkedHashSet<TypeParameterFlowInfo>(); |
| |
| TypeParameterExposureComputer(GeneratorContext context, TypeFilter typeFilter) { |
| this.context = context; |
| this.typeFilter = typeFilter; |
| } |
| |
| /** |
| * Computes flow information for the specified type parameter. If it has |
| * already been computed just return the value of the previous computation. |
| * |
| * @param type the generic type whose type parameter flow we are interested in |
| * @param index the index of the type parameter whose flow we want to compute |
| */ |
| public TypeParameterFlowInfo computeTypeParameterExposure(JGenericType type, int index) { |
| // check if it has already been computed |
| JTypeParameter[] typeParameters = type.getTypeParameters(); |
| assert (index < typeParameters.length); |
| JTypeParameter typeParameter = typeParameters[index]; |
| TypeParameterFlowInfo queryFlow = typeParameterToFlowInfo.get(typeParameter); |
| if (queryFlow != null) { |
| return queryFlow; |
| } |
| |
| // not already computed; compute it |
| queryFlow = getFlowInfo(type, index); // adds it to the work list as a |
| // side effect |
| |
| while (!worklist.isEmpty()) { |
| TypeParameterFlowInfo info = worklist.iterator().next(); |
| worklist.remove(info); |
| |
| boolean didChange = info.updateFlowInfo(); |
| |
| if (didChange) { |
| for (TypeParameterFlowInfo listener : info.getListeners()) { |
| worklist.add(listener); |
| } |
| } |
| } |
| |
| return queryFlow; |
| } |
| |
| public void setTypeFilter(TypeFilter typeFilter) { |
| this.typeFilter = typeFilter; |
| } |
| |
| /** |
| * Return the parameter flow info for a type parameter specified by class and |
| * index. If the flow info did not previously exist, create it and add it to |
| * the work list. |
| */ |
| private TypeParameterFlowInfo getFlowInfo(JGenericType type, int index) { |
| JTypeParameter typeParameter = type.getTypeParameters()[index]; |
| TypeParameterFlowInfo info = typeParameterToFlowInfo.get(typeParameter); |
| if (info == null) { |
| info = new TypeParameterFlowInfo(type, index); |
| typeParameterToFlowInfo.put(typeParameter, info); |
| worklist.add(info); |
| } |
| return info; |
| } |
| } |