001/*
002 * Copyright (c) 2009-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: crawl $'
006 * '$Date: 2015-08-24 22:45:41 +0000 (Mon, 24 Aug 2015) $' 
007 * '$Revision: 33631 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.kepler.kar;
031
032import java.util.HashMap;
033import java.util.HashSet;
034import java.util.Map;
035import java.util.Set;
036
037import com.sun.javadoc.ClassDoc;
038import com.sun.javadoc.Doc;
039import com.sun.javadoc.FieldDoc;
040import com.sun.javadoc.RootDoc;
041import com.sun.javadoc.Tag;
042import com.sun.javadoc.Type;
043
044import ptolemy.kernel.util.IllegalActionException;
045import ptolemy.kernel.util.NameDuplicationException;
046import ptolemy.kernel.util.Workspace;
047import ptolemy.vergil.basic.KeplerDocumentationAttribute;
048
049/**
050 * A doclet that creates documentation for KAR files.
051 * 
052 * @author Daniel Crawl
053 * @version $Id: KarDoclet.java 33631 2015-08-24 22:45:41Z crawl $
054 */
055
056public class KarDoclet {
057        /** The entry point called from javadoc. */
058        public static boolean start(RootDoc root) {
059                _table.clear();
060
061                try {
062                        ClassDoc[] classes = root.classes();
063                        for (int i = 0; i < classes.length; i++) {
064                            // make sure this is not deprecated
065                            if(classes[i].tags("@deprecated").length > 0) {
066                                if(!_generateForDeprecated) {
067                                    System.out.println("WARNING: skipping " + classes[i].qualifiedName() + " because it is deprecated.");
068                                    continue;
069                                }
070                                _deprecated.add(classes[i].qualifiedName());
071                            }
072                            
073                                // javadoc returns inner classes in RootDoc.classes()
074                                // make sure the current class is not an inner class
075                                // NOTE: this assumes we've already parsed the parent
076                                // class and put it in _table.
077                                String name = classes[i].qualifiedName();
078                                name = name.substring(0, name.lastIndexOf("."));
079                                if (_table.get(name) == null) {
080                                        // _out("going to parse class: " + classes[i]);
081                                        _parseClass(classes[i], true);
082                                        // _out("-------");
083                                        // _out(_doc.exportMoML());
084                                        // _out("-------");
085                                } else {
086                                        // _out("skipping inner class: " + classes[i]);
087                                }
088                        }
089                } catch (Exception e) {
090                        System.out.println("ERROR " + e.getClass() + ": " + e.getMessage());
091                        e.printStackTrace();
092                }
093                return true;
094        }
095
096        /**
097         * Retrieve the documentation for a particular class after it has been
098         * created.
099         */
100        public static KeplerDocumentationAttribute getDoc(String name) {
101                return _table.get(name);
102        }
103
104        /** Retrieve all the documentation. */
105        public static Map<String,KeplerDocumentationAttribute> getAllDocs() {
106                return _table;
107        }
108        
109        /** Returns true if the given class name is deprecated.
110         *  NOTE: this method does not look at the tags of the class;
111         *  the doclet must be run on the source file first.
112         */
113        public static boolean isClassDeprecated(String className) {
114            return _deprecated.contains(className);
115        }
116        
117        /** Set if documentation will be generated for deprecated classes. */
118        public static void setGenerateForDeprecated(boolean generate) {
119            _generateForDeprecated = generate;
120        }
121        
122        public static void setWorkspace(Workspace workspace) {
123            _workspace = workspace;
124        }
125
126        /** Create documentation for a class. */
127        private static void _parseClass(ClassDoc classDoc, boolean printHeader)
128                        throws IllegalActionException, NameDuplicationException, Exception {
129                // _out("");
130                // _out("Class: " + classDoc);
131                // _out("commentText: " + classDoc.commentText());
132
133                //String name = classDoc.name();
134
135                if (printHeader) {
136                        // _out("parsing for " + name);
137
138                    if(_workspace == null) {
139                        _workspace = new Workspace();
140                    }
141                    
142                        _doc = new KeplerDocumentationAttribute(_workspace);
143                        _doc.setName("KeplerDocumentation");
144
145                        _table.put(classDoc.qualifiedName(), _doc);
146
147                        // add docs for the authors
148                        String authors = _getAndCombineTags("@author", classDoc);
149                        if (authors.length() == 0) {
150                                _warn("No authors for " + classDoc.qualifiedName());
151                        } else {
152                                _doc.setAuthor(authors);
153                        }
154
155                        // add docs for the class
156                        String comment = classDoc.commentText();
157                        if (comment.length() == 0) {
158                                _warn("No comments for class " + classDoc.qualifiedName());
159                        } else {
160                                _doc.setUserLevelDocumentation("\n" + comment);
161                        }
162
163                        // add docs for the version
164                        String version = _getAndCombineTags("@version", classDoc);
165                        if(version.length() == 0) {
166                            _warn("No version for " + classDoc.qualifiedName());
167                        } else {
168                            // if the first character is $, remove everything after the second $
169                            version = version.trim();
170                            if(version.charAt(0) == '$') {
171                                final int index = version.indexOf('$', 1);
172                                if(index > 1) {
173                                    version = version.substring(0, index+1);
174                                }
175                            }
176                            _doc.setVersion(version); 
177                        }
178                }
179
180                // add docs for all the fields
181                FieldDoc[] fields = classDoc.fields();
182                for (int i = 0; i < fields.length; i++) {
183                        FieldDoc curField = fields[i];
184                        Type type = curField.type();
185
186                        // add docs for a field if it's public, and a class
187                        if (curField.isPublic() && !type.isPrimitive()) {
188                                _parseField(curField);
189                        }
190                }
191
192        // recursively add documentation of the parent class
193        ClassDoc superClass = classDoc.superclass();
194        if (superClass != null) {
195            if (!superClass.toString().equals("ptolemy.actor.TypedAtomicActor")
196                    && !superClass.toString().equals(
197                            "ptolemy.actor.TypedCompositeActor")) {
198                // _out("recursing super class " + superClass.qualifiedName());
199                _parseClass(superClass, false);
200            }
201        }
202    }
203
204    /** Return the FieldType of the field. */
205        private static FieldType _getFieldType(String className)
206                        throws ClassNotFoundException {
207                // _out("check field:" + className);
208
209                if (className.equals("ptolemy.actor.TypedIOPort")) {
210                        return FieldType.Port;
211                } else if (className.equals("ptolemy.actor.parameters.PortParameter")) {
212                    return FieldType.PortParameter;
213                } else if (className.equals("ptolemy.data.expr.Parameter") ||
214                        className.equals("ptolemy.kernel.util.StringAttribute")) {
215                        return FieldType.Parameter;
216                } else if (className.equals("java.lang.Object")) {
217                        return FieldType.Unknown;
218                } else {
219                        String superClsStr = null;
220                        Class<?> cls = null;
221                        try {
222                                cls = Class.forName(className);
223                        } catch(ClassNotFoundException e) {
224                                // try converting to a nested class
225                                String nestedName = new StringBuilder(className).replace(
226                                                className.lastIndexOf('.'),
227                                                className.lastIndexOf('.') + 1, "$").toString();
228                                cls = Class.forName(nestedName);
229                        }
230                        if(cls != null) {
231                                if(cls.isInterface()) {
232                                        return FieldType.Ignore;
233                                }
234                                Class<?> superCls = cls.getSuperclass();
235                                if(superCls != null) {
236                                        superClsStr = superCls.toString();
237                                }
238                        }
239        
240                        if (superClsStr == null) {
241                                _warn("could not determine super class for: " + className);
242                                return FieldType.Unknown;
243                        } else if(superClsStr.indexOf("class ") != 0) {
244                                _warn("super class name does not begin with class: " + superClsStr);
245                                return FieldType.Unknown;
246                        } else {
247                                return _getFieldType(superClsStr.substring(6));
248                        }
249                }
250        }
251
252        /** Create documentation for a port or parameter. */
253        private static void _parseField(FieldDoc fieldDoc)
254                        throws IllegalActionException, NameDuplicationException, Exception {
255                // _out("");
256                // _out("field: " + fieldDoc);
257                // _out("type: " + fieldDoc.type());
258                // _out("commentText: " + fieldDoc.commentText().replaceAll("\n", ""));
259
260                String fullName = fieldDoc.qualifiedName();
261                String name = fieldDoc.name();
262
263                FieldType type = _getFieldType(fieldDoc.type().asClassDoc()
264                                .qualifiedName());
265
266                if (type == FieldType.Unknown) {
267                        //_out("skipping unknown type of field " + fullName +
268                            //" type: " + fieldDoc.type());
269                } else if (type != FieldType.Ignore) {
270                    boolean unhandled = true;
271                        String comment = fieldDoc.commentText().replaceAll("\n", "");
272                        if (comment.length() == 0) {
273                                _warn("No comments for " + type.getName() + " " + fullName);
274                        }
275                        
276                        if (type == FieldType.Port || type == FieldType.PortParameter) {
277                                _doc.addPort(name, comment);
278                                unhandled = false;
279                        }
280                        
281                        if (type == FieldType.Parameter || type == FieldType.PortParameter) {
282                                _doc.addProperty(name, comment);
283                                unhandled = false;
284                        } 
285                        
286                        if(unhandled) {
287                                _warn("Unhandled field type: " + type);
288                        }
289                }
290        }
291
292        private static String _getAndCombineTags(String tag, Doc doc) {
293                String retval = "";
294                Tag[] tags = doc.tags(tag);
295                if (tags != null && tags.length > 0) {
296                        StringBuffer buf = new StringBuffer();
297                        for (int i = 0; i < tags.length; i++) {
298                                buf.append(tags[i].text() + ", ");
299                        }
300
301                        retval = buf.toString();
302                        // remove last comma
303                        retval = retval.replaceAll("\\n", "");
304                        retval = retval.replaceAll("\\s*, $", "");
305                }
306                return retval;
307        }
308
309        private static void _out(String str) {
310                System.out.println(str);
311        }
312
313        private static void _warn(String str) {
314                _out("WARNING: " + str);
315        }
316        
317        /** A mapping of class name to documentation. */
318        private static Map<String, KeplerDocumentationAttribute> _table = 
319        new HashMap<String,KeplerDocumentationAttribute>();
320
321        /** The current doc. */
322        private static KeplerDocumentationAttribute _doc;
323
324        /** The types of fields. */
325        private enum FieldType {
326        Parameter("parameter"),
327        Port("port"),
328        PortParameter("port parameter"),
329        Unknown("unknown"),
330        Ignore("ignore");
331        
332        FieldType(String name) {
333            _name = name;
334        }
335        
336        public String getName() {
337            return _name;
338        }
339        
340        private String _name;
341        };
342        
343        private static Workspace _workspace;
344        
345        /** If false, do not generate documentation for deprecated classes. */
346        private static boolean _generateForDeprecated = true;
347        
348        /** A collection of deprecated classes. */
349        private static Set<String> _deprecated = new HashSet<String>();
350}