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    }