blob: cb98a377689cc05669d077b65e83dfa7d060254b [file] [log] [blame]
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.tapestry5.internal.plastic;
import java.util.Map;
import java.util.Set;
/**
* Used to track which methods are implemented by a base class, which is often needed when transforming
* a subclass.
*/
public class InheritanceData
{
private final InheritanceData parent;
private final String packageName;
private final Set<String> methodNames = PlasticInternalUtils.newSet();
private final Map<String, Boolean> methods = PlasticInternalUtils.newMap();
private final Set<String> interfaceNames = PlasticInternalUtils.newSet();
public InheritanceData(String packageName)
{
this(null, packageName);
}
private InheritanceData(InheritanceData parent, String packageName)
{
this.parent = parent;
this.packageName = packageName;
}
/**
* Is this bundle for a transformed class, or for a base class (typically Object)?
*
* @return true if this bundle is for transformed class, false otherwise
*/
public boolean isTransformed()
{
return parent != null;
}
/**
* Returns a new MethodBundle that represents the methods of a child class
* of this bundle. The returned bundle will always be {@linkplain #isTransformed() transformed}.
*
* @param packageName
* the package that the child class will be created in
* @return new method bundle
*/
public InheritanceData createChild(String packageName)
{
return new InheritanceData(this, packageName);
}
/**
* Adds a new instance method. Only non-private methods should be added (that is, methods which might
* be overridden in subclasses). This can later be queried to see if any base class implements the method.
*
* @param name
* name of method
* @param desc
* describes the parameters and return value of the method
* @param samePackageOnly
* whether the method can only be overridden in classes that are in the same package
*/
public void addMethod(String name, String desc, boolean samePackageOnly)
{
methods.put(toValue(name, desc), samePackageOnly);
methodNames.add(name);
}
/**
* Returns true if this class or a transformed parent class contains an implementation of,
* or abstract placeholder for, the method.
*
* @param name
* method name
* @param desc
* method descriptor
* @return true if this class or a base class implements the method (including abstract methods)
*/
public boolean isImplemented(String name, String desc)
{
return checkForMethod(toValue(name, desc), this);
}
/**
* Returns true if the method is an override of a base class method
*
* @param name
* method name
* @param desc
* method descriptor
* @return true if a base class implements the method (including abstract methods)
*/
public boolean isOverride(String name, String desc)
{
return checkForMethod(toValue(name, desc), parent);
}
private boolean checkForMethod(String value, InheritanceData cursor)
{
String thisPackageName = packageName;
while (cursor != null)
{
if (cursor.methods.containsKey(value))
{
boolean mustBeInSamePackage = cursor.methods.get(value);
if (!mustBeInSamePackage)
{
return true;
}
boolean isInSamePackage = thisPackageName.equals(cursor.packageName);
if (isInSamePackage)
{
return true;
}
}
cursor = cursor.parent;
}
return false;
}
/**
* Returns true if the class represented by this data, or any parent data, implements
* the named interface.
*/
public boolean isInterfaceImplemented(String name)
{
InheritanceData cursor = this;
while (cursor != null)
{
if (cursor.interfaceNames.contains(name))
{
return true;
}
cursor = cursor.parent;
}
return false;
}
public void addInterface(String name)
{
if (!interfaceNames.contains(name))
{
interfaceNames.add(name);
}
}
/**
* Combines a method name and its desc (which describes parameter types and return value) to form
* a value, which is how methods are tracked.
*/
private static String toValue(String name, String desc)
{
// TAP5-2268: ignore return-type to avoid methods with the same number (and type) of parameters but different
// return-types which is illegal in Java.
// desc is something like "(I)Ljava/lang/String;", which means: takes an int, returns a String. We strip
// everything after the parameter list.
int endOfParameterSpecIdx = desc.indexOf(')');
return name + ":" + desc.substring(0, endOfParameterSpecIdx+1);
}
/**
* Returns the names of any methods in this bundle, or from any parent bundles.
*/
public Set<String> methodNames()
{
Set<String> result = PlasticInternalUtils.newSet();
InheritanceData cursor = this;
while (cursor != null)
{
result.addAll(cursor.methodNames);
cursor = cursor.parent;
}
return result;
}
}