001 // Copyright 2011 The Apache Software Foundation 002 // 003 // Licensed under the Apache License, Version 2.0 (the "License"); 004 // you may not use this file except in compliance with the License. 005 // You may obtain a copy of the License at 006 // 007 // http://www.apache.org/licenses/LICENSE-2.0 008 // 009 // Unless required by applicable law or agreed to in writing, software 010 // distributed under the License is distributed on an "AS IS" BASIS, 011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 // See the License for the specific language governing permissions and 013 // limitations under the License. 014 015 package org.apache.tapestry5.javadoc; 016 017 import java.io.File; 018 import java.io.IOException; 019 import java.io.StringWriter; 020 import java.io.Writer; 021 import java.util.List; 022 import java.util.Map; 023 024 import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 025 import org.apache.tapestry5.ioc.internal.util.InternalUtils; 026 027 import com.sun.javadoc.ClassDoc; 028 import com.sun.javadoc.Tag; 029 import com.sun.tools.doclets.Taglet; 030 031 /** 032 * An inline tag allowed inside a type; it produces Tapestry component reference and other information. 033 */ 034 public class TapestryDocTaglet implements Taglet, ClassDescriptionSource 035 { 036 /** 037 * Map from class name to class description. 038 */ 039 private final Map<String, ClassDescription> classDescriptions = CollectionFactory.newMap(); 040 041 private ClassDoc firstSeen; 042 043 private static final String NAME = "tapestrydoc"; 044 045 @SuppressWarnings("unchecked") 046 public static void register(Map paramMap) 047 { 048 paramMap.put(NAME, new TapestryDocTaglet()); 049 } 050 051 public boolean inField() 052 { 053 return false; 054 } 055 056 public boolean inConstructor() 057 { 058 return false; 059 } 060 061 public boolean inMethod() 062 { 063 return false; 064 } 065 066 public boolean inOverview() 067 { 068 return false; 069 } 070 071 public boolean inPackage() 072 { 073 return false; 074 } 075 076 public boolean inType() 077 { 078 return true; 079 } 080 081 public boolean isInlineTag() 082 { 083 return false; 084 } 085 086 public String getName() 087 { 088 return NAME; 089 } 090 091 public ClassDescription getDescription(String className) 092 { 093 ClassDescription result = classDescriptions.get(className); 094 095 if (result == null) 096 { 097 // System.err.printf("*** Search for CD %s ...\n", className); 098 099 ClassDoc cd = firstSeen.findClass(className); 100 101 // System.err.printf("CD %s ... %s\n", className, cd == null ? "NOT found" : "found"); 102 103 result = cd == null ? new ClassDescription() : new ClassDescription(cd, this); 104 105 classDescriptions.put(className, result); 106 } 107 108 return result; 109 } 110 111 public String toString(Tag tag) 112 { 113 throw new IllegalStateException("toString(Tag) should not be called for a non-inline tag."); 114 } 115 116 public String toString(Tag[] tags) 117 { 118 if (tags.length == 0) 119 return null; 120 121 // This should only be invoked with 0 or 1 tags. I suppose someone could put @tapestrydoc in the comment block 122 // more than once. 123 124 Tag tag = tags[0]; 125 126 try 127 { 128 StringWriter writer = new StringWriter(5000); 129 130 ClassDoc classDoc = (ClassDoc) tag.holder(); 131 132 if (firstSeen == null) 133 firstSeen = classDoc; 134 135 ClassDescription cd = getDescription(classDoc.qualifiedName()); 136 137 writeClassDescription(cd, writer); 138 139 streamXdoc(classDoc, writer); 140 141 return writer.toString(); 142 } 143 catch (Exception ex) 144 { 145 System.err.println(ex); 146 System.exit(-1); 147 148 return null; // unreachable 149 } 150 } 151 152 private void element(Writer writer, String elementName, String text) throws IOException 153 { 154 writer.write(String.format("<%s>%s</%1$s>", elementName, text)); 155 } 156 157 private void writeClassDescription(ClassDescription cd, Writer writer) throws IOException 158 { 159 writeParameters(cd, writer); 160 161 writeEvents(cd, writer); 162 } 163 164 private void writeParameters(ClassDescription cd, Writer writer) throws IOException 165 { 166 if (cd.parameters.isEmpty()) 167 return; 168 169 writer.write("<dt><b>Parameters:</b></dt><dd>"); 170 171 writer.write("<table border='1' cellpadding='3' cellspacing='0'>" 172 + "<tr><th>Name</th><th>Type</th><th>Flags</th><th>Default</th><th>Default Prefix</th><th>Since</th><th>Description</th></tr>"); 173 174 for (String name : InternalUtils.sortedKeys(cd.parameters)) 175 { 176 ParameterDescription pd = cd.parameters.get(name); 177 178 writerParameter(pd, writer); 179 } 180 181 writer.write("</table></dd>"); 182 } 183 184 private void writerParameter(ParameterDescription pd, Writer writer) throws IOException 185 { 186 writer.write("<tr>"); 187 188 element(writer, "td", pd.name); 189 element(writer, "td", pd.type); 190 191 List<String> flags = CollectionFactory.newList(); 192 193 if (pd.required) 194 flags.add("Required"); 195 196 if (!pd.cache) 197 flags.add("Not Cached"); 198 199 if (!pd.allowNull) 200 flags.add("Not Null"); 201 202 element(writer, "td", InternalUtils.join(flags)); 203 element(writer, "td", pd.defaultValue); 204 element(writer, "td", pd.defaultPrefix); 205 element(writer, "td", pd.since); 206 207 writer.write("<td>"); 208 209 pd.writeDescription(writer); 210 211 writer.write("</td></tr>"); 212 } 213 214 private void writeEvents(ClassDescription cd, Writer writer) throws IOException 215 { 216 if (cd.events.isEmpty()) 217 return; 218 219 writer.write("<dt><b>Events:</b></dt><dd><dl>"); 220 221 for (String name : InternalUtils.sortedKeys(cd.events)) 222 { 223 element(writer, "dt", name); 224 225 String value = cd.events.get(name); 226 227 if (value.length() > 0) 228 { 229 element(writer, "dd", value); 230 } 231 } 232 233 writer.write("</dl></dd>"); 234 } 235 236 private void streamXdoc(ClassDoc classDoc, Writer writer) throws Exception 237 { 238 File sourceFile = classDoc.position().file(); 239 240 // The .xdoc file will be adjacent to the sourceFile 241 242 String sourceName = sourceFile.getName(); 243 244 String xdocName = sourceName.replaceAll("\\.java$", ".xdoc"); 245 246 File xdocFile = new File(sourceFile.getParentFile(), xdocName); 247 248 if (xdocFile.exists()) 249 { 250 try 251 { 252 // Close the definition list, to avoid unwanted indents. Very, very ugly. 253 254 new XDocStreamer(xdocFile, writer).writeContent(); 255 // Open a new (empty) definition list, that HtmlDoclet will close. 256 } 257 catch (Exception ex) 258 { 259 System.err.println("Error streaming XDOC content for " + classDoc); 260 throw ex; 261 } 262 } 263 } 264 }