    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.Set;
    016
    017/**
    018 * Used to track which methods are implemented by a base class, which is often needed when transforming
    019 * a subclass.
    020 */
    021public class InheritanceData
    022{
    023    private final InheritanceData parent;
    024
    025    private final Set<String> methodNames = PlasticInternalUtils.newSet();
    026    private final Set<String> methods = PlasticInternalUtils.newSet();
    027    private final Set<String> interfaceNames = PlasticInternalUtils.newSet();
    028
    029    public InheritanceData()
    030    {
    031        this(null);
    032    }
    033
    034    private InheritanceData(InheritanceData parent)
    035    {
    036        this.parent = parent;
    037    }
    038
    039    /**
    040     * Is this bundle for a transformed class, or for a base class (typically Object)?
    041     *
    042     * @return true if this bundle is for transformed class, false otherwise
    043     */
    044    public boolean isTransformed()
    045    {
    046        return parent != null;
    047    }
    048
    049    /**
    050     * Returns a new MethodBundle that represents the methods of a child class
    051     * of this bundle. The returned bundle will always be {@linkplain #isTransformed() transformed}.
    052     *
    053     * @return new method bundle
    054     */
    055    public InheritanceData createChild()
    056    {
    057        return new InheritanceData(this);
    058    }
    059
    060    /**
    061     * Adds a new instance method. Only non-private methods should be added (that is, methods which might
    062     * be overridden in subclasses). This can later be queried to see if any base class implements the method.
    063     *
    064     * @param name
    065     *         name of method
    066     * @param desc
    067     *         describes the parameters and return value of the method
    068     */
    069    public void addMethod(String name, String desc)
    070    {
    071        methods.add(toValue(name, desc));
    072        methodNames.add(name);
    073    }
    074
    075
    076    /**
    077     * Returns true if a transformed parent class contains an implementation of, or abstract placeholder for, the method.
    078     *
    079     * @param name
    080     *         method name
    081     * @param desc
    082     *         method descriptor
    083     * @return true if a base class implements the method (including abstract methods)
    084     */
    085    public boolean isImplemented(String name, String desc)
    086    {
    087        return checkForMethod(toValue(name, desc));
    088    }
    089
    090    private boolean checkForMethod(String value)
    091    {
    092        InheritanceData cursor = this;
    093
    094        while (cursor != null)
    095        {
    096            if (cursor.methods.contains(value))
    097            {
    098                return true;
    099            }
    100
    101            cursor = cursor.parent;
    102        }
    103
    104        return false;
    105    }
    106
    107    /**
    108     * Returns true if the class represented by this data, or any parent data, implements
    109     * the named interface.
    110     */
    111    public boolean isInterfaceImplemented(String name)
    112    {
    113        InheritanceData cursor = this;
    114
    115        while (cursor != null)
    116        {
    117            if (cursor.interfaceNames.contains(name))
    118            {
    119                return true;
    120            }
    121
    122            cursor = cursor.parent;
    123        }
    124
    125        return false;
    126    }
    127
    128    public void addInterface(String name)
    129    {
    130        if (!interfaceNames.contains(name))
    131        {
    132            interfaceNames.add(name);
    133        }
    134    }
    135
    136    /**
    137     * Combines a method name and its desc (which describes parameter types and return value) to form
    138     * a value, which is how methods are tracked.
    139     */
    140    private static String toValue(String name, String desc)
    141    {
    142        // TAP5-2268: ignore return-type to avoid methods with the same number (and type) of parameters but different
    143        //            return-types which is illegal in Java.
    144        // desc is something like "(I)Ljava/lang/String;", which means: takes an int, returns a String. We strip
    145        // everything after the parameter list.
    146        int endOfParameterSpecIdx = desc.indexOf(')');
    147
    148        return name + ":" + desc.substring(0, endOfParameterSpecIdx+1);
    149    }
    150
    151    /**
    152     * Returns the names of any methods in this bundle, or from any parent bundles.
    153     */
    154    public Set<String> methodNames()
    155    {
    156        Set<String> result = PlasticInternalUtils.newSet();
    157
    158        InheritanceData cursor = this;
    159
    160        while (cursor != null)
    161        {
    162            result.addAll(cursor.methodNames);
    163            cursor = cursor.parent;
    164        }
    165
    166        return result;
    167    }
    168}