    001// Licensed under the Apache License, Version 2.0 (the "License");
    002// you may not use this file except in compliance with the License.
    003// You may obtain a copy of the License at
    004//
    005// http://www.apache.org/licenses/LICENSE-2.0
    006//
    007// Unless required by applicable law or agreed to in writing, software
    008// distributed under the License is distributed on an "AS IS" BASIS,
    009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    010// See the License for the specific language governing permissions and
    011// limitations under the License.
    012
    013package org.apache.tapestry5.internal.plastic;
    014
    015import java.util.Map;
    016import java.util.Set;
    017
    018/**
    019 * Used to track which methods are implemented by a base class, which is often needed when transforming
    020 * a subclass.
    021 */
    022public class InheritanceData
    023{
    024    private final InheritanceData parent;
    025
    026    private final String packageName;
    027
    028    private final Set<String> methodNames = PlasticInternalUtils.newSet();
    029    private final Map<String, Boolean> methods = PlasticInternalUtils.newMap();
    030    private final Set<String> interfaceNames = PlasticInternalUtils.newSet();
    031
    032    public InheritanceData(String packageName)
    033    {
    034        this(null, packageName);
    035    }
    036
    037    private InheritanceData(InheritanceData parent, String packageName)
    038    {
    039        this.parent = parent;
    040        this.packageName = packageName;
    041    }
    042
    043    /**
    044     * Is this bundle for a transformed class, or for a base class (typically Object)?
    045     *
    046     * @return true if this bundle is for transformed class, false otherwise
    047     */
    048    public boolean isTransformed()
    049    {
    050        return parent != null;
    051    }
    052
    053    /**
    054     * Returns a new MethodBundle that represents the methods of a child class
    055     * of this bundle. The returned bundle will always be {@linkplain #isTransformed() transformed}.
    056     *
    057     * @param packageName
    058     *         the package that the child class will be created in
    059     * @return new method bundle
    060     */
    061    public InheritanceData createChild(String packageName)
    062    {
    063        return new InheritanceData(this, packageName);
    064    }
    065
    066    /**
    067     * Adds a new instance method. Only non-private methods should be added (that is, methods which might
    068     * be overridden in subclasses). This can later be queried to see if any base class implements the method.
    069     *
    070     * @param name
    071     *         name of method
    072     * @param desc
    073     *         describes the parameters and return value of the method
    074     * @param samePackageOnly
    075     *         whether the method can only be overridden in classes that are in the same package
    076     */
    077    public void addMethod(String name, String desc, boolean samePackageOnly)
    078    {
    079        methods.put(toValue(name, desc), samePackageOnly);
    080        methodNames.add(name);
    081    }
    082
    083
    084    /**
    085     * Returns true if this class or a transformed parent class contains an implementation of,
    086     * or abstract placeholder for, the method.
    087     *
    088     * @param name
    089     *         method name
    090     * @param desc
    091     *         method descriptor
    092     * @return true if this class or a base class implements the method (including abstract methods)
    093     */
    094    public boolean isImplemented(String name, String desc)
    095    {
    096        return checkForMethod(toValue(name, desc), this);
    097    }
    098
    099    /**
    100     * Returns true if the method is an override of a base class method
    101     *
    102     * @param name
    103     *         method name
    104     * @param desc
    105     *         method descriptor
    106     * @return true if a base class implements the method (including abstract methods)
    107     */
    108    public boolean isOverride(String name, String desc)
    109    {
    110        return checkForMethod(toValue(name, desc), parent);
    111    }
    112
    113    private boolean checkForMethod(String value, InheritanceData cursor)
    114    {
    115
    116        String thisPackageName = packageName;
    117
    118        while (cursor != null)
    119        {
    120            if (cursor.methods.containsKey(value))
    121            {
    122                boolean mustBeInSamePackage = cursor.methods.get(value);
    123
    124                if (!mustBeInSamePackage)
    125                {
    126                    return true;
    127                }
    128                boolean isInSamePackage = thisPackageName.equals(cursor.packageName);
    129
    130                if (isInSamePackage)
    131                {
    132                    return true;
    133                }
    134            }
    135
    136            cursor = cursor.parent;
    137        }
    138
    139        return false;
    140    }
    141
    142    /**
    143     * Returns true if the class represented by this data, or any parent data, implements
    144     * the named interface.
    145     */
    146    public boolean isInterfaceImplemented(String name)
    147    {
    148        InheritanceData cursor = this;
    149
    150        while (cursor != null)
    151        {
    152            if (cursor.interfaceNames.contains(name))
    153            {
    154                return true;
    155            }
    156
    157            cursor = cursor.parent;
    158        }
    159
    160        return false;
    161    }
    162
    163    public void addInterface(String name)
    164    {
    165        if (!interfaceNames.contains(name))
    166        {
    167            interfaceNames.add(name);
    168        }
    169    }
    170
    171    /**
    172     * Combines a method name and its desc (which describes parameter types and return value) to form
    173     * a value, which is how methods are tracked.
    174     */
    175    private static String toValue(String name, String desc)
    176    {
    177        // TAP5-2268: ignore return-type to avoid methods with the same number (and type) of parameters but different
    178        //            return-types which is illegal in Java.
    179        // desc is something like "(I)Ljava/lang/String;", which means: takes an int, returns a String. We strip
    180        // everything after the parameter list.
    181        int endOfParameterSpecIdx = desc.indexOf(')');
    182
    183        return (name + ":" + desc.substring(0, endOfParameterSpecIdx+1)).intern();
    184    }
    185
    186    /**
    187     * Returns the names of any methods in this bundle, or from any parent bundles.
    188     */
    189    public Set<String> methodNames()
    190    {
    191        Set<String> result = PlasticInternalUtils.newSet();
    192
    193        InheritanceData cursor = this;
    194
    195        while (cursor != null)
    196        {
    197            result.addAll(cursor.methodNames);
    198            cursor = cursor.parent;
    199        }
    200
    201        return result;
    202    }
    203}