/* ******************************************************************* * 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 */ 00039 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 */ 00084 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. */ 00145 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. */ 00471 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 */ 00609 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. */ 00637 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>. */ 00713 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. * */ 00732 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. */ 00790 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. */ 00803 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 */ 00872 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 */ 00884 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); } }