001 // Copyright 2004, 2005 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
015 package org.apache.tapestry.enhance;
016
017 import java.lang.reflect.Method;
018 import java.lang.reflect.Modifier;
019 import java.util.HashMap;
020 import java.util.HashSet;
021 import java.util.Iterator;
022 import java.util.Map;
023 import java.util.Set;
024
025 import org.apache.hivemind.ErrorLog;
026 import org.apache.hivemind.Location;
027 import org.apache.hivemind.service.MethodSignature;
028 import org.apache.tapestry.spec.IComponentSpecification;
029
030 /**
031 * Validates that an enhanced class is correct; checks that all inherited abstract methods are, in
032 * fact, implemented in the class.
033 *
034 * @author Howard M. Lewis Ship
035 * @since 4.0
036 */
037 public class EnhancedClassValidatorImpl implements EnhancedClassValidator
038 {
039 private ErrorLog _errorLog;
040
041 public void validate(Class baseClass, Class enhancedClass, IComponentSpecification specification)
042 {
043 // Set of MethodSignatures for methods that have a non-abstract implementation
044 // The Set is built working from the deepest subclass up to (and including) java.lang.Object
045
046 Set implementedMethods = new HashSet();
047 // Key is MethodSignature, value is Method
048 // Tracks which methods come from interfaces
049 Map interfaceMethods = new HashMap();
050
051 Location location = specification.getLocation();
052
053 Class current = enhancedClass;
054
055 while (true)
056 {
057 addInterfaceMethods(current, interfaceMethods);
058
059 // Inside Eclipse, for abstract classes, getDeclaredMethods() does NOT report methods
060 // inherited from interfaces. For Sun JDK and abstract classes, getDeclaredMethods()
061 // DOES report interface methods
062 // (as if they were declared by the class itself). This code is needlessly complex so
063 // that the checks work in both
064 // situations. Basically, I think Eclipse is right and Sun JDK is wrong and we're using
065 // the interfaceMethods map as a filter to ignore methods that Sun JDK is attributing
066 // to the class.
067
068 Method[] methods = current.getDeclaredMethods();
069
070 for (int i = 0; i < methods.length; i++)
071 {
072 Method m = methods[i];
073
074 MethodSignature s = new MethodSignature(m);
075
076 boolean isAbstract = Modifier.isAbstract(m.getModifiers());
077
078 if (isAbstract)
079 {
080 if (interfaceMethods.containsKey(s))
081 continue;
082
083 // If a superclass defines an abstract method that a subclass implements, then
084 // all's OK.
085
086 if (implementedMethods.contains(s))
087 continue;
088
089 _errorLog.error(EnhanceMessages.noImplForAbstractMethod(
090 m,
091 current,
092 baseClass,
093 enhancedClass), location, null);
094 }
095
096 implementedMethods.add(s);
097 }
098
099 current = current.getSuperclass();
100
101 // No need to check Object.class; it is concrete and doesn't implement any interfaces,
102 // or provide any methods
103 // that might be declared in an interface.
104
105 if (current == null || current == Object.class)
106 break;
107 }
108
109 Iterator i = interfaceMethods.entrySet().iterator();
110 while (i.hasNext())
111 {
112 Map.Entry entry = (Map.Entry) i.next();
113
114 MethodSignature sig = (MethodSignature) entry.getKey();
115
116 if (implementedMethods.contains(sig))
117 continue;
118
119 Method method = (Method) entry.getValue();
120
121 _errorLog.error(EnhanceMessages.unimplementedInterfaceMethod(
122 method,
123 baseClass,
124 enhancedClass), location, null);
125 }
126
127 }
128
129 private void addInterfaceMethods(Class current, Map interfaceMethods)
130 {
131 Class[] interfaces = current.getInterfaces();
132
133 for (int i = 0; i < interfaces.length; i++)
134 addMethodsFromInterface(interfaces[i], interfaceMethods);
135 }
136
137 private void addMethodsFromInterface(Class interfaceClass, Map interfaceMethods)
138 {
139 Method[] methods = interfaceClass.getMethods();
140
141 for (int i = 0; i < methods.length; i++)
142 {
143 MethodSignature sig = new MethodSignature(methods[i]);
144
145 if (interfaceMethods.containsKey(sig))
146 continue;
147
148 interfaceMethods.put(sig, methods[i]);
149 }
150 }
151
152 public void setErrorLog(ErrorLog errorLog)
153 {
154 _errorLog = errorLog;
155 }
156 }