| // Copyright 2014 Google Inc. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| package com.google.devtools.build.docgen; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.devtools.build.lib.syntax.FuncallExpression; |
| import com.google.devtools.build.lib.syntax.SkylarkCallable; |
| import com.google.devtools.build.lib.syntax.SkylarkModule; |
| import com.google.devtools.build.lib.syntax.SkylarkSignature; |
| import com.google.devtools.build.lib.util.StringUtilities; |
| |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeMap; |
| |
| /** |
| * A helper class to collect all the Java objects / methods reachable from Skylark. |
| */ |
| public class SkylarkJavaInterfaceExplorer { |
| /** |
| * A class representing a Java method callable from Skylark with annotation. |
| */ |
| static final class SkylarkJavaMethod { |
| public final String name; |
| public final Method method; |
| public final SkylarkCallable callable; |
| |
| private String getName(Method method, SkylarkCallable callable) { |
| return callable.name().isEmpty() |
| ? StringUtilities.toPythonStyleFunctionName(method.getName()) |
| : callable.name(); |
| } |
| |
| SkylarkJavaMethod(Method method, SkylarkCallable callable) { |
| this.name = getName(method, callable); |
| this.method = method; |
| this.callable = callable; |
| } |
| } |
| |
| /** |
| * A class representing a Skylark built-in object or method. |
| */ |
| static final class SkylarkBuiltinMethod { |
| public final SkylarkSignature annotation; |
| public final Class<?> fieldClass; |
| |
| public SkylarkBuiltinMethod(SkylarkSignature annotation, Class<?> fieldClass) { |
| this.annotation = annotation; |
| this.fieldClass = fieldClass; |
| } |
| } |
| |
| /** |
| * A class representing a Skylark built-in object with its {@link SkylarkSignature} annotation |
| * and the {@link SkylarkCallable} methods it might have. |
| */ |
| static final class SkylarkModuleDoc { |
| |
| private final SkylarkModule module; |
| private final Class<?> classObject; |
| private final Map<String, SkylarkBuiltinMethod> builtin; |
| private ArrayList<SkylarkJavaMethod> methods = null; |
| |
| SkylarkModuleDoc(SkylarkModule module, Class<?> classObject) { |
| this.module = Preconditions.checkNotNull( |
| module, "Class has to be annotated with SkylarkModule: %s", classObject); |
| this.classObject = classObject; |
| this.builtin = new TreeMap<>(); |
| } |
| |
| SkylarkModule getAnnotation() { |
| return module; |
| } |
| |
| Class<?> getClassObject() { |
| return classObject; |
| } |
| |
| private boolean javaMethodsNotCollected() { |
| return methods == null; |
| } |
| |
| private void setJavaMethods(ArrayList<SkylarkJavaMethod> methods) { |
| this.methods = methods; |
| } |
| |
| Map<String, SkylarkBuiltinMethod> getBuiltinMethods() { |
| return builtin; |
| } |
| |
| ArrayList<SkylarkJavaMethod> getJavaMethods() { |
| return methods; |
| } |
| } |
| |
| /** |
| * Collects and returns all the Java objects reachable in Skylark from (and including) |
| * firstClassObject with the corresponding SkylarkSignature annotations. |
| * |
| * <p>Note that the {@link SkylarkSignature} annotation for firstClassObject - firstAnnotation - |
| * is also an input parameter, because some top level Skylark built-in objects and methods |
| * are not annotated on the class, but on a field referencing them. |
| */ |
| void collect(SkylarkModule firstModule, Class<?> firstClass, |
| Map<String, SkylarkModuleDoc> modules) { |
| Set<Class<?>> processedClasses = new HashSet<>(); |
| LinkedList<Class<?>> classesToProcess = new LinkedList<>(); |
| Map<Class<?>, SkylarkModule> annotations = new HashMap<>(); |
| |
| classesToProcess.addLast(firstClass); |
| annotations.put(firstClass, firstModule); |
| |
| while (!classesToProcess.isEmpty()) { |
| Class<?> classObject = classesToProcess.removeFirst(); |
| SkylarkModule annotation = annotations.get(classObject); |
| processedClasses.add(classObject); |
| if (!modules.containsKey(annotation.name())) { |
| modules.put(annotation.name(), new SkylarkModuleDoc(annotation, classObject)); |
| } |
| SkylarkModuleDoc module = modules.get(annotation.name()); |
| |
| if (module.javaMethodsNotCollected()) { |
| ImmutableMap<Method, SkylarkCallable> methods = |
| FuncallExpression.collectSkylarkMethodsWithAnnotation(classObject); |
| ArrayList<SkylarkJavaMethod> methodList = new ArrayList<>(); |
| for (Map.Entry<Method, SkylarkCallable> entry : methods.entrySet()) { |
| methodList.add(new SkylarkJavaMethod(entry.getKey(), entry.getValue())); |
| } |
| module.setJavaMethods(methodList); |
| |
| for (Map.Entry<Method, SkylarkCallable> method : methods.entrySet()) { |
| Class<?> returnClass = method.getKey().getReturnType(); |
| if (returnClass.isAnnotationPresent(SkylarkModule.class) |
| && !processedClasses.contains(returnClass)) { |
| classesToProcess.addLast(returnClass); |
| annotations.put(returnClass, returnClass.getAnnotation(SkylarkModule.class)); |
| } |
| } |
| } |
| } |
| } |
| } |