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("</dl>" 170 + "<table width='100%' cellspacing='0' cellpadding='3' border='1' class='parameters'>" 171 + "<thead><tr class='TableHeadingColor' bgcolor='#CCCCFF'>" 172 + "<th align='left' colspan='7'>" 173 + "<font size='+2'><b>Component Parameters</b></font>" 174 + "</th></tr>" 175 + "<tr class='columnHeaders'>" 176 + "<th>Name</th><th>Description</th><th>Type</th><th>Flags</th><th>Default</th>" 177 + "<th>Default Prefix</th><th>Since</th>" 178 + "</tr></thead><tbody>"); 179 180 for (String name : InternalUtils.sortedKeys(cd.parameters)) 181 { 182 ParameterDescription pd = cd.parameters.get(name); 183 184 writerParameter(pd, writer); 185 } 186 187 writer.write("</tbody></table></dd>"); 188 } 189 190 private void writerParameter(ParameterDescription pd, Writer writer) throws IOException 191 { 192 writer.write("<tr>"); 193 194 element(writer, "th", pd.name); 195 196 writer.write("<td>"); 197 pd.writeDescription(writer); 198 writer.write("</td>"); 199 200 element(writer, "td", addWordBreaks(shortenClassName(pd.type))); 201 202 List<String> flags = CollectionFactory.newList(); 203 204 if (pd.required) 205 flags.add("Required"); 206 207 if (!pd.cache) 208 flags.add("Not Cached"); 209 210 if (!pd.allowNull) 211 flags.add("Not Null"); 212 213 element(writer, "td", InternalUtils.join(flags)); 214 element(writer, "td", addWordBreaks(pd.defaultValue)); 215 element(writer, "td", pd.defaultPrefix); 216 element(writer, "td", pd.since); 217 218 writer.write("</tr>"); 219 } 220 221 private void writeEvents(ClassDescription cd, Writer writer) throws IOException 222 { 223 if (cd.events.isEmpty()) 224 return; 225 226 writer.write("<p><table width='100%' cellspacing='0' cellpadding='3' border='1' class='parameters'>" 227 + "<thead><tr class='TableHeadingColor' bgcolor='#CCCCFF'>" 228 + "<th align='left'>" 229 + "<font size='+2'><b>Events:</b></font></th></tr></thead></table></p><dl>"); 230 231 for (String name : InternalUtils.sortedKeys(cd.events)) 232 { 233 element(writer, "dt", name); 234 235 String value = cd.events.get(name); 236 237 if (value.length() > 0) 238 { 239 element(writer, "dd", value); 240 } 241 } 242 243 writer.write("</dl>"); 244 } 245 246 /** 247 * Insert a <wbr/> tag after each period and colon in the given string, to 248 * allow browsers to break words at those points. (Otherwise the Parameters 249 * tables are too wide.) 250 * 251 * @param words 252 * any string, possibly containing periods or colons 253 * @return the new string, possibly containing <wbr/> tags 254 */ 255 private String addWordBreaks(String words) 256 { 257 return words.replace(".", ".<wbr/>").replace(":", ":<wbr/>"); 258 } 259 260 /** 261 * Shorten the given class name by removing built-in Java packages 262 * (currently just java.lang) 263 * 264 * @param className name of class, with package 265 * @return potentially shorter class name 266 */ 267 private String shortenClassName(String name) 268 { 269 return name.replace("java.lang.", ""); 270 } 271 272 private void streamXdoc(ClassDoc classDoc, Writer writer) throws Exception 273 { 274 File sourceFile = classDoc.position().file(); 275 276 // The .xdoc file will be adjacent to the sourceFile 277 278 String sourceName = sourceFile.getName(); 279 280 String xdocName = sourceName.replaceAll("\\.java$", ".xdoc"); 281 282 File xdocFile = new File(sourceFile.getParentFile(), xdocName); 283 284 if (xdocFile.exists()) 285 { 286 try 287 { 288 // Close the definition list, to avoid unwanted indents. Very, very ugly. 289 290 new XDocStreamer(xdocFile, writer).writeContent(); 291 // Open a new (empty) definition list, that HtmlDoclet will close. 292 } 293 catch (Exception ex) 294 { 295 System.err.println("Error streaming XDOC content for " + classDoc); 296 throw ex; 297 } 298 } 299 } 300 }