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    }