Home | History | Annotate | Download | only in monkeyrunner
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.monkeyrunner;
     17 
     18 import com.google.clearsilver.jsilver.JSilver;
     19 import com.google.clearsilver.jsilver.data.Data;
     20 import com.google.clearsilver.jsilver.resourceloader.ClassLoaderResourceLoader;
     21 import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
     22 import com.google.common.base.Function;
     23 import com.google.common.base.Predicate;
     24 import com.google.common.collect.Collections2;
     25 import com.google.common.collect.Lists;
     26 import com.google.common.collect.Sets;
     27 
     28 import com.android.monkeyrunner.doc.MonkeyRunnerExported;
     29 
     30 import java.io.IOException;
     31 import java.lang.reflect.Constructor;
     32 import java.lang.reflect.Field;
     33 import java.lang.reflect.Member;
     34 import java.lang.reflect.Method;
     35 import java.util.Arrays;
     36 import java.util.Collection;
     37 import java.util.Comparator;
     38 import java.util.List;
     39 import java.util.Set;
     40 
     41 /**
     42  * Utility class for generating inline help documentation
     43  */
     44 public final class MonkeyRunnerHelp {
     45     private MonkeyRunnerHelp() { }
     46 
     47     private static final String HELP = "help";
     48     private static final String NAME = "name";
     49     private static final String DOC = "doc";
     50     private static final String ARGUMENT = "argument";
     51     private static final String RETURNS = "returns";
     52     private static final String TYPE = "type";
     53 
     54     // Enum used to describe documented types.
     55     private enum Type {
     56         ENUM, FIELD, METHOD
     57     }
     58 
     59     private static void getAllExportedClasses(Set<Field> fields,
     60             Set<Method> methods,
     61             Set<Constructor<?>> constructors,
     62             Set<Class<?>> enums) {
     63         final Set<Class<?>> classesVisited = Sets.newHashSet();
     64         Set<Class<?>> classesToVisit = Sets.newHashSet();
     65         classesToVisit.add(MonkeyRunner.class);
     66 
     67         Predicate<Class<?>> haventSeen = new Predicate<Class<?>>() {
     68             public boolean apply(Class<?> clz) {
     69                 return !classesVisited.contains(clz);
     70             }
     71         };
     72 
     73         while (!classesToVisit.isEmpty()) {
     74             classesVisited.addAll(classesToVisit);
     75 
     76             List<Class<?>> newClasses = Lists.newArrayList();
     77             for (Class<?> clz : classesToVisit) {
     78                 // See if the class itself is annotated and is an enum
     79                 if (clz.isEnum() && clz.isAnnotationPresent(MonkeyRunnerExported.class)) {
     80                     enums.add(clz);
     81                 }
     82 
     83                 // Constructors
     84                 for (Constructor<?> c : clz.getConstructors()) {
     85                     newClasses.addAll(Collections2.filter(Arrays.asList(c.getParameterTypes()),
     86                             haventSeen));
     87                     if (c.isAnnotationPresent(MonkeyRunnerExported.class)) {
     88                         constructors.add(c);
     89                     }
     90                 }
     91 
     92                 // Fields
     93                 for (Field f : clz.getFields()) {
     94                     if (haventSeen.apply(f.getClass())) {
     95                         newClasses.add(f.getClass());
     96                     }
     97                     if (f.isAnnotationPresent(MonkeyRunnerExported.class)) {
     98                         fields.add(f);
     99                     }
    100                 }
    101 
    102                 // Methods
    103                 for (Method m : clz.getMethods()) {
    104                     newClasses.addAll(Collections2.filter(Arrays.asList(m.getParameterTypes()),
    105                             haventSeen));
    106                     if (haventSeen.apply(m.getReturnType())) {
    107                         newClasses.add(m.getReturnType());
    108                     }
    109 
    110                     if (m.isAnnotationPresent(MonkeyRunnerExported.class)) {
    111                         methods.add(m);
    112                     }
    113                 }
    114 
    115                 // Containing classes
    116                 for (Class<?> toAdd : clz.getClasses()) {
    117                     if (haventSeen.apply(toAdd)) {
    118                         newClasses.add(toAdd);
    119                     }
    120                 }
    121             }
    122 
    123             classesToVisit.clear();
    124             classesToVisit.addAll(newClasses);
    125         }
    126     }
    127 
    128     private static Comparator<Member> MEMBER_SORTER = new Comparator<Member>() {
    129         public int compare(Member o1, Member o2) {
    130             return o1.getName().compareTo(o2.getName());
    131         }
    132     };
    133 
    134     private static Comparator<Class<?>> CLASS_SORTER = new Comparator<Class<?>>() {
    135         public int compare(Class<?> o1, Class<?> o2) {
    136             return o1.getName().compareTo(o2.getName());
    137         }
    138     };
    139 
    140     public static String helpString(String format) {
    141         ResourceLoader resourceLoader = new ClassLoaderResourceLoader(
    142             MonkeyRunner.class.getClassLoader(), "com/android/monkeyrunner");
    143         JSilver jsilver = new JSilver(resourceLoader);
    144 
    145         // Quick check for support formats
    146         if ("html".equals(format) || "text".equals(format) || "sdk-docs".equals(format)) {
    147             try {
    148                 Data hdf = buildHelpHdf(jsilver);
    149                 return jsilver.render(format + ".cs", hdf);
    150             } catch (IOException e) {
    151                 return "";
    152             }
    153         } else if ("hdf".equals(format)) {
    154             Data hdf = buildHelpHdf(jsilver);
    155             return hdf.toString();
    156         }
    157         return "";
    158     }
    159 
    160     /**
    161      * Parse the value string into paragraphs and put them into the
    162      * HDF under this specified prefix.  Each paragraph will appear
    163      * numbered under the prefix.  For example:
    164      *
    165      * paragraphsIntoHDF("a.b.c", ....)
    166      *
    167      * Will create paragraphs under "a.b.c.0", "a.b.c.1", etc.
    168      *
    169      * @param prefix The prefix to put the values under.
    170      * @param value the value to parse paragraphs from.
    171      * @param hdf the HDF to add the entries to.
    172      */
    173     private static void paragraphsIntoHDF(String prefix, String value, Data hdf) {
    174         String paragraphs[] = value.split("\n");
    175         int x = 0;
    176         for (String para : paragraphs) {
    177             hdf.setValue(prefix + "." + x, para);
    178             x++;
    179         }
    180     }
    181 
    182     private static Data buildHelpHdf(JSilver jsilver) {
    183         Data hdf = jsilver.createData();
    184         int outputItemCount = 0;
    185 
    186         Set<Field> fields = Sets.newTreeSet(MEMBER_SORTER);
    187         Set<Method> methods = Sets.newTreeSet(MEMBER_SORTER);
    188         Set<Constructor<?>> constructors = Sets.newTreeSet(MEMBER_SORTER);
    189         Set<Class<?>> classes = Sets.newTreeSet(CLASS_SORTER);
    190         getAllExportedClasses(fields, methods, constructors, classes);
    191 
    192         for (Class<?> clz : classes) {
    193             String prefix = HELP + "." + outputItemCount + ".";
    194 
    195             hdf.setValue(prefix + NAME, clz.getCanonicalName());
    196             MonkeyRunnerExported annotation = clz.getAnnotation(MonkeyRunnerExported.class);
    197             paragraphsIntoHDF(prefix + DOC, annotation.doc(), hdf);
    198             hdf.setValue(prefix + TYPE, Type.ENUM.name());
    199 
    200             // Now go through the enumeration constants
    201             Object[] constants = clz.getEnumConstants();
    202             String[] argDocs = annotation.argDocs();
    203             if (constants.length > 0) {
    204                 for (int x = 0; x < constants.length; x++) {
    205                     String argPrefix = prefix + ARGUMENT + "." + x + ".";
    206                     hdf.setValue(argPrefix + NAME, constants[x].toString());
    207                     if (argDocs.length > x) {
    208                         paragraphsIntoHDF(argPrefix + DOC, argDocs[x], hdf);
    209                     }
    210                 }
    211             }
    212             outputItemCount++;
    213         }
    214 
    215         for (Method m : methods) {
    216             String prefix = HELP + "." + outputItemCount + ".";
    217 
    218             MonkeyRunnerExported annotation = m.getAnnotation(MonkeyRunnerExported.class);
    219             String className = m.getDeclaringClass().getCanonicalName();
    220             String methodName = className + "." + m.getName();
    221             hdf.setValue(prefix + NAME, methodName);
    222             paragraphsIntoHDF(prefix + DOC, annotation.doc(), hdf);
    223             if (annotation.args().length > 0) {
    224                 String[] argDocs = annotation.argDocs();
    225                 String[] aargs = annotation.args();
    226                 for (int x = 0; x < aargs.length; x++) {
    227                     String argPrefix = prefix + ARGUMENT + "." + x + ".";
    228 
    229                     hdf.setValue(argPrefix + NAME, aargs[x]);
    230                     if (argDocs.length > x) {
    231                         paragraphsIntoHDF(argPrefix + DOC, argDocs[x], hdf);
    232                     }
    233                 }
    234             }
    235             if (!"".equals(annotation.returns())) {
    236                 paragraphsIntoHDF(prefix + RETURNS, annotation.returns(), hdf);
    237             }
    238             outputItemCount++;
    239         }
    240 
    241         return hdf;
    242     }
    243 
    244     public static Collection<String> getAllDocumentedClasses() {
    245         Set<Field> fields = Sets.newTreeSet(MEMBER_SORTER);
    246         Set<Method> methods = Sets.newTreeSet(MEMBER_SORTER);
    247         Set<Constructor<?>> constructors = Sets.newTreeSet(MEMBER_SORTER);
    248         Set<Class<?>> classes = Sets.newTreeSet(CLASS_SORTER);
    249         getAllExportedClasses(fields, methods, constructors, classes);
    250 
    251         // The classes object only captures classes that are specifically exporter, which isn't
    252         // good enough.  So go through all the fields, methods, etc. and collect those classes as
    253         // as well
    254         Set<Class<?>> allClasses = Sets.newHashSet();
    255         allClasses.addAll(classes);
    256         for (Field f : fields) {
    257             allClasses.add(f.getDeclaringClass());
    258         }
    259         for (Method m : methods) {
    260             allClasses.add(m.getDeclaringClass());
    261         }
    262         for (Constructor<?> constructor : constructors) {
    263             allClasses.add(constructor.getDeclaringClass());
    264         }
    265 
    266         // And transform that collection into a list of simple names.
    267         return Collections2.transform(allClasses, new Function<Class<?>, String>() {
    268             @Override
    269             public String apply(Class<?> clz) {
    270                 return clz.getName();
    271             }
    272         });
    273     }
    274 }
    275