001// Copyright 2011 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007// http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry5.internal.plastic;
016
017import java.util.Set;
018
019/**
020 * Used to track which methods are implemented by a base class, which is often needed when transforming
021 * a subclass.
022 */
023public class InheritanceData
024{
025    private final InheritanceData parent;
026
027    private final Set<String> methodNames = PlasticInternalUtils.newSet();
028    private final Set<String> methods = PlasticInternalUtils.newSet();
029    private final Set<String> interfaceNames = PlasticInternalUtils.newSet();
030
031    public InheritanceData()
032    {
033        this(null);
034    }
035
036    private InheritanceData(InheritanceData parent)
037    {
038        this.parent = parent;
039    }
040
041    /**
042     * Is this bundle for a transformed class, or for a base class (typically Object)?
043     *
044     * @return true if this bundle is for transformed class, false otherwise
045     */
046    public boolean isTransformed()
047    {
048        return parent != null;
049    }
050
051    /**
052     * Returns a new MethodBundle that represents the methods of a child class
053     * of this bundle. The returned bundle will always be {@linkplain #isTransformed() transformed}.
054     *
055     * @param childClassName name of subclass
056     * @return new method bundle
057     */
058    public InheritanceData createChild(String childClassName)
059    {
060        return new InheritanceData(this);
061    }
062
063    /**
064     * Adds a new instance method. Only non-private, non-abstract methods should be added (that is, methods which might
065     * be overridden in subclasses). This can later be queried to see if any base class implements the method.
066     *
067     * @param name name of method
068     * @param desc method descriptor
069     */
070    public void addMethod(String name, String desc)
071    {
072        String value = toValue(name, desc);
073
074        methods.add(value);
075        methodNames.add(name);
076    }
077
078    /**
079     * Returns true if a transformed parent class contains the indicated method.
080     *
081     * @param name method name
082     * @param desc method descriptor
083     * @return the <em>internal name</em> of the implementing base class for this method,
084     *         or null if no base class implements the method
085     */
086    public boolean isImplemented(String name, String desc)
087    {
088        return checkForMethod(toValue(name, desc));
089    }
090
091
092    private boolean checkForMethod(String value)
093    {
094        if (methods.contains(value))
095            return true;
096
097        return parent == null ? false : parent.checkForMethod(value);
098    }
099
100    /**
101     * Returns true if the class represented by this data, or any parent data, implements
102     * the named interface.
103     */
104    public boolean isInterfaceImplemented(String name)
105    {
106        InheritanceData cursor = this;
107
108        while (cursor != null)
109        {
110            if (cursor.interfaceNames.contains(name))
111            {
112                return true;
113            }
114
115            cursor = cursor.parent;
116        }
117
118        return false;
119    }
120
121    public void addInterface(String name) {
122        interfaceNames.add(name);
123    }
124
125    /**
126     * Combines a method name and its desc (which describes parameter types and return value) to form
127     * a value, which is how methods are tracked.
128     */
129    private String toValue(String name, String desc)
130    {
131        return name + ":" + desc;
132    }
133
134    /**
135     * Returns the names of any methods in this bundle, or from any parent bundles.
136     */
137    public Set<String> methodNames()
138    {
139        Set<String> result = PlasticInternalUtils.newSet();
140
141        InheritanceData cursor = this;
142
143        while (cursor != null)
144        {
145            result.addAll(cursor.methodNames);
146            cursor = cursor.parent;
147        }
148
149        return result;
150    }
151}