001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.devtools.doclet;
017
018import com.sun.javadoc.AnnotationDesc;
019import com.sun.javadoc.ClassDoc;
020import com.sun.javadoc.MethodDoc;
021import com.sun.javadoc.ProgramElementDoc;
022import com.sun.javadoc.RootDoc;
023import com.sun.tools.javadoc.Main;
024
025import java.io.FileOutputStream;
026import java.io.IOException;
027import java.util.Arrays;
028import java.util.Collections;
029import java.util.Enumeration;
030import java.util.Properties;
031import java.util.Vector;
032
033/**
034 * A JavaDoc tools doclet class that generates a properties file containing information about
035 * any @BeanTag classes @BeanTagAttribute properties
036 *
037 * @author Kuali Rice Team (rice.collab@kuali.org)
038 */
039public class KRADLibraryPropertiesDoclet {
040
041    // TODO : remove hard coded arguments
042    public static void main(String[] args) {
043        Main.execute(new String[]{"-doclet", "org.kuali.rice.krad.demo.uif.library.tools.KRADLibraryPropertiesDoclet",
044                "-sourcepath", "C:/Java/Projects/Rice/Trunk/krad/krad-web-framework/src/main/java", "-subpackages",
045                "org.kuali.rice.krad.uif:org.kuali.rice.krad.datadictionary.validation.constraint"});
046    }
047
048    /*
049     * The method that will be called by the JavaDoc tool when executing this doclet
050     *
051     * @param root - the RootDoc containing all the class information as specified by the javadoc call arguments
052     * @return boolean
053     */
054    public static boolean start(RootDoc root) {
055        storeToPropertyFile(root);
056        return true;
057    }
058
059    /*
060     * Writes all BeanTag properties to a propties file with their getter javadoc description
061     *
062     * @param root- the RootDoc containing all the class information as specified by the javadoc call arguments
063     */
064    private static void storeToPropertyFile(RootDoc root) {
065        ClassDoc[] classes = root.classes();
066        SortedProperties prop = new SortedProperties();
067
068        for (ClassDoc classDoc : classes) {
069
070            // Check only BeanTag classes
071            if (isAnnotatedWith(classDoc, "org.kuali.rice.krad.datadictionary.parse.BeanTag",
072                    "org.kuali.rice.krad.datadictionary.parse.BeanTags")) {
073
074                String className = classDoc.qualifiedName();
075                String classDescription = classDoc.commentText();
076                prop.setProperty(className, classDescription);
077
078                MethodDoc[] methods = classDoc.methods();
079
080                for (MethodDoc methodDoc : methods) {
081
082                    // Check only getters that i BeanTagAttribute annotated
083                    if (methodDoc.parameters().length == 0 &&
084                            (methodDoc.name().startsWith("get") || methodDoc.name().startsWith("is")) &&
085                            !(methodDoc.name().equals("get") || methodDoc.name().equals("is")) &&
086                            isAnnotatedWith(methodDoc, "org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute")) {
087                        String methodName;
088
089                        if (methodDoc.name().startsWith("get")) {
090                            methodName = methodDoc.name().replaceFirst("get", "");
091                        } else {
092                            methodName = methodDoc.name().replaceFirst("is", "");
093                        }
094                        String propertyName = Character.toLowerCase(methodName.charAt(0)) + (methodName.length() > 1 ?
095                                methodName.substring(1) : "");
096                        String propertyType = methodDoc.returnType().typeName();
097                        String propertyDescription = getDocText(methodDoc, root);
098
099                        prop.setProperty(className + "|" + propertyName + "|" + propertyType, propertyDescription);
100                    }
101                }
102            }
103        }
104
105        // TODO : remove hard coding of filepath
106        try {
107            prop.store(new FileOutputStream("C:/Java/Projects/Rice/Trunk/sampleapp/src/main/resources/"
108                    + "org/kuali/rice/devtools/krad/documentation/PropertiesDescription.properties"), null);
109        } catch (IOException e) {
110            e.printStackTrace();
111        }
112    }
113
114    /*
115     * Returns the javadoc text for a method, when it is a @see comment the referenced methods documentation will
116     * be returned
117     *
118     * @param method
119     * @return String
120     */
121    private static String getDocText(MethodDoc method, RootDoc root) {
122
123        if (method.commentText() != null && !method.commentText().equals("")) {
124            return method.commentText();
125        }
126
127        String docText = method.getRawCommentText();
128
129        if (docText != null && docText.contains("@see")) {
130            String classMethodName = docText.replace("@see", "").replace("\n", "").replace(" ", "");
131            String className = classMethodName.substring(0, classMethodName.indexOf("#"));
132            String methodName = classMethodName.substring(classMethodName.indexOf("#") + 1).replace("()", "");
133            ClassDoc classDoc = root.classNamed(className);
134
135            if (classDoc == null) {
136                System.err.println("warning - Comment on "
137                        + method.toString()
138                        + " does not have valid fully qualified "
139                        + "method in @see annotation.\n"
140                        + docText);
141                return "";
142            }
143            MethodDoc methodDoc = getNoParamMethodFromClassDocByName(classDoc, methodName);
144            return methodDoc.commentText();
145        }
146
147        return "";
148    }
149
150    /*
151     * Finds the no arguments method's MethodDoc on a class with a specified name
152     *
153     * @param classDoc
154     * @param methodName
155     * @return MethodDoc
156     */
157    private static MethodDoc getNoParamMethodFromClassDocByName(ClassDoc classDoc, String methodName) {
158        MethodDoc[] methods = classDoc.methods();
159
160        for (MethodDoc methodDoc : methods) {
161
162            if (methodDoc.name().equals(methodName) && methodDoc.parameters().length == 0) {
163                return methodDoc;
164            }
165        }
166        return null;
167    }
168
169    /*
170     * Checks if a specific ClassDoc or MethodDoc is annotated with the specified annotations
171     *
172     * @param elementDoc
173     * @param tagString
174     * @return
175     */
176    private static boolean isAnnotatedWith(ProgramElementDoc elementDoc, String... tagString) {
177        AnnotationDesc[] annotations = elementDoc.annotations();
178
179        for (AnnotationDesc annotation : annotations) {
180
181            if (Arrays.asList(tagString).contains(annotation.annotationType().toString())) {
182                return true;
183            }
184        }
185
186        return false;
187    }
188
189    /*
190     * This class keeps properties sorted in order to make the file easier to read
191     */
192    static class SortedProperties extends Properties {
193
194        public Enumeration keys() {
195            Enumeration keysEnum = super.keys();
196            Vector<String> keyList = new Vector<String>();
197
198            while (keysEnum.hasMoreElements()) {
199                keyList.add((String) keysEnum.nextElement());
200            }
201            Collections.sort(keyList);
202            return keyList.elements();
203        }
204    }
205
206}