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