| // Copyright 2014 The Bazel Authors. 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.annotations.VisibleForTesting; | 
 | import com.google.common.base.Preconditions; | 
 | import com.google.common.collect.ImmutableMap; | 
 | import com.google.devtools.build.docgen.skylark.SkylarkBuiltinMethodDoc; | 
 | import com.google.devtools.build.docgen.skylark.SkylarkJavaMethodDoc; | 
 | import com.google.devtools.build.docgen.skylark.SkylarkModuleDoc; | 
 | import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable; | 
 | import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; | 
 | import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory; | 
 | import com.google.devtools.build.lib.skylarkinterface.SkylarkSignature; | 
 | import com.google.devtools.build.lib.syntax.FuncallExpression; | 
 | import com.google.devtools.build.lib.syntax.Runtime; | 
 | import com.google.devtools.build.lib.util.Classpath; | 
 | import com.google.devtools.build.lib.util.Classpath.ClassPathException; | 
 | import java.lang.reflect.Field; | 
 | import java.lang.reflect.Method; | 
 | import java.util.Deque; | 
 | import java.util.HashMap; | 
 | import java.util.HashSet; | 
 | import java.util.LinkedList; | 
 | import java.util.List; | 
 | import java.util.Map; | 
 | import java.util.Set; | 
 | import java.util.TreeMap; | 
 |  | 
 | /** | 
 |  * A helper class that collects Skylark module documentation. | 
 |  */ | 
 | final class SkylarkDocumentationCollector { | 
 |   @SkylarkModule( | 
 |     name = "globals", | 
 |     title = "Globals", | 
 |     category = SkylarkModuleCategory.TOP_LEVEL_TYPE, | 
 |     doc = "Objects, functions and modules registered in the global environment." | 
 |   ) | 
 |   private static final class TopLevelModule {} | 
 |  | 
 |   // Common prefix of packages that may contain Skylark modules. | 
 |   private static final String MODULES_PACKAGE_PREFIX = "com/google/devtools/build"; | 
 |  | 
 |   private SkylarkDocumentationCollector() {} | 
 |  | 
 |   /** | 
 |    * Returns the SkylarkModule annotation for the top-level Skylark module. | 
 |    */ | 
 |   public static SkylarkModule getTopLevelModule() { | 
 |     return TopLevelModule.class.getAnnotation(SkylarkModule.class); | 
 |   } | 
 |  | 
 |   /** | 
 |    * Collects the documentation for all Skylark modules and returns a map that maps Skylark module | 
 |    * name to the module documentation. | 
 |    * | 
 |    * <p>WARNING: This method no longer supports the specification of additional module classes via | 
 |    * parameters. Instead, all module classes are being picked up automatically. | 
 |    * | 
 |    * @param clazz DEPRECATED. | 
 |    */ | 
 |   public static Map<String, SkylarkModuleDoc> collectModules(@Deprecated String... clazz) | 
 |       throws ClassPathException { | 
 |     Map<String, SkylarkModuleDoc> modules = new TreeMap<>(); | 
 |     Map<SkylarkModule, Class<?>> builtinModules = new HashMap<>(); | 
 |     for (Class<?> candidateClass : Classpath.findClasses(MODULES_PACKAGE_PREFIX)) { | 
 |       SkylarkModule annotation = candidateClass.getAnnotation(SkylarkModule.class); | 
 |       if (annotation != null) { | 
 |         collectBuiltinModule(builtinModules, candidateClass); | 
 |         collectJavaObjects(annotation, candidateClass, modules); | 
 |       } | 
 |       collectBuiltinDoc(modules, candidateClass.getDeclaredFields()); | 
 |     } | 
 |     return modules; | 
 |   } | 
 |  | 
 |   /** | 
 |    * Collects and returns all the Java objects reachable in Skylark from (and including) | 
 |    * firstClass with the corresponding SkylarkModule annotation. | 
 |    * | 
 |    * <p>Note that the {@link SkylarkModule} annotation for firstClass - firstModule - | 
 |    * 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. | 
 |    */ | 
 |   @VisibleForTesting | 
 |   static void collectJavaObjects(SkylarkModule firstModule, Class<?> firstClass, | 
 |       Map<String, SkylarkModuleDoc> modules) { | 
 |     Set<Class<?>> done = new HashSet<>(); | 
 |     Deque<Class<?>> toProcess = new LinkedList<>(); | 
 |     Map<Class<?>, SkylarkModule> annotations = new HashMap<>(); | 
 |  | 
 |     toProcess.addLast(firstClass); | 
 |     annotations.put(firstClass, firstModule); | 
 |  | 
 |     while (!toProcess.isEmpty()) { | 
 |       Class<?> c = toProcess.removeFirst(); | 
 |       SkylarkModule annotation = annotations.get(c); | 
 |       done.add(c); | 
 |       if (!modules.containsKey(annotation.name())) { | 
 |         modules.put(annotation.name(), new SkylarkModuleDoc(annotation, c)); | 
 |       } | 
 |       SkylarkModuleDoc module = modules.get(annotation.name()); | 
 |  | 
 |       if (module.javaMethodsNotCollected()) { | 
 |         ImmutableMap<Method, SkylarkCallable> methods = | 
 |             FuncallExpression.collectSkylarkMethodsWithAnnotation(c); | 
 |         for (Map.Entry<Method, SkylarkCallable> entry : methods.entrySet()) { | 
 |           module.addMethod(new SkylarkJavaMethodDoc(module, entry.getKey(), entry.getValue())); | 
 |         } | 
 |  | 
 |         for (Map.Entry<Method, SkylarkCallable> method : methods.entrySet()) { | 
 |           Class<?> returnClass = method.getKey().getReturnType(); | 
 |           if (returnClass.isAnnotationPresent(SkylarkModule.class) | 
 |               && !done.contains(returnClass)) { | 
 |             toProcess.addLast(returnClass); | 
 |             annotations.put(returnClass, returnClass.getAnnotation(SkylarkModule.class)); | 
 |           } | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   private static void collectBuiltinDoc(Map<String, SkylarkModuleDoc> modules, Field[] fields) { | 
 |     for (Field field : fields) { | 
 |       if (field.isAnnotationPresent(SkylarkSignature.class)) { | 
 |         SkylarkSignature skylarkSignature = field.getAnnotation(SkylarkSignature.class); | 
 |         Class<?> moduleClass = skylarkSignature.objectType(); | 
 |         SkylarkModule skylarkModule = moduleClass.equals(Object.class) | 
 |             ? getTopLevelModule() | 
 |             : Runtime.getCanonicalRepresentation(moduleClass).getAnnotation(SkylarkModule.class); | 
 |         if (skylarkModule == null) { | 
 |           // TODO(bazel-team): we currently have undocumented methods on undocumented data | 
 |           // structures, namely java.util.List. Remove this case when we are done. | 
 |           Preconditions.checkState(!skylarkSignature.documented()); | 
 |           Preconditions.checkState(moduleClass == List.class); | 
 |         } else { | 
 |           if (!modules.containsKey(skylarkModule.name())) { | 
 |             modules.put(skylarkModule.name(), new SkylarkModuleDoc(skylarkModule, moduleClass)); | 
 |           } | 
 |           SkylarkModuleDoc module = modules.get(skylarkModule.name()); | 
 |           module.addMethod(new SkylarkBuiltinMethodDoc(module, skylarkSignature, field.getType())); | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   private static void collectBuiltinModule( | 
 |       Map<SkylarkModule, Class<?>> modules, Class<?> moduleClass) { | 
 |     if (moduleClass.isAnnotationPresent(SkylarkModule.class)) { | 
 |       SkylarkModule skylarkModule = moduleClass.getAnnotation(SkylarkModule.class); | 
 |       modules.put(skylarkModule, moduleClass); | 
 |     } | 
 |   } | 
 | } |