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}