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.JmlTokenKind; import org.jmlspecs.openjml.JmlTree; import org.jmlspecs.openjml.JmlSpecs.TypeSpecs; import org.jmlspecs.openjml.JmlTree.JmlTypeClause; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseDecl; import org.jmlspecs.openjml.JmlTree.JmlTypeClauseRepresents; import org.jmlspecs.openjml.JmlTree.JmlVariableDecl; import com.sun.javadoc.ClassDoc; import com.sun.javadoc.FieldDoc; import com.sun.javadoc.ProgramElementDoc; import com.sun.javadoc.Tag; import com.sun.tools.doclets.formats.html.FieldWriterImpl; import com.sun.tools.doclets.formats.html.SubWriterHolderWriter; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.VarSymbol; import com.sun.tools.javac.comp.JmlAttr; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.util.Context; 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.FieldDocImpl; /** * This class extends its Javadoc parent class in order to write out any JML * specifications associated with fields. * * @author David R. Cok */ public class FieldWriterJml extends FieldWriterImpl { /** Constructor for this JML-enabled writer of field documentation. * * @param writer the writer to which writing is delegated * @param classdoc the class that owns the fields being described */ //@ ensures this.classdoc == classdoc; public FieldWriterJml(@NonNull SubWriterHolderWriter writer, @NonNull ClassDoc classdoc) { super(writer, classdoc); currentClassSym = Utils.findNewClassSymbol(classdoc); } /** Constructor for this JML-enabled writer of field documentation. * * @param writer the writer to which writing is delegated */ public FieldWriterJml(@NonNull SubWriterHolderWriter writer) { super(writer); currentClassSym = Utils.findNewClassSymbol(classdoc); } /** The ClassSymbol (in the JML compilation context) of the class * whose nested classes are being described. */ protected @NonNull ClassSymbol currentClassSym; /** Set to true if the JML field is a model field, false if it is ghost or Java field. */ boolean isModel = false; // FIXME - major change in b144 // /** Overrides the parent class method in order to write out any JML specs // * after writing out the comments about the field. // */ // public void writeComments(@NonNull FieldDoc field) { // super.writeComments(field); // writeJmlSpecs(field); // } /** Writes out the JML specs about the field. * @param field the field to describe */ public void writeJmlSpecs(@NonNull FieldDoc field) { Context context = org.jmlspecs.openjml.jmldoc.Main.jmlContext; Name newFieldName = Names.instance(context).fromString(field.name()); VarSymbol newField = (VarSymbol)currentClassSym.members().lookup(newFieldName).sym; //System.out.println("Sym " + newField + " for " + newFieldName + " in " + currentClassSym); if (newField == null) return; // Inherited Java fxields? JmlSpecs.FieldSpecs fspecs = JmlSpecs.instance(context).getSpecs(newField); // FIXME - if the only specs are represent clauses, this won't print them String s = Utils.jmlAnnotations(newField); if (fspecs != null && (!fspecs.list.isEmpty() || s.length()!=0) ){ // FIXME - what if there are JML annotations but no clauses strong("JML Specifications: "); writer.print(s); writer.dl(); writer.preNoNewLine(); for (JmlTree.JmlTypeClause clause: fspecs.list) { writer.print(" "); writer.print(clause); writer.println(); } if (isModel) { TypeSpecs tspecs = JmlSpecs.instance(context).get(currentClassSym); for (JmlTypeClause t : tspecs.clauses) { if (!(t instanceof JmlTypeClauseRepresents)) continue; JmlTypeClauseRepresents tr = (JmlTypeClauseRepresents)t; if (!(tr.ident instanceof JCTree.JCIdent)) continue; Name n = ((JCTree.JCIdent)(tr.ident)).name; if (n == newFieldName) { writer.print(" "); writer.print(JmlPretty.write(t,false)); writer.println(); } } } writer.preEnd(); writer.dlEnd(); } } // FIXME - major change in b144 // /** // * Write the footer for the field documentation, and then add any // * field detail information about ghost and model fields. // * // * @param classDoc the class that the fields belong to. // */ // public void writeFooter(@NonNull ClassDoc classDoc) { // super.writeFooter(classDoc); // writeJmlGhostModelFieldDetail(classDoc,JmlToken.GHOST,"Ghost"); // writeJmlGhostModelFieldDetail(classDoc,JmlToken.MODEL,"Model"); // writeJmlRepresentsDetail(classDoc); // } public void writeJmlGhostModelFieldDetail(@NonNull ClassDoc classDoc, @NonNull JmlTokenKind token,String showString) { // Hard coding this // <Header/> // <FieldDoc> // <FieldHeader/> // <Signature/> // <DeprecationInfo/> // <FieldComments/> // <TagInfo/> // <FieldFooter/> // </FieldDoc> // <Footer/> DocEnv denv = ((ClassDocImpl)classDoc).docenv(); // Find ghost fields to see if we need to do anything at all LinkedList<JmlVariableDecl> list = new LinkedList<JmlVariableDecl>(); TypeSpecs tspecs = JmlSpecs.instance(org.jmlspecs.openjml.jmldoc.Main.jmlContext).get(currentClassSym); for (JmlTypeClause tc: tspecs.decls) { if (tc instanceof JmlTypeClauseDecl && ((JmlTypeClauseDecl)tc).decl instanceof JmlVariableDecl) { JmlVariableDecl d = (JmlVariableDecl)((JmlTypeClauseDecl)tc).decl; boolean use = JmlAttr.instance(org.jmlspecs.openjml.jmldoc.Main.jmlContext).findMod(d.mods,token) != null; if (use && denv.shouldDocument(d.sym)) { list.add(d); } } } if (list.isEmpty()) return; // Header writer.printTableHeadingBackground("JML " + showString + " Field Detail"); writer.println(); //Field Header boolean isFirst = true; // FIXME - major change in b144 // for (JmlVariableDecl tc: list) { // FieldDoc fd = new FieldDocImpl(((ClassDocImpl)classDoc).docenv(),tc.sym,tc.docComment,tc,null); // // field header // writeFieldHeader(fd,isFirst); isFirst = false; // // // signature // writeSignature(fd); // // // deprecation // writeDeprecated(fd); // // // field comments // isModel = token == JmlToken.MODEL; // try { // //writeComments(fd); // // Extract some of the contents of writeComments to avoid some // // problems with the dual contexts - in particular, a field looks // // like it is inherited from a class or interface // writer.dd(); // writer.printInlineComment(fd); // writeJmlSpecs(fd); // writer.ddEnd(); // } finally { // isModel = false; // } // // // tag info // writeTags(fd); // // // Field footer // writeFieldFooter(); // } // // // //Footer // super.writeFooter(classDoc); } public java.util.List<JmlTypeClauseRepresents> makeRepresentsList(ClassDoc classDoc) { java.util.List<JmlTypeClauseRepresents> list = new java.util.LinkedList<JmlTypeClauseRepresents>(); TypeSpecs tspecs = JmlSpecs.instance(org.jmlspecs.openjml.jmldoc.Main.jmlContext).get(currentClassSym); for (JmlTypeClause t : tspecs.clauses) { if (!(t instanceof JmlTypeClauseRepresents)) continue; JmlTypeClauseRepresents tr = (JmlTypeClauseRepresents)t; if (!(tr.ident instanceof JCTree.JCIdent)) continue; boolean found = false; for (JmlTypeClause tc: tspecs.decls) { // FIXME - only model fields // FIXME - only only fields that are in classDoc if it is not null if (tc instanceof JmlTypeClauseDecl && ((JmlTypeClauseDecl)tc).decl instanceof JmlVariableDecl) { JmlVariableDecl d = (JmlVariableDecl)((JmlTypeClauseDecl)tc).decl; if (d.name.equals(((JCTree.JCIdent)tr.ident).name)) { found = true; break; } } } if (!found) list.add(tr); } return list; } /** This writes out a documentation block containing detail on any represents * clauses for model fields in parent classes * @param classDoc the class in which the represents clauses appear */ public void writeJmlRepresentsDetail(ClassDoc classDoc) { java.util.List<JmlTypeClauseRepresents> list = makeRepresentsList(null); if (list.isEmpty()) return; // FIXME - do this as a two-column table to align the columns writer.anchor("JmlRepresentsClauses"); writer.printTableHeadingBackground("Represents clauses for parent class model fields"); writer.println(); writer.code(); writer.br(); for (JmlTypeClauseRepresents tr : list) { writer.print("    "); writer.print(linkedName((JCTree.JCIdent)tr.ident)); writer.print(": "); writer.println(JmlPretty.write(tr,false)); writer.br(); } writer.br(); writer.codeEnd(); // FIXME - major change in b144 // super.writeFooter(classDoc); } // FIXME - fix this link - problem is that the stuff in the represents clause // is not attributed, so we don't know which class it comes from. // We could do a lookup I suppose??? /** Returns the HTML that links this id to its own HTML page * @param id the id to link * @return the String giving the link in HTML */ public String linkedName(JCTree.JCIdent id) { return "<A HREF=\" \">" + JmlPretty.write(id,false) + "</A>"; } /** Writes out a block of HTML that documents any represents clauses for * model fields that are not in this class * @param classDoc the base class - ignore represents for fields in this class */ public void writeJmlRepresentsSummary(ClassDoc classDoc) { java.util.List<JmlTypeClauseRepresents> list = makeRepresentsList(classDoc); if (list.isEmpty()) return; writer.br(); writer.strong("<A HREF=\"#JmlRepresentsClauses\">Represents specification of super class fields</A>: "); //Collections.sort(list); // FIXME - major change in b144 // boolean isFirst = true; // for (JmlTypeClauseRepresents tr: list) { // FieldDoc field = new FieldDocImpl(((ClassDocImpl)classDoc).docenv(),(VarSymbol)((JCTree.JCIdent)tr.ident).sym); // writer.printInheritedSummaryMember(this, classDoc, field, isFirst); // isFirst = false; // } // super.writeInheritedMemberSummaryFooter(classDoc); } /** This is overridden in order to include annotation information in the * field summary entry. Immediately after this, the modifiers are written. * @param member the (field) 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 is overridden in order to write out the summary footer and to // * follow it by the summary of JML fields. // * @param classDoc the class that owns the fields // */ // @Override // public void writeMemberSummaryFooter(@NonNull ClassDoc classDoc) { // super.writeMemberSummaryFooter(classDoc); // writeJmlFieldSummary(classDoc); // } public void checkJmlSummary(@NonNull ClassDoc classDoc) { if (summaryHeaderWritten) return; writeJmlFieldSummary(classDoc); } // FIXME - major change in b144 // /** This is overridden in order to write out the summary footer for // * inherited fields and to // * follow it by the summary of JML inherited fields. // * @param classDoc the class whose inherited fields are to be described // */ // public void writeInheritedMemberSummaryFooter(@NonNull ClassDoc classDoc) { // writeJmlInheritedMemberSummary(classDoc); // writeJmlRepresentsSummary(classDoc); // super.writeInheritedMemberSummaryFooter(classDoc); // } // // public void checkJmlInheritedSummary(@NonNull ClassDoc classDoc, java.util.List inhmembers) { // if (summaryHeaderWritten) return; // super.writeInheritedMemberSummaryHeader(classDoc); // writeInheritedMemberSummaryFooter(classDoc); // } /** This writes out summary information about JML fields. * @param classDoc the class whose inherited fields are to be described. * */ public void writeJmlInheritedMemberSummary(@NonNull ClassDoc classDoc) { ClassSymbol csym = Utils.findNewClassSymbol(classDoc); TypeSpecs tspecs = JmlSpecs.instance(Main.jmlContext).get(csym); DocEnv denv = ((ClassDocImpl)classDoc).docenv(); ArrayList<FieldDoc> list = new ArrayList<FieldDoc>(); for (JmlTypeClause tc : tspecs.decls) { if (tc instanceof JmlTypeClauseDecl && ((JmlTypeClauseDecl)tc).decl instanceof JCTree.JCVariableDecl) { JmlVariableDecl vdecl = ((JmlVariableDecl)((JmlTypeClauseDecl)tc).decl); VarSymbol msym = vdecl.sym; if (!denv.shouldDocument(msym)) continue; if (!Utils.isInherited(msym,currentClassSym)) continue; FieldDoc field = new FieldDocImpl(((ClassDocImpl)classDoc).docenv(),msym,vdecl.docComment,vdecl,null); list.add(field); } } // FIXME - major change in b144 // if (!list.isEmpty()) { // writer.br(); // writer.strong("Inherited JML ghost and model fields: "); // Collections.sort(list); // boolean isFirst = true; // for (FieldDoc field: list) { // writer.printInheritedSummaryMember(this, classDoc, field, isFirst); // isFirst = false; // } // } } /** Write out a summary of JML fields for the given class * * @param classDoc the class whose JML fields are to be written */ public void writeJmlFieldSummary(@NonNull ClassDoc classDoc) { ArrayList<FieldDoc> list = new ArrayList<FieldDoc>(); TypeSpecs tspecs = JmlSpecs.instance(Main.jmlContext).get(currentClassSym); DocEnv denv = ((ClassDocImpl)classDoc).docenv(); for (JmlTypeClause tc : tspecs.decls) { if (tc instanceof JmlTypeClauseDecl && ((JmlTypeClauseDecl)tc).decl instanceof JCTree.JCVariableDecl) { JmlVariableDecl vdecl = ((JmlVariableDecl)((JmlTypeClauseDecl)tc).decl); VarSymbol msym = vdecl.sym; if (!denv.shouldDocument(msym)) continue; FieldDoc field = new FieldDocImpl(denv,msym,vdecl.docComment,vdecl,null); list.add(field); } } if (list.isEmpty()) return; Collections.sort(list); writeJmlFieldSummaryHeader(classDoc); // The following loop is copied with modifications from MemberSummaryBuilder.buildSummary ??? FIXME // FIXME - major change in b144 // for (int i = 0; i<list.size(); i++) { // FieldDoc member = list.get(i); // Tag[] firstSentenceTags = member.firstSentenceTags(); // writeMemberSummary(classDoc, member, firstSentenceTags, // i == 0, i == list.size() - 1); // } // super.writeMemberSummaryFooter(classDoc); } /** This writes the header for the summary of JML ghost and model fields * * @param classDoc the class whose ghost and model fields are to be described */ public void writeJmlFieldSummaryHeader(@NonNull ClassDoc classDoc) { //printSummaryAnchor(cd); Utils.writeHeader(writer,this,classDoc,"JML Ghost and Model Field Summary",2); summaryHeaderWritten = true; } protected boolean summaryHeaderWritten = false; }