/* -*- Mode: JDE; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * This file is part of the debugger and core tools for the AspectJ(tm) * programming language; see http://aspectj.org * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * either http://www.mozilla.org/MPL/ or http://aspectj.org/MPL/. * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is AspectJ. * * The Initial Developer of the Original Code is Xerox Corporation. Portions * created by Xerox Corporation are Copyright (C) 1999-2002 Xerox Corporation. * All Rights Reserved. */ package org.aspectj.tools.doclets.standard; import org.aspectj.ajdoc.AspectDoc; import com.sun.javadoc.ClassDoc; import com.sun.javadoc.DocErrorReporter; import com.sun.javadoc.PackageDoc; import com.sun.javadoc.RootDoc; import com.sun.tools.doclets.ClassTree; import com.sun.tools.doclets.DocletAbortException; import com.sun.tools.doclets.HtmlDocWriter; import com.sun.tools.doclets.IndexBuilder; import java.io.IOException; import java.lang.reflect.Constructor; import java.util.Arrays; /** * An abstract allowing one to customize the writers * used in ajdoc. Subclasses should define the three * generate methods to specify the documentation made. * * @see #preGenerationClasses() * @see #postGenerationClasses() * @see #checkClasses() * @author Jeff Palm */ public abstract class AbstractStandard extends com.sun.tools.doclets.standard.Standard { // todo wes removed restriction, but implemented Standard as singleton via proxy private static int refCount = 0; { if (refCount > 0) { System.err.println("Warning: " + refCount + " AbstractStandard already "); } refCount++; } /** * The ClassTree that is available to subclasses and it gaurateed * to be created before pre-generating classes. */ protected ClassTree classtree; protected static boolean start(AbstractStandard as, RootDoc root) throws IOException { try { as.getConfiguration().setOptions(root); as.startGeneration(root); } catch (DocletAbortException exc) { return false; } return true; } /** * Returns the types of {@link Pass}es that will * run before generating classes. * * @return an array of Class, where each entry * is a subclass of {@link Pass}, ordered * to run directly before generating the * classes. */ protected abstract Class[] preGenerationClasses(); /** * Returns the types of {@link Pass}es that will * run after generating classes. * * @return an array of Class, where each entry * is a subclass of {@link Pass}, ordered * to run directly after generating the * classes. */ protected abstract Class[] postGenerationClasses(); /** * Returns the types of {@link CheckPass}es that will * run to check the classes. * * @return an array of Class, where each entry * is a subclass of {@link CheckPass}, ordered * to run in order to check the classes passed * into the class generation phase. */ protected abstract Class[] checkClasses(); /** * Return the configuration used by a subclass. This * allows the subclass to specify it's own kind. * * @return a customized configuration. */ public abstract ConfigurationStandard getConfiguration(); protected ConfigurationStandard makeConfiguration() { return new ConfigurationStandard(); } /** * Returns the configuration, and ensures that * HtmlDocWriter.configuration is of the type used by * this class. * * @return the current instanceof ConfigurationStandard being * used and creates one if needed. This will not * be null. */ // todo these are the heinous globals that impose one process per classloader public static com.sun.tools.doclets.standard.ConfigurationStandard configuration() { if (HtmlDocWriter.configuration == null || !(HtmlDocWriter.configuration instanceof ConfigurationStandard)) { HtmlDocWriter.configuration = new ConfigurationStandard(); //TODO: change to makeConfiguration() } return (ConfigurationStandard)HtmlDocWriter.configuration; } /** * Creates and returns an IndexBuilder that includes aspects. * * @param root RootDoc to pass the new IndexBuilder. * @param classesOnly true if only classes * should be included. * @return an IndexBuilder that includes aspects. */ protected IndexBuilder indexBuilder(RootDoc root, boolean classesOnly) { class MyIndexBuilder extends IndexBuilder { public MyIndexBuilder(RootDoc r, boolean n) { super(r, n); } public MyIndexBuilder(RootDoc r, boolean n, boolean b) { super(r, n, b); } protected void putMembersInIndexMap(ClassDoc classdoc) { super.putMembersInIndexMap(classdoc); if (classdoc instanceof org.aspectj.ajdoc.ClassDoc) { org.aspectj.ajdoc.ClassDoc cd = (org.aspectj.ajdoc.ClassDoc)classdoc; adjustIndexMap(cd.pointcuts()); if (cd instanceof AspectDoc) { adjustIndexMap(((AspectDoc)cd).advice()); } } } } return new MyIndexBuilder(root, configuration().nodeprecated, classesOnly); } /** * Does the work in generating the documentation. * First, call all the passes return from {@link #generateCheckPasses} * them perform some copying. Second build the classtree, run the * pre-classgeneration passes, generate the packages, generate the * classes, then call all the postGenerationClasses. * * @param root the root of the documentation. */ protected void startGeneration(RootDoc root) throws DocletAbortException { if (!generateCheckPasses(getConfiguration(), root)) return; performCopy(getConfiguration().destdirname, getConfiguration().helpfile); performCopy(getConfiguration().destdirname, getConfiguration().stylesheetfile); classtree = new ClassTree(root, getConfiguration().nodeprecated); generatePrePasses(getConfiguration(), root); generatePackageCycle(getConfiguration().packages, getConfiguration().createtree, getConfiguration().nodeprecated); generateClassFiles(root, classtree); generatePostPasses(getConfiguration(), root); } /** * A class representing a single pass in the generation cycles. It * does some of the dirty work for you. */ public static abstract class Pass { /** The root available to this pass. */ protected RootDoc root; /** The configuration available to this pass. */ protected ConfigurationStandard cs; /** The doclet available to this pass. */ protected AbstractStandard std; public Pass() {} /** * Returns the title of the pass for logging. * * @return the unique title of this pass. This can * be null to disable display. */ public abstract String title(); /** * Do the generation work. All instance variables * are guaranteed to be set. */ protected abstract void gen() throws DocletAbortException; /** * Do the actual generation if {@link #cond} returns * true. Do some other logging, too. * * @param std the AbstractStandard to use. * @param cs the ConfigurationStandard to use. * @param root the RootDoc to use. */ public final void generate(AbstractStandard std, ConfigurationStandard cs, RootDoc root) throws DocletAbortException { this.std = std; this.cs = cs; this.root = root; if (cond()) { String title = title(); long start = System.currentTimeMillis(); if (cs.log && title != null) { cs.standardmessage.notice("doclet.pass_msg", title); } gen(); if (cs.log && title != null) { long stop = System.currentTimeMillis(); cs.standardmessage.notice("doclet.done_msg", title, (stop-start)+""); } } } /** * Returns whether the generation should proceed. Override * this method for conditional passes. * * @return true is this pass shoulud proceed. */ protected boolean cond() { return true; } } /** * A convenience class for doing checks. */ public abstract static class Check extends Pass { /** * Returns the error message if check fails. * * @return error message if check fails. */ protected abstract String message(); /** * Returns whether check has failed or not. * * @return true is check fails. */ protected abstract boolean cond(); /** * Prints message, because we've failed and throws * a DocletAbortException to notify the doclet * that we've failed. */ protected void gen() throws DocletAbortException { cs.standardmessage.error(message()); throw new DocletAbortException(); } /** * Returns null, because we don't want to be displayed. * * @return null. */ public String title() { return null; } } /** * Generates the passes to run before generating the classes. */ private final void generatePrePasses(ConfigurationStandard cs, RootDoc root) throws DocletAbortException { generatePasses(cs, root, preGenerationClasses()); } /** * Generates the passes to run after generating the classes. */ private final void generatePostPasses(ConfigurationStandard cs, RootDoc root) throws DocletAbortException { generatePasses(cs, root, postGenerationClasses()); } /** * Generates the passes that run before doing anything. These * passes check that it's OK to do anything. */ private final boolean generateCheckPasses(ConfigurationStandard cs, RootDoc root) throws DocletAbortException { try { generatePasses(cs, root, checkClasses()); } catch (DocletAbortException e) { return false; } return true; } /** * Generates passes from classes. For each * class found in classes a constructor taking zero * or one-argument is called. Then the generate method is * called on that Pass passing it this, the * configuration, and root. * * @param cs configuration to use. * @param root root we're documenting. * @param classes list of subtypes of {@link Pass} that * will be run. */ private final void generatePasses(ConfigurationStandard cs, RootDoc root, Class[] classes) throws DocletAbortException { if (classes == null) return; nextClass: for (int i = 0; i < classes.length; i++) { try { Constructor[] ctrs = classes[i].getConstructors(); nextCtr: for (int j = 0; j < ctrs.length; j++) { Pass pass = null; if (ctrs[j].getParameterTypes().length == 0) { pass = (Pass)ctrs[j].newInstance(new Object[]{}); } else if (ctrs[j].getParameterTypes().length == 1) { pass = (Pass)ctrs[j].newInstance(new Object[]{this}); } if (pass != null) { pass.generate(this,cs,root); continue nextClass; } } throw new Exception("Can't create pass for class " + classes[i]); } catch (Exception e) { e.printStackTrace(); Standard.configuration().standardmessage. error("doclet.exception", e+""); throw new DocletAbortException(); } } } /** * Generates the packages. */ protected void generatePackageCycle(PackageDoc[] pkgs, boolean createtree, boolean nodeprecated) throws DocletAbortException { Arrays.sort(pkgs); for (int i = 0; i < pkgs.length; i++) { PackageDoc prev = i == 0 ? null : pkgs[i-1]; PackageDoc curr = pkgs[i]; PackageDoc next = i == pkgs.length-1 ? null : pkgs[i+1]; generatePackages(prev, curr, next, createtree, nodeprecated); } } /** * Generates a package doc for the three PackageDocs passed. */ protected void generatePackages(PackageDoc prev, PackageDoc curr, PackageDoc next, boolean createtree, boolean nodeprecated) throws DocletAbortException { PackageWriter.generate(curr, prev, next); if (createtree) { PackageTreeWriter.generate(curr, prev, next, nodeprecated); } PackageFrameWriter.generate(curr); } /** * Generates all the classes. */ protected void generateClassCycle(ClassDoc[] cs, ClassTree classtree, boolean nopackage) throws DocletAbortException { Arrays.sort(cs); for(int i = 0; i < cs.length; i++) { if (configuration().nodeprecated && cs[i].tags("deprecated").length > 0) { continue; } ClassDoc prev = i == 0 ? null : cs[i-1]; ClassDoc curr = cs[i]; ClassDoc next = i == cs.length-1 ? null : cs[i+1]; generateClasses(prev, curr, next, classtree, nopackage); } } /** * Generates class docs for the three ClassDocs passed. */ protected void generateClasses(ClassDoc prev, ClassDoc curr, ClassDoc next, ClassTree classtree, boolean nopackage) throws DocletAbortException { ClassWriter.generate(curr, prev, next, classtree, nopackage); } /** * Returns the delegation to {@link #configuration()}. */ public static int optionLength(String option) { return configuration().optionLength(option); } /** * Returns the delegation to {@link #configuration()}. */ public static boolean validOptions(String options[][], DocErrorReporter reporter) throws IOException { return configuration().validOptions(options, reporter); } }