package writers; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.Locale; import java.util.TimeZone; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import com.sun.javadoc.ClassDoc; import com.sun.javadoc.ConstructorDoc; import com.sun.javadoc.Doc; import com.sun.javadoc.ExecutableMemberDoc; import com.sun.javadoc.FieldDoc; import com.sun.javadoc.MethodDoc; import com.sun.javadoc.ParamTag; import com.sun.javadoc.Parameter; import com.sun.javadoc.ProgramElementDoc; import com.sun.javadoc.SeeTag; import com.sun.javadoc.Tag; public class BaseWriter { // Some utilities public final static String MODE_JAVASCRIPT = "js"; public BaseWriter() { } protected static boolean needsWriting(ProgramElementDoc doc){ if( (doc != null) && Shared.i().isWebref(doc) ) { return hasXMLDocument( doc ); } return false; } protected static BufferedWriter makeWriter(String anchor) throws IOException { return makeWriter(anchor, false); } protected static String getWriterPath( String anchor, Boolean isLocal ) { if (!isLocal) { return Shared.i().getOutputDirectory() + "/" + anchor; } else { return Shared.i().getLocalOutputDirectory() + anchor; } } protected static BufferedWriter makeWriter(String anchor, Boolean isLocal) throws IOException { FileWriter fw = new FileWriter( getWriterPath( anchor, isLocal ) ); return new BufferedWriter(fw); } protected static String getAnchor(ProgramElementDoc doc) { String ret = getAnchorFromName(getName(doc)); if(doc.containingClass() != null && !Shared.i().isRootLevel(doc.containingClass())) { ret = doc.containingClass().name() + "_" + ret; } if(!Shared.i().isCore(doc)){ //add package name to anchor String[] parts = doc.containingPackage().name().split("\\."); String pkg = parts[parts.length-1] + "/"; ret = "libraries/" + pkg + ret; } return ret; } protected static String getLocalAnchor(ProgramElementDoc doc) { String ret = getAnchorFromName(getName(doc)); if(doc.containingClass() != null){ ret = doc.containingClass().name() + "_" + ret; } return ret; } protected static String getReturnTypes(MethodDoc doc) { String ret = nameInPDE(doc.returnType().toString()); if(doc.containingClass() != null) { for(MethodDoc m : doc.containingClass().methods()) { if( m.name().equals(doc.name()) && m.returnType() != doc.returnType() ) { String name = getSimplifiedType( nameInPDE(m.returnType().toString()) ); if( ! ret.contains( name ) ) { // add return type name if it's not already included ret += ", " + name; } } } } // add "or" (split first to make sure we don't mess up the written description) ret = ret.replaceFirst( ",([^,]+)$", ", or$1" ); if( ! ret.matches(".+,.+,.+") ) { ret = ret.replace( ",", "" ); } return ret; } protected static String getSimplifiedType( String str ) { if( str.equals("long") ){ return "int"; } if( str.equals("double") ){ return "float"; } return str; } protected static String getName(Doc doc) { // handle String ret = doc.name(); if(doc instanceof MethodDoc) { ret = ret.concat("()"); } else if (doc.isField()){ // add [] if needed FieldDoc d = (FieldDoc) doc; ret = ret.concat(d.type().dimension()); } return ret; } protected static String getAnchorFromName(String name){ // change functionName() to functionName_ if( name.endsWith("()") ){ name = name.replace("()", "_"); } // change "(some thing)" to something if( name.contains("(") && name.contains(")") ){ int start = name.lastIndexOf("(") + 1; int end = name.lastIndexOf(")"); name = name.substring(start, end); name = name.replace(" ", ""); } // change thing[] to thing if( name.contains("[]")){ name = name.replaceAll("\\[\\]", ""); } // change "some thing" to "some_thing.html" name = name.replace(" ", "_").concat(".html"); return name; } static protected String getBasicDescriptionFromSource(ProgramElementDoc doc) { return getBasicDescriptionFromSource(longestText(doc)); } static protected String getBriefDescriptionFromSource(ProgramElementDoc doc) { Tag[] sta = doc.tags("brief"); if(sta.length > 0){ return sta[0].text(); } return getBasicDescriptionFromSource(doc); } static protected String longestText(ProgramElementDoc doc){ if(Shared.i().isWebref(doc)){ //override longest rule if the element has an @webref tag return doc.commentText(); } String s = doc.commentText(); if( doc.isMethod() ){ for(ProgramElementDoc d : doc.containingClass().methods()){ if(d.name().equals(doc.name() ) ){ if(d.commentText().length() > s.length()){ s = d.commentText(); } } } } else if(doc.isField()){ for(ProgramElementDoc d : doc.containingClass().fields()){ if(d.name().equals(doc.name() ) ){ if(d.commentText().length() > s.length()){ s = d.commentText(); } } } } return s; } static protected String getBasicDescriptionFromSource(String s){ String[] sa = s.split("(?i)(Advanced:?)|(=advanced)"); if (sa.length != 0) s = sa[0]; return s; } static protected String getAdvancedDescriptionFromSource(ProgramElementDoc doc) { return getAdvancedDescriptionFromString(longestText(doc)); } static private String getAdvancedDescriptionFromString(String s) { String[] sa = s.split("(?i)(Advanced:?)|(=advanced)"); if (sa.length > 1) s = sa[1]; return s; } // protected static String getXMLPath(ProgramElementDoc doc) { String path = Shared.i().getXMLDirectory(); String name = doc.name(); String suffix = ".xml"; if(doc.containingClass() != null){ if(Shared.i().isRootLevel(doc.containingClass())){ //inside PApplet or other root-level class if(doc instanceof FieldDoc){ //if there is a method of the same name, append _var for( Doc d : doc.containingClass().methods()){ if(d.name().equals(doc.name())){ suffix = "_var" + suffix; break; //don't append multiple times } } } } else { name = doc.containingClass().name() + "_" + name; } } if( !Shared.i().isCore(doc)){ //if documentation is for a library element String[] pkg = doc.containingPackage().name().split("\\."); path = path + "LIB_" + pkg[pkg.length-1] + "/"; } return path + name + suffix; } static protected String getExamples(ProgramElementDoc doc) throws IOException{ return getExamples(getXMLPath(doc)); } static private Document getXMLDocument(String path) throws IOException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder = null; Document doc = null; try { builder = factory.newDocumentBuilder(); } catch (ParserConfigurationException e) { System.out.println( "Failed to load XML: " + e.getMessage()); } if( builder != null ) { try { doc = builder.parse(path); } catch (SAXException e) { System.out.println( "Failed to parse XML: " + e.getMessage() ); } catch (IOException e) { System.out.println( "Failed to parse XML: " + e.getMessage() ); } } return doc; } private static boolean hasXMLDocument( ProgramElementDoc doc ) { String path = getXMLPath( doc ); File f = new File( path ); if( f.exists() ) { return true; } return false; } static protected String getExamples(String path) throws IOException { Document doc = getXMLDocument(path); if( doc != null ) return getExamples(doc); else { System.out.println("Unable to get examples from " + path + "; returning an empty string."); return ""; } } protected static String getExamples(Document doc) throws IOException{ //Parse the examples from an XML document TemplateWriter templateWriter = new TemplateWriter(); ArrayList> exampleList = new ArrayList>(); XPathFactory xpathFactory = XPathFactory.newInstance(); XPath xpath = xpathFactory.newXPath(); try { XPathExpression expr = xpath.compile("//example"); Object result = expr.evaluate(doc, XPathConstants.NODESET); NodeList examples = (NodeList) result; for (int i = 0; i < examples.getLength(); i++) { HashMap example = new HashMap(); expr = xpath.compile("image"); String img = (String) expr.evaluate(examples.item(i), XPathConstants.STRING); expr = xpath.compile("code"); String code = (String) expr.evaluate(examples.item(i), XPathConstants.STRING); example.put("image", Shared.i().getImageDirectory() + img); if(img.equals("")) { // if no image, replace with empty string example.put("image", ""); } example.put("code", code); exampleList.add(example); } } catch (XPathExpressionException e) { e.printStackTrace(); } String exampleInner = templateWriter.writeLoop("/example.partial.html", exampleList); HashMap map = new HashMap(); map.put("examples", exampleInner); return templateWriter.writePartial("examples.partial.html", map); } protected static String getXMLDescription(ProgramElementDoc doc) throws IOException { Document xmlDoc = getXMLDocument(getXMLPath(doc)); if( xmlDoc != null ) return getXMLDescription(xmlDoc); else { System.out.println("Unable to get description from " + getXMLPath(doc) + "; returning an empty string."); return ""; } } /** * Based upon Shared.addDescriptionTag(). * * Hint: this loads and adds js_mode/description as well */ protected static String getXMLDescription(Document doc) { XPathFactory xpathFactory = XPathFactory.newInstance(); XPath xpath = xpathFactory.newXPath(); TemplateWriter templateWriter = new TemplateWriter(); String desc = ""; for( String component : Shared.i().getDescriptionSets() ) { try { XPathExpression expr = xpath.compile(component); String result = (String) expr.evaluate(doc, XPathConstants.STRING); HashMap vars = getDefaultDescriptionVars(); if ( component.indexOf("js_mode") != -1 ) { vars.put( "description title", "JavaScript
\nNotes" ); } if ( !result.equals("") ) { vars.put( "description text", result ); result = templateWriter.writePartial( "description.partial.html", vars ); } desc += result; } catch ( XPathExpressionException e) { System.out.println("Error getting description from xml with expression: //" + component); e.printStackTrace(); } } return desc; } protected static HashMap getDefaultDescriptionVars () { HashMap vars = new HashMap(); vars.put("description title", "Description"); vars.put("description text", ""); return vars; } protected static String getTimestamp() { Calendar now = Calendar.getInstance(); Locale us = new Locale("en"); return now.getDisplayName(Calendar.MONTH, Calendar.LONG, us) + " " + now.get(Calendar.DAY_OF_MONTH) + ", " + now.get(Calendar.YEAR) + " " + FileUtils.nf(now.get(Calendar.HOUR), 2) + ":" + FileUtils.nf(now.get(Calendar.MINUTE), 2) + ":" + FileUtils.nf(now.get(Calendar.SECOND), 2) + now.getDisplayName(Calendar.AM_PM, Calendar.SHORT, us) .toLowerCase() + " " + TimeZone.getDefault().getDisplayName( TimeZone.getDefault().inDaylightTime(now.getTime()), TimeZone.SHORT, us); } /* * Get all the syntax possibilities for a method */ protected static ArrayList> getSyntax(MethodDoc doc, String instanceName) throws IOException { TemplateWriter templateWriter = new TemplateWriter(); ArrayList> ret = new ArrayList>(); for( MethodDoc methodDoc : doc.containingClass().methods() ) { if(Shared.i().shouldOmit(methodDoc)){ continue; } if( methodDoc.name().equals(doc.name() )) { HashMap map = new HashMap(); map.put("name", methodDoc.name()); map.put("object", instanceName); ArrayList> parameters = new ArrayList>(); for( Parameter p : methodDoc.parameters() ) { HashMap paramMap = new HashMap(); paramMap.put("parameter", p.name()); parameters.add(paramMap); } String params = templateWriter.writeLoop("method.parameter.partial.html", parameters, ", "); map.put("parameters", params); if( ! ret.contains(map) ) { //don't put in duplicate function syntax ret.add(map); } } } return ret; } private static String removePackage(String name) { // keep everything after the last dot if( name.contains(".") ) { return name.substring( name.lastIndexOf(".") + 1 ); } return name; } private static String nameInPDE(String fullName) { if( fullName.contains("<") && fullName.endsWith(">") ) { // if this type uses Java generics String parts[] = fullName.split("<"); String generic = removePackage( parts[0] ); String specialization = removePackage( parts[1] ); specialization = specialization.substring( 0, specialization.length() - 1 ); return generic + "<" + specialization + ">"; } return removePackage( fullName ); } protected static String getUsage(ProgramElementDoc doc){ Tag[] tags = doc.tags("usage"); if(tags.length != 0){ return tags[0].text(); } tags = doc.containingClass().tags("usage"); if(tags.length != 0){ return tags[0].text(); } // return empty string if no usage is found return ""; } protected static String getInstanceName(ProgramElementDoc doc){ Tag[] tags = doc.containingClass().tags("instanceName"); if(tags.length != 0){ return tags[0].text().split("\\s")[0]; } return ""; } protected static String getInstanceDescription(ProgramElementDoc doc){ Tag[] tags = doc.containingClass().tags("instanceName"); if(tags.length != 0){ String s = tags[0].text(); return s.substring(s.indexOf(" ")); } return ""; } protected static String getParameters(MethodDoc doc) throws IOException{ //get parent ClassDoc cd = doc.containingClass(); ArrayList> ret = new ArrayList>(); if(!Shared.i().isRootLevel(cd)){ //add the parent parameter if this isn't a function of PApplet HashMap parent = new HashMap(); parent.put("name", getInstanceName(doc)); parent.put("description", cd.name() + ": " + getInstanceDescription(doc)); ret.add(parent); } //get parameters from this and all other declarations of method for( MethodDoc m : cd.methods() ){ if(Shared.i().shouldOmit(m)){ continue; } if(m.name().equals(doc.name())){ ret.addAll(parseParameters(m)); } } removeDuplicateParameters(ret); TemplateWriter templateWriter = new TemplateWriter(); return templateWriter.writeLoop("parameter.partial.html", ret); } protected static String getParameters(ClassDoc doc) throws IOException{ ArrayList> ret = new ArrayList>(); for( ConstructorDoc m : doc.constructors() ){ if(Shared.i().shouldOmit(m)){ continue; } ret.addAll(parseParameters(m)); } removeDuplicateParameters(ret); TemplateWriter templateWriter = new TemplateWriter(); return templateWriter.writeLoop("parameter.partial.html", ret); } protected static void removeDuplicateParameters(ArrayList> ret){ // combine duplicate parameter names with differing types for(HashMap parameter : ret) { String desc = parameter.get("description"); if(!desc.endsWith(": ")) { // if the chosen description has actual text // e.g. float: something about the float for(HashMap parameter2 : ret) { String desc2 = parameter2.get("description"); if( desc2.endsWith(": ") && parameter2.get("name").equals( parameter.get("name") ) ) { // freshen up our variable with the latest description desc = parameter.get("description"); if( ! desc.contains( desc2.substring( 0, desc2.indexOf(": ") ) ) ) { // if the similar item doesn't have actual text // e.g. Boolean: String newDescription = desc2.replace(":", ",").concat( desc ); parameter.put("description", newDescription); } } } } } //remove parameters without descriptions for( int i=ret.size()-1; i >= 0; i-- ) { if(ret.get(i).get("description").endsWith(": ")) { ret.remove(i); } } // add "or" (split first to make sure we don't mess up the written description) for( HashMap param : ret ) { String desc = param.get("description"); String start = desc.substring( 0, desc.indexOf(":")+1 ).replaceFirst( ",([^,]+:)", ", or$1" ); String end = desc.substring( desc.indexOf(":")+1, desc.length() ); param.put( "description", start.concat( end ) ); } } protected static ArrayList> parseParameters(ExecutableMemberDoc doc){ ArrayList> ret = new ArrayList>(); for( Parameter param : doc.parameters()){ String type = getSimplifiedType( nameInPDE(param.type().toString()) ).concat(": "); String name = param.name(); String desc = ""; for( ParamTag tag : doc.paramTags() ){ if(tag.parameterName().equals(name)){ desc = desc.concat( tag.parameterComment() ); } } HashMap map = new HashMap(); map.put("name", name); map.put("description", type + desc); ret.add(map); } return ret; } /** * Modes should support all API, so if XML not explicitly states "not supported", then assume it does. */ protected static boolean isModeSupported ( ProgramElementDoc doc, String mode ) { Document xmlDoc = null; try { String xmlPath = getXMLPath( doc ); xmlDoc = getXMLDocument( xmlPath ); } catch ( IOException ioe ) { ioe.printStackTrace(); return true; } XPathFactory xpathFactory = XPathFactory.newInstance(); XPath xpath = xpathFactory.newXPath(); try { String umraw = xpath.evaluate("//unsupported_modes", xmlDoc, XPathConstants.STRING).toString(); String[] ums = umraw.split(","); for ( String s : ums ) { if ( s.trim().toLowerCase().equals(mode) ) return false; } } catch ( XPathExpressionException e ) { e.printStackTrace(); } return true; } protected static ArrayList getAllSeeTags( ProgramElementDoc doc ) { ArrayList ret = new ArrayList(); ClassDoc cd = doc.containingClass(); if( cd != null && doc.isMethod() ) { // if there is a containing class, get @see tags for all // methods with the same name as this one // Fixes gh issue 293 for( MethodDoc m : cd.methods() ) { if(m.name().equals(doc.name())) { for( SeeTag tag : m.seeTags() ) { ret.add( tag ); } } } } else { // if no containing class (e.g. doc is a class) // just grab the see tags in the class doc comment for( SeeTag tag : doc.seeTags() ) { ret.add( tag ); } } return ret; } protected static String getRelated( ProgramElementDoc doc ) throws IOException { TemplateWriter templateWriter = new TemplateWriter(); ArrayList> vars = new ArrayList>(); // keep track of class members so that links to methods in e.g. PGraphics // that are copied into PApplet are correctly linked. HashMap classMethods = new HashMap(); HashMap classFields = new HashMap(); if( doc.isMethod() || doc.isField() ) { // fill our maps ClassDoc containingClass = doc.containingClass(); for( MethodDoc m : containingClass.methods() ) { if( needsWriting( m ) ) { classMethods.put( m.name(), m ); } } for( FieldDoc f : containingClass.fields() ) { if( needsWriting( f ) ) { classFields.put( f.name(), f ); } } } // add link to each @see item for( SeeTag tag : getAllSeeTags( doc ) ) { HashMap map = new HashMap(); ProgramElementDoc ref = tag.referencedClass(); if( tag.referencedMember() != null ) { ref = tag.referencedMember(); if( ref.isMethod() && classMethods.containsKey( ref.name() ) ) { // link to the member as it is in this class, instead of // as it is in another class ProgramElementDoc prior = classMethods.get( ref.name() ); ref = prior; } else if ( ref.isField() && classFields.containsKey( ref.name() ) ) { ProgramElementDoc prior = classFields.get( ref.name() ); ref = prior; } } if( needsWriting( ref ) ) { // add links to things that are actually written map.put("name", getName( ref )); map.put("anchor", getAnchor( ref )); vars.add(map); } } // add link to each @see_external item for( Tag tag : doc.tags( Shared.i().getSeeAlsoTagName() ) ) { // get xml for method String filename = tag.text() + ".xml"; String basePath = Shared.i().getXMLDirectory(); File f = new File( basePath + filename ); if( ! f.exists() ) { basePath = Shared.i().getIncludeDirectory(); f = new File( basePath + filename ); } if( f.exists() ) { Document xmlDoc = Shared.loadXmlDocument( f.getPath() ); XPathFactory xpathFactory = XPathFactory.newInstance(); XPath xpath = xpathFactory.newXPath(); try { String name = (String) xpath.evaluate("//name", xmlDoc, XPathConstants.STRING); // get anchor from original filename String path = f.getAbsolutePath(); String anchorBase = path.substring( path.lastIndexOf("/")+1, path.indexOf(".xml")); if( name.endsWith("()") ) { if( !anchorBase.endsWith("_" ) ) { anchorBase += "_"; } } String anchor = anchorBase + ".html"; // get method name from xml // get anchor from method name HashMap map = new HashMap(); map.put( "name", name ); map.put( "anchor", anchor ); vars.add( map ); } catch (XPathExpressionException e) { e.printStackTrace(); } } } return templateWriter.writeLoop("related.partial.html", vars); } protected static String getEvents(ProgramElementDoc doc){ return ""; } }