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 String toValue(String name, String desc)
141    {
142        return name + ":" + desc;
143    }
144
145    /**
146     * Returns the names of any methods in this bundle, or from any parent bundles.
147     */
148    public Set<String> methodNames()
149    {
150        Set<String> result = PlasticInternalUtils.newSet();
151
152        InheritanceData cursor = this;
153
154        while (cursor != null)
155        {
156            result.addAll(cursor.methodNames);
157            cursor = cursor.parent;
158        }
159
160        return result;
161    }
162}