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 }