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
015package org.apache.tapestry.enhance;
016
017import java.lang.reflect.Method;
018import java.lang.reflect.Modifier;
019import java.util.HashMap;
020import java.util.HashSet;
021import java.util.Iterator;
022import java.util.Map;
023import java.util.Set;
024
025import org.apache.hivemind.ErrorLog;
026import org.apache.hivemind.Location;
027import org.apache.hivemind.service.MethodSignature;
028import 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 */
037public 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}