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