| // 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 org.apache.tapestry5.internal.plastic; |
| |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Used to track which methods are implemented by a base class, which is often needed when transforming |
| * a subclass. |
| */ |
| public class InheritanceData |
| { |
| private final InheritanceData parent; |
| |
| private final String packageName; |
| |
| private final Set<String> methodNames = PlasticInternalUtils.newSet(); |
| private final Map<String, Boolean> methods = PlasticInternalUtils.newMap(); |
| private final Set<String> interfaceNames = PlasticInternalUtils.newSet(); |
| |
| public InheritanceData(String packageName) |
| { |
| this(null, packageName); |
| } |
| |
| private InheritanceData(InheritanceData parent, String packageName) |
| { |
| this.parent = parent; |
| this.packageName = packageName; |
| } |
| |
| /** |
| * Is this bundle for a transformed class, or for a base class (typically Object)? |
| * |
| * @return true if this bundle is for transformed class, false otherwise |
| */ |
| public boolean isTransformed() |
| { |
| return parent != null; |
| } |
| |
| /** |
| * Returns a new MethodBundle that represents the methods of a child class |
| * of this bundle. The returned bundle will always be {@linkplain #isTransformed() transformed}. |
| * |
| * @param packageName |
| * the package that the child class will be created in |
| * @return new method bundle |
| */ |
| public InheritanceData createChild(String packageName) |
| { |
| return new InheritanceData(this, packageName); |
| } |
| |
| /** |
| * Adds a new instance method. Only non-private methods should be added (that is, methods which might |
| * be overridden in subclasses). This can later be queried to see if any base class implements the method. |
| * |
| * @param name |
| * name of method |
| * @param desc |
| * describes the parameters and return value of the method |
| * @param samePackageOnly |
| * whether the method can only be overridden in classes that are in the same package |
| */ |
| public void addMethod(String name, String desc, boolean samePackageOnly) |
| { |
| methods.put(toValue(name, desc), samePackageOnly); |
| methodNames.add(name); |
| } |
| |
| |
| /** |
| * Returns true if this class or a transformed parent class contains an implementation of, |
| * or abstract placeholder for, the method. |
| * |
| * @param name |
| * method name |
| * @param desc |
| * method descriptor |
| * @return true if this class or a base class implements the method (including abstract methods) |
| */ |
| public boolean isImplemented(String name, String desc) |
| { |
| return checkForMethod(toValue(name, desc), this); |
| } |
| |
| /** |
| * Returns true if the method is an override of a base class method |
| * |
| * @param name |
| * method name |
| * @param desc |
| * method descriptor |
| * @return true if a base class implements the method (including abstract methods) |
| */ |
| public boolean isOverride(String name, String desc) |
| { |
| return checkForMethod(toValue(name, desc), parent); |
| } |
| |
| private boolean checkForMethod(String value, InheritanceData cursor) |
| { |
| |
| String thisPackageName = packageName; |
| |
| while (cursor != null) |
| { |
| if (cursor.methods.containsKey(value)) |
| { |
| boolean mustBeInSamePackage = cursor.methods.get(value); |
| |
| if (!mustBeInSamePackage) |
| { |
| return true; |
| } |
| boolean isInSamePackage = thisPackageName.equals(cursor.packageName); |
| |
| if (isInSamePackage) |
| { |
| return true; |
| } |
| } |
| |
| cursor = cursor.parent; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Returns true if the class represented by this data, or any parent data, implements |
| * the named interface. |
| */ |
| public boolean isInterfaceImplemented(String name) |
| { |
| InheritanceData cursor = this; |
| |
| while (cursor != null) |
| { |
| if (cursor.interfaceNames.contains(name)) |
| { |
| return true; |
| } |
| |
| cursor = cursor.parent; |
| } |
| |
| return false; |
| } |
| |
| public void addInterface(String name) |
| { |
| if (!interfaceNames.contains(name)) |
| { |
| interfaceNames.add(name); |
| } |
| } |
| |
| /** |
| * Combines a method name and its desc (which describes parameter types and return value) to form |
| * a value, which is how methods are tracked. |
| */ |
| private static String toValue(String name, String desc) |
| { |
| // TAP5-2268: ignore return-type to avoid methods with the same number (and type) of parameters but different |
| // return-types which is illegal in Java. |
| // desc is something like "(I)Ljava/lang/String;", which means: takes an int, returns a String. We strip |
| // everything after the parameter list. |
| int endOfParameterSpecIdx = desc.indexOf(')'); |
| |
| return name + ":" + desc.substring(0, endOfParameterSpecIdx+1); |
| } |
| |
| /** |
| * Returns the names of any methods in this bundle, or from any parent bundles. |
| */ |
| public Set<String> methodNames() |
| { |
| Set<String> result = PlasticInternalUtils.newSet(); |
| |
| InheritanceData cursor = this; |
| |
| while (cursor != null) |
| { |
| result.addAll(cursor.methodNames); |
| cursor = cursor.parent; |
| } |
| |
| return result; |
| } |
| } |