/* * Copyright (C) 2010 The Android Open Source Project * * 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.android.monkeyrunner; import com.google.clearsilver.jsilver.JSilver; import com.google.clearsilver.jsilver.data.Data; import com.google.clearsilver.jsilver.resourceloader.ClassLoaderResourceLoader; import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.android.monkeyrunner.doc.MonkeyRunnerExported; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Set; /** * Utility class for generating inline help documentation */ public final class MonkeyRunnerHelp { private MonkeyRunnerHelp() { } private static final String HELP = "help"; private static final String NAME = "name"; private static final String DOC = "doc"; private static final String ARGUMENT = "argument"; private static final String RETURNS = "returns"; private static final String TYPE = "type"; // Enum used to describe documented types. private enum Type { ENUM, FIELD, METHOD } private static void getAllExportedClasses(Set<Field> fields, Set<Method> methods, Set<Constructor<?>> constructors, Set<Class<?>> enums) { final Set<Class<?>> classesVisited = Sets.newHashSet(); Set<Class<?>> classesToVisit = Sets.newHashSet(); classesToVisit.add(MonkeyRunner.class); Predicate<Class<?>> haventSeen = new Predicate<Class<?>>() { public boolean apply(Class<?> clz) { return !classesVisited.contains(clz); } }; while (!classesToVisit.isEmpty()) { classesVisited.addAll(classesToVisit); List<Class<?>> newClasses = Lists.newArrayList(); for (Class<?> clz : classesToVisit) { // See if the class itself is annotated and is an enum if (clz.isEnum() && clz.isAnnotationPresent(MonkeyRunnerExported.class)) { enums.add(clz); } // Constructors for (Constructor<?> c : clz.getConstructors()) { newClasses.addAll(Collections2.filter(Arrays.asList(c.getParameterTypes()), haventSeen)); if (c.isAnnotationPresent(MonkeyRunnerExported.class)) { constructors.add(c); } } // Fields for (Field f : clz.getFields()) { if (haventSeen.apply(f.getClass())) { newClasses.add(f.getClass()); } if (f.isAnnotationPresent(MonkeyRunnerExported.class)) { fields.add(f); } } // Methods for (Method m : clz.getMethods()) { newClasses.addAll(Collections2.filter(Arrays.asList(m.getParameterTypes()), haventSeen)); if (haventSeen.apply(m.getReturnType())) { newClasses.add(m.getReturnType()); } if (m.isAnnotationPresent(MonkeyRunnerExported.class)) { methods.add(m); } } // Containing classes for (Class<?> toAdd : clz.getClasses()) { if (haventSeen.apply(toAdd)) { newClasses.add(toAdd); } } } classesToVisit.clear(); classesToVisit.addAll(newClasses); } } private static Comparator<Member> MEMBER_SORTER = new Comparator<Member>() { public int compare(Member o1, Member o2) { return o1.getName().compareTo(o2.getName()); } }; private static Comparator<Class<?>> CLASS_SORTER = new Comparator<Class<?>>() { public int compare(Class<?> o1, Class<?> o2) { return o1.getName().compareTo(o2.getName()); } }; public static String helpString(String format) { ResourceLoader resourceLoader = new ClassLoaderResourceLoader( MonkeyRunner.class.getClassLoader(), "com/android/monkeyrunner"); JSilver jsilver = new JSilver(resourceLoader); // Quick check for support formats if ("html".equals(format) || "text".equals(format) || "sdk-docs".equals(format)) { try { Data hdf = buildHelpHdf(jsilver); return jsilver.render(format + ".cs", hdf); } catch (IOException e) { return ""; } } else if ("hdf".equals(format)) { Data hdf = buildHelpHdf(jsilver); return hdf.toString(); } return ""; } /** * Parse the value string into paragraphs and put them into the * HDF under this specified prefix. Each paragraph will appear * numbered under the prefix. For example: * * paragraphsIntoHDF("a.b.c", ....) * * Will create paragraphs under "a.b.c.0", "a.b.c.1", etc. * * @param prefix The prefix to put the values under. * @param value the value to parse paragraphs from. * @param hdf the HDF to add the entries to. */ private static void paragraphsIntoHDF(String prefix, String value, Data hdf) { String paragraphs[] = value.split("\n"); int x = 0; for (String para : paragraphs) { hdf.setValue(prefix + "." + x, para); x++; } } private static Data buildHelpHdf(JSilver jsilver) { Data hdf = jsilver.createData(); int outputItemCount = 0; Set<Field> fields = Sets.newTreeSet(MEMBER_SORTER); Set<Method> methods = Sets.newTreeSet(MEMBER_SORTER); Set<Constructor<?>> constructors = Sets.newTreeSet(MEMBER_SORTER); Set<Class<?>> classes = Sets.newTreeSet(CLASS_SORTER); getAllExportedClasses(fields, methods, constructors, classes); for (Class<?> clz : classes) { String prefix = HELP + "." + outputItemCount + "."; hdf.setValue(prefix + NAME, clz.getCanonicalName()); MonkeyRunnerExported annotation = clz.getAnnotation(MonkeyRunnerExported.class); paragraphsIntoHDF(prefix + DOC, annotation.doc(), hdf); hdf.setValue(prefix + TYPE, Type.ENUM.name()); // Now go through the enumeration constants Object[] constants = clz.getEnumConstants(); String[] argDocs = annotation.argDocs(); if (constants.length > 0) { for (int x = 0; x < constants.length; x++) { String argPrefix = prefix + ARGUMENT + "." + x + "."; hdf.setValue(argPrefix + NAME, constants[x].toString()); if (argDocs.length > x) { paragraphsIntoHDF(argPrefix + DOC, argDocs[x], hdf); } } } outputItemCount++; } for (Method m : methods) { String prefix = HELP + "." + outputItemCount + "."; MonkeyRunnerExported annotation = m.getAnnotation(MonkeyRunnerExported.class); String className = m.getDeclaringClass().getCanonicalName(); String methodName = className + "." + m.getName(); hdf.setValue(prefix + NAME, methodName); paragraphsIntoHDF(prefix + DOC, annotation.doc(), hdf); if (annotation.args().length > 0) { String[] argDocs = annotation.argDocs(); String[] aargs = annotation.args(); for (int x = 0; x < aargs.length; x++) { String argPrefix = prefix + ARGUMENT + "." + x + "."; hdf.setValue(argPrefix + NAME, aargs[x]); if (argDocs.length > x) { paragraphsIntoHDF(argPrefix + DOC, argDocs[x], hdf); } } } if (!"".equals(annotation.returns())) { paragraphsIntoHDF(prefix + RETURNS, annotation.returns(), hdf); } outputItemCount++; } return hdf; } public static Collection<String> getAllDocumentedClasses() { Set<Field> fields = Sets.newTreeSet(MEMBER_SORTER); Set<Method> methods = Sets.newTreeSet(MEMBER_SORTER); Set<Constructor<?>> constructors = Sets.newTreeSet(MEMBER_SORTER); Set<Class<?>> classes = Sets.newTreeSet(CLASS_SORTER); getAllExportedClasses(fields, methods, constructors, classes); // The classes object only captures classes that are specifically exporter, which isn't // good enough. So go through all the fields, methods, etc. and collect those classes as // as well Set<Class<?>> allClasses = Sets.newHashSet(); allClasses.addAll(classes); for (Field f : fields) { allClasses.add(f.getDeclaringClass()); } for (Method m : methods) { allClasses.add(m.getDeclaringClass()); } for (Constructor<?> constructor : constructors) { allClasses.add(constructor.getDeclaringClass()); } // And transform that collection into a list of simple names. return Collections2.transform(allClasses, new Function<Class<?>, String>() { @Override public String apply(Class<?> clz) { return clz.getName(); } }); } }