/* *******************************************************************
* 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(AsmManager model, 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(model, 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("<BR>\nClass ");
if (classStartIndex == -1) {
classStartIndex = fileContents.toString().indexOf("<H2>\nClass ");
br = false;
}
if (classStartIndex != -1) {
int classEndIndex = fileContents.toString().indexOf("</H2>", classStartIndex);
if (classStartIndex != -1 && classEndIndex != -1) {
String classLine = fileContents.toString().substring(classStartIndex, classEndIndex);
String aspectLine = "";
if (br) {
aspectLine += "<BR>\n" + "Aspect " + classLine.substring(11, classLine.length());
} else {
aspectLine += "<H2>\n" + "Aspect " + classLine.substring(11, classLine.length());
}
fileContents.delete(classStartIndex, classEndIndex);
fileContents.insert(classStartIndex, aspectLine);
}
}
int secondClassStartIndex = fileContents.toString().indexOf("class <B>");
if (secondClassStartIndex != -1) {
String name = decl.toSignatureString();
int classEndIndex = fileContents.toString().indexOf(name + "</B><DT>");
if (secondClassStartIndex != -1 && classEndIndex != -1) {
StringBuffer sb = new StringBuffer(fileContents.toString().substring(secondClassStartIndex, classEndIndex));
sb.replace(0, 5, "aspect");
fileContents.delete(secondClassStartIndex, classEndIndex);
fileContents.insert(secondClassStartIndex, sb.toString());
}
}
}
}
file.delete();
FileOutputStream fos = new FileOutputStream(file);
fos.write(fileContents.toString().getBytes());
reader.close();
fos.close();
}
static void addAspectDocumentation(IProgramElement node, StringBuffer fileBuffer, int index) {
List pointcuts = new ArrayList();
List advice = new ArrayList();
List declares = new ArrayList();
List methodsDeclaredOn = StructureUtil.getDeclareInterTypeTargets(node, IProgramElement.Kind.INTER_TYPE_METHOD);
if (methodsDeclaredOn != null && !methodsDeclaredOn.isEmpty()) {
insertDeclarationsSummary(fileBuffer, methodsDeclaredOn, ITD_METHOD_SUMMARY, index);
}
List fieldsDeclaredOn = StructureUtil.getDeclareInterTypeTargets(node, IProgramElement.Kind.INTER_TYPE_FIELD);
if (fieldsDeclaredOn != null && !fieldsDeclaredOn.isEmpty()) {
insertDeclarationsSummary(fileBuffer, fieldsDeclaredOn, ITD_FIELD_SUMMARY, index);
}
List constDeclaredOn = StructureUtil.getDeclareInterTypeTargets(node, IProgramElement.Kind.INTER_TYPE_CONSTRUCTOR);
if (fieldsDeclaredOn != null && !constDeclaredOn.isEmpty()) {
insertDeclarationsSummary(fileBuffer, constDeclaredOn, ITD_CONSTRUCTOR_SUMMARY, index);
}
for (Iterator it = node.getChildren().iterator(); it.hasNext();) {
IProgramElement member = (IProgramElement) it.next();
if (member.getKind().equals(IProgramElement.Kind.POINTCUT)) {
pointcuts.add(member);
} else if (member.getKind().equals(IProgramElement.Kind.ADVICE)) {
advice.add(member);
} else if (member.getKind().isDeclare() || member.getKind().isInterTypeMember()) {
declares.add(member);
}
}
if (declares.size() > 0) {
insertDeclarationsDetails(fileBuffer, declares, DECLARE_DETAIL, index);
insertDeclarationsSummary(fileBuffer, declares, DECLARE_SUMMARY, index);
}
if (pointcuts.size() > 0) {
insertDeclarationsSummary(fileBuffer, pointcuts, POINTCUT_SUMMARY, index);
insertDeclarationsDetails(fileBuffer, pointcuts, POINTCUT_DETAIL, index);
}
if (advice.size() > 0) {
insertDeclarationsSummary(fileBuffer, advice, ADVICE_SUMMARY, index);
insertDeclarationsDetails(fileBuffer, advice, ADVICE_DETAIL, index);
}
// add the 'aspect declarations' information against the type
List parentsDeclaredOn = StructureUtil.getDeclareInterTypeTargets(node, IProgramElement.Kind.DECLARE_PARENTS);
if (parentsDeclaredOn != null && parentsDeclaredOn.size() > 0) {
decorateDocWithRel(node, fileBuffer, index, parentsDeclaredOn, HtmlRelationshipKind.ASPECT_DECLARATIONS);
}
// add the 'annotated by' information against the type
List annotatedBy = StructureUtil.getTargets(node, IRelationship.Kind.DECLARE_INTER_TYPE, "annotated by");
if (annotatedBy != null && annotatedBy.size() > 0) {
decorateDocWithRel(node, fileBuffer, index, annotatedBy, HtmlRelationshipKind.ANNOTATED_BY);
}
// add the 'advised by' information against the type
List advisedBy = StructureUtil.getTargets(node, IRelationship.Kind.ADVICE);
if (advisedBy != null && advisedBy.size() > 0) {
decorateDocWithRel(node, fileBuffer, index, advisedBy, HtmlRelationshipKind.ADVISED_BY);
}
}
static void insertDeclarationsSummary(StringBuffer fileBuffer, List decls, String kind, int index) {
if (!declsAboveVisibilityExist(decls))
return;
int insertIndex = findSummaryIndex(fileBuffer, index);
// insert the head of the table
String tableHead = "<!-- ======== " + kind.toUpperCase() + " ======= -->\n\n"
+ "<TABLE BORDER=\"1\" WIDTH=\"100%\" CELLPADDING=\"1\""
+ "CELLSPACING=\"0\"><TR><TD COLSPAN=2 BGCOLOR=\"#CCCCFF\">" + "<FONT SIZE=\"+2\"><B>" + kind
+ "</B></FONT></TD></TR>\n";
fileBuffer.insert(insertIndex, tableHead);
insertIndex += tableHead.length();
// insert the body of the table
for (int i = 0; i < decls.size(); i++) {
IProgramElement decl = (IProgramElement) decls.get(i);
if (isAboveVisibility(decl)) {
// insert the table row accordingly
String comment = generateSummaryComment(decl);
String entry = "";
if (kind.equals(ADVICE_SUMMARY)) {
entry += "<TR><TD>" + "<A HREF=\"#" + generateHREFName(decl) + "\">" + "<TT>" + generateSignatures(decl)
+ "</TT></A><BR> ";
if (!comment.equals("")) {
entry += comment + "<P>";
}
entry += generateAffects(decl) + "</TD>" + "</TR><TD>\n";
} else if (kind.equals(POINTCUT_SUMMARY)) {
entry += "<TR><TD WIDTH=\"1%\">" + "<FONT SIZE=-1><TT>" + genAccessibility(decl) + "</TT></FONT>" + "</TD>\n"
+ "<TD>" + "<TT><A HREF=\"#" + generateHREFName(decl) + "\">" + decl.toLabelString()
+ "</A></TT><BR> ";
if (!comment.equals("")) {
entry += comment + "<P>";
}
entry += "</TR></TD>\n";
} else if (kind.equals(DECLARE_SUMMARY)) {
entry += "<TR><TD WIDTH=\"1%\">" + "<FONT SIZE=-1><TT>" + generateModifierInformation(decl, false)
+ "</TT></FONT>" + "</TD>" + "<TD>" + "<A HREF=\"#" + generateHREFName(decl) + "\">" + "<TT>"
+ decl.toLabelString() + "</TT></A><P>" + generateAffects(decl);
} else if (kind.equals(ITD_FIELD_SUMMARY) || kind.equals(ITD_METHOD_SUMMARY)) {
entry += "<TR><TD WIDTH=\"1%\">" + "<FONT SIZE=-1><TT>" + generateModifierInformation(decl, false)
+ "</TT></FONT>" + "</TD>" + "<TD>" + "<A HREF=\"#" + generateHREFName(decl) + "\">" + "<TT>"
+ decl.toLabelString() + "</TT></A><P>" + generateDeclaredBy(decl);
} else if (kind.equals(ITD_CONSTRUCTOR_SUMMARY)) {
entry += "<TD>" + "<A HREF=\"#" + generateHREFName(decl) + "\">" + "<TT>" + decl.toLabelString()
+ "</TT></A><P>" + generateDeclaredBy(decl);
}
// insert the entry
fileBuffer.insert(insertIndex, entry);
insertIndex += entry.length();
}
}
// insert the end of the table
String tableTail = "</TABLE><P> \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 = "<P> \n" + "<!-- ======== " + kind.toUpperCase() + " SUMMARY ======= -->\n\n"
+ "<TABLE BORDER=\"1\" CELLPADDING=\"3\" CELLSPACING=\"0\" WIDTH=\"100%\">\n"
+ "<TR BGCOLOR=\"#CCCCFF\" CLASS=\"TableHeadingColor\">\n" + "<TD COLSPAN=1><FONT SIZE=\"+2\">\n" + "<B>" + kind
+ "</B></FONT></TD>\n" + "</TR>\n" + "</TABLE>";
fileBuffer.insert(insertIndex, detailsHeading);
insertIndex += detailsHeading.length();
// insert the details
for (int i = 0; i < decls.size(); i++) {
IProgramElement decl = (IProgramElement) decls.get(i);
if (isAboveVisibility(decl)) {
String entry = "";
// insert the table row accordingly
entry += "<A NAME=\"" + generateHREFName(decl) + "\"><!-- --></A>\n";
if (kind.equals(ADVICE_DETAIL)) {
entry += "<H3>" + decl.getName() + "</H3><P>";
entry += "<TT>" + generateSignatures(decl) + "</TT>\n" + "<P>" + generateDetailsComment(decl) + "<P>"
+ generateAffects(decl);
} else if (kind.equals(POINTCUT_DETAIL)) {
entry += "<H3>" + decl.toLabelString() + "</H3><P>" + generateDetailsComment(decl);
} else if (kind.equals(DECLARE_DETAIL)) {
entry += "<H3>" + decl.toLabelString() + "</H3><P>" + 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()) {
String sigs = generateSignatures(decl);
entry += sigs + "<P>";
}
entry += generateAffects(decl) + generateDetailsComment(decl);
}
// insert the entry
if (i != decls.size() - 1) {
entry += "<P><HR>\n";
} else {
entry += "<P>";
}
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 = "<!-- =========== FIELD SUMMARY =========== -->";
String MARKER_2 = "<!-- ======== CONSTRUCTOR SUMMARY ======== -->";
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 = "<!-- ========= CONSTRUCTOR DETAIL ======== -->";
String MARKER_2 = "<!-- ============ FIELD DETAIL =========== -->";
String MARKER_3 = "<!-- ============ METHOD DETAIL ========== -->";
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 = "<TABLE WIDTH=\"100%\" BGCOLOR=#FFFFFF><TR>"
+ "<TD width=\"15%\" bgcolor=\"#FFD8B0\"><B><FONT COLOR=000000>" + relKind.toString() + "</font></b></td><td>";
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 = node.getModel().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 + "<A HREF=\"" + hrefLink + "\"><tt>" + hrefName.replace('/', '.') + "</tt></A>";
if (it.hasNext())
adviceDoc += ", ";
addedNames.add(hrefName);
}
}
adviceDoc += "</TR></TD></TABLE>\n";
fileContentsBuffer.insert(index, adviceDoc);
}
}
static void decorateMemberDocumentation(IProgramElement node, StringBuffer fileContentsBuffer, int index) {
List targets = StructureUtil.getTargets(node, IRelationship.Kind.ADVICE);
decorateDocWithRel(node, fileContentsBuffer, index, targets, HtmlRelationshipKind.ADVISED_BY);
List warnings = StructureUtil.getTargets(node, IRelationship.Kind.DECLARE, "matches declare");
decorateDocWithRel(node, fileContentsBuffer, index, warnings, HtmlRelationshipKind.MATCHES_DECLARE);
List softenedBy = StructureUtil.getTargets(node, IRelationship.Kind.DECLARE, "softened by");
decorateDocWithRel(node, fileContentsBuffer, index, softenedBy, HtmlRelationshipKind.SOFTENED_BY);
List annotatedBy = StructureUtil.getTargets(node, IRelationship.Kind.DECLARE_INTER_TYPE, "annotated by");
decorateDocWithRel(node, fileContentsBuffer, index, annotatedBy, HtmlRelationshipKind.ANNOTATED_BY);
}
/**
* pr119453 - adding "declared by" relationship
*/
static String generateDeclaredBy(IProgramElement decl) {
String entry = "<TABLE WIDTH=\"100%\" BGCOLOR=#FFFFFF><TR>"
+ "<TD width=\"10%\" bgcolor=\"#FFD8B0\"><B><FONT COLOR=000000>" + " Declared by:</b></font></td><td>";
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 += "<A HREF=\"" + hrefLink + "\"><tt>" + hrefName.replace('/', '.') + "</tt></A>"; // !!! don't replace
}
entry += "</B></FONT></TD></TR></TABLE>\n</TR></TD>\n";
return entry;
}
/**
* TODO: probably want to make this the same for intros and advice.
*/
static String generateAffects(IProgramElement decl) {
List targets = null;
if (decl.getKind().isDeclare() || decl.getKind().isInterTypeMember()) {
targets = StructureUtil.getDeclareTargets(decl);
} else {
targets = StructureUtil.getTargets(decl, IRelationship.Kind.ADVICE);
}
if (targets == null)
return "";
String entry = "<TABLE WIDTH=\"100%\" BGCOLOR=#FFFFFF><TR>";
IProgramElement.Kind kind = decl.getKind();
if (kind.equals(IProgramElement.Kind.ADVICE)) {
entry += "<TD width=\"10%\" bgcolor=\"#FFD8B0\"><B><FONT COLOR=000000>" + HtmlRelationshipKind.ADVISES.toString()
+ "</b></font></td><td>";
} else if (kind.equals(IProgramElement.Kind.DECLARE_WARNING) || kind.equals(IProgramElement.Kind.DECLARE_ERROR)) {
entry += "<TD width=\"10%\" bgcolor=\"#FFD8B0\"><B><FONT COLOR=000000>" + HtmlRelationshipKind.MATCHED_BY.toString()
+ "</b></font></td><td>";
} else if (kind.isDeclareAnnotation()) {
entry += "<TD width=\"10%\" bgcolor=\"#FFD8B0\"><B><FONT COLOR=000000>" + HtmlRelationshipKind.ANNOTATES.toString()
+ "</b></font></td><td>";
} else if (kind.equals(IProgramElement.Kind.DECLARE_SOFT)) {
entry += "<TD width=\"10%\" bgcolor=\"#FFD8B0\"><B><FONT COLOR=000000>" + HtmlRelationshipKind.SOFTENS.toString()
+ "</b></font></td><td>";
} else {
entry += "<TD width=\"10%\" bgcolor=\"#FFD8B0\"><B><FONT COLOR=000000>" + HtmlRelationshipKind.DECLARED_ON.toString()
+ "</b></font></td><td>";
}
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 = decl.getModel().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 += "<A HREF=\"" + hrefLink + "\"><tt>" + hrefName.replace('/', '.') + "</tt></A>"; // !!! don't replace
if (it.hasNext())
entry += ", ";
addedNames.add(hrefName);
}
}
}
entry += "</B></FONT></TD></TR></TABLE>\n</TR></TD>\n";
return entry;
}
/**
* Generates a relative directory path fragment that can be used to navigate "upwards" from the directory location implied by
* the argument.
*
* @param packagePath
* @return String consisting of multiple "../" parts, one for each component part of the input <code>packagePath</code>.
*/
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 + "</TT>";
}
if (isDetails || !decl.getAccessibility().equals(IProgramElement.Accessibility.PUBLIC)) {
intro += "<TT>" + decl.getAccessibility().toString() + " ";
}
if (decl.getKind().equals(IProgramElement.Kind.INTER_TYPE_FIELD)) {
return intro + decl.getCorrespondingType() + "</TT>";
} else if (decl.getKind().equals(IProgramElement.Kind.INTER_TYPE_CONSTRUCTOR) && isDetails) {
return intro + "</TT>";
} else {
return intro + decl.getCorrespondingType(true) + "</TT>";
}
}
static String generateIntroductionSignatures(IProgramElement decl, boolean isDetails) {
return "<not implemented>";
}
static String generateSignatures(IProgramElement decl) {
return "<B>" + decl.toLabelString() + "</B>";
}
static String generateSummaryComment(IProgramElement decl) {
String COMMENT_INDENT = " "; // !!!
String formattedComment = getFormattedComment(decl);
int periodIndex = formattedComment.indexOf('.');
if (formattedComment.equals("")) {
return "";
} else if (periodIndex != -1) {
return COMMENT_INDENT + formattedComment.substring(0, periodIndex + 1);
} else {
return COMMENT_INDENT + formattedComment;
}
}
static String generateDetailsComment(IProgramElement decl) {
return getFormattedComment(decl);
}
static String generateHREFName(IProgramElement decl) {
StringBuffer hrefLinkBuffer = new StringBuffer();
char[] declChars = decl.toLabelString().toCharArray();
for (int i = 0; i < declChars.length; i++) {
if (declChars[i] == '"') {
hrefLinkBuffer.append("quot;");
} else {
hrefLinkBuffer.append(declChars[i]);
}
}
return hrefLinkBuffer.toString();
}
/**
* Figure out the link relative to the package.
*/
static String generateAffectsHREFLink(String declaringType) {
String link = rootDir.getAbsolutePath() + "/" + declaringType + ".html";
return link;
}
/**
* This formats a comment according to the rules in the Java Langauge Spec: <I>The text of a docuemntation comment consists of
* the characters between the /** that begins the comment and the 'star-slash' that ends it. The text is devided into one or
* more lines. On each of these lines, the leading * characters are ignored; for lines other than the first, blanks and tabs
* preceding the initial * characters are also discarded.</I>
*
* TODO: implement formatting or linking for tags.
*/
static String getFormattedComment(IProgramElement decl) {
String comment = decl.getFormalComment();
if (comment == null)
return "";
String formattedComment = "";
// strip the comment markers
int startIndex = comment.indexOf("/**");
int endIndex = comment.indexOf("*/");
if (startIndex == -1) {
startIndex = 0;
} else {
startIndex += 3;
}
if (endIndex == -1) {
endIndex = comment.length();
}
comment = comment.substring(startIndex, endIndex);
// string the leading whitespace and '*' characters at the beginning of each line
BufferedReader reader = new BufferedReader(new StringReader(comment));
try {
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
line = line.trim();
for (int i = 0; i < line.length(); i++) {
if (line.charAt(0) == '*') {
line = line.substring(1, line.length());
} else {
break;
}
}
// !!! remove any @see and @link tags from the line
// int seeIndex = line.indexOf("@see");
// int linkIndex = line.indexOf("@link");
// if ( seeIndex != -1 ) {
// line = line.substring(0, seeIndex) + line.substring(seeIndex);
// }
// if ( linkIndex != -1 ) {
// line = line.substring(0, linkIndex) + line.substring(linkIndex);
// }
formattedComment += line;
}
} catch (IOException ioe) {
throw new Error("Couldn't format comment for declaration: " + decl.getName());
}
return formattedComment;
}
static public IProgramElement[] getProgramElements(AsmManager model, String filename) {
IProgramElement file = (IProgramElement) model.getHierarchy().findElementForSourceFile(filename);
final List nodes = new ArrayList();
HierarchyWalker walker = new HierarchyWalker() {
public void preProcess(IProgramElement node) {
IProgramElement p = (IProgramElement) node;
if (accept(node))
nodes.add(p);
}
};
file.walk(walker);
return (IProgramElement[]) nodes.toArray(new IProgramElement[nodes.size()]);
}
/**
* Rejects anonymous kinds by checking if their name is an integer
*/
static private boolean accept(IProgramElement node) {
if (node.getKind().isType()) {
boolean isAnonymous = StructureUtil.isAnonymous(node);
return !node.getParent().getKind().equals(IProgramElement.Kind.METHOD) && !isAnonymous;
} else {
return !node.getKind().equals(IProgramElement.Kind.IMPORT_REFERENCE);
}
}
/**
* TypeSafeEnum for the entries which need to be put in the html doc
*/
public static class HtmlRelationshipKind extends TypeSafeEnum {
public HtmlRelationshipKind(String name, int key) {
super(name, key);
}
public static HtmlRelationshipKind read(DataInputStream s) throws IOException {
int key = s.readByte();
switch (key) {
case 1:
return ADVISES;
case 2:
return ADVISED_BY;
case 3:
return MATCHED_BY;
case 4:
return MATCHES_DECLARE;
case 5:
return DECLARED_ON;
case 6:
return ASPECT_DECLARATIONS;
case 7:
return SOFTENS;
case 8:
return SOFTENED_BY;
case 9:
return ANNOTATES;
case 10:
return ANNOTATED_BY;
case 11:
return USES_POINTCUT;
case 12:
return POINTCUT_USED_BY;
}
throw new Error("weird relationship kind " + key);
}
public static final HtmlRelationshipKind ADVISES = new HtmlRelationshipKind(" Advises:", 1);
public static final HtmlRelationshipKind ADVISED_BY = new HtmlRelationshipKind(" Advised by:", 2);
public static final HtmlRelationshipKind MATCHED_BY = new HtmlRelationshipKind(" Matched by:", 3);
public static final HtmlRelationshipKind MATCHES_DECLARE = new HtmlRelationshipKind(" Matches declare:", 4);
public static final HtmlRelationshipKind DECLARED_ON = new HtmlRelationshipKind(" Declared on:", 5);
public static final HtmlRelationshipKind ASPECT_DECLARATIONS = new HtmlRelationshipKind(" Aspect declarations:",
6);
public static final HtmlRelationshipKind SOFTENS = new HtmlRelationshipKind(" Softens:", 7);
public static final HtmlRelationshipKind SOFTENED_BY = new HtmlRelationshipKind(" Softened by:", 8);
public static final HtmlRelationshipKind ANNOTATES = new HtmlRelationshipKind(" Annotates:", 9);
public static final HtmlRelationshipKind ANNOTATED_BY = new HtmlRelationshipKind(" Annotated by:", 10);
public static final HtmlRelationshipKind USES_POINTCUT = new HtmlRelationshipKind(" Uses pointcut:", 11);
public static final HtmlRelationshipKind POINTCUT_USED_BY = new HtmlRelationshipKind(" Pointcut used by:",
12);
}
}