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