/* *******************************************************************
* Copyright (c) 1999-2001 Xerox Corporation,
* 2002 Palo Alto Research Center, Incorporated (PARC).
* All rights reserved.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License v1.0
* which accompanies this distribution and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Xerox/PARC initial implementation
* Mik Kersten port to AspectJ 1.1+ code base
* ******************************************************************/
package org.aspectj.tools.ajdoc;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import org.aspectj.asm.AsmManager;
import org.aspectj.asm.HierarchyWalker;
import org.aspectj.asm.IProgramElement;
import org.aspectj.asm.IRelationship;
import org.aspectj.util.TypeSafeEnum;
/**
* @author Mik Kersten
*/
class HtmlDecorator {
private static final String POINTCUT_DETAIL = "Pointcut Detail";
private static final String ADVICE_DETAIL = "Advice Detail";
private static final String DECLARE_DETAIL = "Declare Detail";
private static final String ADVICE_SUMMARY = "Advice Summary";
private static final String POINTCUT_SUMMARY = "Pointcut Summary";
private static final String DECLARE_SUMMARY = "Declare Summary";
private static final String ITD_METHOD_SUMMARY = "Inter-Type Method Summary";
private static final String ITD_FIELD_SUMMARY = "Inter-Type Field Summary";
private static final String ITD_CONSTRUCTOR_SUMMARY = "Inter-Type Constructor Summary";
static List visibleFileList = new ArrayList();
static Hashtable declIDTable = null;
static File rootDir = null;
static String docVisibilityModifier;
static void decorateHTMLFromInputFiles(Hashtable table,
File newRootDir,
File[] inputFiles,
String docModifier ) throws IOException {
rootDir = newRootDir;
declIDTable = table;
docVisibilityModifier = docModifier;
for (int i = 0; i < inputFiles.length; i++) {
decorateHTMLFromIPEs(getProgramElements(inputFiles[i].getCanonicalPath()),
rootDir.getCanonicalPath() + Config.DIR_SEP_CHAR,
docModifier,
false);
}
}
static void decorateHTMLFromIPEs(IProgramElement[] decls, String base, String docModifier, boolean exceededNestingLevel) throws IOException {
if ( decls != null ) {
for (int i = 0; i < decls.length; i++) {
IProgramElement decl = decls[i];
decorateHTMLFromIPE(decl, base, docModifier, exceededNestingLevel);
}
}
}
/**
* Before attempting to decorate the HTML file we have to verify that it exists,
* which depends on the documentation visibility specified to c.
*
* Depending on docModifier, can document
* - public: only public
* - protected: protected and public (default)
* - package: package protected and public
* - private: everything
*/
static void decorateHTMLFromIPE(IProgramElement decl,
String base,
String docModifier,
boolean exceededNestingLevel ) throws IOException {
boolean nestedClass = false;
if ( decl.getKind().isType() ) {
boolean decorateFile = true;
if (isAboveVisibility(decl)) {
visibleFileList.add(decl.toSignatureString());
String packageName = decl.getPackageName();
String filename = "";
if ( packageName != null ) {
int index1 = base.lastIndexOf(Config.DIR_SEP_CHAR);
int index2 = base.lastIndexOf(".");
String currFileClass = "";
if (index1 > -1 && index2 > 0 && index1 < index2) {
currFileClass = base.substring(index1+1, index2);
}
// XXX only one level of nexting
if (currFileClass.equals(decl.getDeclaringType())) {
nestedClass = true;
packageName = packageName.replace( '.','/' );
String newBase = "";
if ( base.lastIndexOf(Config.DIR_SEP_CHAR) > 0 ) {
newBase = base.substring(0, base.lastIndexOf(Config.DIR_SEP_CHAR));
}
String signature = constructNestedTypeName(decl);
filename = newBase + Config.DIR_SEP_CHAR + packageName +
Config.DIR_SEP_CHAR + currFileClass + //"." +
signature + ".html";
} else {
packageName = packageName.replace( '.','/' );
filename = base + packageName + Config.DIR_SEP_CHAR + decl.toSignatureString() + ".html";
}
}
else {
filename = base + decl.toSignatureString() + ".html";
}
if (!exceededNestingLevel) {
decorateHTMLFile(new File(filename));
}
else {
System.out.println("Warning: can not generate documentation for nested " +
"inner class: " + decl.toSignatureString() );
}
}
}
}
private static String constructNestedTypeName(IProgramElement node) {
if (node.getParent().getKind().isSourceFile()) {
return node.getName();
} else {
String nodeName = "";
if (node.getKind().isType()) nodeName += '.' + node.getName();
return constructNestedTypeName(node.getParent()) + nodeName;
}
}
/**
* Skips files that are public in the model but not public in the source,
* e.g. nested aspects.
*/
static void decorateHTMLFile(File file) throws IOException {
if (!file.exists()) return;
System.out.println( "> Decorating " + file.getCanonicalPath() + "..." );
BufferedReader reader = new BufferedReader(new FileReader(file));
StringBuffer fileContents = new StringBuffer();
String line = reader.readLine();
while( line != null ) {
fileContents.append(line + "\n");
line = reader.readLine();
}
boolean isSecond = false;
int index = 0;
IProgramElement decl;
while ( true ) {
//---this next part is an inlined procedure that returns two values---
//---the next declaration and the index at which that declaration's---
//---DeclID sits in the .html file ---
String contents = fileContents.toString();
int start = contents.indexOf( Config.DECL_ID_STRING, index);
int end = contents.indexOf( Config.DECL_ID_TERMINATOR, index );
if ( start == -1 )
decl = null;
else if ( end == -1 )
throw new Error("Malformed DeclID.");
else {
String tid = contents.substring(start + Config.DECL_ID_STRING.length(), end);
decl = (IProgramElement)declIDTable.get(tid);
index = start;
}
//--- ---
//--- ---
if ( decl == null ) break;
fileContents.delete(start, end + Config.DECL_ID_TERMINATOR.length());
if ( decl.getKind().isType() ) {
isSecond = true;
String fullname = "";
if (decl.getParent().getKind().equals(IProgramElement.Kind.ASPECT)
|| decl.getParent().getKind().equals(IProgramElement.Kind.CLASS)) {
fullname += decl.getParent().toSignatureString().concat(".").concat(decl.toSignatureString());
} else {
fullname += decl.toSignatureString();
}
// only add aspect documentation if we're in the correct
// file for the given IProgramElement
if (file.getName().indexOf(fullname + ".html") != -1) {
addAspectDocumentation(decl, fileContents, index);
}
}
else {
decorateMemberDocumentation(decl, fileContents, index);
}
// Change "Class" to "Aspect"
// moved this here because then can use the IProgramElement.Kind
// rather than checking to see if there's advice - this fixes
// the case with an inner aspect not having the title "Aspect"
if(decl.getKind().equals(IProgramElement.Kind.ASPECT)
&& file.getName().indexOf(decl.toSignatureString()) != -1) {
// only want to change "Class" to "Aspect" if we're in the
// file corresponding to the IProgramElement
String fullname = "";
if (decl.getParent().getKind().equals(IProgramElement.Kind.ASPECT)
|| decl.getParent().getKind().equals(IProgramElement.Kind.CLASS)) {
fullname += decl.getParent().toSignatureString().concat(".").concat(decl.toSignatureString());
} else {
fullname += decl.toSignatureString();
}
if (file.getName().indexOf(fullname + ".html") == -1) {
// we're still in the file for a parent IPE
continue;
}
boolean br = true;
int classStartIndex = fileContents.toString().indexOf("
\nClass ");
if (classStartIndex == -1) {
classStartIndex = fileContents.toString().indexOf("
" + "" + kind + " | ||
" +
"" +
"" + generateSignatures(decl) +
" "; if (!comment.equals("")) { entry += comment + " "; } entry += generateAffects(decl) + " | " +
"\n"; } else if ( kind.equals( POINTCUT_SUMMARY ) ) { entry += " | |
" + "" + genAccessibility(decl) + "" + " | \n" + "" +
"" +
decl.toLabelString() + " "; if (!comment.equals("")) { entry += comment + " "; } entry += " | |
" + "" + generateModifierInformation(decl,false) + "" + " | " + "" +
"" +
"" + decl.toLabelString() + " " + generateAffects(decl); } else if ( kind.equals( ITD_FIELD_SUMMARY ) || kind.equals( ITD_METHOD_SUMMARY)) { entry += " | |
" + "" + generateModifierInformation(decl,false) + "" + " | " + "" +
"" +
"" + decl.toLabelString() + " "+ generateDeclaredBy(decl); } else if ( kind.equals( ITD_CONSTRUCTOR_SUMMARY ) ) { entry +=" | " +
"" +
"" + decl.toLabelString() + " "+ generateDeclaredBy(decl); } // insert the entry fileBuffer.insert(insertIndex, entry); insertIndex += entry.length(); } } // insert the end of the table String tableTail = " |
\n"; fileBuffer.insert(insertIndex, tableTail); insertIndex += tableTail.length(); } private static boolean declsAboveVisibilityExist(List decls) { boolean exist = false; for (Iterator it = decls.iterator(); it.hasNext();) { IProgramElement element = (IProgramElement) it.next(); if (isAboveVisibility(element)) exist = true; } return exist; } private static boolean isAboveVisibility(IProgramElement element) { IProgramElement.Accessibility acc = element.getAccessibility(); if (docVisibilityModifier.equals("private")) { // show all classes and members return true; } else if (docVisibilityModifier.equals("package")) { // show package, protected and public classes and members return acc.equals(IProgramElement.Accessibility.PACKAGE) || acc.equals(IProgramElement.Accessibility.PROTECTED) || acc.equals(IProgramElement.Accessibility.PUBLIC); } else if (docVisibilityModifier.equals("protected")) { // show protected and public classes and members return acc.equals(IProgramElement.Accessibility.PROTECTED) || acc.equals(IProgramElement.Accessibility.PUBLIC); } else if (docVisibilityModifier.equals("public")){ // show public classes and members return acc.equals(IProgramElement.Accessibility.PUBLIC); } return false; } private static String genAccessibility(IProgramElement decl) { if (decl.getAccessibility().equals(IProgramElement.Accessibility.PACKAGE)) { return "(package private)"; } else { return decl.getAccessibility().toString(); } } static void insertDeclarationsDetails(StringBuffer fileBuffer, List decls, String kind, int index) { if (!declsAboveVisibilityExist(decls)) return; int insertIndex = findDetailsIndex(fileBuffer, index); // insert the table heading String detailsHeading = "
\n" + "\n\n" + "
\n" + "" + kind + " | \n" + "
"; entry += "" + generateSignatures(decl) + "\n" + "
" + generateDetailsComment(decl) + "
" + generateAffects(decl); } else if (kind.equals(POINTCUT_DETAIL)) { entry += "
" + generateDetailsComment(decl); } else if (kind.equals(DECLARE_DETAIL)) { entry += "
" + generateModifierInformation(decl,true); if (!decl.getKind().equals(IProgramElement.Kind.INTER_TYPE_CONSTRUCTOR)) { entry += " "; } // if we're not a declare statement then we need to generate the signature. // If we did this for declare statements we get two repeated lines if (!decl.getKind().isDeclare()) { entry += generateSignatures(decl) + "
"; } entry += generateAffects(decl) + generateDetailsComment(decl); } // insert the entry if (i != decls.size()-1) { entry += "
"; } fileBuffer.insert(insertIndex, entry); insertIndex += entry.length(); } } } /** * TODO: don't place the summary first. */ static int findSummaryIndex(StringBuffer fileBuffer, int index) { String fbs = fileBuffer.toString(); String MARKER_1 = ""; String MARKER_2 = ""; int index1 = fbs.indexOf(MARKER_1, index); int index2 = fbs.indexOf(MARKER_2, index); if (index1 < index2 && index1 != -1) { return index1; } else if (index2 != -1){ return index2; } else { return index; } } static int findDetailsIndex(StringBuffer fileBuffer, int index) { String fbs = fileBuffer.toString(); String MARKER_1 = ""; String MARKER_2 = ""; String MARKER_3 = ""; int index1 = fbs.indexOf(MARKER_1, index); int index2 = fbs.indexOf(MARKER_2, index); int index3 = fbs.indexOf(MARKER_3, index); if (index1 != -1 && index1 < index2 && index1 < index3) { return index1; } else if (index2 != -1 && index2 < index1 && index2 < index3) { return index2; } else if (index3 != -1) { return index3; } else { return index; } } static void decorateDocWithRel( IProgramElement node, StringBuffer fileContentsBuffer, int index, List targets, HtmlRelationshipKind relKind) { if (targets != null && !targets.isEmpty()) { String adviceDoc = "
" + relKind.toString() + " | "; String relativePackagePath = getRelativePathFromHere( node.getPackageName().replace('.', '/') + Config.DIR_SEP_CHAR); List addedNames = new ArrayList(); for (Iterator it = targets.iterator(); it.hasNext(); ) { Object o = it.next(); IProgramElement currDecl = null; if (o instanceof String) { String currHandle = (String)o; currDecl = AsmManager.getDefault().getHierarchy().findElementForHandle(currHandle); } else if (o instanceof IProgramElement){ currDecl = (IProgramElement)o; } else { return; } String packagePath = ""; if (currDecl.getPackageName() != null && !currDecl.getPackageName().equals("")) { packagePath = currDecl.getPackageName().replace('.', '/') + Config.DIR_SEP_CHAR; } String hrefName = ""; String hrefLink = ""; // Start the hRefLink with the relative path based on where // *this* type (i.e. the advised) is in the package structure. hrefLink = relativePackagePath + packagePath; if (currDecl.getPackageName() != null ) { hrefName = currDecl.getPackageName().replace('.', '/'); } // in the case of nested classes, in order for the links to work, // need to have the correct file name which is something of the // form parentClass.nestedAspect.html List names = new ArrayList(); IProgramElement parent = currDecl; while (parent != null && parent.getParent() != null && (!parent.getParent().getKind().equals(IProgramElement.Kind.FILE_JAVA) && !parent.getParent().getKind().equals(IProgramElement.Kind.FILE_ASPECTJ))) { parent = parent.getParent(); names.add(parent.toLinkLabelString()); } StringBuffer sbuff = new StringBuffer(); for (int i = names.size() - 1; i >= 0; i--) { String element = (String)names.get(i); if (i == 0) { sbuff.append(element); } else { sbuff.append(element + "."); } } // use the currDecl.toLabelString rather than currDecl.getName() // because two distinct advice blocks can have the same // currDecl.getName() and wouldn't both appear in the ajdoc hrefName += Config.DIR_SEP_CHAR + sbuff.toString() + "." + currDecl.toLabelString(); // need to replace " with quot; otherwise the links wont work // for 'matches declare' relationship StringBuffer sb = new StringBuffer(currDecl.toLabelString()); int nextQuote = sb.toString().indexOf("\""); while (nextQuote != -1) { sb.deleteCharAt(nextQuote); sb.insert(nextQuote,"quot;"); nextQuote = sb.toString().indexOf("\""); } hrefLink += sbuff.toString() + ".html" + "#" + sb.toString(); if (!addedNames.contains(hrefName)) { adviceDoc = adviceDoc + "" + hrefName.replace('/', '.') + ""; if (it.hasNext()) adviceDoc += ", "; addedNames.add(hrefName); } } adviceDoc += " |
" + " Declared by: | "; String relativePackagePath = getRelativePathFromHere( decl.getPackageName().replace('.', '/') + Config.DIR_SEP_CHAR); if (decl != null && !StructureUtil.isAnonymous(decl.getParent())) { String packagePath = ""; if (decl.getPackageName() != null && !decl.getPackageName().equals("")) { packagePath = decl.getPackageName().replace('.', '/') + Config.DIR_SEP_CHAR; } String typeSignature = constructNestedTypeName(decl); String hrefName = packagePath + typeSignature; // The hrefLink needs to just be the corresponding aspect String hrefLink = relativePackagePath + packagePath + typeSignature + ".html"; entry += "" + hrefName.replace('/', '.') + ""; // !!! don't replace } entry += " |
" + HtmlRelationshipKind.ADVISES.toString() + " | "; } else if (kind.equals(IProgramElement.Kind.DECLARE_WARNING) || kind.equals(IProgramElement.Kind.DECLARE_ERROR)) { entry += " | " + HtmlRelationshipKind.MATCHED_BY.toString() + " | "; } else if (kind.isDeclareAnnotation()) { entry += " | " + HtmlRelationshipKind.ANNOTATES.toString() + " | "; } else if (kind.equals(IProgramElement.Kind.DECLARE_SOFT)) { entry += " | " + HtmlRelationshipKind.SOFTENS.toString() + " | "; } else { entry += " | " + HtmlRelationshipKind.DECLARED_ON.toString() + " | "; } String relativePackagePath = getRelativePathFromHere( decl.getPackageName().replace('.', '/') + Config.DIR_SEP_CHAR); List addedNames = new ArrayList(); // for ensuring that we don't add duplciates for (Iterator it = targets.iterator(); it.hasNext(); ) { String currHandle = (String)it.next(); IProgramElement currDecl = AsmManager.getDefault().getHierarchy().findElementForHandle(currHandle); if (currDecl.getKind().equals(IProgramElement.Kind.CODE)) { currDecl = currDecl.getParent(); // promote to enclosing } if (currDecl != null && !StructureUtil.isAnonymous(currDecl.getParent())) { String packagePath = ""; if (currDecl.getPackageName() != null && !currDecl.getPackageName().equals("")) { packagePath = currDecl.getPackageName().replace('.', '/') + Config.DIR_SEP_CHAR; } String typeSignature = constructNestedTypeName(currDecl); String hrefName = packagePath + typeSignature; // Start the hRefLink with the relative path based on where // *this* type (i.e. the advisor) is in the package structure. String hrefLink = relativePackagePath + packagePath + typeSignature + ".html"; if (!currDecl.getKind().isType()) { hrefName += '.' + currDecl.getName(); hrefLink += "#" + currDecl.toLabelString(); } if (!addedNames.contains(hrefName)) { entry += "" + hrefName.replace('/', '.') + ""; // !!! don't replace if (it.hasNext()) entry += ", "; addedNames.add(hrefName); } } } entry += " |
packagePath
.
*/
private static String getRelativePathFromHere(String packagePath) {
StringBuffer result = new StringBuffer("");
if (packagePath != null && (packagePath.indexOf("/") != -1)) {
StringTokenizer sTok = new StringTokenizer(packagePath, "/", false);
while (sTok.hasMoreTokens()) {
sTok.nextToken(); // don't care about the token value
result.append(".." + Config.DIR_SEP_CHAR);
}// end while
}// end if
return result.toString();
}
/**
* Generate the "public int"-type information about the given IProgramElement.
* Used when dealing with ITDs. To mirror the behaviour of methods and fields
* in classes, if we're generating the summary information we don't want to
* include "public" if the accessibility of the IProgramElement is public.
*
*/
private static String generateModifierInformation(IProgramElement decl, boolean isDetails) {
String intro = "";
if (decl.getKind().isDeclare()) {
return intro + "";
}
if (isDetails ||
!decl.getAccessibility().equals(IProgramElement.Accessibility.PUBLIC)) {
intro += "" + decl.getAccessibility().toString() + " " ;
}
if (decl.getKind().equals(IProgramElement.Kind.INTER_TYPE_FIELD)) {
return intro + decl.getCorrespondingType() + "";
} else if (decl.getKind().equals(IProgramElement.Kind.INTER_TYPE_CONSTRUCTOR)
&& isDetails) {
return intro + "";
} else {
return intro + decl.getCorrespondingType(true) + "";
}
}
static String generateIntroductionSignatures(IProgramElement decl, boolean isDetails) {
return "