package org.zstack.tool.doclet; import com.sun.javadoc.*; import com.sun.tools.doclets.standard.Standard; import groovy.lang.GroovyClassLoader; import org.zstack.header.exception.CloudRuntimeException; import org.zstack.tool.doclet.APIEventDoc.EventField; import org.zstack.tool.doclet.APIMessageDoc.ParameterDoc; import org.zstack.tool.doclet.InventoryDoc.InventoryField; import org.zstack.utils.StringDSL; import org.zstack.utils.gson.JSONObjectUtil; import java.io.InputStream; import java.util.*; /** */ public class DocLet extends Standard { private static final String INVENTORY_TAG = "inventory"; private static final String EXAMPLE_TAG = "example"; private static final String SINCE = "since"; private static final String DESCRIPTION = "desc"; private static final String IGNORE = "ignore"; private static final String CHOICES = "choices"; private static final String NULLABLE = "nullable"; private static final String OPTIONAL = "optional"; private static final String API = "api"; private static final String MSG = "msg"; private static final String HTTP_MSG = "httpMsg"; private static final String CLI = "cli"; private static final String API_RESULT = "apiResult"; private static final String RESULT = "result"; private static Map<String, InventoryDoc> inventories = new HashMap<String, InventoryDoc>(); private static Map<String, APIMessageDoc> api = new HashMap<String, APIMessageDoc>(); private static Map<String, APIEventDoc> events = new HashMap<String, APIEventDoc>(); private static String currentEntityName; private static void log(String msg) { System.out.println(msg); } private static Tag getTag(Doc doc, String tagName, boolean exceptionOnMissing) { Tag[] tags = doc.tags(tagName); if (tags.length == 0 && exceptionOnMissing) { throw new RuntimeException(String.format("can not find tag[%s] on field[%s] of %s", tagName, doc.name(), currentEntityName)); } if (tags.length == 0) { return null; } return tags[0]; } private static Tag getTag(Doc doc, String tagName) { return getTag(doc, tagName, false); } private static boolean hasTag(Doc doc, String tagName) { Tag[] tags = doc.tags(tagName); return tags.length != 0; } private static List<FieldDoc> getAllFieldDocs(ClassDoc clazz) { List<FieldDoc> ret = new ArrayList<FieldDoc>(); do { for (FieldDoc doc : clazz.fields()) { if (hasTag(doc, IGNORE)) { continue; } if (doc.isStatic()) { continue; } ret.add(doc); } clazz = clazz.superclass(); } while (clazz != null && !clazz.qualifiedName().equals(Object.class.getName())); return ret; } private static void handleInventory(ClassDoc clazz) { log(String.format("generating doc for inventory[%s]...", clazz)); currentEntityName = clazz.qualifiedName(); InventoryDoc doc = new InventoryDoc(); Tag tag = getTag(clazz, INVENTORY_TAG); doc.setName(clazz.simpleTypeName()); doc.setFullName(clazz.qualifiedTypeName()); doc.setDescription(tag.text()); tag = getTag(clazz, EXAMPLE_TAG, true); Map example = JSONObjectUtil.toObject(tag.text(), LinkedHashMap.class); doc.setExample(JSONObjectUtil.dumpPretty(example)); tag = getTag(clazz, SINCE, true); doc.setSince(tag.text()); List<FieldDoc> fieldDocs = getAllFieldDocs(clazz); for (FieldDoc fieldDoc : fieldDocs) { if (hasTag(fieldDoc, IGNORE)) { continue; } InventoryField fdoc = new InventoryField(); fdoc.setName(fieldDoc.name()); Tag desc = getTag(fieldDoc, DESCRIPTION, true); fdoc.setDescription(desc.text()); Tag choices = getTag(fieldDoc, CHOICES); if (choices != null) { fdoc.setChoices(choices.text()); } fdoc.setType(fieldDoc.type().simpleTypeName()); fdoc.setNullable(hasTag(fieldDoc, NULLABLE)); Tag tsince = getTag(fieldDoc, SINCE); String since = tsince == null ? doc.getSince() : tsince.text(); fdoc.setSince(since); doc.getFields().add(fdoc); } inventories.put(doc.getName(), doc); log(JSONObjectUtil.toJsonString(doc)); } private static String makeDirectoryPath(String dirName) { return String.format(String.format("%s/%s/%s", System.getProperty("user.home"), "zstack-rst-doc", dirName)); } private static RestructuredTextWriter getWriter() throws IllegalAccessException, InstantiationException { ClassLoader parent = DocLet.class.getClassLoader(); GroovyClassLoader loader = new GroovyClassLoader(parent); InputStream in = DocLet.class.getClassLoader().getResourceAsStream("scripts/RestructuredTextWriterImpl.groovy"); String script = StringDSL.inputStreamToString(in); Class writerClass = loader.parseClass(script); Object obj = writerClass.newInstance(); return (RestructuredTextWriter) obj; } public static boolean start(RootDoc root) { ClassDoc[] classes = root.classes(); for (ClassDoc c : classes) { if (hasTag(c, INVENTORY_TAG)) { handleInventory(c); } else if (hasTag(c, API)) { handleApi(c); } else if (hasTag(c, API_RESULT)) { handleApiEvent(c); } } try { RestructuredTextWriter writer = getWriter(); String inventoryPath = makeDirectoryPath("inventory"); writer.writeInventory(inventoryPath, inventories); String apiPath = makeDirectoryPath("api"); writer.writeApiMessage(apiPath, api); apiPath = makeDirectoryPath("apiEvent"); writer.writeApiEvent(apiPath, events); } catch (Exception e) { throw new CloudRuntimeException(e); } return true; } private static void handleApiEvent(ClassDoc c) { log(String.format("Generating doc for api event[%s]...", c)); currentEntityName = c.qualifiedName(); APIEventDoc doc = new APIEventDoc(); doc.setName(c.simpleTypeName()); doc.setFullName(c.qualifiedTypeName()); Tag desc = getTag(c, API_RESULT, true); doc.setDescription(desc.text()); Tag tag = getTag(c, EXAMPLE_TAG, true); Map example = JSONObjectUtil.toObject(tag.text(), LinkedHashMap.class); doc.setExample(JSONObjectUtil.dumpPretty(example)); tag = getTag(c, SINCE, true); doc.setSince(tag.text()); for (FieldDoc fieldDoc : getAllFieldDocs(c)) { EventField edoc = new EventField(); edoc.setName(fieldDoc.name()); desc = getTag(fieldDoc, DESCRIPTION, true); edoc.setDescription(desc.text()); Tag choices = getTag(fieldDoc, CHOICES); if (choices != null) { edoc.setChoices(choices.text()); } edoc.setNullable(hasTag(fieldDoc, NULLABLE)); Tag tsince = getTag(fieldDoc, SINCE); String since = tsince == null ? doc.getSince() : tsince.text(); edoc.setSince(since); doc.getFields().add(edoc); } events.put(doc.getName(), doc); } private static void handleApi(ClassDoc c) { log(String.format("Generating doc for api[%s]...", c)); currentEntityName = c.qualifiedName(); APIMessageDoc doc = new APIMessageDoc(); doc.setName(c.simpleTypeName()); doc.setFullName(c.qualifiedTypeName()); Tag desc = getTag(c, API, true); doc.setDescription(desc.text()); Tag msg = getTag(c, MSG, true); Map msgObj = JSONObjectUtil.toObject(msg.text(), LinkedHashMap.class); doc.setMessage(JSONObjectUtil.dumpPretty(msgObj)); msg = getTag(c, HTTP_MSG, true); msgObj = JSONObjectUtil.toObject(msg.text(), LinkedHashMap.class); doc.setHttpMessage(JSONObjectUtil.dumpPretty(msgObj)); Tag cli = getTag(c, CLI); if (cli != null) { doc.setCli(cli.text()); } Tag since = getTag(c, SINCE, true); doc.setSince(since.text()); Tag result = getTag(c, RESULT, true); doc.setResult(result.text()); for (FieldDoc fieldDoc : getAllFieldDocs(c)) { ParameterDoc pdoc = new ParameterDoc(); pdoc.setName(fieldDoc.name()); desc = getTag(fieldDoc, DESCRIPTION, true); pdoc.setDescription(desc.text()); pdoc.setOptional(hasTag(fieldDoc, OPTIONAL)); since = getTag(c, SINCE); if (since != null) { pdoc.setSince(since.text()); } else { pdoc.setSince(doc.getSince()); } Tag choices = getTag(fieldDoc, CHOICES); if (choices != null) { pdoc.setChoices(choices.text()); } doc.getParameters().add(pdoc); } log(JSONObjectUtil.toJsonString(doc)); api.put(doc.getName(), doc); } }