package org.jmlspecs.openjml.jmldoc; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import org.jmlspecs.annotation.NonNull; import org.jmlspecs.openjml.JmlPretty; import org.jmlspecs.openjml.JmlSpecs; import org.jmlspecs.openjml.JmlSpecs.TypeSpecs; import org.jmlspecs.openjml.JmlTree.JmlMethodDecl; import org.jmlspecs.openjml.JmlTree.JmlTypeClause; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseDecl; import com.sun.javadoc.ClassDoc; import com.sun.javadoc.ExecutableMemberDoc; import com.sun.javadoc.MethodDoc; import com.sun.javadoc.ProgramElementDoc; import com.sun.javadoc.Tag; import com.sun.tools.doclets.formats.html.MethodWriterImpl; import com.sun.tools.doclets.formats.html.SubWriterHolderWriter; import com.sun.tools.doclets.internal.toolkit.util.DocFinder; import com.sun.tools.javac.code.Scope; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Types; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Symbol.VarSymbol; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Name; import com.sun.tools.javac.util.Names; import com.sun.tools.javadoc.ClassDocImpl; import com.sun.tools.javadoc.DocEnv; import com.sun.tools.javadoc.MethodDocImpl; /** * This class extends its parent in order (a) to add JML specification * information to the documentation of Java methods and (b) to document model * methods. * * @author David R. Cok */ public class MethodWriterJml extends MethodWriterImpl { /** The class symbol of the class owning the method being documented, * in the JML compilation context. */ protected @NonNull ClassSymbol currentClassSym; /** * Construct a new MethodWriterImpl. * * @param writer the writer for the class that the methods belong to. * @param classDoc the class being documented. */ public MethodWriterJml(@NonNull SubWriterHolderWriter writer, @NonNull ClassDoc classDoc) { super(writer, classDoc); currentClassSym = Utils.findNewClassSymbol(classdoc); } /** * Write the javadoc tags (e.g. param and return tags) for the given method * and then write any JML specifications. * * @param method the method being documented. */ // FIXME - major change in b144 // @Override // public void writeTags(@NonNull MethodDoc method) { // super.writeTags(method); // writeJmlSpecs(method); // } // /** Write the JML specifications for the given method. * * @param method the method whose specs are to be written */ public void writeJmlSpecs(MethodDoc method) { if (method instanceof MethodDocImpl) { MethodSymbol oldMethodSym = ((MethodDocImpl)method).sym; Context context = org.jmlspecs.openjml.jmldoc.Main.jmlContext; Name newMethodName = Names.instance(context).fromString(oldMethodSym.name.toString()); Scope.Entry e = currentClassSym.members().lookup(newMethodName); while (!(e.sym instanceof MethodSymbol) || !match((MethodSymbol)e.sym,oldMethodSym)) { //Scope.Entry ee = e; e = e.sibling; if (e == null) { //if (ee.scope == null) { // FIXME - this happens when public fields are inherited from package super classes // System.out.println("NOT FOUND " + newMethodName + " IN " + currentClassSym); return; // FIXME - not found? //} //e = ee.scope.lookup(newMethodName); } } MethodSymbol newMethodSym = (MethodSymbol)e.sym; JmlSpecs.MethodSpecs mspecs = JmlSpecs.instance(context).getSpecs(newMethodSym); if (mspecs != null) { writer.br(); // Need this if there is tag info, otherwise not // FIXME String s = Utils.jmlAnnotations(newMethodSym); if (Utils.hasSpecs(mspecs) || s.length()!=0) { strong("JML Method Specifications: "); writer.print(s); writer.preNoNewLine(); writer.print(JmlPretty.write(mspecs.cases,false)); writer.preEnd(); } for (ClassSymbol c: Utils.getSupers(currentClassSym)) { MethodSymbol m = findSameContext(newMethodSym,c); if (m != null) { mspecs = JmlSpecs.instance(context).getSpecs(m); String ss = Utils.jmlAnnotations(m); if (Utils.hasSpecs(mspecs) || ss.length()!=0) { strong("JML Specifications inherited from " + c + ": "); writer.print(ss); writer.preNoNewLine(); writer.print(JmlPretty.write(mspecs.cases,false)); writer.preEnd(); } } } } } } /** Returns true if the two method symbols refer to the same method - * same name, same parameter names, same parameter types, same type arguments. * The first argument is a method symbol in the JML compilation context; * the second argument is a method symbol in the Javadoc compilation context. * @param m method symbol in JML compilation context * @param mm method symbol in the Javadoc compilation context * @return true if the two symbols refer to the same method */ public boolean match(MethodSymbol m, MethodSymbol mm) { if (!m.name.toString().equals(mm.name.toString())) return false; List<VarSymbol> mp = m.params(); List<VarSymbol> mmp = mm.params(); if (mp.size() != mmp.size()) return false; while (mp.tail != null) { if (!mp.head.name.toString().equals(mmp.head.name.toString())) return false; if (!mp.head.type.toString().equals(mmp.head.type.toString())) return false; mp = mp.tail; mmp = mmp.tail; } return true; } /** This method finds a method, if any, in the given class with the same * signature as the given method - same name, same parameter types, same * type arguments (the parameter names may be different). * The class and method are both in the JML compilation * context, but the class may be a superclass or superinterface of the * method's owner. * @param m a method * @param c a super-class or interface of m's owner in which to find an overridden method * @return the overridden method, or null if none present */ // FIXME - need a better way to find overridden methods - can't be doing signature matching by hand - complications if there are type arguments with different names public MethodSymbol findSameContext(MethodSymbol m, ClassSymbol c) { Scope.Entry e = c.members().lookup(m.name); loop: while (e != null && e.sym != null) { Symbol s = e.sym; e = e.sibling; // We use sibling instead of next() so that we stay // within the scope and do not look in enclosing scopes. But then // we do have to check that names match if (!(s instanceof MethodSymbol)) continue; MethodSymbol mm = (MethodSymbol)s; if (m.name != mm.name) continue; List<VarSymbol> mp = m.params(); List<VarSymbol> mmp = mm.params(); if (mp.size() != mmp.size()) continue; while (mp.tail != null) { if (!Types.instance(Main.jmlContext).isSameType(mp.head.type,mmp.head.type)) continue loop; mp = mp.tail; mmp = mmp.tail; } return mm; } return null; } /** Overrides the parent class method in order to follow writing the * footer with writing out the detail information on all the JML model * methods. * @param classDoc the class whose model methods are to be documented */ // FIXME - major change in b144 // public void writeFooter(@NonNull ClassDoc classDoc) { // super.writeFooter(classDoc); // writeJmlModelMethods(classDoc); // } /** Write out HTML documentation on JML model methods. * * @param classDoc the class whose model methods are to be documented */ public void writeJmlModelMethods(@NonNull ClassDoc classDoc) { // Hard coding this // <Header/> // <MethodDoc> // <MethodHeader/> // <Signature/> // <DeprecationInfo/> // <MethodComments/> // <TagInfo/> // <MethodFooter/> // </MethodDoc> // <Footer/> // Find model methods to see if we need to do anything at all DocEnv denv = ((ClassDocImpl)classDoc).docenv(); LinkedList<JmlMethodDecl> list = new LinkedList<JmlMethodDecl>(); TypeSpecs tspecs = JmlSpecs.instance(org.jmlspecs.openjml.jmldoc.Main.jmlContext).get(currentClassSym); for (JmlTypeClause tc: tspecs.clauses) { if (tc instanceof JmlTypeClauseDecl && ((JmlTypeClauseDecl)tc).decl instanceof JmlMethodDecl) { JmlMethodDecl d = (JmlMethodDecl)((JmlTypeClauseDecl)tc).decl; if (!d.sym.isConstructor() && denv.shouldDocument(d.sym)) { list.add(d); } } } if (list.isEmpty()) return; // Header writer.printTableHeadingBackground("JML Model Method Detail"); writer.println(); boolean isFirst = true; // FIXME - major change in b144 // for (JmlMethodDecl tc: list) { // MethodDoc md = new MethodDocImpl(((ClassDocImpl)classDoc).docenv(),tc.sym,tc.docComment,tc,null); // // Method header // writeMethodHeader(md,isFirst); isFirst = false; // // // signature // writeSignature(md); // // // deprecation // writeDeprecated(md); // // // field comments // writeComments(classDoc,md); // // // tag info // writeTags(md); // includes writeJmlSpecs(md); // // // Method footer // writeMethodFooter(); // } // // // //Footer // super.writeFooter(classDoc); } // FIXME - major change in b144 // /** Overridden to follow the member summary footer with the summary of // * JML model methods. // * @param classDoc the class that owns the methods and model methods // */ // public void writeMemberSummaryFooter(@NonNull ClassDoc classDoc) { // super.writeMemberSummaryFooter(classDoc); // writeJmlMethodSummary(classDoc); // } // FIXME - major change in b144 // /** Overridden to follow the summary footer for inherited methods // * with the summary of inherited // * JML model methods. // * @param classDoc the class whose inherited methods are being documented // */ // public void writeInheritedMemberSummaryFooter(ClassDoc classDoc) { // writeJmlInheritedMemberSummaryFooter(classDoc); // super.writeInheritedMemberSummaryFooter(classDoc); // } /** Writes out information about the inherited model methods for the given * class. * @param classDoc the class whose inherited model methods are begin listed */ public void writeJmlInheritedMemberSummaryFooter(ClassDoc classDoc) { ClassSymbol csym = Utils.findNewClassSymbol(classDoc); TypeSpecs tspecs = JmlSpecs.instance(Main.jmlContext).get(csym); ArrayList<MethodDoc> list = new ArrayList<MethodDoc>(); DocEnv denv = ((ClassDocImpl)classDoc).docenv(); // collect the methods to be documented for (JmlTypeClause tc : tspecs.clauses) { if (tc instanceof JmlTypeClauseDecl && ((JmlTypeClauseDecl)tc).decl instanceof JCTree.JCMethodDecl) { JmlMethodDecl mdecl = (JmlMethodDecl)((JmlTypeClauseDecl)tc).decl; MethodSymbol msym = mdecl.sym; if (msym.isConstructor()) continue; if (!denv.shouldDocument(msym)) continue; if (!Utils.isInherited(msym,currentClassSym)) continue; MethodDoc modelMethod = new MethodDocImpl(((ClassDocImpl)classDoc).docenv(),msym,mdecl.docComment,mdecl,null); list.add(modelMethod); } } // FIXME - major change in b144 // // If there are any, emit documentation // if (!list.isEmpty()) { // writer.br(); // writer.strong("Inherited JML model methods: "); // Collections.sort(list); // boolean isFirst = true; // for (MethodDoc method: list) { // writer.printInheritedSummaryMember(this, classDoc, method, isFirst); // isFirst = false; // } // } } /** Writes out documentation for JML model methods. * * @param classDoc the class whose model methods are being documented */ public void writeJmlMethodSummary(@NonNull ClassDoc classDoc) { ArrayList<MethodDoc> list = new ArrayList<MethodDoc>(); DocEnv denv = ((ClassDocImpl)classDoc).docenv(); TypeSpecs tspecs = JmlSpecs.instance(Main.jmlContext).get(currentClassSym); for (JmlTypeClause tc : tspecs.clauses) { if (tc instanceof JmlTypeClauseDecl && ((JmlTypeClauseDecl)tc).decl instanceof JCTree.JCMethodDecl) { JmlMethodDecl mdecl = ((JmlMethodDecl)((JmlTypeClauseDecl)tc).decl); MethodSymbol msym = mdecl.sym; if (!msym.isConstructor() && denv.shouldDocument(msym)) { MethodDoc modelMethod = new MethodDocImpl(((ClassDocImpl)classDoc).docenv(),msym,mdecl.docComment,mdecl,null); list.add(modelMethod); } } } if (list.isEmpty()) return; Collections.sort(list); writeJmlMethodSummaryHeader(classDoc); // The following loop is copied with modifications from MemberSummaryBuilder.buildSummary // FIXME - major change in b144 // for (int i = 0; i<list.size(); i++) { // MethodDoc member = list.get(i); // Tag[] firstSentenceTags = member.firstSentenceTags(); // if (firstSentenceTags.length == 0) { // //Inherit comments from overridden or implemented method if // //necessary. // DocFinder.Output inheritedDoc = // DocFinder.search(new DocFinder.Input(member)); // if (inheritedDoc.holder != null && // inheritedDoc.holder.firstSentenceTags().length > 0) { // firstSentenceTags = inheritedDoc.holder.firstSentenceTags(); // } // } // writeMemberSummary(classDoc, member, firstSentenceTags, // i == 0, i == list.size() - 1); // } // super.writeMemberSummaryFooter(classDoc); } /** Writes the beginning of the HTML for a (model) method summary. * * @param classDoc the class whose methods are being documented */ public void writeJmlMethodSummaryHeader(@NonNull ClassDoc classDoc) { Utils.writeHeader(writer,this,classDoc,"JML Model Method Summary",2); } /** This is overridden in order to include annotation information in the * method summary entry. Immediately after this, the modifiers are written. * @param member the (method) Doc element whose annotation is to be printed */ @Override protected void printModifier(@NonNull ProgramElementDoc member) { writer.writeAnnotationInfo(member); super.printModifier(member); } // FIXME - major change in b144 // /** This override has the effect of always printing out annotation information // * when a method signature is written; in particular, in javadoc it is not // * written out in the parameters within the member summary section. // * @param member the Doc element whose information is being printed // * @param includeAnnotations boolean switch now being ignored // */ // @Override // protected void writeParameters(ExecutableMemberDoc member, // boolean includeAnnotations) { // super.writeParameters(member,true); // } }