001/*
002 * Copyright (c) 2014, 2015, 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.nodeinfo.processor;
024
025import static javax.lang.model.element.Modifier.*;
026
027import java.util.*;
028
029import javax.annotation.processing.*;
030import javax.lang.model.element.*;
031import javax.lang.model.type.*;
032import javax.lang.model.util.*;
033
034/**
035 * Verifies static constraints on nodes.
036 */
037public class GraphNodeVerifier {
038
039    private final GraphNodeProcessor env;
040    private final Types types;
041    private final Elements elements;
042
043    // Checkstyle: stop
044    private final TypeElement Input;
045    private final TypeElement OptionalInput;
046    private final TypeElement Successor;
047
048    final TypeElement Node;
049    private final TypeElement NodeInputList;
050    private final TypeElement NodeSuccessorList;
051
052    private final TypeElement object;
053
054    // Checkstyle: resume
055
056    public GraphNodeVerifier(GraphNodeProcessor processor) {
057        this.env = processor;
058
059        this.types = processor.getProcessingEnv().getTypeUtils();
060        this.elements = processor.getProcessingEnv().getElementUtils();
061
062        this.Input = getTypeElement("com.oracle.graal.graph.Node.Input");
063        this.OptionalInput = getTypeElement("com.oracle.graal.graph.Node.OptionalInput");
064        this.Successor = getTypeElement("com.oracle.graal.graph.Node.Successor");
065        this.Node = getTypeElement("com.oracle.graal.graph.Node");
066        this.NodeInputList = getTypeElement("com.oracle.graal.graph.NodeInputList");
067        this.NodeSuccessorList = getTypeElement("com.oracle.graal.graph.NodeSuccessorList");
068        this.object = getTypeElement("java.lang.Object");
069    }
070
071    /**
072     * Returns a type element given a canonical name.
073     *
074     * @throw {@link NoClassDefFoundError} if a type element does not exist for {@code name}
075     */
076    public TypeElement getTypeElement(String name) {
077        TypeElement typeElement = elements.getTypeElement(name);
078        if (typeElement == null) {
079            throw new NoClassDefFoundError(name);
080        }
081        return typeElement;
082    }
083
084    public TypeElement getTypeElement(Class<?> cls) {
085        return getTypeElement(cls.getName());
086    }
087
088    public TypeMirror getType(String name) {
089        return getTypeElement(name).asType();
090    }
091
092    public ProcessingEnvironment getProcessingEnv() {
093        return env.getProcessingEnv();
094    }
095
096    public boolean isAssignableWithErasure(Element from, Element to) {
097        TypeMirror fromType = types.erasure(from.asType());
098        TypeMirror toType = types.erasure(to.asType());
099        return types.isAssignable(fromType, toType);
100    }
101
102    private void scanFields(TypeElement node) {
103        TypeElement currentClazz = node;
104        do {
105            for (VariableElement field : ElementFilter.fieldsIn(currentClazz.getEnclosedElements())) {
106                Set<Modifier> modifiers = field.getModifiers();
107                if (modifiers.contains(STATIC) || modifiers.contains(TRANSIENT)) {
108                    continue;
109                }
110
111                List<? extends AnnotationMirror> annotations = field.getAnnotationMirrors();
112
113                boolean isNonOptionalInput = findAnnotationMirror(annotations, Input) != null;
114                boolean isOptionalInput = findAnnotationMirror(annotations, OptionalInput) != null;
115                boolean isSuccessor = findAnnotationMirror(annotations, Successor) != null;
116
117                if (isNonOptionalInput || isOptionalInput) {
118                    if (findAnnotationMirror(annotations, Successor) != null) {
119                        throw new ElementException(field, "Field cannot be both input and successor");
120                    } else if (isNonOptionalInput && isOptionalInput) {
121                        throw new ElementException(field, "Inputs must be either optional or non-optional");
122                    } else if (isAssignableWithErasure(field, NodeInputList)) {
123                        if (modifiers.contains(FINAL)) {
124                            throw new ElementException(field, "Input list field must not be final");
125                        }
126                        if (modifiers.contains(PUBLIC)) {
127                            throw new ElementException(field, "Input list field must not be public");
128                        }
129                    } else {
130                        if (!isAssignableWithErasure(field, Node) && field.getKind() == ElementKind.INTERFACE) {
131                            throw new ElementException(field, "Input field type must be an interface or assignable to Node");
132                        }
133                        if (modifiers.contains(FINAL)) {
134                            throw new ElementException(field, "Input field must not be final");
135                        }
136                        if (modifiers.contains(PUBLIC)) {
137                            throw new ElementException(field, "Input field must not be public");
138                        }
139                    }
140                } else if (isSuccessor) {
141                    if (isAssignableWithErasure(field, NodeSuccessorList)) {
142                        if (modifiers.contains(FINAL)) {
143                            throw new ElementException(field, "Successor list field must not be final");
144                        }
145                        if (modifiers.contains(PUBLIC)) {
146                            throw new ElementException(field, "Successor list field must not be public");
147                        }
148                    } else {
149                        if (!isAssignableWithErasure(field, Node)) {
150                            throw new ElementException(field, "Successor field must be a Node type");
151                        }
152                        if (modifiers.contains(FINAL)) {
153                            throw new ElementException(field, "Successor field must not be final");
154                        }
155                        if (modifiers.contains(PUBLIC)) {
156                            throw new ElementException(field, "Successor field must not be public");
157                        }
158                    }
159
160                } else {
161                    if (isAssignableWithErasure(field, Node) && !field.getSimpleName().contentEquals("Null")) {
162                        throw new ElementException(field, "Node field must be annotated with @" + Input.getSimpleName() + ", @" + OptionalInput.getSimpleName() + " or @" + Successor.getSimpleName());
163                    }
164                    if (isAssignableWithErasure(field, NodeInputList)) {
165                        throw new ElementException(field, "NodeInputList field must be annotated with @" + Input.getSimpleName() + " or @" + OptionalInput.getSimpleName());
166                    }
167                    if (isAssignableWithErasure(field, NodeSuccessorList)) {
168                        throw new ElementException(field, "NodeSuccessorList field must be annotated with @" + Successor.getSimpleName());
169                    }
170                    if (modifiers.contains(PUBLIC) && !modifiers.contains(FINAL)) {
171                        throw new ElementException(field, "Data field must be final if public");
172                    }
173                }
174            }
175            currentClazz = getSuperType(currentClazz);
176        } while (!isObject(getSuperType(currentClazz).asType()));
177    }
178
179    private AnnotationMirror findAnnotationMirror(List<? extends AnnotationMirror> mirrors, TypeElement expectedAnnotationType) {
180        for (AnnotationMirror mirror : mirrors) {
181            if (sameType(mirror.getAnnotationType(), expectedAnnotationType.asType())) {
182                return mirror;
183            }
184        }
185        return null;
186    }
187
188    private boolean isObject(TypeMirror type) {
189        return sameType(object.asType(), type);
190    }
191
192    private boolean sameType(TypeMirror type1, TypeMirror type2) {
193        return env.getProcessingEnv().getTypeUtils().isSameType(type1, type2);
194    }
195
196    private TypeElement getSuperType(TypeElement element) {
197        if (element.getSuperclass() != null) {
198            return (TypeElement) env.getProcessingEnv().getTypeUtils().asElement(element.getSuperclass());
199        }
200        return null;
201    }
202
203    void verify(TypeElement node) {
204        scanFields(node);
205
206        boolean foundValidConstructor = false;
207        for (ExecutableElement constructor : ElementFilter.constructorsIn(node.getEnclosedElements())) {
208            if (constructor.getModifiers().contains(PRIVATE)) {
209                continue;
210            } else if (!constructor.getModifiers().contains(PUBLIC) && !constructor.getModifiers().contains(PROTECTED)) {
211                throw new ElementException(constructor, "Node class constructor must be public or protected");
212            }
213
214            foundValidConstructor = true;
215        }
216
217        if (!foundValidConstructor) {
218            throw new ElementException(node, "Node class must have at least one protected constructor");
219        }
220    }
221}