blob: 746d240ff6955dcfc7c44f25f2fecca5f91fa32c [file] [log] [blame]
/*
* 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();
}
});
}
}