• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
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 
getAllExportedClasses(Set<Field> fields, Set<Method> methods, Set<Constructor<?>> constructors, Set<Class<?>> enums)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 
helpString(String format)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      */
paragraphsIntoHDF(String prefix, String value, Data hdf)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 
buildHelpHdf(JSilver jsilver)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 
getAllDocumentedClasses()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