/******************************************************************************* * Copyright 2012 Pearson Education * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package org.semantictools.context.view; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.ObjectWriter; import org.codehaus.jackson.map.SerializationConfig; import org.codehaus.jackson.node.ObjectNode; import org.semantictools.context.renderer.DiagramGenerator; import org.semantictools.context.renderer.MediaTypeFileManager; import org.semantictools.context.renderer.NodeComparatorFactory; import org.semantictools.context.renderer.NodeUtil; import org.semantictools.context.renderer.StreamFactory; import org.semantictools.context.renderer.TermNotFoundException; import org.semantictools.context.renderer.TreeGenerator; import org.semantictools.context.renderer.impl.NodeComparatorFactoryImpl; import org.semantictools.context.renderer.model.ContextProperties; import org.semantictools.context.renderer.model.CreateDiagramRequest; import org.semantictools.context.renderer.model.FrameConstraints; import org.semantictools.context.renderer.model.GlobalProperties; import org.semantictools.context.renderer.model.JsonContext; import org.semantictools.context.renderer.model.ObjectPresentation; import org.semantictools.context.renderer.model.SampleJson; import org.semantictools.context.renderer.model.TermInfo; import org.semantictools.context.renderer.model.TermInfo.TermCategory; import org.semantictools.context.renderer.model.TreeNode; import org.semantictools.frame.api.FrameNotFoundException; import org.semantictools.frame.api.GeneratorProperties; import org.semantictools.frame.api.TypeManager; import org.semantictools.frame.model.Datatype; import org.semantictools.frame.model.Field; import org.semantictools.frame.model.Frame; import org.semantictools.frame.model.NamedIndividual; import org.semantictools.frame.model.RdfType; import org.semantictools.frame.model.RestCategory; import org.semantictools.json.JsonManager; import org.semantictools.json.JsonPrettyPrinter; import org.semantictools.json.JsonSampleGenerator; import org.semantictools.uml.api.UmlFileManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.hp.hpl.jena.ontology.OntProperty; import com.hp.hpl.jena.vocabulary.OWL; import com.hp.hpl.jena.vocabulary.RDFS; public class ContextHtmlPrinter extends PrintEngine { private static final String CONTAINER = "http://www.w3.org/ns/ldp#Container"; private static final String PAGE = "http://www.w3.org/ns/ldp#Page"; private static Logger logger = LoggerFactory .getLogger(ContextHtmlPrinter.class); private static final String TOC_MARKER = "<!-- TOC -->"; // private static final String XMLSCHEMA_URI = // "http://www.w3.org/2001/XMLSchema#"; private static final String VOWEL = "aeiou"; // private static final String[] STANDARD_URI = { XMLSCHEMA_URI, // "http://www.w3.org/2002/07/owl#", // "http://www.w3.org/1999/02/22-rdf-syntax-ns#", // "http://www.w3.org/2000/01/rdf-schema#", // "http://purl.org/semantictools/v1/vocab/bind#" }; // private static boolean isStandard(String uri) { // for (int i = 0; i < STANDARD_URI.length; i++) { // if (uri.startsWith(STANDARD_URI[i])) // return true; // } // return false; // } private TypeManager typeManager; private MediaTypeFileManager namer; private boolean includeOverviewDiagram; private boolean includeClassDiagrams; private Caption overviewDiagram; private StreamFactory streamFactory; private List<Frame> frameList; private List<Datatype> datatypeList; private JsonContext context; private ContextProperties contextProperties; private DiagramGenerator diagramGenerator; private TreeGenerator treeGenerator; private JsonSampleGenerator sampleGenerator; private JsonManager jsonManager; private GeneratorProperties generatorProperties; private NodeComparatorFactory nodeComparatorFactory; private UmlFileManager umlFileManager; private Frame root; private List<Frame> graphTypes; private int figureNumber; private int tableNumber; private boolean defaultTemplate; private CaptionManager captionManager; private GlobalProperties global; private DocumentPrinter documentPrinter; public ContextHtmlPrinter(GlobalProperties global, GeneratorProperties properties, TypeManager typeManager, MediaTypeFileManager namer, StreamFactory streamFactory, DiagramGenerator generator, UmlFileManager umlFileManager) { super(new PrintContext()); this.global = global; generatorProperties = properties; diagramGenerator = generator; this.umlFileManager = umlFileManager; this.streamFactory = streamFactory; this.typeManager = typeManager; this.namer = namer; } public List<Frame> getGraphTypes() { if (graphTypes == null) { List<String> uriList = contextProperties.getGraphTypes(); if (uriList != null && !uriList.isEmpty()) { graphTypes = new ArrayList<Frame>(); for (String uri : uriList) { Frame frame = typeManager.getFrameByUri(uri); if (frame == null) { throw new FrameNotFoundException(uri); } graphTypes.add(frame); } } } return graphTypes; } public Frame getRootFrame() { return root; } public void printHtml(JsonContext context) throws IOException { printHtml(context, null); } public void printHtml(JsonContext context, ContextProperties properties) throws IOException { this.contextProperties = properties; this.context = context; documentPrinter = createDocumentPrinter(); defaultTemplate = isDefaultTemplate(); treeGenerator = new TreeGenerator(typeManager, context, properties); sampleGenerator = new JsonSampleGenerator(typeManager); root = context == null ? null : typeManager.getFrameByUri(context .getRootType()); overviewDiagram = overviewDiagramCaption(); captionManager = new CaptionManager(); jsonManager = new JsonManager(typeManager, context); nodeComparatorFactory = new NodeComparatorFactoryImpl(jsonManager); beginHTML(); pushIndent(); collectFrames(); printTitlePage(); printAbstract(); printToc(); printIntroduction(); printMediaTypeConformance(); printDataBindings(); documentPrinter.printReferences(); documentPrinter.printFooter(); popIndent(); endHTML(); writeOutput(); } private DocumentPrinter createDocumentPrinter() { DocumentPrinter printer = null; String template = contextProperties.getTemplateName(); if (DocumentPrinter.TEMPLATE_IMS.equalsIgnoreCase(template)) { printer = new IMSDocumentPrinter(getPrintContext()); } else { printer = new DefaultDocumentPrinter(getPrintContext()); } printer.setMetadata(contextProperties); printer.setClassificationPrinter(new MyClassificationPrinter()); return printer; } class MyClassificationPrinter implements ClassificationPrinter { @Override public void printClassifiers() { String mediaType = contextProperties.getMediaType(); String rdfType = (root == null) ? null : root.getUri(); String contextURI = context == null ? null : context.getContextURI(); String contextHref = namer.getJsonContextFileName(context); String rdfTypeHref = null; if (umlFileManager != null) { String path = namer.getIndexFileName(); File sourceFile = streamFactory.getOutputFile(path); if (sourceFile != null && root != null) { rdfTypeHref = umlFileManager.getTypeRelativePath(sourceFile, root); } } indent().print("<TABLE"); printAttr("class", "mediaTypeProperties"); println(">"); pushIndent(); indent().println("<TR>"); pushIndent(); indent().println("<TH>Media Type</TH>"); indent().print("<TD>").print(mediaType).println("</TD>"); popIndent(); indent().println("</TR>"); indent().println("<TR>"); pushIndent(); indent().println("<TH>RDF Type</TH>"); indent().print("<TD>"); if (rdfTypeHref == null) { print(rdfType); } else { print("<a "); printAttr("href", rdfTypeHref); print(">"); print(rdfType); print("</a>"); } println("</TD>"); popIndent(); indent().println("</TR>"); indent().println("<TR>"); pushIndent(); indent().println("<TH>JSON-LD</TH>"); indent().print("<TD>"); print("<A"); printAttr("HREF", contextHref); print(">").print(contextURI).println("</A></TD>"); popIndent(); indent().println("</TR>"); popIndent(); indent().println("</TABLE>"); indent().println("<p></p>"); } } public void printTitlePage() { documentPrinter.printTitlePage(); } private boolean isDefaultTemplate() { String template = contextProperties.getTemplateName(); if (template == null) { template = global.getTemplateName(); } return !DocumentPrinter.TEMPLATE_SIMPLE.equalsIgnoreCase(template); } private Caption overviewDiagramCaption() { String text = null; if (root == null) { text = "Graphical representation of the " + contextProperties.getMediaType() + " media type"; } else { text = "Complete JSON representation of " + root.getLocalName(); } return new Caption(CaptionType.Figure, text, "completeRep", null); } private void printDataBindings() throws IOException { if (!defaultTemplate) return; Heading heading = documentPrinter.createHeading("JSON Data Bindings"); documentPrinter.beginSection(heading); printOverviewDiagram(); printFrames(); printDatatypes(); documentPrinter.endSection(); } private void printDatatypes() { for (Datatype type : datatypeList) { if (typeManager.isStandardDatatype(type.getNamespace())) continue; printDatatype(type); } } private void printMediaTypeConformance() { documentPrinter.endSection(); // TODO: move the endHeading call to the same scope where the heading // begins. if (!contextProperties.isMediaTypeSection()) return; if (context == null) { return; } String headingTemplate = "The {0} Media Type"; String typeName = context.rewrite(root.getUri()); String mediaType = contextProperties.getMediaType(); String contextRef = contextProperties.getContextRef(); Heading heading = documentPrinter.createHeading(headingTemplate.replace( "{0}", typeName)); documentPrinter.print(heading); String text = "The following list defines the necessary and sufficient conditions for a document " + "to conform to the <code>{0}</code> media type."; printParagraph(text.replace("{0}", mediaType)); indent().print("<OL"); printAttr("class", "uncondensed"); println(">"); pushIndent(); printListItem("The document MUST be a valid JSON document, in accordance with [RFC4627]."); printListItem("The document MUST contain either a single top-level JSON object, or an array " + "of top-level JSON objects. The first object encountered (either the single top-level object or " + "the first element of the array) is called the <em>root</em> object."); boolean hasSubtypes = root.getSubtypeList().size() > 1; if (hasSubtypes) { text = "The root object must have a <code>@type</code> property whose value is \"<code>{0}</code>\" or a " + "subtype of <code>{0}</code>."; } else { text = "The root object must have a <code>@type</code> property whose value is \"<code>{0}</code>\"."; } printListItem(text.replace("{0}", typeName)); text = "Every top-level object MUST have a <code>@context</code> property that references one or more " + "JSON-LD contexts (either by URI or by value)."; printListItem(text); text = "Collectively, the set of contexts imported by the root object MUST contain all of the " + "terms found in the <em>standard context</em> {0}. In particular, the set of imported contexts must " + "contain all the simple names that appear in the standard context, and those simple names must " + "resolve to the same values that appear in the standard context. This requirement may be " + "satisfied by ensuring that the root object imports the standard context explicitly, or by " + "importing a collection of other contexts that contain equivalent terms."; printListItem(text.replace("{0}", contextRef)); text = "The set of contexts imported by the root object MAY include additional terms that do not " + "appear in the standard context {0}."; printListItem(text.replace("{0}", contextRef)); text = "Duplicate mappings for names among the imported contexts MUST be overwritten on a " + "last-defined-overrides basis."; printListItem(text); text = "If the JSON-LD context coerces a property to a URI reference, then values of that " + "property MUST be expressed as a fully-qualified URI reference, or a CURIE or a simple name " + "declared by the context."; printListItem(text); text = "A <em>collection property</em> is any property whose maximum cardinality is greater than 1. " + "Except for the <code>@context</code> property, " + "a non-empty collection MUST always be represented as a JSON array whose values are enclosed " + "in square brackets. Whereas, in general, the JSON-LD syntax specification allows a collection " + "containing a single value to omit the square brackets, the <code>" + mediaType + "</code> media type " + "requires square brackets for all non-empty collections other than the <code>@context</code> property."; printListItem(text); text = "An empty collection property may be represented either by an empty array (i.e. square brackets " + "containing no elements), or by omitting the property altogether."; printListItem(text); text = "Like all other properties, the <code>@id</code> property of a given object is mandatory " + "if the minimum cardinality of that property, as defined by this specification, is greater than " + "zero. The <code>@id</code> property is optional for all other objects (even if it is not " + "explicitly listed in the set of properties for an object). Conforming implementations SHOULD " + "include the <code>@id</code> property for all addressable objects."; printListItem(text); text = "If the <code>@id</code> property is mandatory, then the value MUST NOT treat the object as " + "a blank node. In this case, the <code>@id</code> value MUST NOT be a CURIE with an underscore " + "as the prefix."; printListItem(text); text = "Every top-level object MUST contain a <code>@type</code> property and a @context property."; printListItem(text); text = "An embedded object MUST contain a <code>@type</code> property if the object value is a " + "subtype of the declared range of the property."; printListItem(text); text = "Values for properties named in the standard context {0}, MUST not utilize the String Internationalization or Typed Value syntax as described in [JSON-LD-syntax]."; printListItem(text.replace("{0}", contextRef)); text = "If the context does not coerce the value of an object property to a URI reference, " + "then the object must be rendered as an embedded object."; printListItem(text); text = "The properties of embedded objects must respect the cardinality constraints specified in " + "the section titled JSON Data Bindings."; printListItem(text); popIndent(); indent().println("</OL>"); } private void printListItem(String text) { indent().print("<LI>").print(text).println("</LI>"); } private void writeOutput() throws IOException { documentPrinter.insertTableOfContents(); String text = documentPrinter.popText(); String path = namer.getIndexFileName(); OutputStream stream = streamFactory.createOutputStream(path); PrintStream printStream = (stream instanceof PrintStream) ? (PrintStream) stream : new PrintStream(stream); printStream.print(text); printStream.close(); } private void printToc() { if (!defaultTemplate) return; println(TOC_MARKER); } private void printAbstract() { if (!defaultTemplate) return; String abstractText = contextProperties == null ? null : contextProperties .getAbstactText(); if (abstractText == null) return; indent().println("<H2>Abstract</H2>"); indent().println("<DIV>"); print(abstractText); println("</DIV>"); } private void printIntroduction() throws IOException { String text = contextProperties.getIntroduction(); if (defaultTemplate) { String headingText = "Introduction"; Heading heading = documentPrinter.createHeading(headingText); documentPrinter.beginSection(heading); } if (text != null) { indent().println("<DIV>"); print(text); println("</DIV>"); } printSample(); if (defaultTemplate) { printHowToRead(); } } private void printSample() throws IOException { String typeName = null; if (context != null && root != null) { typeName = context.rewrite(root.getUri()); } String defaultText = contextProperties.getSampleText(); if (defaultText == null) { if (typeName == null) { defaultText = "<p>Figure 1 shows the representation of a resource in the <code>" + contextProperties.getMediaType() + "</code> format.</p>"; } else { defaultText = "<p>Figure 1 shows the representation of " + article(typeName) + typeName + " resource in the <code>" + contextProperties.getMediaType() + "</code> format.</p>"; } } List<SampleJson> list = contextProperties.getSampleJsonList(); if (list.isEmpty()) { print(defaultText); printDefaultSample(typeName); } else { if (list.size() == 1) { print(defaultText); } else { print("<p>The following "); print(list.size()); print(" figures illustrate representations of different "); print(typeName); print(" resources in the <code>"); print(contextProperties.getMediaType()); print("</code> format."); } printOtherSamples(); } } private void printHowToRead() throws IOException { if (context == null || !contextProperties.isHowToReadThisDocument()) { return; } Heading heading = documentPrinter .createHeading("How To Read this Document"); documentPrinter.print(heading); List<Field> fieldList = getFieldList(); printSampleObject(); printPropertyRepresentation(fieldList); printOptionalPropertyFigure(fieldList); printRepeatedPropertyFigure(fieldList); printObjectRepresentation(fieldList); printReservedTerms(); printContextDiscussion(); } private void sort(List<Field> fieldList) { Collections.sort(fieldList, new Comparator<Field>() { @Override public int compare(Field a, Field b) { return a.getLocalName().compareTo(b.getLocalName()); } }); } private void printContextDiscussion() { if (!contextProperties.isJsonldIntroduction()) return; Heading heading = documentPrinter.createHeading("The JSON-LD Context"); documentPrinter.print(heading); printContextSnippet(); printTypeCoercionSample(); } private void printTypeCoercionSample() { TermInfo term = getTypeCoercionSample(new HashSet<String>(), root); if (term == null) return; printParagraph("A context may specify that the values of certain object properties must be rendered as URI references. " + "The following snippet presents an example of such a rule."); beginCodeSnippet(); println(" {"); println(" \"@context\" = {"); println(" ..."); printTerm(term); println(); println(" ..."); println(" }"); endCodeSnippet(); printParagraph("This rule is an example of <em>type coercion</em>. " + "For more details about the syntax of a JSON-LD context, see [JSON-LD-syntax]."); } private TermInfo getTypeCoercionSample(Set<String> history, Frame frame) { if (history.contains(frame.getUri())) return null; history.add(frame.getUri()); List<Field> list = frame.listAllFields(); for (Field field : list) { TermInfo term = context.getTermInfoByURI(field.getURI()); if (term == null) continue; if (term.hasObjectValue() && "@id".equals(term.getObjectValue().getType())) return term; RdfType type = field.getRdfType(); if (type != null && type.canAsFrame()) { term = getTypeCoercionSample(history, type.asFrame()); if (term != null) return term; } } return null; } private void printContextSnippet() { String contextRef = contextProperties.getContextRef(); String typeName = context.rewrite(root.getUri()); Field field = getSampleToken(); String template = "In JSON-LD, a context is used to map simple names that appear in a JSON document " + "to URI values for properties or data types in a formal vocabulary (typically an RDF ontology). "; String example = "For example, the standard context {0} for a {1} contains the following rewrite rules (among others):"; if (field != null) { example = example.replace("{0}", contextRef); template += example.replace("{1}", typeName); } printParagraph(template); if (field == null) return; TermInfo term = context.getTermInfoByURI(field.getURI()); String namespace = field.getProperty().getNameSpace(); String prefix = context.rewrite(namespace); beginCodeSnippet(); println(" {"); println(" \"@context\" = {"); if (!prefix.equals(namespace)) { print(" \"").print(prefix).print("\" : \"").print(namespace) .println("\","); } printTerm(term); println(","); println(" ..."); println(" }"); println(" }"); endCodeSnippet(); } private void printTerm(TermInfo term) { if (term.hasObjectValue()) { print(" \""); print(term.getTermName()); println("\" : {"); print(" \"@id\" : \""); print(term.getObjectValue().getId()); println("\","); print(" \"@type\" : \""); print(term.getObjectValue().getType()); println("\""); print(" }"); } else { print(" \""); print(term.getTermName()); print("\" : \""); print(term.getIri()); print("\""); } } private void beginCodeSnippet() { indent().print("<DIV"); printAttr("class", "code-snippet"); println(">"); println("<PRE>"); } private void endCodeSnippet() { println("</PRE>"); indent().println("</DIV>"); } private Field getSampleToken() { List<Field> list = root.listAllFields(); for (Field field : list) { TermInfo term = context.getTermInfoByURI(field.getURI()); if (term == null) continue; if (!term.hasObjectValue()) return field; } return null; } private void printReservedTerms() { if (!contextProperties.isReservedTermsSection()) return; Heading heading = documentPrinter.createHeading("Reserved Terms"); documentPrinter.print(heading); printParagraph("The JSON-LD standard reserves a handful of property names and tokens " + "that have special meaning. These names and tokens, described below, begin with the '@' symbol."); indent().println("<DL"); printAttr("class", "reservedTerms"); println(">"); pushIndent(); printDefinition( "@context", "Used to reference (by URI or by value) a <em>context</em> which declares the simple names " + "that appear throughout a JSON document."); printDefinition( "@id", "Used to uniquely identify things that are being described in the JSON document. " + "The value of an @id property is either a fully-qualified URI, a CURIE, or a simple name " + "that expands to a fully-qualified URI by virtue of the rules defined in the JSON-LD Context." + "<P>The @id property may identify a so-called blank node by using a CURIE with an underscore " + "as the prefix. The binding of a JSON-LD document MAY include identifiers for blank nodes, " + "but these identifiers are not required."); printDefinition("@type", "Used to set the data type of an object or property value."); popIndent(); indent().println("</DL>"); String text = "JSON-LD specifies four other reserved terms (@value, @language, @container, @list). " + "Ordinarily, these terms are not used in the JSON binding for <code>{0}</code> objects. However, " + "implementations that extend this specification by including additional properties may utilize " + "these reserved terms in accordance with the rules defined by [JSON-LD-syntax]."; String typeName = root.getLocalName(); printParagraph(text.replace("{0}", typeName)); } private void printDefinition(String termName, String description) { indent().print("<DT>").print(termName).println("</DT>"); pushIndent(); indent().print("<DD>").print(description).println("</DD>"); popIndent(); } private void printParagraph(String text) { indent().print("<P>").print(text).println("</P>"); } private void printPropertyRepresentation(List<Field> fieldList) throws IOException { // Find a field that has a simple type, like a string, number or date. // This is our first choice for the node that we'll use as the sample // property. Field field = getSimpleTypeField(fieldList); if (field == null) { field = getAnyField(root); } indent() .print( "<P>Each box representing a property specifies the name and type of the property , as shown in "); Caption caption = new Caption(CaptionType.Figure, "Graphical notation for a property", "sampleProperty", field.getURI()); assignNumber(caption); documentPrinter.printLink(caption); print(".</P>"); String src = namer.getImagesDir() + "/sampleProperty.png"; printFigure(src, caption); if (diagramGenerator == null) return; TreeNode node = treeGenerator.generateNode(field); CreateDiagramRequest request = new CreateDiagramRequest(context, node, src); diagramGenerator.generateNotationDiagram(request); } private void printObjectRepresentation(List<Field> fieldList) throws IOException { Field uriRefField = getUriRefField(fieldList); Field snRefField = getSnRefField(fieldList); indent() .println( "<P>An object within a JSON-LD document may have one of four possible representations:<P>"); indent().println("<OL>"); pushIndent(); indent() .println( "<LI>The object may be identified by a fully-qualified URI reference.</LI>"); indent() .println( "<LI>The object may be identified by a Compact URI reference, known as a CURIE " + "[CURIE-syntax], that can be expanded to a fully qualified URI</LI>"); indent() .println( "<LI>The object may be identified by a simple name that is mapped to a " + "fully-qualified URI. This mapping is defined by the JSON-LD context.</LI>"); indent() .println("<LI>The object may be embedded within the document.</LI>"); popIndent(); indent().println("</OL>"); if (uriRefField != null) { printUriRefDiscussion(uriRefField); } printSnRefDiscussion(snRefField); } private void printUriRefDiscussion(Field field) throws IOException { Caption captionRef = captionManager.getFigureCaptionByURI(field.getURI()); Caption caption = null; if (captionRef == null) { caption = captionRef = new Caption(CaptionType.Figure, "Property whose value is a URI reference", "uriRef", field.getURI()); } indent() .println( "<P>When an object is to be identified by a fully-qualified URI or a CURIE, the box " + "representing the object will be decorated with the #uri hash tag, as shown in "); documentPrinter.printLink(captionRef); println(".</P>"); if (caption == null) return; assignNumber(caption); String src = namer.getImagesDir() + "/uriRef.png"; TreeNode node = treeGenerator.generateNode(field); CreateDiagramRequest request = new CreateDiagramRequest(context, node, src); // Node node = createNode(field); // // // CreateDiagramRequest request = new CreateDiagramRequest(context, node, // src, // 0, false, false); diagramGenerator.generateDiagram(request); printFigure(src, caption); } private void printSnRefDiscussion(Field field) throws IOException { String fieldURI = field == null ? null : field.getURI(); Caption captionRef = captionManager.getFigureCaptionByURI(fieldURI); Caption caption = null; if (captionRef == null) { captionRef = caption = new Caption( CaptionType.Figure, "Property whose value is a simple name reference for an individual object or enumerable value", "snRef", fieldURI); assignNumber(caption); } indent() .println( "<P>When an object or enumerable value is to be identified by a simple name, the box " + "representing the corresponding property will be decorated with the #sn hash tag, as shown in "); documentPrinter.printLink(captionRef); println(".</P>"); if (caption == null) return; String src = namer.getImagesDir() + "/snRef.png"; TreeNode node = null; if (field == null) { node = new TreeNode(); node.setObjectPresentation(ObjectPresentation.SIMPLE_NAME); node.setMinCardinality(1); node.setMaxCardinality(1); node.setLocalName("@type"); node.setTypeName("owl:Class"); node.setTypeURI(OWL.Class.getURI()); } else { node = treeGenerator.generateNode(field); } CreateDiagramRequest request = new CreateDiagramRequest(context, node, src); // // Node node = createNode(field); // CreateDiagramRequest request = new CreateDiagramRequest(context, node, // src, // 0, false, false); diagramGenerator.generateDiagram(request); printFigure(src, caption); } private void printSampleObject() throws IOException { Caption caption = new Caption(CaptionType.Figure, "Representation of a JSON object", "sampleObj", null); assignNumber(caption); indent() .print( "<P>This specification defines the structure of a JSON document using a graphical notation. " + "In this notatation, an object is represented by a box that branches out to other boxes corresponding to " + "the properties of that object, as shown in "); documentPrinter.printLink(caption); println(".</P>"); String src = namer.getImagesDir() + "/sampleObj.png"; List<Frame> graphTypes = getGraphTypes(); TreeNode node = (graphTypes == null) ? treeGenerator.generateRoot(root, 1) : treeGenerator.generateGraph(graphTypes, 1); node.sort(); CreateDiagramRequest request = new CreateDiagramRequest(context, node, src); // // CreateDiagramRequest request = new CreateDiagramRequest(context, root, // src, // 1, false, false); diagramGenerator.generateDiagram(request); printFigure(src, caption); // If the root object contains an embedded object then the sample diagram is // not // a complete representation. Add a disclaimer about this condition. List<Field> embedded = listEmbeddedObjects(root); if (embedded.size() > 1) { // This is the case where there is more than one embedded object. String article = article(root.getLocalName()); indent().print( "<P>" + caption.getNumber() + " is not a complete representation of " + article + " <code>" + root.getLocalName() + "</code> object because there are embedded objects ("); String comma = ""; for (Field e : embedded) { print(comma); TermInfo term = context.getTermInfoByURI(e.getURI()); if (term != null) { print("<code>"); print(term.getTermName()); print("</code>"); comma = ", "; } } print("). A complete diagram would show branches emanating from the embedded objects to " + "reveal their properties, and so on, recursively. For a complete representation, see "); documentPrinter.printForwardRef(overviewDiagram); print(" below.</P>"); } else if (embedded.size() == 1) { // This is the case where there is exactly one embedded object Field e = embedded.get(0); TermInfo term = context.getTermInfoByURI(e.getURI()); if (term == null) { throw new TermNotFoundException(e.getURI()); } String article = Character.toUpperCase(root.getLocalName().charAt(0)) == 'A' ? "an" : "a"; indent() .print( "<P>" + caption.getNumber() + " is not a complete representation of " + article + " <code>" + root.getLocalName() + "</code> object because <code>" + term.getTermName() + "</code> is an embedded object. A complete diagram would show branches emanating from <code>" + term.getTermName() + "</code> " + "to reveal its properties, and so on, recursively. For a complete representation, see "); documentPrinter.printForwardRef(overviewDiagram); print(" below.</P>"); } } private String article(String text) { char c = Character.toLowerCase(text.charAt(0)); return VOWEL.indexOf(c) >= 0 ? "an " : "a "; } private List<Field> listEmbeddedObjects(Frame frame) { List<Field> result = new ArrayList<Field>(); List<Field> list = frame.listAllFields(); for (Field field : list) { if (isEmbeddedObject(field, field.getRdfType()) && isIncluded(frame, field)) { result.add(field); } } return result; } private boolean isIncluded(Frame frame, Field field) { String fieldName = field.getLocalName(); return (context.getTermInfoByShortName(fieldName) != null); } private boolean isEmbeddedObject(Field field, RdfType type) { if (type == null) return false; if (type.canAsListType()) { return isEmbeddedObject(field, type.asListType().getElementType()); } if (!type.canAsFrame() || type.asFrame().getCategory() == RestCategory.ENUMERABLE) { return false; } if (isShortCircuit(type.asFrame())) return false; TermInfo term = context.getTermInfoByURI(field.getURI()); if (term != null && term.isCoercedAsIriRef()) { return false; } return true; } private boolean isShortCircuit(Frame frame) { if (!frame.isAbstract()) return false; List<Frame> frameList = frame.listAllSubtypes(); List<Datatype> typeList = frame.getSubdatatypeList(); return frameList.size() + typeList.size() == 1; } private void printRepeatedPropertyFigure(List<Field> fieldList) throws IOException { String typeText = null; Field field = getRepeatedSimpleType(fieldList); if (field != null) { typeText = "xs:" + field.getType().getLocalName(); } else { field = getAnyRepeatedField(new HashSet<String>(), root); } if (field == null) return; if (typeText == null) { RdfType rdfType = field.getRdfType(); if (rdfType.canAsListType()) { rdfType = rdfType.asListType().getElementType(); } String typeURI = rdfType.getUri(); typeText = context.rewrite(typeURI); } Caption captionRef = captionManager.getFigureCaptionByURI(field.getURI()); Caption caption = null; if (captionRef == null) { captionRef = caption = new Caption(CaptionType.Figure, "Example of a repeatable property", "repeatable-property", field.getURI()); assignNumber(caption); } indent() .print( "<P>If a property can have multiple values, then its box in the graphical " + "notation is decorated with a circle that contains a plus sign (+) as shown in "); documentPrinter.printLink(captionRef); String fieldName = field.getLocalName(); String value = (field.getRdfType() != null && field.getRdfType() .canAsFrame()) ? " object" : " value"; value = "<code>" + typeText + "</code>" + value; println(". In this example, the <code>" + fieldName + "</code> property may reference more than one " + value + ". Ordinarily, these values are encapsulated within a JSON array, but if it turns out that " + " only one value is present, then the square brackets for the array are optional.</P>"); if (caption == null) return; String src = namer.getImagesDir() + "/repeatableproperty.png"; // Node node = new Node(); // node.setNameText(field.getLocalName()); // node.setTypeText(typeText); // node.setModifier(Modifier.REPEATABLE); // CreateDiagramRequest request = new CreateDiagramRequest(context, node, // src, // 0, false, false); TreeNode node = treeGenerator.generateNode(field); if (node == null) { logger.error("Cannot generated diagram for repeated field using {} for class {}", field.getLocalName(), contextProperties.getRdfTypeURI()); return; } CreateDiagramRequest request = new CreateDiagramRequest(context, node, src); diagramGenerator.generateDiagram(request); printFigure(src, caption); } private void printOptionalPropertyFigure(List<Field> fieldList) throws IOException { Field field = getOptionalSimpleType(fieldList); if (field == null) { field = getAnyOptionalField(new HashSet<String>(), root); } if (field == null) { return; } String fieldURI = field.getURI(); Caption captionRef = captionManager.getFigureCaptionByURI(fieldURI); Caption caption = null; if (captionRef == null) { captionRef = caption = new Caption(CaptionType.Figure, "Example of an optional property", "optional-property", fieldURI); assignNumber(caption); } indent() .print( "<P>If a property is optional, its box will be decorated with a circle that contains a question mark, " + "as shown in "); documentPrinter.printLink(captionRef); println(".</P>"); if (caption == null) return; String src = namer.getImagesDir() + "/optionalproperty.png"; TreeNode node = null; node = treeGenerator.generateNode(field); CreateDiagramRequest request = new CreateDiagramRequest(context, node, src); diagramGenerator.generateDiagram(request); printFigure(src, caption); } private Field getUriRefField(List<Field> list) { for (Field field : list) { String shortName = field.getLocalName(); TermInfo info = context.getTermInfoByShortName(shortName); if (info == null) { continue; } RdfType type = field.getRdfType(); if (info.hasObjectValue() && "@id".equals(info.getObjectValue().getType()) && type != null && type.canAsFrame() && type.asFrame().getCategory() != RestCategory.ENUMERABLE) { return field; } } return null; } private Field getSnRefField(List<Field> list) { for (Field field : list) { String shortName = field.getLocalName(); TermInfo info = context.getTermInfoByShortName(shortName); if (info == null) { continue; } RdfType type = field.getRdfType(); if (type != null && type.canAsFrame()) { Frame obj = type.asFrame(); if (obj.getCategory() == RestCategory.ENUMERABLE) { return field; } } } return null; } private Field getRepeatedSimpleType(List<Field> list) { for (Field field : list) { if (context.getTermInfoByURI(field.getURI()) == null) { continue; } if (field.getMaxCardinality() > 0 && field.getMaxCardinality() < 2) continue; String typeURI = field.getType().getURI(); if (isSimpleType(field)) { return field; } } return null; } private Field getOptionalSimpleType(List<Field> list) { for (Field field : list) { if (field.getMinCardinality() == 0 && field.getMaxCardinality() == 1 && field.getType().getURI() != null && isSimpleType(field) ) { return field; } } return null; } private boolean isSimpleType(Field field) { RdfType rdfType = field.getRdfType(); return typeManager.isStandard(field.getType().getNameSpace()) || (rdfType!=null && rdfType.canAsDatatype() ); } private Field getAnyOptionalField(HashSet<String> history, Frame frame) { if (history.contains(frame.getUri())) return null; history.add(frame.getUri()); List<Field> list = frame.listAllFields(); if (list.isEmpty()) return null; FrameConstraints constraints = contextProperties.getFrameConstraints(frame.getLocalName()); for (Field field : list) { if (field.getMinCardinality() == 0 && field.getMaxCardinality() == 1 && !field.getRdfType().canAsListType() && context.getTermInfoByURI(field.getURI()) != null && (constraints==null || constraints.isIncludedProperty(field.getURI())) ) { return field; } RdfType type = field.getRdfType(); if (type != null && type.canAsFrame()) { Field result = getAnyOptionalField(history, type.asFrame()); if (result != null) return result; } } return null; } private Field getAnyRepeatedField(HashSet<String> history, Frame frame) { if (history.contains(frame.getUri())) return null; history.add(frame.getUri()); List<Field> list = frame.listAllFields(); for (Field field : list) { if (context.getTermInfoByURI(field.getURI()) == null) continue; if (field.getRdfType().canAsListType()) { return field; } if (field.getMaxCardinality() != 1) { return field; } RdfType type = field.getRdfType(); if (type != null && type.canAsFrame()) { Field result = getAnyRepeatedField(history, type.asFrame()); if (result != null) return result; } } return null; } private Field getAnyField(Frame root) { List<Field> list = root.listAllFields(); if (list.isEmpty()) return null; FrameConstraints constraints = contextProperties.getFrameConstraints(root.getLocalName()); // Ideally, we'd rather not return an RDFS property (like "label") // or an OWL property (like "sameAs"). // Better to return a property that is defined within a custom // namespace. for (Field field : list) { String uri = field.getType().getURI(); if (uri != null && !uri.startsWith(RDFS.getURI()) && !uri.startsWith(OWL.NS) && context.getTermInfoByURI(field.getURI()) != null && (constraints==null || constraints.isIncludedProperty(field.getURI())) ) { return field; } } return null; } private void printFigure(String src, Caption caption) { indent().print("<DIV"); printAttr("class", "figure"); printAttr("id", caption.getId()); println(">"); pushIndent(); indent().print("<IMG"); printAttr("src", src); println("/>"); printCaption(caption); popIndent(); indent().println("</DIV>"); } private void printCaption(Caption caption) { captionManager.add(caption); indent().print("<DIV"); printAttr("class", "caption"); println(">"); print(caption.getNumber()); print(".  "); print(caption.getText()); indent().println("</DIV>"); } private List<Field> getFieldList() { Set<String> visited = new HashSet<String>(); List<Field> fieldList = new ArrayList<Field>(); logger.debug("getFieldList for {}", contextProperties.getRdfTypeURI()); addFields(visited, fieldList, root); sort(fieldList); return fieldList; } private void addFields(Set<String> visited, List<Field> sink, Frame frame) { String frameURI = frame.getUri(); if (!visited.contains(frameURI)) { visited.add(frameURI); List<Field> declared = frame.getDeclaredFields(); filterFields(visited, sink, declared); List<Frame> supertypeList = frame.getSupertypeList(); if (supertypeList != null) { for (Frame supertype : supertypeList) { addFields(visited, sink, supertype); } } } } private void filterFields(Set<String> visited, List<Field> sink, List<Field> declared) { for (Field field : declared) { String uri = field.getURI(); if (!visited.contains(uri)) { visited.add(uri); TermInfo term = context.getTermInfoByURI(uri); if (term != null) { logger.debug(" {}", field.getLocalName()); sink.add(field); RdfType type = field.getRdfType(); if (type != null && type.canAsFrame()) { addFields(visited, sink, type.asFrame()); } } } } } private Field getSimpleTypeField(List<Field> list) { for (Field field : list) { RdfType type = field.getRdfType(); if (isSimpleType(field)) { return field; } } return null; } private void assignNumber(Caption caption) { String number = null; switch (caption.getType()) { case Figure: figureNumber++; number = "Figure " + figureNumber; break; case Table: tableNumber++; number = "Table " + tableNumber; } caption.setNumber(number); } public List<Frame> listFrames() { return frameList; } public boolean isIncludeOverviewDiagram() { return includeOverviewDiagram && (context != null) && contextProperties.isOverviewDiagram(); } public void setIncludeOverviewDiagram(boolean includeOverviewDiagram) { this.includeOverviewDiagram = includeOverviewDiagram; } public boolean isIncludeClassDiagrams() { return includeClassDiagrams; } public void setIncludeClassDiagrams(boolean includeClassDiagrams) { this.includeClassDiagrams = includeClassDiagrams; } private void beginHTML() { println("<HTML>"); println("<HEAD>"); pushIndent(); printStyleSheetLink(); popIndent(); println("</HEAD>"); println("<BODY>"); } private void printStyleSheetLink() { String mediaType = contextProperties.getMediaType(); String href = namer.pathToStyleSheet(mediaType); indent().print("<LINK"); printAttr("REL", "StyleSheet"); printAttr("HREF", href); printAttr("TYPE", "text/css"); println(">"); } private void endHTML() { println("</BODY>"); println("</HTML>"); } private void printOverviewDiagram() throws IOException { if (!isIncludeOverviewDiagram()) return; String typeName = context.rewrite(root.getUri()); String text = "{0} presents a complete graphical representation of the JSON binding for a {1} object. " + "The subsections following this figure provide details about each object " + "that appears in the JSON binding for a {1} object."; Caption caption = overviewDiagram; assignNumber(caption); text = text.replace("{0}", caption.getNumber()).replace("{1}", typeName); printParagraph(text); indent().print("<div"); printAttr("class", "overviewDiagram"); printAttr("id", caption.getId()); println(">"); pushIndent(); String src = namer.getOverviewDiagramPath(); indent().print("<img"); printAttr("src", src); println("/>"); popIndent(); println("</div>"); printCaption(caption); if (diagramGenerator != null) { String rdfProperty = contextProperties.getRdfProperty(); List<Frame> graphTypes = getGraphTypes(); TreeNode node = null; if (graphTypes == null) { if (root.isSubclassOf(CONTAINER)) { Frame page = typeManager.getFrameByUri(PAGE); if (page == null) { throw new FrameNotFoundException(PAGE); } node = treeGenerator.generateRoot(page, null, -1); } else { node = treeGenerator.generateRoot(root, rdfProperty, -1); } } else { node = treeGenerator.generateGraph(graphTypes, -1); } sortAll(node); CreateDiagramRequest request = new CreateDiagramRequest(context, node, src); // CreateDiagramRequest request = new CreateDiagramRequest(context, root, // src, -1, true, true); diagramGenerator.generateDiagram(request); } } private void printDefaultSample(String typeName) throws IOException { String sampleText = getSampleText(); indent().print("<DIV"); printAttr("class", "jsonSample"); println(">"); println("<PRE>"); println(sampleText); println("</PRE>"); indent().print("</DIV>"); String sampleCaptionText = null; if (typeName == null) { sampleCaptionText = "Example JSON document in the format " + contextProperties.getMediaType(); } else { sampleCaptionText = "Example JSON document containing {0} {1} object"; sampleCaptionText = sampleCaptionText.replace("{0}", article(typeName)); sampleCaptionText = sampleCaptionText.replace("{1}", typeName); } Caption sampleCaption = new Caption(CaptionType.Figure, sampleCaptionText, "completeSample", null); assignNumber(sampleCaption); printCaption(sampleCaption); } private void printOtherSamples() throws IOException { List<SampleJson> list = contextProperties.getSampleJsonList(); for (SampleJson sample : list) { printSample(sample); } } private void printSample(SampleJson sample) throws IOException { InputStream input = streamFactory.createInputStream(sample.getFileName()); if (input == null) { logger.warn("File not found " + sample.getFileName()); return; } BufferedReader buffer = new BufferedReader(new InputStreamReader(input)); StringBuilder builder = new StringBuilder(); try { String line = null; while ((line = buffer.readLine()) != null) { builder.append(line); builder.append("\n"); } } finally { buffer.close(); } String sampleText = builder.toString(); jsonManager.add(sampleText); indent().print("<DIV"); printAttr("class", "jsonSample"); println(">"); println("<PRE>"); println(sampleText); println("</PRE>"); indent().print("</DIV>"); Caption sampleCaption = new Caption(CaptionType.Figure, sample.getCaption(), sample.getFileName(), null); assignNumber(sampleCaption); printCaption(sampleCaption); } private void sortAll(TreeNode node) { String typeURI = node.getTypeURI(); List<TreeNode> kids = node.getChildren(); if (typeURI != null && kids != null && !kids.isEmpty()) { Collections.sort(kids, nodeComparatorFactory.getComparator(typeURI)); } if (kids != null) { for (TreeNode child : kids) { sortAll(child); } } } private String getSampleText() throws IOException { String fileName = namer.getJsonSampleFileName(); InputStream input = streamFactory.createInputStream(fileName); String result = null; if (input == null) { result = createJsonSample(); } else { BufferedReader buffer = new BufferedReader(new InputStreamReader(input)); StringBuilder builder = new StringBuilder(); try { String line = null; while ((line = buffer.readLine()) != null) { builder.append(line); builder.append("\n"); } } finally { buffer.close(); } result = builder.toString(); } jsonManager.add(result); return result; } private String createJsonSample() throws IOException { ObjectNode node = sampleGenerator .generateSample(context, contextProperties); String fileName = namer.getJsonSampleFileName(); OutputStream out = streamFactory.createOutputStream(fileName); StringWriter buffer = new StringWriter(); try { ObjectMapper mapper = new ObjectMapper(); ObjectWriter writer = mapper.writer(new JsonPrettyPrinter()); mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true); writer.writeValue(out, node); writer.writeValue(buffer, node); } finally { out.close(); } return buffer.toString(); } private void printFrames() throws IOException { for (Frame frame : frameList) { printFrame(frame); } } private void printDatatype(Datatype type) { if (typeManager.isStandardDatatype(type.getNamespace())) { return; } String termName = null; Heading heading = null; TermInfo term = context.getTermInfoByURI(type.getUri()); if (term != null) { termName = term.getTermName(); heading = documentPrinter.createHeading(termName); } else { // This datatype does not have a JSON-LD term defined in the // context. if (typeManager.isStandardLiteralType(type.getUri())) { return; } TreeNode node = NodeUtil.createDefaultTypeNode(typeManager, context, type.getUri()); termName = node.getTypeName(); Level level = documentPrinter.getCurrentHeading().getLevel() .getNextLevel(); String id = node.getTypeHref().substring(1); heading = new Heading(level, termName, id); documentPrinter.getCurrentHeading().add(heading); } heading.setClassName("rdfType"); documentPrinter.print(heading); String baseURI = typeManager.getXsdBaseURI(type); beginTable("propertiesTable"); beginRow(); printTH("Restriction Base"); printTD(baseURI); endRow(); printStringFacet("pattern", type.getPattern()); printNumberFacet("length", type.getLength()); printNumberFacet("minLength", type.getMinLength()); printNumberFacet("maxLength", type.getMaxLength()); printNumberFacet("minInclusive", type.getMinInclusive()); printNumberFacet("maxInclusive", type.getMaxInclusive()); printNumberFacet("minExclusive", type.getMinExclusive()); printNumberFacet("maxExclusive", type.getMaxExclusive()); printNumberFacet("totalDigits", type.getTotalDigits()); printNumberFacet("fractionDigits", type.getFractionDigits()); endTable(); String captionText = "Facets of " + termName; Caption caption = new Caption(CaptionType.Table, captionText, termName, type.getUri()); assignNumber(caption); printCaption(caption); } private void printNumberFacet(String name, Number value) { if (value == null) return; beginRow(); printTH(name); printTD(value.toString()); endRow(); } private void printStringFacet(String name, String value) { if (value == null) return; beginRow(); printTH(name); printTD(value); endRow(); } protected void beginTable(String className) { indent().print("<TABLE"); printAttr("class", className); println(">"); pushIndent(); } protected void endTable() { popIndent(); indent().println("</TABLE>"); } protected void beginRow() { indent().println("<TR>"); pushIndent(); } protected void endRow() { popIndent(); indent().println("</TR>"); } protected void printTH(String value) { indent().print("<TH>").print(value).println("</TH>"); } protected void printTD(String value) { indent().print("<TD>").print(value).println("</TD>"); } private void printFrame(Frame frame) throws IOException { println(); TreeGenerator generator = new TreeGenerator(typeManager, context, contextProperties); List<Frame> graphTypes = getGraphTypes(); boolean isRoot = isRoot(frame); TreeNode node = (isRoot && graphTypes == null) ? generator .generateRoot(frame, 1) : generator.generateNode(frame, 1); List<TreeNode> fieldList = node.getChildren(); if (fieldList != null) { Collections.sort(fieldList, nodeComparatorFactory.getComparator(frame.getUri())); } Heading heading = documentPrinter.createHeading(node.getTypeName()); String comment = node.getDescription(); heading.setClassName("rdfType"); documentPrinter.print(heading); if (comment.length() > 0) { print("<div"); printAttr("class", "classComment"); println(">"); print(comment); println("</div>"); } printClassDiagram(frame, node); printSubtypes(frame); printProperties(node); printIndividuals(frame); } private boolean isRoot(Frame frame) { if (frame == root) return true; List<Frame> list = root.listAllSubtypes(); return list.contains(frame); } private void printIndividuals(Frame frame) { String frameURI = frame.getUri(); if ( frameURI.startsWith("http://www.w3.org/1999/02/22-rdf-syntax-ns#") || frameURI.startsWith("http://www.w3.org/2000/01/rdf-schema#") ){ return; } if (frame.getCategory() != RestCategory.ENUMERABLE) return; List<NamedIndividual> list = frame.listInstances(false); if (list.isEmpty()) return; Collections.sort(list, new Comparator<NamedIndividual>() { @Override public int compare(NamedIndividual a, NamedIndividual b) { String aName = a.getLocalName(); String bName = b.getLocalName(); return (aName==null || bName==null) ? 0 : aName.compareTo(bName); } }); String typeName = context.rewrite(frame.getUri()); String text = "Known simple names for {0} objects".replace("{0}", typeName); Caption caption = new Caption(CaptionType.Table, text, typeName + "-known-sn", frame.getUri()); assignNumber(caption); text = "<code>{0}</code> instances are enumerable, and they must be referenced by a simple name. " + "The default vocabulary of simple names for instances of the <code>{0}</code> class are listed in {1}."; println(); printParagraph(text.replace("{0}", typeName).replace("{1}", caption.getNumber())); indent().print("<TABLE"); printAttr("class", "enumTable"); println(">"); pushIndent(); indent().println("<TR>"); pushIndent(); indent().print("<TH>Simple Name</TH>"); indent().print("<TH>URI / Description</TH>"); popIndent(); indent().println("</TR>"); for (NamedIndividual n : list) { String uri = n.getUri(); TermInfo term = context.getTermInfoByURI(uri); if (term == null) { continue; } String localName = term.getTermName(); String comment = n.getComment(); if (comment != null) { comment = comment.trim(); if (comment.length() == 0) { comment = null; } } indent().println("<TR>"); pushIndent(); indent().print("<TD>").print(localName).println("</TD>"); indent().print("<TD>"); print("<div class=\"individual-uri\">").print(uri).print("</div>"); if (comment != null) { print("<div class=\"enumText\">").print(comment).print("</div>"); } println("</TD>"); popIndent(); indent().println("</TR>"); } popIndent(); indent().println("</TABLE>"); printCaption(caption); } private void printClassDiagram(Frame frame, TreeNode node) throws IOException { if (!includeClassDiagrams) return; String src = namer.getClassDiagramPath(frame); String typeName = context.rewrite(frame.getUri()); Caption caption = new Caption(CaptionType.Figure, typeName, typeName, frame.getUri()); assignNumber(caption); String jsonExample = getJsonSample(frame); if (jsonExample != null) { print("<PRE"); printAttr("class", "jsonSnippet"); println(">"); println(jsonExample); println("</PRE>"); } indent().print("<DIV"); printAttr("class", "classDiagram"); println(">"); pushIndent(); indent().print("<img"); printAttr("src", src); println("/>"); printCaption(caption); popIndent(); indent().println("</DIV>"); if (diagramGenerator == null) return; // boolean extras = (frame == root) ? true : false; // CreateDiagramRequest request = new CreateDiagramRequest(context, frame, // src, 1, extras, extras); CreateDiagramRequest request = new CreateDiagramRequest(context, node, src); diagramGenerator.generateDiagram(request); } private String getJsonSample(Frame frame) { String text = jsonManager.getJsonText(frame.getUri()); if (text != null) { text = text.replaceAll("\\[\\s*\\]", "[ ... ]").replaceAll("\\{\\s*\\}", "{ ... }"); } return text; } private void printProperties(TreeNode node) { List<TreeNode> list = node.getChildren(); if (list == null || list.isEmpty()) return; indent().print("<TABLE"); printAttr("class", "propertiesTable"); printAttr("border", "0"); printAttr("width", "100%"); printAttr("cellspacing", "0"); println(">"); pushIndent(); indent().println("<TR>"); pushIndent(); indent().println("<TH>Property</TH>"); indent().println("<TH>Mult</TH>"); indent().println("<TH>Description</TH>"); indent().println("<TH>Type</TH>"); popIndent(); indent().println("</TR>"); for (TreeNode field : list) { printField(field); } popIndent(); indent().println("</TABLE>"); String typeName = node.getTypeName(); Caption caption = new Caption(CaptionType.Table, typeName + " properties", typeName + "-properties", null); assignNumber(caption); printCaption(caption); } private void printSubtypes(Frame frame) { List<Frame> sublist = frame.listAllSubtypes(); if (sublist.isEmpty()) return; indent().print("<div"); printAttr("class", "list-heading"); println(">Direct Known Subtypes:</div>"); indent().print("<div"); printAttr("class", "running-list"); println(">"); pushIndent(); boolean addComma = false; for (Frame subtype : sublist) { TermInfo info = context.getTermInfoByURI(subtype.getUri()); if (info == null) continue; if (addComma) { println(","); } addComma = true; String href = "#" + info.getTermName(); String typeName = info.getTermName(); indent().print("<A "); printAttr("href", href); print(">"); print(typeName); print("</a>"); } println(); popIndent(); indent().println("</div>"); } private void printField(TreeNode field) { String localName = field.getLocalName(); String mult = getMultiplicity(field); String description = field.getDescription(); String typeLabel = field.getTypeName(); String href = field.getTypeHref(); indent().println("<TR>"); pushIndent(); indent().print("<TD>"); print(localName); println("</TD>"); indent().print("<TD>"); print(mult); if (field.isSequential()) { print("<DIV"); printAttr("class", "qualifier"); println(">(ordered)</DIV>"); } println("</TD>"); indent().print("<TD>"); if (field.isReadOnly()) { print("<em>Read Only</em>. "); } print(description); String value = field.getValueRestriction(); List<String> knownValues = field.getKnownValues(); if (value != null) { println(); indent().print("<P>"); indent().print("This property is restricted and must have the value <code>\""); print(value).println("\"</code>."); indent().println("</P>"); } else if (knownValues != null) { println(); indent().print("<P>"); if (knownValues.size()==1) { indent().print("The only known value for this property is <code>"); print(knownValues.get(0)); println("</code>."); } indent().print("Known values for this property include "); indent().print("("); String comma = ""; Collections.sort(knownValues); for (String known : knownValues) { print(comma).print("<code>").print(known).print("</code>"); comma = ", "; } println(")."); indent().println("</P>"); } println("</TD>"); indent().print("<TD>"); if (href != null) { print("<A"); printAttr("href", href); print(">"); } print(typeLabel); if (href != null) { print("</A>"); } switch (field.getObjectPresentation()) { case MIXED_VALUE: print("<DIV"); printAttr("class", "qualifier"); println(">(Mixed - URI reference OR Embedded value)</DIV>"); break; case URI_REFERENCE: print("<DIV"); printAttr("class", "qualifier"); println(">(URI reference)</DIV>"); break; case SIMPLE_NAME: print("<DIV"); printAttr("class", "qualifier"); println(">(Simple Name reference)</DIV>"); break; } println("</TD>"); popIndent(); indent().println("</TR>"); } private String getMultiplicity(TreeNode node) { int min = node.getMinCardinality(); int max = node.getMaxCardinality(); if (min == 0 && max < 0) return "*"; if (min == 1 && max == 1) return "1"; String maxLabel = (max < 0) ? "*" : Integer.toString(max); return min + ".." + maxLabel; } private void collectFrames() { frameList = new ArrayList<Frame>(); datatypeList = new ArrayList<Datatype>(); if (context == null) return; Set<String> reachable = getReachableTypes(); List<TermInfo> termList = context.getTerms(); for (TermInfo term : termList) { if (term.getCategory() == TermCategory.TYPE) { String qname = term.getIri(); String uri = context.toAbsoluteIRI(qname); Frame frame = typeManager.getFrameByUri(uri); if (frame == null) { if (typeManager.isStandard(uri)) continue; // Check to see if the given type is a primitive type. Datatype type = typeManager.getDatatypeByUri(uri); if (type != null) { if (reachable.contains(uri)) { datatypeList.add(type); } continue; } throw new FrameNotFoundException(uri); } if (!reachable.contains(frame.getUri())) continue; frameList.add(frame); } } addMissingTypes(reachable); Collections.sort(frameList); Collections.sort(datatypeList, new Comparator<Datatype>() { @Override public int compare(Datatype a, Datatype b) { TermInfo aTerm = context.getTermInfoByURI(a.getUri()); TermInfo bTerm = context.getTermInfoByURI(b.getUri()); String aName = aTerm == null ? a.getLocalName() : aTerm.getTermName(); String bName = bTerm == null ? b.getLocalName() : bTerm.getTermName(); return aName.compareTo(bName); } }); } private void addMissingTypes(Set<String> reachable) { for (String uri : reachable) { TermInfo term = context.getTermInfoByURI(uri); if (term != null) continue; Datatype type = typeManager.getDatatypeByUri(uri); if (type != null) { datatypeList.add(type); } } } private Set<String> getReachableTypes() { Set<String> set = new HashSet<String>(); List<Frame> graphTypes = getGraphTypes(); if (graphTypes != null) { for (Frame frame : graphTypes) { addReachableTypes(set, frame); } } else if (root != null) { addReachableTypes(set, root); addSubtypes(set, root); } return set; } private void addReachableTypes(Set<String> set, Frame frame) { String uri = frame.getUri(); if (set.contains(uri)) return; set.add(uri); List<Field> fieldList = frame.listAllFields(); for (Field field : fieldList) { if (contextProperties.isIdRef(field.getURI())) continue; if (!isIncluded(frame, field)) continue; OntProperty p = field.getProperty(); String propertyURI = p.getURI(); TermInfo info = context.getTermInfoByURI(propertyURI); RdfType type = field.getRdfType(); if (type.canAsListType()) { type = type.asListType().getElementType(); } if (info != null && type.canAsDatatype()) { set.add(type.getUri()); continue; } if (info != null && info.hasObjectValue() && "@id".equals(info.getObjectValue().getType()) && type != null && type.canAsFrame() && type.asFrame().getCategory() != RestCategory.ENUMERABLE ) { continue; } if (type == null || !type.canAsFrame()) { continue; } // Frame fieldFrame = type.asFrame(); // // if (isShortCircuit(fieldFrame)) { // frame = fieldFrame.listAllSubtypes().get(0); // } addReachableTypes(set, type.asFrame()); addSubtypes(set, type.asFrame()); addSubdatatypes(set, type.asFrame()); } } private void addSubdatatypes(Set<String> set, Frame frame) { // Don't add subtypes for rdfs:Resource. // This is a bit of a hack. // TODO: find a more elegant solution. if (frame.getUri().equals(RDFS.Resource.getURI())) { return; } List<Datatype> list = frame.getSubdatatypeList(); for (Datatype type : list) { set.add(type.getUri()); } } private void addSubtypes(Set<String> set, Frame baseFrame) { List<Frame> list = baseFrame.listAllSubtypes(); for (Frame frame : list) { addReachableTypes(set, frame); } } static class CaptionManager { Map<String, Caption> uri2FigureCaption = new HashMap<String, Caption>(); public void add(Caption caption) { if (caption.getUri() == null) return; switch (caption.getType()) { case Figure: uri2FigureCaption.put(caption.getUri(), caption); break; } } public Caption getFigureCaptionByURI(String uri) { return uri2FigureCaption.get(uri); } } }