001    /**
002     * Copyright (c) 2000-2011 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.tools;
016    
017    import com.liferay.portal.kernel.io.unsync.UnsyncStringReader;
018    import com.liferay.portal.kernel.util.GetterUtil;
019    import com.liferay.portal.kernel.util.StringBundler;
020    import com.liferay.portal.kernel.util.StringPool;
021    import com.liferay.portal.kernel.util.StringUtil;
022    import com.liferay.portal.kernel.util.Tuple;
023    import com.liferay.portal.kernel.util.Validator;
024    import com.liferay.portal.kernel.xml.Document;
025    import com.liferay.portal.kernel.xml.Element;
026    import com.liferay.portal.tools.servicebuilder.ServiceBuilder;
027    import com.liferay.portal.util.FileImpl;
028    import com.liferay.portal.xml.SAXReaderImpl;
029    import com.liferay.util.xml.DocUtil;
030    
031    import com.thoughtworks.qdox.JavaDocBuilder;
032    import com.thoughtworks.qdox.model.AbstractBaseJavaEntity;
033    import com.thoughtworks.qdox.model.AbstractJavaEntity;
034    import com.thoughtworks.qdox.model.Annotation;
035    import com.thoughtworks.qdox.model.DocletTag;
036    import com.thoughtworks.qdox.model.JavaClass;
037    import com.thoughtworks.qdox.model.JavaField;
038    import com.thoughtworks.qdox.model.JavaMethod;
039    import com.thoughtworks.qdox.model.JavaPackage;
040    import com.thoughtworks.qdox.model.JavaParameter;
041    import com.thoughtworks.qdox.model.Type;
042    
043    import jargs.gnu.CmdLineParser;
044    
045    import java.io.File;
046    import java.io.FileInputStream;
047    import java.io.InputStream;
048    import java.io.Reader;
049    
050    import java.util.ArrayList;
051    import java.util.Collection;
052    import java.util.HashMap;
053    import java.util.HashSet;
054    import java.util.List;
055    import java.util.Map;
056    import java.util.Set;
057    import java.util.TreeMap;
058    import java.util.regex.Matcher;
059    import java.util.regex.Pattern;
060    
061    import org.apache.tools.ant.DirectoryScanner;
062    
063    /**
064     * @author Brian Wing Shun Chan
065     * @author Connor McKay
066     * @author James Hinkey
067     */
068    public class JavadocFormatter {
069    
070            public static void main(String[] args) {
071                    try {
072                            new JavadocFormatter(args);
073                    }
074                    catch (Exception e) {
075                            e.printStackTrace();
076                    }
077            }
078    
079            public JavadocFormatter(String[] args) throws Exception {
080                    CmdLineParser cmdLineParser = new CmdLineParser();
081    
082                    CmdLineParser.Option limitOption = cmdLineParser.addStringOption(
083                            "limit");
084                    CmdLineParser.Option initOption = cmdLineParser.addStringOption(
085                            "init");
086                    CmdLineParser.Option updateOption = cmdLineParser.addStringOption(
087                            "update");
088    
089                    cmdLineParser.parse(args);
090    
091                    String limit = (String)cmdLineParser.getOptionValue(limitOption);
092                    String init = (String)cmdLineParser.getOptionValue(initOption);
093                    String update = (String)cmdLineParser.getOptionValue(updateOption);
094    
095                    if (Validator.isNotNull(init) && !init.startsWith("$")) {
096                            _initializeMissingJavadocs = GetterUtil.getBoolean(init);
097                    }
098    
099                    if (Validator.isNotNull(update) && !update.startsWith("$")) {
100                            _updateJavadocs = GetterUtil.getBoolean(update);
101                    }
102    
103                    DirectoryScanner ds = new DirectoryScanner();
104    
105                    ds.setBasedir(_basedir);
106                    ds.setExcludes(
107                            new String[] {"**\\classes\\**", "**\\portal-client\\**"});
108    
109                    List<String> includes = new ArrayList<String>();
110    
111                    if (Validator.isNotNull(limit) && !limit.startsWith("$")) {
112                            System.out.println("Limit on " + limit);
113    
114                            String[] limitArray = StringUtil.split(limit, '/');
115    
116                            for (String curLimit : limitArray) {
117                                    includes.add(
118                                            "**\\" + StringUtil.replace(curLimit, ".", "\\") +
119                                                    "\\**\\*.java");
120                                    includes.add("**\\" + curLimit + ".java");
121                            }
122                    }
123                    else {
124                            includes.add("**\\*.java");
125                    }
126    
127                    ds.setIncludes(includes.toArray(new String[includes.size()]));
128    
129                    ds.scan();
130    
131                    String[] fileNames = ds.getIncludedFiles();
132    
133                    if ((fileNames.length == 0) && Validator.isNotNull(limit) &&
134                            !limit.startsWith("$")) {
135    
136                            StringBundler sb = new StringBundler("Limit file not found: ");
137    
138                            sb.append(limit);
139    
140                            if (limit.contains(".")) {
141                                    sb.append(" Specify limit filename without package path or ");
142                                    sb.append("file type suffix.");
143                            }
144    
145                            System.out.println(sb.toString());
146                    }
147    
148                    for (String fileName : fileNames) {
149                            fileName = StringUtil.replace(fileName, "\\", "/");
150    
151                            _format(fileName);
152                    }
153    
154                    for (Map.Entry<String, Tuple> entry : _javadocxXmlTuples.entrySet()) {
155                            Tuple javadocsXmlTuple = entry.getValue();
156    
157                            File javadocsXmlFile = (File)javadocsXmlTuple.getObject(1);
158                            String oldJavadocsXmlContent =
159                                    (String)javadocsXmlTuple.getObject(2);
160                            Document javadocsXmlDocument =
161                                    (Document)javadocsXmlTuple.getObject(3);
162    
163                            Element javadocsXmlRootElement =
164                                    javadocsXmlDocument.getRootElement();
165    
166                            javadocsXmlRootElement.sortElementsByChildElement(
167                                    "javadoc", "type");
168    
169                            String newJavadocsXmlContent =
170                                    javadocsXmlDocument.formattedString();
171    
172                            if (!oldJavadocsXmlContent.equals(newJavadocsXmlContent)) {
173                                    _fileUtil.write(javadocsXmlFile, newJavadocsXmlContent);
174                            }
175                    }
176            }
177    
178            private void _addClassCommentElement(
179                    Element rootElement, JavaClass javaClass) {
180    
181                    Element commentElement = rootElement.addElement("comment");
182    
183                    String comment = _getCDATA(javaClass);
184    
185                    if (comment.startsWith("Copyright (c) 2000-2010 Liferay, Inc.")) {
186                            comment = StringPool.BLANK;
187                    }
188    
189                    commentElement.addCDATA(comment);
190            }
191    
192            private void _addDocletElements(
193                            Element parentElement, AbstractJavaEntity abstractJavaEntity,
194                            String name)
195                    throws Exception {
196    
197                    DocletTag[] docletTags = abstractJavaEntity.getTagsByName(name);
198    
199                    for (DocletTag docletTag : docletTags) {
200                            String value = docletTag.getValue();
201    
202                            value = _trimMultilineText(value);
203    
204                            value = StringUtil.replace(value, " </", "</");
205    
206                            Element element = parentElement.addElement(name);
207    
208                            element.addCDATA(value);
209                    }
210    
211                    if ((docletTags.length == 0) && name.equals("author")) {
212                            Element element = parentElement.addElement(name);
213    
214                            element.addCDATA(ServiceBuilder.AUTHOR);
215                    }
216            }
217    
218            private String _addDocletTags(
219                    Element parentElement, String[] names, String indent,
220                    boolean publicAccess) {
221    
222                    StringBundler sb = new StringBundler();
223    
224                    int maxNameLength = 0;
225    
226                    for (String name : names) {
227                            if (name.length() < maxNameLength) {
228                                    continue;
229                            }
230    
231                            List<Element> elements = parentElement.elements(name);
232    
233                            for (Element element : elements) {
234                                    Element commentElement = element.element("comment");
235    
236                                    String comment = null;
237    
238                                    if (commentElement != null) {
239                                            comment = commentElement.getText();
240                                    }
241                                    else {
242                                            comment = element.getText();
243                                    }
244    
245                                    if (!name.equals("deprecated") && !_initializeMissingJavadocs &&
246                                            Validator.isNull(comment)) {
247    
248                                            continue;
249                                    }
250    
251                                    maxNameLength = name.length();
252    
253                                    break;
254                            }
255                    }
256    
257                    // There should be one space after the name and an @ before it
258    
259                    maxNameLength += 2;
260    
261                    String nameIndent = _getSpacesIndent(maxNameLength);
262    
263                    boolean hasComments = false;
264    
265                    for (String name : names) {
266                            List<Element> elements = parentElement.elements(name);
267    
268                            for (Element element : elements) {
269                                    Element commentElement = element.element("comment");
270    
271                                    String comment = null;
272    
273                                    if (commentElement != null) {
274                                            comment = commentElement.getText();
275                                    }
276                                    else {
277                                            comment = element.getText();
278                                    }
279    
280                                    if (Validator.isNotNull(comment)) {
281                                            hasComments = true;
282                                    }
283    
284                                    if (!name.equals("deprecated") && !_initializeMissingJavadocs &&
285                                            !_updateJavadocs && Validator.isNull(comment)) {
286    
287                                            continue;
288                                    }
289    
290                                    if (commentElement != null) {
291                                            String elementName = element.elementText("name");
292    
293                                            if (Validator.isNotNull(elementName)) {
294                                                    if (Validator.isNotNull(comment)) {
295                                                            comment = elementName + " " + comment;
296                                                    }
297                                                    else {
298                                                            comment = elementName;
299                                                    }
300                                            }
301                                    }
302    
303                                    if (Validator.isNull(comment)) {
304                                            sb.append(indent);
305                                            sb.append(StringPool.AT);
306                                            sb.append(name);
307                                            sb.append(StringPool.NEW_LINE);
308                                    }
309                                    else {
310                                            comment = _wrapText(comment, indent + nameIndent);
311    
312                                            String firstLine = indent + "@" + name;
313    
314                                            comment = firstLine + comment.substring(firstLine.length());
315    
316                                            sb.append(comment);
317                                    }
318                            }
319                    }
320    
321                    if (!publicAccess && !hasComments) {
322                            return null;
323                    }
324    
325                    if (!_initializeMissingJavadocs && !hasComments) {
326                            return null;
327                    }
328    
329                    return sb.toString();
330            }
331    
332            private void _addFieldElement(Element rootElement, JavaField javaField)
333                    throws Exception {
334    
335                    Element fieldElement = rootElement.addElement("field");
336    
337                    DocUtil.add(fieldElement, "name", javaField.getName());
338    
339                    Element commentElement = fieldElement.addElement("comment");
340    
341                    commentElement.addCDATA(_getCDATA(javaField));
342    
343                    _addDocletElements(fieldElement, javaField, "version");
344                    _addDocletElements(fieldElement, javaField, "see");
345                    _addDocletElements(fieldElement, javaField, "since");
346                    _addDocletElements(fieldElement, javaField, "deprecated");
347            }
348    
349            private void _addMethodElement(Element rootElement, JavaMethod javaMethod)
350                    throws Exception {
351    
352                    Element methodElement = rootElement.addElement("method");
353    
354                    DocUtil.add(methodElement, "name", javaMethod.getName());
355    
356                    Element commentElement = methodElement.addElement("comment");
357    
358                    commentElement.addCDATA(_getCDATA(javaMethod));
359    
360                    _addDocletElements(methodElement, javaMethod, "version");
361                    _addParamElements(methodElement, javaMethod);
362                    _addReturnElement(methodElement, javaMethod);
363                    _addThrowsElements(methodElement, javaMethod);
364                    _addDocletElements(methodElement, javaMethod, "see");
365                    _addDocletElements(methodElement, javaMethod, "since");
366                    _addDocletElements(methodElement, javaMethod, "deprecated");
367            }
368    
369            private void _addParamElement(
370                    Element methodElement, JavaParameter javaParameter,
371                    DocletTag[] paramDocletTags) {
372    
373                    String name = javaParameter.getName();
374    
375                    String value = null;
376    
377                    for (DocletTag paramDocletTag : paramDocletTags) {
378                            String curValue = paramDocletTag.getValue();
379    
380                            if (!curValue.startsWith(name)) {
381                                    continue;
382                            }
383                            else {
384                                    value = curValue;
385    
386                                    break;
387                            }
388                    }
389    
390                    Element paramElement = methodElement.addElement("param");
391    
392                    DocUtil.add(paramElement, "name", name);
393                    DocUtil.add(paramElement, "type", _getTypeValue(javaParameter));
394    
395                    if (value != null) {
396                            value = value.substring(name.length());
397                    }
398    
399                    value = _trimMultilineText(value);
400    
401                    Element commentElement = paramElement.addElement("comment");
402    
403                    commentElement.addCDATA(value);
404            }
405    
406            private void _addParamElements(
407                    Element methodElement, JavaMethod javaMethod) {
408    
409                    JavaParameter[] javaParameters = javaMethod.getParameters();
410    
411                    DocletTag[] paramDocletTags = javaMethod.getTagsByName("param");
412    
413                    for (JavaParameter javaParameter : javaParameters) {
414                            _addParamElement(methodElement, javaParameter, paramDocletTags);
415                    }
416            }
417    
418            private void _addReturnElement(
419                            Element methodElement, JavaMethod javaMethod)
420                    throws Exception {
421    
422                    Type returns = javaMethod.getReturns();
423    
424                    if (returns == null) {
425                            return;
426                    }
427    
428                    String returnsValue = returns.getValue();
429    
430                    if (returnsValue.equals("void")) {
431                            return;
432                    }
433    
434                    Element returnElement = methodElement.addElement("return");
435    
436                    Element commentElement = returnElement.addElement("comment");
437    
438                    DocletTag[] returnDocletTags = javaMethod.getTagsByName("return");
439    
440                    String comment = StringPool.BLANK;
441    
442                    if (returnDocletTags.length > 0) {
443                            DocletTag returnDocletTag = returnDocletTags[0];
444    
445                            comment = GetterUtil.getString(returnDocletTag.getValue());
446                    }
447    
448                    comment = _trimMultilineText(comment);
449    
450                    commentElement.addCDATA(comment);
451            }
452    
453            private void _addThrowsElement(
454                    Element methodElement, Type exceptionType,
455                    DocletTag[] throwsDocletTags) {
456    
457                    JavaClass javaClass = exceptionType.getJavaClass();
458    
459                    String name = javaClass.getName();
460    
461                    String value = null;
462    
463                    for (DocletTag throwsDocletTag : throwsDocletTags) {
464                            String curValue = throwsDocletTag.getValue();
465    
466                            if (!curValue.startsWith(name)) {
467                                    continue;
468                            }
469                            else {
470                                    value = curValue;
471    
472                                    break;
473                            }
474                    }
475    
476                    Element throwsElement = methodElement.addElement("throws");
477    
478                    DocUtil.add(throwsElement, "name", name);
479                    DocUtil.add(throwsElement, "type", exceptionType.getValue());
480    
481                    if (value != null) {
482                            value = value.substring(name.length());
483                    }
484    
485                    value = _trimMultilineText(value);
486    
487                    Element commentElement = throwsElement.addElement("comment");
488    
489                    commentElement.addCDATA(_getCDATA(value));
490    
491            }
492    
493            private void _addThrowsElements(
494                    Element methodElement, JavaMethod javaMethod) {
495    
496                    Type[] exceptionTypes = javaMethod.getExceptions();
497    
498                    DocletTag[] throwsDocletTags = javaMethod.getTagsByName("throws");
499    
500                    for (Type exceptionType : exceptionTypes) {
501                            _addThrowsElement(methodElement, exceptionType, throwsDocletTags);
502                    }
503            }
504    
505            private void _format(String fileName) throws Exception {
506                    InputStream inputStream = new FileInputStream(_basedir + fileName);
507    
508                    byte[] bytes = new byte[inputStream.available()];
509    
510                    inputStream.read(bytes);
511    
512                    inputStream.close();
513    
514                    String originalContent = new String(bytes, StringPool.UTF8);
515    
516                    if (fileName.endsWith("JavadocFormatter.java") ||
517                            fileName.endsWith("SourceFormatter.java") ||
518                            _isGenerated(originalContent)) {
519    
520                            return;
521                    }
522    
523                    JavaClass javaClass = _getJavaClass(
524                            fileName, new UnsyncStringReader(originalContent));
525    
526                    String javadocLessContent = _removeJavadocFromJava(
527                            javaClass, originalContent);
528    
529                    Document document = _getJavadocDocument(javaClass);
530    
531                    _updateJavadocsXmlFile(fileName, javaClass, document);
532    
533                    _updateJavaFromDocument(
534                            fileName, originalContent, javadocLessContent, document);
535            }
536    
537            private String _formatInlines(String text) {
538    
539                    // Capitalize ID
540    
541                    text = text.replaceAll("(?i)\\bid(s)?\\b", "ID$1");
542    
543                    // Wrap special constants in code tags
544    
545                    text = text.replaceAll(
546                            "(?i)(?<!<code>|\\w)(null|false|true)(?!\\w)", "<code>$1</code>");
547    
548                    return text;
549            }
550    
551            private List<JavaClass> _getAncestorJavaClasses(JavaClass javaClass) {
552                    List<JavaClass> ancestorJavaClasses = new ArrayList<JavaClass>();
553    
554                    while ((javaClass = javaClass.getSuperJavaClass()) != null) {
555                            ancestorJavaClasses.add(javaClass);
556                    }
557    
558                    return ancestorJavaClasses;
559            }
560    
561            private String _getCDATA(AbstractJavaEntity abstractJavaEntity) {
562                    return _getCDATA(abstractJavaEntity.getComment());
563            }
564    
565            private String _getCDATA(String cdata) {
566                    if (cdata == null) {
567                            return StringPool.BLANK;
568                    }
569    
570                    cdata = cdata.replaceAll(
571                            "(?s)\\s*<(p|pre|[ou]l)>\\s*(.*?)\\s*</\\1>\\s*",
572                            "\n\n<$1>\n$2\n</$1>\n\n");
573                    cdata = cdata.replaceAll(
574                            "(?s)\\s*<li>\\s*(.*?)\\s*</li>\\s*", "\n<li>\n$1\n</li>\n");
575                    cdata = StringUtil.replace(cdata, "</li>\n\n<li>", "</li>\n<li>");
576                    cdata = cdata.replaceAll("\n\\s+\n", "\n\n");
577                    cdata = cdata.replaceAll(" +", " ");
578    
579                    // Trim whitespace inside paragraph tags or in the first paragraph
580    
581                    Pattern pattern = Pattern.compile(
582                            "(^.*?(?=\n\n|$)+|(?<=<p>\n).*?(?=\n</p>))", Pattern.DOTALL);
583    
584                    Matcher matcher = pattern.matcher(cdata);
585    
586                    StringBuffer sb = new StringBuffer();
587    
588                    while (matcher.find()) {
589                            String trimmed = _trimMultilineText(matcher.group());
590    
591                            // Escape dollar signs so they are not treated as replacement groups
592    
593                            trimmed = trimmed.replaceAll("\\$", "\\\\\\$");
594    
595                            matcher.appendReplacement(sb, trimmed);
596                    }
597    
598                    matcher.appendTail(sb);
599    
600                    cdata = sb.toString();
601    
602                    return cdata.trim();
603            }
604    
605            private String _getClassName(String fileName) {
606                    int pos = fileName.indexOf("src/");
607    
608                    if (pos == -1) {
609                            pos = fileName.indexOf("test/");
610                    }
611    
612                    if (pos == -1) {
613                            pos = fileName.indexOf("service/");
614                    }
615    
616                    if (pos == -1) {
617                            throw new RuntimeException(fileName);
618                    }
619    
620                    pos = fileName.indexOf("/", pos);
621    
622                    String srcFile = fileName.substring(pos + 1, fileName.length());
623    
624                    return StringUtil.replace(
625                            srcFile.substring(0, srcFile.length() - 5), "/", ".");
626            }
627    
628            private String _getFieldKey(Element fieldElement) {
629                    return fieldElement.elementText("name");
630            }
631    
632            private String _getFieldKey(JavaField javaField) {
633                    return javaField.getName();
634            }
635    
636            private String _getIndent(
637                    String[] lines, AbstractBaseJavaEntity abstractBaseJavaEntity) {
638    
639                    String line = lines[abstractBaseJavaEntity.getLineNumber() - 1];
640    
641                    String indent = StringPool.BLANK;
642    
643                    for (char c : line.toCharArray()) {
644                            if (Character.isWhitespace(c)) {
645                                    indent += c;
646                            }
647                            else {
648                                    break;
649                            }
650                    }
651    
652                    return indent;
653            }
654    
655            private int _getIndentLength(String indent) {
656                    int indentLength = 0;
657    
658                    for (char c : indent.toCharArray()) {
659                            if (c == '\t') {
660                                    indentLength = indentLength + 4;
661                            }
662                            else {
663                                    indentLength++;
664                            }
665                    }
666    
667                    return indentLength;
668            }
669    
670            private JavaClass _getJavaClass(String fileName, Reader reader)
671                    throws Exception {
672    
673                    String className = _getClassName(fileName);
674    
675                    JavaDocBuilder javadocBuilder = new JavaDocBuilder();
676    
677                    if (reader == null) {
678                            File file = new File(fileName);
679    
680                            if (!file.exists()) {
681                                    return null;
682                            }
683    
684                            javadocBuilder.addSource(file);
685                    }
686                    else {
687                            javadocBuilder.addSource(reader);
688                    }
689    
690                    return javadocBuilder.getClassByName(className);
691            }
692    
693            private String _getJavaClassComment(
694                    Element rootElement, JavaClass javaClass) {
695    
696                    StringBundler sb = new StringBundler();
697    
698                    String indent = StringPool.BLANK;
699    
700                    sb.append("/**\n");
701    
702                    String comment = rootElement.elementText("comment");
703    
704                    if (_initializeMissingJavadocs || Validator.isNotNull(comment)) {
705                            sb.append(_wrapText(comment, indent + " * "));
706                    }
707    
708                    String docletTags = _addDocletTags(
709                            rootElement,
710                            new String[] {
711                                    "author", "version", "see", "since", "serial", "deprecated"
712                            },
713                            indent + " * ", _hasPublicModifier(javaClass));
714    
715                    if (Validator.isNotNull(docletTags)) {
716                            if (_initializeMissingJavadocs || Validator.isNotNull(comment)) {
717                                    sb.append(" *\n");
718                            }
719    
720                            sb.append(docletTags);
721                    }
722    
723                    sb.append(" */\n");
724    
725                    return sb.toString();
726            }
727    
728            private int _getJavaClassLineNumber(JavaClass javaClass) {
729                    int lineNumber = javaClass.getLineNumber();
730    
731                    Annotation[] annotations = javaClass.getAnnotations();
732    
733                    if (annotations.length == 0) {
734                            return lineNumber;
735                    }
736    
737                    for (Annotation annotation : annotations) {
738                            int annotationLineNumber = annotation.getLineNumber();
739    
740                            Map<String, String> propertyMap = annotation.getPropertyMap(); 
741    
742                            if (propertyMap.isEmpty()) {
743                                    annotationLineNumber--;
744                            }
745    
746                            if (annotationLineNumber < lineNumber) {
747                                    lineNumber = annotationLineNumber;
748                            }
749                    }
750    
751                    return lineNumber;
752            }
753    
754            private Document _getJavadocDocument(JavaClass javaClass) throws Exception {
755                    Element rootElement = _saxReaderUtil.createElement("javadoc");
756    
757                    Document document = _saxReaderUtil.createDocument(rootElement);
758    
759                    DocUtil.add(rootElement, "name", javaClass.getName());
760                    DocUtil.add(rootElement, "type", javaClass.getFullyQualifiedName());
761    
762                    _addClassCommentElement(rootElement, javaClass);
763                    _addDocletElements(rootElement, javaClass, "author");
764                    _addDocletElements(rootElement, javaClass, "version");
765                    _addDocletElements(rootElement, javaClass, "see");
766                    _addDocletElements(rootElement, javaClass, "since");
767                    _addDocletElements(rootElement, javaClass, "serial");
768                    _addDocletElements(rootElement, javaClass, "deprecated");
769    
770                    JavaMethod[] javaMethods = javaClass.getMethods();
771    
772                    for (JavaMethod javaMethod : javaMethods) {
773                            _addMethodElement(rootElement, javaMethod);
774                    }
775    
776                    JavaField[] javaFields = javaClass.getFields();
777    
778                    for (JavaField javaField : javaFields) {
779                            _addFieldElement(rootElement, javaField);
780                    }
781    
782                    return document;
783            }
784    
785            private Tuple _getJavadocsXmlTuple(String fileName) throws Exception {
786                    File file = new File(fileName);
787    
788                    String absolutePath = file.getAbsolutePath();
789    
790                    absolutePath = StringUtil.replace(absolutePath, "\\", "/");
791    
792                    int pos = absolutePath.indexOf("/portal-impl/src/");
793    
794                    String srcDirName = null;
795    
796                    if (pos != -1) {
797                            srcDirName = absolutePath.substring(0, pos + 17);
798                    }
799                    else {
800                            pos = absolutePath.indexOf("/WEB-INF/src/");
801    
802                            if (pos != -1) {
803                                    srcDirName = absolutePath.substring(0, pos + 13);
804                            }
805                    }
806    
807                    if (srcDirName == null) {
808                            return null;
809                    }
810    
811                    Tuple tuple = _javadocxXmlTuples.get(srcDirName);
812    
813                    if (tuple != null) {
814                            return tuple;
815                    }
816    
817                    File javadocsXmlFile = new File(srcDirName, "META-INF/javadocs.xml");
818    
819                    if (!javadocsXmlFile.exists()) {
820                            _fileUtil.write(
821                                    javadocsXmlFile,
822                                    "<?xml version=\"1.0\"?>\n\n<javadocs>\n</javadocs>");
823                    }
824    
825                    String javadocsXmlContent = _fileUtil.read(javadocsXmlFile);
826    
827                    Document javadocsXmlDocument = _saxReaderUtil.read(javadocsXmlContent);
828    
829                    tuple = new Tuple(
830                            srcDirName, javadocsXmlFile, javadocsXmlContent,
831                            javadocsXmlDocument);
832    
833                    _javadocxXmlTuples.put(srcDirName, tuple);
834    
835                    return tuple;
836            }
837    
838            private String _getJavaFieldComment(
839                    String[] lines, Map<String, Element> fieldElementsMap,
840                    JavaField javaField) {
841    
842                    String fieldKey = _getFieldKey(javaField);
843    
844                    Element fieldElement = fieldElementsMap.get(fieldKey);
845    
846                    if (fieldElement == null) {
847                            return null;
848                    }
849    
850                    String indent = _getIndent(lines, javaField);
851    
852                    StringBundler sb = new StringBundler();
853    
854                    sb.append(indent);
855                    sb.append("/**\n");
856    
857                    String comment = fieldElement.elementText("comment");
858    
859                    if (_initializeMissingJavadocs || Validator.isNotNull(comment)) {
860                            sb.append(_wrapText(comment, indent + " * "));
861                    }
862    
863                    String docletTags = _addDocletTags(
864                            fieldElement,
865                            new String[] {"version", "see", "since", "deprecated"},
866                            indent + " * ", _hasPublicModifier(javaField));
867    
868                    if (Validator.isNotNull(docletTags)) {
869                            if (_initializeMissingJavadocs || Validator.isNotNull(comment)) {
870                                    sb.append(indent);
871                                    sb.append(" *\n");
872                            }
873    
874                            sb.append(docletTags);
875                    }
876    
877                    sb.append(indent);
878                    sb.append(" */\n");
879    
880                    if (!_initializeMissingJavadocs && Validator.isNull(comment) &&
881                            Validator.isNull(docletTags)) {
882    
883                            return null;
884                    }
885    
886                    if (!_hasPublicModifier(javaField) && Validator.isNull(comment) &&
887                            Validator.isNull(docletTags)) {
888    
889                            return null;
890                    }
891    
892                    return sb.toString();
893            }
894    
895            private String _getJavaMethodComment(
896                    String[] lines, Map<String, Element> methodElementsMap,
897                    JavaMethod javaMethod) {
898    
899                    String methodKey = _getMethodKey(javaMethod);
900    
901                    Element methodElement = methodElementsMap.get(methodKey);
902    
903                    if (methodElement == null) {
904                            return null;
905                    }
906    
907                    String indent = _getIndent(lines, javaMethod);
908    
909                    StringBundler sb = new StringBundler();
910    
911                    sb.append(indent);
912                    sb.append("/**\n");
913    
914                    String comment = methodElement.elementText("comment");
915    
916                    if (_initializeMissingJavadocs || Validator.isNotNull(comment)) {
917                            sb.append(_wrapText(comment, indent + " * "));
918                    }
919    
920                    String docletTags = _addDocletTags(
921                            methodElement,
922                            new String[] {
923                                    "version", "param", "return", "throws", "see", "since",
924                                    "deprecated"
925                            },
926                            indent + " * ", _hasPublicModifier(javaMethod));
927    
928                    if (Validator.isNotNull(docletTags)) {
929                            if (_initializeMissingJavadocs || Validator.isNotNull(comment)) {
930                                    sb.append(indent);
931                                    sb.append(" *\n");
932                            }
933    
934                            sb.append(docletTags);
935                    }
936    
937                    sb.append(indent);
938                    sb.append(" */\n");
939    
940                    if (!_initializeMissingJavadocs && Validator.isNull(comment) &&
941                            Validator.isNull(docletTags)) {
942    
943                            return null;
944                    }
945    
946                    if (!_hasPublicModifier(javaMethod) && Validator.isNull(comment) &&
947                            Validator.isNull(docletTags)) {
948    
949                            return null;
950                    }
951    
952                    return sb.toString();
953            }
954    
955            private String _getMethodKey(Element methodElement) {
956                    StringBundler sb = new StringBundler();
957    
958                    sb.append(methodElement.elementText("name"));
959                    sb.append("(");
960    
961                    List<Element> paramElements = methodElement.elements("param");
962    
963                    for (Element paramElement : paramElements) {
964                            sb.append(paramElement.elementText("name"));
965                            sb.append("|");
966                            sb.append(paramElement.elementText("type"));
967                            sb.append(",");
968                    }
969    
970                    sb.append(")");
971    
972                    return sb.toString();
973            }
974    
975            private String _getMethodKey(JavaMethod javaMethod) {
976                    StringBundler sb = new StringBundler();
977    
978                    sb.append(javaMethod.getName());
979                    sb.append("(");
980    
981                    JavaParameter[] javaParameters = javaMethod.getParameters();
982    
983                    for (JavaParameter javaParameter : javaParameters) {
984                            sb.append(javaParameter.getName());
985                            sb.append("|");
986                            sb.append(_getTypeValue(javaParameter));
987                            sb.append(",");
988                    }
989    
990                    sb.append(")");
991    
992                    return sb.toString();
993            }
994    
995            private String _getSpacesIndent(int length) {
996                    String indent = StringPool.BLANK;
997    
998                    for (int i = 0; i < length; i++) {
999                            indent += StringPool.SPACE;
1000                    }
1001    
1002                    return indent;
1003            }
1004    
1005            private String _getTypeValue(JavaParameter javaParameter) {
1006                    Type type = javaParameter.getType();
1007    
1008                    String typeValue = type.getValue();
1009    
1010                    if (type.isArray()) {
1011                            typeValue += "[]";
1012                    }
1013    
1014                    return typeValue;
1015            }
1016    
1017            private boolean _hasAnnotation(
1018                    AbstractBaseJavaEntity abstractBaseJavaEntity, String annotationName) {
1019    
1020                    Annotation[] annotations = abstractBaseJavaEntity.getAnnotations();
1021    
1022                    if (annotations == null) {
1023                            return false;
1024                    }
1025    
1026                    for (int i = 0; i < annotations.length; i++) {
1027                            Type type = annotations[i].getType();
1028    
1029                            JavaClass javaClass = type.getJavaClass();
1030    
1031                            if (annotationName.equals(javaClass.getName())) {
1032                                    return true;
1033                            }
1034                    }
1035    
1036                    return false;
1037            }
1038    
1039            private boolean _isGenerated(String content) {
1040                    if (content.contains("* @generated") || content.contains("$ANTLR")) {
1041                            return true;
1042                    }
1043                    else {
1044                            return false;
1045                    }
1046            }
1047    
1048            private boolean _hasPublicModifier(AbstractJavaEntity abstractJavaEntity) {
1049                    String[] modifiers = abstractJavaEntity.getModifiers();
1050    
1051                    if (modifiers == null) {
1052                            return false;
1053                    }
1054    
1055                    for (String modifier : modifiers) {
1056                            if (modifier.equals("public")) {
1057                                    return true;
1058                            }
1059                    }
1060    
1061                    return false;
1062            }
1063    
1064            private boolean  _isOverrideMethod(
1065                    JavaClass javaClass, JavaMethod javaMethod,
1066                    Collection<JavaClass> ancestorJavaClasses) {
1067    
1068                    if (javaClass.isInterface() || javaMethod.isConstructor() ||
1069                            javaMethod.isPrivate() || javaMethod.isStatic()) {
1070    
1071                            return false;
1072                    }
1073    
1074                    String methodName = javaMethod.getName();
1075    
1076                    JavaParameter[] javaParameters = javaMethod.getParameters();
1077    
1078                    Type[] types = new Type[javaParameters.length];
1079    
1080                    for (int i = 0; i < javaParameters.length; i++) {
1081                            types[i] = javaParameters[i].getType();
1082                    }
1083    
1084                    // Check for matching method in each ancestor
1085    
1086                    for (JavaClass ancestorJavaClass : ancestorJavaClasses) {
1087                            JavaMethod ancestorJavaMethod =
1088                                    ancestorJavaClass.getMethodBySignature(methodName, types);
1089    
1090                            if (ancestorJavaMethod == null) {
1091                                    continue;
1092                            }
1093    
1094                            boolean samePackage = false;
1095    
1096                            JavaPackage ancestorJavaPackage = ancestorJavaClass.getPackage();
1097    
1098                            if (ancestorJavaPackage != null) {
1099                                    samePackage = ancestorJavaPackage.equals(
1100                                            javaClass.getPackage());
1101                            }
1102    
1103                            // Check if the method is in scope
1104    
1105                            if (samePackage) {
1106                                    return !ancestorJavaMethod.isPrivate();
1107                            }
1108                            else {
1109                                    if (ancestorJavaMethod.isProtected() ||
1110                                            ancestorJavaMethod.isPublic()) {
1111    
1112                                            return true;
1113                                    }
1114                                    else {
1115                                            return false;
1116                                    }
1117                            }
1118                    }
1119    
1120                    return false;
1121            }
1122    
1123            private String _removeJavadocFromJava(
1124                    JavaClass javaClass, String content) {
1125    
1126                    Set<Integer> lineNumbers = new HashSet<Integer>();
1127    
1128                    lineNumbers.add(_getJavaClassLineNumber(javaClass));
1129    
1130                    JavaMethod[] javaMethods = javaClass.getMethods();
1131    
1132                    for (JavaMethod javaMethod : javaMethods) {
1133                            lineNumbers.add(javaMethod.getLineNumber());
1134                    }
1135    
1136                    JavaField[] javaFields = javaClass.getFields();
1137    
1138                    for (JavaField javaField : javaFields) {
1139                            lineNumbers.add(javaField.getLineNumber());
1140                    }
1141    
1142                    String[] lines = StringUtil.splitLines(content);
1143    
1144                    for (int lineNumber : lineNumbers) {
1145                            if (lineNumber == 0) {
1146                                    continue;
1147                            }
1148    
1149                            int pos = lineNumber - 2;
1150    
1151                            String line = lines[pos];
1152    
1153                            if (line == null) {
1154                                    continue;
1155                            }
1156    
1157                            line = line.trim();
1158    
1159                            if (line.endsWith("*/")) {
1160                                    while (true) {
1161                                            lines[pos] = null;
1162    
1163                                            if (line.startsWith("/**")) {
1164                                                    break;
1165                                            }
1166    
1167                                            line = lines[--pos].trim();
1168                                    }
1169                            }
1170                    }
1171    
1172                    StringBundler sb = new StringBundler(content.length());
1173    
1174                    for (String line : lines) {
1175                            if (line != null) {
1176                                    sb.append(line);
1177                                    sb.append("\n");
1178                            }
1179                    }
1180    
1181                    return sb.toString().trim();
1182            }
1183    
1184            private String _trimMultilineText(String text) {
1185                    String[] textArray = StringUtil.splitLines(text);
1186    
1187                    for (int i = 0; i < textArray.length; i++) {
1188                            textArray[i] = textArray[i].trim();
1189                    }
1190    
1191                    return StringUtil.merge(textArray, " ");
1192            }
1193    
1194            private void _updateJavadocsXmlFile(
1195                            String fileName, JavaClass javaClass, Document javaClassDocument)
1196                    throws Exception {
1197    
1198                    String javaClassFullyQualifiedName = javaClass.getFullyQualifiedName();
1199    
1200                    if (!javaClassFullyQualifiedName.contains(".service.") ||
1201                            !javaClassFullyQualifiedName.endsWith("ServiceImpl")) {
1202    
1203                            return;
1204                    }
1205    
1206                    Tuple javadocsXmlTuple = _getJavadocsXmlTuple(fileName);
1207    
1208                    if (javadocsXmlTuple == null) {
1209                            return;
1210                    }
1211    
1212                    Document javadocsXmlDocument = (Document)javadocsXmlTuple.getObject(3);
1213    
1214                    Element javadocsXmlRootElement = javadocsXmlDocument.getRootElement();
1215    
1216                    List<Element> javadocElements = javadocsXmlRootElement.elements(
1217                            "javadoc");
1218    
1219                    for (Element javadocElement : javadocElements) {
1220                            String type = javadocElement.elementText("type");
1221    
1222                            if (type.equals(javaClassFullyQualifiedName)) {
1223                                    Element javaClassRootElement =
1224                                            javaClassDocument.getRootElement();
1225    
1226                                    if (Validator.equals(
1227                                                    javadocElement.formattedString(),
1228                                                    javaClassRootElement.formattedString())) {
1229    
1230                                            return;
1231                                    }
1232    
1233                                    javadocElement.detach();
1234    
1235                                    break;
1236                            }
1237                    }
1238    
1239                    javadocsXmlRootElement.add(javaClassDocument.getRootElement());
1240            }
1241    
1242            private void _updateJavaFromDocument(
1243                            String fileName, String originalContent, String javadocLessContent,
1244                            Document document)
1245                    throws Exception {
1246    
1247                    String[] lines = StringUtil.splitLines(javadocLessContent);
1248    
1249                    JavaClass javaClass = _getJavaClass(
1250                            fileName, new UnsyncStringReader(javadocLessContent));
1251    
1252                    List<JavaClass> ancestorJavaClasses = _getAncestorJavaClasses(
1253                            javaClass);
1254    
1255                    Element rootElement = document.getRootElement();
1256    
1257                    Map<Integer, String> commentsMap = new TreeMap<Integer, String>();
1258    
1259                    commentsMap.put(
1260                            _getJavaClassLineNumber(javaClass),
1261                            _getJavaClassComment(rootElement, javaClass));
1262    
1263                    Map<String, Element> methodElementsMap = new HashMap<String, Element>();
1264    
1265                    List<Element> methodElements = rootElement.elements("method");
1266    
1267                    for (Element methodElement : methodElements) {
1268                            String methodKey = _getMethodKey(methodElement);
1269    
1270                            methodElementsMap.put(methodKey, methodElement);
1271                    }
1272    
1273                    JavaMethod[] javaMethods = javaClass.getMethods();
1274    
1275                    for (JavaMethod javaMethod : javaMethods) {
1276                            if (commentsMap.containsKey(javaMethod.getLineNumber())) {
1277                                    continue;
1278                            }
1279    
1280                            String javaMethodComment = _getJavaMethodComment(
1281                                    lines, methodElementsMap, javaMethod);
1282    
1283                            // Handle override tag insertion
1284    
1285                            if (!_hasAnnotation(javaMethod, "Override")) {
1286                                    if (_isOverrideMethod(
1287                                                    javaClass, javaMethod, ancestorJavaClasses)) {
1288    
1289                                            String overrideLine =
1290                                                    _getIndent(lines, javaMethod) + "@Override\n";
1291    
1292                                            if (Validator.isNotNull(javaMethodComment)) {
1293                                                    javaMethodComment =     javaMethodComment + overrideLine;
1294                                            }
1295                                            else {
1296                                                    javaMethodComment = overrideLine;
1297                                            }
1298                                    }
1299                            }
1300    
1301                            commentsMap.put(javaMethod.getLineNumber(), javaMethodComment);
1302                    }
1303    
1304                    Map<String, Element> fieldElementsMap = new HashMap<String, Element>();
1305    
1306                    List<Element> fieldElements = rootElement.elements("field");
1307    
1308                    for (Element fieldElement : fieldElements) {
1309                            String fieldKey = _getFieldKey(fieldElement);
1310    
1311                            fieldElementsMap.put(fieldKey, fieldElement);
1312                    }
1313    
1314                    JavaField[] javaFields = javaClass.getFields();
1315    
1316                    for (JavaField javaField : javaFields) {
1317                            if (commentsMap.containsKey(javaField.getLineNumber())) {
1318                                    continue;
1319                            }
1320    
1321                            commentsMap.put(
1322                                    javaField.getLineNumber(),
1323                                    _getJavaFieldComment(lines, fieldElementsMap, javaField));
1324                    }
1325    
1326                    StringBundler sb = new StringBundler(javadocLessContent.length());
1327    
1328                    for (int lineNumber = 1; lineNumber <= lines.length; lineNumber++) {
1329                            String line = lines[lineNumber - 1];
1330    
1331                            String comments = commentsMap.get(lineNumber);
1332    
1333                            if (comments != null) {
1334                                    sb.append(comments);
1335                            }
1336    
1337                            sb.append(line);
1338                            sb.append("\n");
1339                    }
1340    
1341                    String formattedContent = sb.toString().trim();
1342    
1343                    if (!originalContent.equals(formattedContent)) {
1344                            File file = new File(_basedir + fileName);
1345    
1346                            _fileUtil.write(file, formattedContent.getBytes(StringPool.UTF8));
1347    
1348                            System.out.println("Writing " + file);
1349                    }
1350            }
1351    
1352            private String _wrapText(String text, String indent) {
1353                    int indentLength = _getIndentLength(indent);
1354    
1355                    // Do not wrap text inside <pre>
1356    
1357                    if (text.contains("<pre>")) {
1358                            Pattern pattern = Pattern.compile(
1359                                    "(?<=^|</pre>).+?(?=$|<pre>)", Pattern.DOTALL);
1360    
1361                            Matcher matcher = pattern.matcher(text);
1362    
1363                            StringBuffer sb = new StringBuffer();
1364    
1365                            while (matcher.find()) {
1366                                    String wrapped = _formatInlines(matcher.group());
1367    
1368                                    wrapped = StringUtil.wrap(
1369                                            wrapped, 80 - indentLength, "\n");
1370    
1371                                    matcher.appendReplacement(sb, wrapped);
1372                            }
1373    
1374                            matcher.appendTail(sb);
1375    
1376                            sb.append("\n");
1377    
1378                            text = sb.toString();
1379                    }
1380                    else {
1381                            text = _formatInlines(text);
1382    
1383                            text = StringUtil.wrap(text, 80 - indentLength, "\n");
1384                    }
1385    
1386                    text = text.replaceAll("(?m)^", indent);
1387                    text = text.replaceAll("(?m) +$", StringPool.BLANK);
1388    
1389                    return text;
1390            }
1391    
1392            private static FileImpl _fileUtil = FileImpl.getInstance();
1393    
1394            private static SAXReaderImpl _saxReaderUtil = SAXReaderImpl.getInstance();
1395    
1396            private String _basedir = "./";
1397            private boolean _initializeMissingJavadocs;
1398            private Map<String, Tuple> _javadocxXmlTuples =
1399                    new HashMap<String, Tuple>();
1400            private boolean _updateJavadocs;
1401    
1402    }