001/*
002 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.
008 *
009 * This code is distributed in the hope that it will be useful, but WITHOUT
010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
011 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
012 * version 2 for more details (a copy is included in the LICENSE file that
013 * accompanied this code).
014 *
015 * You should have received a copy of the GNU General Public License version
016 * 2 along with this work; if not, write to the Free Software Foundation,
017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
018 *
019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
020 * or visit www.oracle.com if you need additional information or have any
021 * questions.
022 */
023package com.oracle.graal.replacements.verifier;
024
025import java.lang.annotation.*;
026import java.util.*;
027
028import javax.annotation.processing.*;
029import javax.lang.model.element.*;
030import javax.lang.model.type.*;
031import javax.lang.model.util.*;
032import javax.tools.Diagnostic.Kind;
033
034import com.oracle.graal.api.replacements.*;
035
036public final class MethodSubstitutionVerifier extends AbstractVerifier {
037
038    private static final boolean DEBUG = false;
039
040    private static final String ORIGINAL_METHOD_NAME = "value";
041    private static final String ORIGINAL_IS_STATIC = "isStatic";
042    private static final String ORIGINAL_SIGNATURE = "signature";
043
044    private static final String ORIGINAL_METHOD_NAME_DEFAULT = "";
045    private static final String ORIGINAL_SIGNATURE_DEFAULT = "";
046
047    public MethodSubstitutionVerifier(ProcessingEnvironment env) {
048        super(env);
049    }
050
051    @Override
052    public Class<? extends Annotation> getAnnotationClass() {
053        return MethodSubstitution.class;
054    }
055
056    @SuppressWarnings("unused")
057    @Override
058    public void verify(Element element, AnnotationMirror annotation) {
059        if (element.getKind() != ElementKind.METHOD) {
060            assert false : "Element is guaranteed to be a method.";
061            return;
062        }
063        ExecutableElement substitutionMethod = (ExecutableElement) element;
064        TypeElement substitutionType = findEnclosingClass(substitutionMethod);
065        assert substitutionType != null;
066
067        AnnotationMirror substitutionClassAnnotation = VerifierAnnotationProcessor.findAnnotationMirror(env, substitutionType.getAnnotationMirrors(), ClassSubstitution.class);
068        if (substitutionClassAnnotation == null) {
069            env.getMessager().printMessage(Kind.ERROR, String.format("A @%s annotation is required on the enclosing class.", ClassSubstitution.class.getSimpleName()), element, annotation);
070            return;
071        }
072        boolean optional = resolveAnnotationValue(Boolean.class, findAnnotationValue(substitutionClassAnnotation, "optional"));
073        if (optional) {
074            return;
075        }
076
077        TypeElement originalType = ClassSubstitutionVerifier.resolveOriginalType(env, substitutionType, substitutionClassAnnotation);
078        if (originalType == null) {
079            env.getMessager().printMessage(Kind.ERROR, String.format("The @%s annotation is invalid on the enclosing class.", ClassSubstitution.class.getSimpleName()), element, annotation);
080            return;
081        }
082
083        if (!substitutionMethod.getModifiers().contains(Modifier.STATIC)) {
084            env.getMessager().printMessage(Kind.ERROR, String.format("A @%s method must be static.", MethodSubstitution.class.getSimpleName()), element, annotation);
085        }
086
087        if (substitutionMethod.getModifiers().contains(Modifier.ABSTRACT) || substitutionMethod.getModifiers().contains(Modifier.NATIVE)) {
088            env.getMessager().printMessage(Kind.ERROR, String.format("A @%s method must not be native or abstract.", MethodSubstitution.class.getSimpleName()), element, annotation);
089        }
090
091        String originalName = originalName(substitutionMethod, annotation);
092        boolean isStatic = resolveAnnotationValue(Boolean.class, findAnnotationValue(annotation, ORIGINAL_IS_STATIC));
093        TypeMirror[] originalSignature = originalSignature(originalType, substitutionMethod, annotation, isStatic);
094        if (originalSignature == null) {
095            return;
096        }
097        ExecutableElement originalMethod = originalMethod(substitutionMethod, annotation, originalType, originalName, originalSignature, isStatic);
098        if (DEBUG && originalMethod != null) {
099            env.getMessager().printMessage(Kind.NOTE, String.format("Found original method %s in type %s.", originalMethod, findEnclosingClass(originalMethod)));
100        }
101    }
102
103    private TypeMirror[] originalSignature(TypeElement originalType, ExecutableElement method, AnnotationMirror annotation, boolean isStatic) {
104        AnnotationValue signatureValue = findAnnotationValue(annotation, ORIGINAL_SIGNATURE);
105        String signatureString = resolveAnnotationValue(String.class, signatureValue);
106        List<TypeMirror> parameters = new ArrayList<>();
107        if (signatureString.equals(ORIGINAL_SIGNATURE_DEFAULT)) {
108            for (int i = 0; i < method.getParameters().size(); i++) {
109                parameters.add(method.getParameters().get(i).asType());
110            }
111            if (!isStatic) {
112                if (parameters.isEmpty()) {
113                    env.getMessager().printMessage(Kind.ERROR, "Method signature must be a static method with the 'this' object as its first parameter", method, annotation);
114                    return null;
115                } else {
116                    TypeMirror thisParam = parameters.remove(0);
117                    if (!isSubtype(originalType.asType(), thisParam)) {
118                        Name thisName = method.getParameters().get(0).getSimpleName();
119                        env.getMessager().printMessage(Kind.ERROR, String.format("The type of %s must assignable from %s", thisName, originalType), method, annotation);
120                    }
121                }
122            }
123            parameters.add(0, method.getReturnType());
124        } else {
125            try {
126                APHotSpotSignature signature = new APHotSpotSignature(signatureString);
127                parameters.add(signature.getReturnType(env));
128                for (int i = 0; i < signature.getParameterCount(false); i++) {
129                    parameters.add(signature.getParameterType(env, i));
130                }
131            } catch (Exception e) {
132                /*
133                 * That's not good practice and should be changed after APHotSpotSignature has
134                 * received a cleanup.
135                 */
136                env.getMessager().printMessage(Kind.ERROR, String.format("Parsing the signature failed: %s", e.getMessage() != null ? e.getMessage() : e.toString()), method, annotation,
137                                signatureValue);
138                return null;
139            }
140        }
141        return parameters.toArray(new TypeMirror[parameters.size()]);
142    }
143
144    private static String originalName(ExecutableElement substituteMethod, AnnotationMirror substitution) {
145        String originalMethodName = resolveAnnotationValue(String.class, findAnnotationValue(substitution, ORIGINAL_METHOD_NAME));
146        if (originalMethodName.equals(ORIGINAL_METHOD_NAME_DEFAULT)) {
147            originalMethodName = substituteMethod.getSimpleName().toString();
148        }
149        return originalMethodName;
150    }
151
152    private ExecutableElement originalMethod(ExecutableElement substitutionMethod, AnnotationMirror substitutionAnnotation, TypeElement originalType, String originalName,
153                    TypeMirror[] originalSignature, boolean isStatic) {
154        TypeMirror signatureReturnType = originalSignature[0];
155        TypeMirror[] signatureParameters = Arrays.copyOfRange(originalSignature, 1, originalSignature.length);
156        List<ExecutableElement> searchElements;
157        if (originalName.equals("<init>")) {
158            searchElements = ElementFilter.constructorsIn(originalType.getEnclosedElements());
159        } else {
160            searchElements = ElementFilter.methodsIn(originalType.getEnclosedElements());
161        }
162
163        ExecutableElement originalMethod = null;
164        outer: for (ExecutableElement searchElement : searchElements) {
165            if (searchElement.getSimpleName().toString().equals(originalName) && searchElement.getParameters().size() == signatureParameters.length) {
166                for (int i = 0; i < signatureParameters.length; i++) {
167                    VariableElement parameter = searchElement.getParameters().get(i);
168                    if (!isTypeCompatible(parameter.asType(), signatureParameters[i])) {
169                        continue outer;
170                    }
171                }
172                originalMethod = searchElement;
173                break;
174            }
175        }
176        if (originalMethod == null) {
177            boolean optional = resolveAnnotationValue(Boolean.class, findAnnotationValue(substitutionAnnotation, "optional"));
178            if (!optional) {
179                env.getMessager().printMessage(Kind.ERROR, String.format("Could not find the original method with name '%s' and parameters '%s'.", originalName, Arrays.toString(signatureParameters)),
180                                substitutionMethod, substitutionAnnotation);
181            }
182            return null;
183        }
184
185        if (originalMethod.getModifiers().contains(Modifier.STATIC) != isStatic) {
186            boolean optional = resolveAnnotationValue(Boolean.class, findAnnotationValue(substitutionAnnotation, "optional"));
187            if (!optional) {
188                env.getMessager().printMessage(Kind.ERROR, String.format("The %s element must be set to %s.", ORIGINAL_IS_STATIC, !isStatic), substitutionMethod, substitutionAnnotation);
189            }
190            return null;
191        }
192
193        if (!isTypeCompatible(originalMethod.getReturnType(), signatureReturnType)) {
194            env.getMessager().printMessage(
195                            Kind.ERROR,
196                            String.format("The return type of the substitution method '%s' must match with the return type of the original method '%s'.", signatureReturnType,
197                                            originalMethod.getReturnType()), substitutionMethod, substitutionAnnotation);
198            return null;
199        }
200
201        return originalMethod;
202    }
203
204    private boolean isTypeCompatible(TypeMirror originalType, TypeMirror substitutionType) {
205        TypeMirror original = originalType;
206        TypeMirror substitution = substitutionType;
207        if (needsErasure(original)) {
208            original = env.getTypeUtils().erasure(original);
209        }
210        if (needsErasure(substitution)) {
211            substitution = env.getTypeUtils().erasure(substitution);
212        }
213        return env.getTypeUtils().isSameType(original, substitution);
214    }
215
216    /**
217     * Tests whether one type is a subtype of another. Any type is considered to be a subtype of
218     * itself.
219     *
220     * @param t1 the first type
221     * @param t2 the second type
222     * @return {@code true} if and only if the first type is a subtype of the second
223     */
224    private boolean isSubtype(TypeMirror t1, TypeMirror t2) {
225        TypeMirror t1Erased = t1;
226        TypeMirror t2Erased = t2;
227        if (needsErasure(t1Erased)) {
228            t1Erased = env.getTypeUtils().erasure(t1Erased);
229        }
230        if (needsErasure(t2Erased)) {
231            t2Erased = env.getTypeUtils().erasure(t2Erased);
232        }
233        return env.getTypeUtils().isSubtype(t1Erased, t2Erased);
234    }
235
236    private static boolean needsErasure(TypeMirror typeMirror) {
237        return typeMirror.getKind() != TypeKind.NONE && typeMirror.getKind() != TypeKind.VOID && !typeMirror.getKind().isPrimitive() && typeMirror.getKind() != TypeKind.OTHER &&
238                        typeMirror.getKind() != TypeKind.NULL;
239    }
240
241    private static TypeElement findEnclosingClass(Element element) {
242        if (element.getKind().isClass()) {
243            return (TypeElement) element;
244        }
245
246        Element enclosing = element.getEnclosingElement();
247        while (enclosing != null && enclosing.getKind() != ElementKind.PACKAGE) {
248            if (enclosing.getKind().isClass()) {
249                return (TypeElement) enclosing;
250            }
251            enclosing = enclosing.getEnclosingElement();
252        }
253        return null;
254    }
255
256}