mergedoc.core.Comment.java Source code

Java tutorial

Introduction

Here is the source code for mergedoc.core.Comment.java

Source

/*
 * Copyright (c) 2003- Shinji Kashihara. All rights reserved.
 * This program are made available under the terms of the Common Public License
 * v1.0 which accompanies this distribution, and is available at cpl-v10.html.
 */
package mergedoc.core;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * ?????
 * <p>
 * @author Shinji Kashihara
 */
public class Comment {

    /**  */
    private static final Log log = LogFactory.getLog(Comment.class);

    /** ??? */
    private static final int DEFAULT_WIDTH = Integer.MAX_VALUE;;

    //-----------------------------------------
    //  API ???
    //-----------------------------------------

    /** ?? */
    private final Signature sig;

    /**  */
    private String docBody;

    /** deprecated  */
    private String deprecate;

    /** see ? */
    private List<String> sees;

    /** since ? */
    private List<String> sinces;

    /** param ? */
    private List<String> params;

    /** return ? */
    private List<String> returns;

    /** throws ? */
    private List<String> throwses;

    //-----------------------------------------
    // Java ???
    //-----------------------------------------

    /** ? Java ?? */
    private String srcBody;

    /** author ? */
    private List<String> srcAuthors;

    /** version ? */
    private List<String> srcVersions;

    /** serial ? */
    private List<String> srcSerials;

    /** serialField ? */
    private List<String> srcSerialFields;

    /** serialData ? */
    private List<String> srcSerialDatas;

    /** spec ?JSR No.? */
    private List<String> srcSpecs;

    /**
     * ??
     * <p>
     * @param sig ??
     */
    public Comment(Signature sig) {
        this.sig = sig;
    }

    /**
     * ???
     * <p>
     * @param docBody 
     */
    public void setDocumentBody(String docBody) {
        this.docBody = formatHTML(docBody);
    }

    /**
     * ??? HTML ???
     * <p>
     * @param comment 
     * @return HTML ?
     */
    private String formatHTML(String comment) {

        // HTML ?????????
        boolean hasHtmlTag = comment.contains("<");

        // HTML ??
        if (hasHtmlTag) {
            Pattern pat = PatternCache.getPattern("</?\\w+");
            Matcher mat = pat.matcher(comment);
            StringBuffer sb = new StringBuffer();
            while (mat.find()) {
                String tag = mat.group().toLowerCase();
                mat.appendReplacement(sb, tag);
            }
            mat.appendTail(sb);
            comment = sb.toString();
        }

        comment = FastStringUtils.replaceAll(comment, "\\*/", "*&#47;");
        comment = FastStringUtils.replaceAll(comment, "\\\\u", "&#92;u");

        // 
        comment = FastStringUtils.replaceAll(comment, "(?m)^ ", "");

        // HTML ?
        if (hasHtmlTag) {

            // </p> 
            comment = FastStringUtils.replace(comment, "</p>", "");

            // <blockquote> 
            comment = FastStringUtils.replaceAll(comment, "\\s*(</?blockquote>)\\s*", "\n$1\n");

            // <pre> 
            if (comment.contains("<pre>")) {
                comment = FastStringUtils.replaceAll(comment, "\\s*(</?pre>)\\s*", "\n$1\n");
                comment = FastStringUtils.replaceAll(comment, "(<blockquote>)\n(<pre>)", "$1$2");
                comment = FastStringUtils.replaceAll(comment, "(</pre>)\n(</blockquote>)", "$1$2");
            }

            // <table> 
            if (comment.contains("<table")) {
                comment = FastStringUtils.replaceAll(comment, "\\s*(</?table|</?tr>)", "\n$1");
                comment = FastStringUtils.replaceAll(comment, "\\s*(<(th|td))", "\n  $1");
                comment = FastStringUtils.replaceAll(comment, "\\s*(<blockquote>)\n(<table)", "\n\n$1$2");
                comment = FastStringUtils.replaceAll(comment, "(</table>)\n(</blockquote>)", "$1$2");
            }

            // <ol> <ul> <li> 
            comment = FastStringUtils.replaceAll(comment, "\\s*(</?(ol|ul|li)>)", "\n$1");

            // <p> 
            if (comment.contains("<p>")) {
                comment = FastStringUtils.replaceAll(comment, "\\s*(<p>)\\s*", "\n\n$1");
                comment = FastStringUtils.replaceAll(comment, "(\\s*<p>)+$", "");
            }

            // <br/> 
            if (comment.contains("<br")) {
                comment = FastStringUtils.replaceAll(comment, "<br\\s*/>", "<br>");
            }
        }

        // ????
        comment = comment.trim();

        return comment;
    }

    /**
     * deprecated ???
     * <p>
     * @param comment 
     */
    public void addDeprecated(String comment) {
        comment = formatHTML(comment);
        deprecate = comment;
    }

    /**
     * see ???
     * <p>
     * @param comment 
     */
    public void addSee(String comment) {
        if (sees == null) {
            sees = new LinkedList<String>();
        }
        sees.add(comment);
    }

    /**
     * since ???
     * <p>
     * @param comment 
     */
    public void addSince(String comment) {
        if (sinces == null) {
            sinces = new LinkedList<String>();
        }
        sinces.add(comment);
    }

    /**
     * param ???
     * <p>
     * @param comment 
     */
    public void addParam(String name, String desc) {
        if (params == null) {
            params = new LinkedList<String>();
        }
        desc = formatHTML(desc);
        params.add(name + " " + desc);
    }

    /**
     * return ???
     * <p>
     * @param comment 
     */
    public void addReturn(String comment) {
        if (returns == null) {
            returns = new LinkedList<String>();
        }
        comment = formatHTML(comment);
        returns.add(comment);
    }

    /**
     * throws ???
     * <p>
     * @param comment 
     */
    public void addThrows(String comment) {
        if (throwses == null) {
            throwses = new LinkedList<String>();
        }
        comment = formatHTML(comment);
        throwses.add(comment);
    }

    /**
     * ? Java ???
     * ?????????
     * <p>
     * @param srcBody ? Java 
     */
    public void setSourceBody(String srcBody) {

        // @exception  @throws ??
        this.srcBody = FastStringUtils.replaceAll(srcBody, "\\s@exception\\s", " @throws ");

        // author ?????
        srcAuthors = createWrapTagList("@author");

        // throws ?????
        // {@inheritDoc} ???????API ?
        // ???{@inheritDoc} ??????
        List<String> srcThrowses = createWrapTagList("@throws");
        if (srcThrowses != null && throwses != null) {
            for (String src : srcThrowses) {

                Pattern pat = PatternCache.getPattern("(?s)(\\w+)\\s+(.*)");
                Matcher mat = pat.matcher(src);

                if (mat.find() && mat.group(2).contains("@inheritDoc")) {

                    String exceptionClassName = mat.group(1);

                    for (int i = 0; i < throwses.size(); i++) {
                        String doc = throwses.get(i);
                        if (doc.startsWith(exceptionClassName)) {
                            throwses.set(i, exceptionClassName + " {@inheritDoc}");
                        }
                    }
                }
            }
        }

        // ??????????
        srcVersions = createTagList("@version");
        srcSerials = createTagList("@serial");
        srcSerialFields = createTagList("@serialField");
        srcSerialDatas = createTagList("@serialData");
        srcSpecs = createTagList("@spec");
    }

    /**
     * ? Java ??? Javadoc ??????
     * ????????????????
     * <p>
     * @param tagName ??
     * @return ?
     */
    private List<String> createWrapTagList(String tagName) {

        List<String> tagValues = null;
        if (srcBody.contains(tagName)) {

            String undeco = FastStringUtils.replaceAll(srcBody, "(?m)^ *\\* *", "");
            Pattern pat = PatternCache.getPattern("(?s)" + tagName + " *(.*?)([^\\{]@\\w+|/\\s*$)");
            Matcher mat = pat.matcher(undeco);
            for (int start = 0; mat.find(start); start = mat.end(1)) {
                if (tagValues == null) {
                    tagValues = new LinkedList<String>();
                }
                tagValues.add(mat.group(1));
            }
        }
        return tagValues;
    }

    /**
     * ? Java ??? Javadoc ??????
     * ?????????????
     * <p>
     * @param tagName ??
     * @return ?
     */
    private List<String> createTagList(String tagName) {

        List<String> tagValues = null;
        if (srcBody.contains(tagName)) {

            Pattern pat = PatternCache.getPattern(" +" + tagName + " *(.*)\n");
            Matcher mat = pat.matcher(srcBody);
            while (mat.find()) {
                if (tagValues == null) {
                    tagValues = new LinkedList<String>();
                }
                tagValues.add(mat.group(1));
            }
        }
        return tagValues;
    }

    /**
     * ??????
     * <p>
     * @return ??
     */
    public String buildComment() {

        if (srcBody == null) {
            throw new IllegalStateException("Source comment is null. require #setSourceComment.");
        }

        // Java ? @deprecated ???????
        // API ?? @deprecated ????
        // ??????????????
        if (!srcBody.contains("@deprecated")) {
            deprecate = null;
        }

        //  Java ????????
        // ?? API ? Java ?????
        if (FastStringUtils.matches(srcBody, "(?s)\\s*/\\*\\*[\\s\\*]*@.*")) {
            docBody = null;
            params = omitTags(params, "@param");
            returns = omitTag(returns, "@return");
            throwses = omitTags(throwses, "@throws");
            sees = omitTags(sees, "@see");
        }

        //  Java ????
        int decoSize = 2;
        int originDecoHeight = FastStringUtils.heightOf(srcBody);
        if (originDecoHeight <= 0) {
            //            throw new IllegalStateException(
            //            "Illegal comment height " + originDecoHeight + "\n" + srcBody);
        }
        int originHeight = originDecoHeight;
        if (originDecoHeight > decoSize) {
            originHeight = originDecoHeight - decoSize;
        }
        String indent = FastStringUtils.replaceFirst(srcBody, "(?s)^( *?)/\\*\\*.*", "$1");

        // API ?? pre  Java ????
        if (docBody != null) {
            replacePreBody();
        }

        //  Java ???? 2 ??
        if (originDecoHeight <= 2) {
            StringBuilder sb = new StringBuilder();
            sb.append(indent);
            sb.append("/** ");
            if (docBody != null && docBody.length() > 0) {
                String str = FastStringUtils.replaceAll(docBody, "\n", "");
                sb.append(str);
            } else if (sinces != null && sinces.size() > 0) {
                sb.append("@since ");
                String str = sinces.get(0);
                str = FastStringUtils.replace(str, "\n", "");
                sb.append(str);
            }
            if (originDecoHeight == 2) {
                sb.append("\n");
                sb.append(indent);
            }
            sb.append(" */");
            if (srcBody.endsWith("\n")) {
                sb.append("\n");
            }
            return sb.toString();
        }

        // ??
        int width = DEFAULT_WIDTH - indent.length() - OutputComment.LINE_PREFIX.length();
        OutputComment o = new OutputComment(originHeight, width);
        String decoComment = o.toString();
        if (decoComment.length() > 0) {
            if (o.resultHeight() != o.originHeight) {
                decoComment = resizeComment(o, decoComment);
            }
            decoComment = FastStringUtils.replaceAll(decoComment, "(?m)^", indent);
            decoComment = FastStringUtils.replaceAll(decoComment, "(?m)^ +$", "");
        }

        return decoComment;
    }

    /**
     * Java ??? Javadoc
     * ?????????????API ??
     * ??????????? Java ??
     * API ??????????
     * API ????? Java ?????
     * null ???
     * <p>
     * ??????? {@link #omitTags(List, String)}
     * ???????
     * <p>
     * @param docTagList API ?
     * @param tagName ??
     * @return ?
     */
    private List<String> omitTag(List<String> docTagList, String tagName) {

        if (docTagList != null && docTagList.size() > 0) {

            List<String> srcTagList = createWrapTagList(tagName);
            if (srcTagList == null || srcTagList.size() == 0) {
                return null;
            }
        }
        return docTagList;
    }

    /**
     * Java ??? Javadoc
     * ?????????????API ??
     * ??????????? Java ??
     * API ??????????
     * API ?? Java ???
     * API ????????Java ???????
     * <p>
     * @param docTagList API ?
     * @param tagName ??
     * @return ?
     */
    private List<String> omitTags(List<String> docTagList, String tagName) {

        if (docTagList != null && docTagList.size() > 0) {

            List<String> srcTagList = createWrapTagList(tagName);
            if (srcTagList == null || srcTagList.size() == 0) {
                return null;
            }
            if (docTagList.size() > srcTagList.size()) {
                List<String> names = new ArrayList<String>();
                for (String src : srcTagList) {
                    String name = FastStringUtils.replaceFirst(src, "(?s)^(\\S+)\\s.*$", "$1");
                    names.add(name);
                }
                List<String> newTagList = new ArrayList<String>();
                for (String doc : docTagList) {
                    String name = FastStringUtils.replaceFirst(doc, "(?s)^(\\S+)\\s.*$", "$1");
                    if (names.contains(name)) {
                        newTagList.add(doc);
                    }
                }
                return newTagList;
            }
        }
        return docTagList;
    }

    /**
     * JDK1.5  API ???? pre HTML ??
     * ???????????Java ?????
     * ????pre ????????????? API ?
     * ???
     */
    private void replacePreBody() {

        // pre ??????????
        if (!docBody.contains("<pre>")) {
            return;
        }

        // Java ? pre ??
        LinkedList<String> pres = null;
        String commentBody = FastStringUtils.replaceAll(srcBody, "(?m)^\\s*\\*( |)", "");
        Pattern pat = PatternCache.getPattern("(?s)(<pre>\n)(.+?)(\n</pre>)");
        Matcher mat = pat.matcher(commentBody);
        while (mat.find()) {
            if (pres == null) {
                pres = new LinkedList<String>();
            }
            pres.add(mat.group(2));
        }
        if (pres == null) {
            return;
        }

        // API ? pre ?? Java ???
        Matcher descMatcher = pat.matcher(docBody);
        StringBuffer sb = new StringBuffer();
        while (descMatcher.find()) {

            // pre ??????????
            if (pres.size() == 0) {
                return;
            }
            String value = FastStringUtils.quoteReplacement(pres.removeFirst());
            descMatcher.appendReplacement(sb, "$1" + value + "$3");
        }
        descMatcher.appendTail(sb);

        // pre ????????
        if (pres.size() == 0) {
            docBody = sb.toString();
        }
    }

    /**
     * ??????
     */
    private class OutputComment {

        final static String LINE_PREFIX = " * ";
        final int originHeight;
        final int initWidth;
        int width;
        String comment;
        boolean enabledFirstLine;

        OutputComment(int originHeight, int width) {
            this.originHeight = originHeight;
            this.initWidth = width;
            this.width = width;
            build();
        }

        void build() {
            this.comment = formatComment(width, originHeight);
        }

        void rebuild() {
            resetWidth();
            build();
        }

        int resultHeight() {
            if (enabledFirstLine) {
                return FastStringUtils.heightOf(comment) - 1;
            } else {
                return FastStringUtils.heightOf(comment);
            }
        }

        void resetWidth() {
            width = initWidth;
        }

        @Override
        public String toString() {
            String str = comment;
            if (str.length() > 0) {
                StringBuilder sb = new StringBuilder();
                str = FastStringUtils.replaceAll(str, "(?m)^", LINE_PREFIX);
                if (enabledFirstLine) {
                    sb.append("/**");
                    str = FastStringUtils.replaceFirst(str, "^ \\*", "");
                } else {
                    sb.append("/**\n");
                }
                sb.append(str);
                sb.append(" */\n");
                str = sb.toString();
            }
            return str;
        }
    }

/**
 * ???
 *
 * @param o ?
 * @return ????
 */
private String resizeComment(OutputComment o, String decoComment) {

    // ????????????
    if (o.resultHeight() > o.originHeight) {
        shrinkComment(o);

        // ?????????
        // docBody ? <pre> ???????
        if (docBody != null && o.resultHeight() > o.originHeight && docBody.contains("\n")) {

            StringBuilder sb = new StringBuilder();
            boolean inPreTag = false;
            for (int i = 0; i < docBody.length(); i++) {
                char c = docBody.charAt(i);
                sb.append(c);
                String buf = sb.toString();
                if (buf.endsWith("<pre>")) {
                    inPreTag = true;
                    sb.insert(sb.length() - 5, '\n');
                } else if (buf.endsWith("</pre>")) {
                    inPreTag = false;
                    sb.append('\n');
                }
                if (c == '\n' && !inPreTag) {
                    sb.deleteCharAt(sb.length() - 1);
                }
            }
            docBody = sb.toString();

            o.rebuild();
            if (o.resultHeight() > o.originHeight) {
                shrinkComment(o);
            }
        }

        // ???????????? 1 ??????
        // ???? JDK5.0 API ? shrinkComment ?
        // ?????????
        // BasicEditorPaneUI#installUI(JComponent) ??????
        if (docBody != null && o.resultHeight() > o.originHeight) {

            int pos = docBody.indexOf('');
            if (pos != -1) {
                docBody = docBody.substring(0, pos + 1);

                o.rebuild();
                if (o.resultHeight() > o.originHeight) {
                    shrinkComment(o);
                }
            }
        }

        // ??????????...
        if (o.resultHeight() > o.originHeight) {

            // JDK5.0 API ???????
            log.warn(sig + " ???????????\n" + "-------------------------------------------------\n" + ":\n" + srcBody
                    + "\n:\n" + o.toString() + "-------------------------------------------------\n");
            return srcBody;
        }
        decoComment = o.toString();
    }

    // ?????????????
    if (o.resultHeight() < o.originHeight) {
        expandComment(o);
        decoComment = o.toString();
    }

    // ????????
    if (o.resultHeight() < o.originHeight) {
        int sub = o.originHeight - o.resultHeight();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < sub; i++) {
            sb.append("\n");
        }
        decoComment = sb + decoComment;
    }

    return decoComment;
}

    /**
     * ???????
     * <p>
     * @param o 
     */
    private void shrinkComment(OutputComment o) {

        // ? Java ????
        String emptyDescRegex = "(?s)\\s*/\\*\\*\\s*\\*\\s*@.*";
        if (FastStringUtils.matches(srcBody, emptyDescRegex)) {
            docBody = null;
            o.build();
            if (o.resultHeight() <= o.originHeight) {
                return;
            }
        }

        // ? Java ????
        // -> buildComment ?????????????????
        boolean b1 = shrinkTagList("@see", sees);
        boolean b2 = shrinkTagList("@throws", throwses);
        boolean b3 = shrinkTagList("@param", params);
        boolean b4 = shrinkTagList("@return", returns);
        if (b1 || b2 || b3 || b4) {
            o.build();
            if (o.resultHeight() <= o.originHeight) {
                return;
            }
        }

        // ?
        int height = o.resultHeight();

        // ? <p> ????
        Pattern pTagPat = PatternCache.getPattern("<p>\n\n(<p>)");
        if (o.comment.contains("<p>\n\n<p>")) {
            StringBuffer sb = new StringBuffer();
            Matcher pTagMat = pTagPat.matcher(o.comment);
            while (height > o.originHeight && pTagMat.find()) {
                pTagMat.appendReplacement(sb, "$1");
                height -= 2;
            }
            pTagMat.appendTail(sb);
            o.comment = sb.toString();
            if (height <= o.originHeight) {
                return;
            }
        }

        // <th?<td?</tr ???
        Pattern tdTagPat = PatternCache.getPattern("\\s+(<(t[hd]|/tr))");
        if (o.comment.contains("<table")) {
            StringBuffer sb = new StringBuffer();
            Matcher tdTagMat = tdTagPat.matcher(o.comment);
            while (height > o.originHeight && tdTagMat.find()) {
                tdTagMat.appendReplacement(sb, "$1");
                height--;
            }
            tdTagMat.appendTail(sb);
            o.comment = sb.toString();
            if (height <= o.originHeight) {
                return;
            }
        }

        // <li?</ul?</ol ???
        Pattern liTagPat = PatternCache.getPattern("\\s+(<(li|/[uo]l))");
        if (o.comment.contains("<li")) {
            StringBuffer sb = new StringBuffer();
            Matcher liTagMat = liTagPat.matcher(o.comment);
            while (height > o.originHeight && liTagMat.find()) {
                liTagMat.appendReplacement(sb, "$1");
                height--;
            }
            liTagMat.appendTail(sb);
            o.comment = sb.toString();
            if (height <= o.originHeight) {
                return;
            }
        }

        // 
        Pattern emptyLinePat = PatternCache.getPattern("(?m)^\\s*?\n");
        Matcher emptyLineMat = emptyLinePat.matcher(o.comment);
        StringBuffer sb = new StringBuffer();
        while (height > o.originHeight && emptyLineMat.find()) {
            emptyLineMat.appendReplacement(sb, "");
            height--;
        }
        emptyLineMat.appendTail(sb);
        o.comment = sb.toString();
        if (height <= o.originHeight) {
            return;
        }

        //  Java ? 1 ????
        String firstLineRegex = "(?s)\\s*/\\*\\*\\s*\n.*";
        if (!FastStringUtils.matches(srcBody, firstLineRegex)) {
            o.enabledFirstLine = true;
            if (o.resultHeight() <= o.originHeight) {
                return;
            }
        }

        // ???
        final int maxWidth = 160;
        while (o.resultHeight() > o.originHeight && o.width < maxWidth) {

            o.build();
            if (o.resultHeight() <= o.originHeight) {
                return;
            }

            if (o.comment.contains("<")) {

                o.comment = pTagPat.matcher(o.comment).replaceAll("$1");
                if (o.resultHeight() <= o.originHeight) {
                    return;
                }

                o.comment = tdTagPat.matcher(o.comment).replaceAll("$1");
                if (o.resultHeight() <= o.originHeight) {
                    return;
                }

                o.comment = liTagPat.matcher(o.comment).replaceAll("$1");
                if (o.resultHeight() <= o.originHeight) {
                    return;
                }
            }

            o.comment = emptyLinePat.matcher(o.comment).replaceAll("");
            if (o.resultHeight() <= o.originHeight) {
                return;
            }

            if (o.width < 100) {
                o.width += 4;
            } else {
                o.width += 8;
            }
        }
    }

    /**
     * ??? Java ???????
     * <p>
     * @param tagRegex ??
     * @param tagList 
     * @return ???? true
     */
    private boolean shrinkTagList(String tagRegex, List<String> tagList) {
        if (tagList != null) {
            int tagCount = FastStringUtils.split(srcBody, "\\s" + tagRegex + "\\s", -1).length - 1;
            if (tagCount == 0) {
                tagList.clear();
                return true;
            }
        }
        return false;
    }

    /**
     * ???????
     * <p>
     * @param o 
     */
    private void expandComment(OutputComment o) {

        // HTML ??????????
        if (!o.comment.contains("<")) {
            return;
        }

        // ?
        int height = o.resultHeight();

        // <pre>?<blockquote>?<ol>?<ul> ??
        StringBuffer sb = new StringBuffer();
        Pattern pat = PatternCache.getPattern("([^\n])(\n(<blockquote>)?<pre>|\n<(blockquote|ol|ul)>)");
        Matcher mat = pat.matcher(o.comment);
        while (height < o.originHeight && mat.find()) {
            mat.appendReplacement(sb, "$1\n$2");
            height++;
        }
        mat.appendTail(sb);
        o.comment = sb.toString();
        if (height == o.originHeight) {
            return;
        }

        // </pre>?</blockquote>?</ol>?</ul> ??
        sb = new StringBuffer();
        pat = PatternCache.getPattern("(</pre>(</blockquote>)?\n|</(blockquote|ol|ul)>\n)([^\n])");
        mat = pat.matcher(o.comment);
        while (height < o.originHeight && mat.find()) {
            mat.appendReplacement(sb, "$1\n$4");
            height++;
        }
        mat.appendTail(sb);
        o.comment = sb.toString();
        if (height == o.originHeight) {
            return;
        }
    }

    /**
     * ???????
     * <p>
     * @param width 
     * @param originHeight 
     * @return ???
     */
    private String formatComment(int width, int originHeight) {

        StringBuilder sb = new StringBuilder();

        // ???
        if (docBody != null && docBody.length() > 0) {
            if (originHeight == 1) {
                sb.append(FastStringUtils.replaceAll(docBody, "\n", ""));
                sb.append("\n");
                return sb.toString();
            }
            sb.append(adjustWidth(docBody, width));
            sb.append("\n");
        }

        // deprecated ???
        if (deprecate != null && deprecate.length() > 0) {
            String depre = "@deprecated " + deprecate;
            sb.append(adjustWidth(depre, width));
            sb.append("\n");
        }

        appendTo("@author  ", srcAuthors, sb, width);
        appendTo("@version ", srcVersions, sb, width);

        // param ???
        if (params != null && params.size() > 0) {

            // name ?
            int paramsSize = params.size();
            int[] nameLens = new int[paramsSize];
            int nameLenMax = 3;
            final int nameLenLimit = 12;

            for (int i = 0; i < paramsSize; i++) {
                String comment = params.get(i);
                String name = FastStringUtils.replaceFirst(comment, "(?s)(\\w+)\\s.*", "$1");
                nameLens[i] = name.length();
                if (nameLens[i] > nameLenMax && nameLens[i] < nameLenLimit) {
                    nameLenMax = nameLens[i];
                }
            }

            // 2 ???
            String tag = "@param   ";
            int indentCnt = tag.length() + nameLenMax + 1;
            StringBuilder indent = new StringBuilder(indentCnt);
            for (; indentCnt > 0; indentCnt--) {
                indent.append(' ');
            }

            // ??
            for (int i = 0; i < paramsSize; i++) {

                int spaceCnt = nameLenMax - nameLens[i];
                StringBuilder space = new StringBuilder();
                for (; spaceCnt > 0; spaceCnt--) {
                    space.append(' ');
                }

                String comment = params.get(i);
                comment = FastStringUtils.replaceFirst(comment, "(?s)(\\w+)\\s+(.*)", "$1" + space + " $2");
                comment = adjustWidth(comment, width - tag.length());
                StringTokenizer st = new StringTokenizer(comment, "\n");

                // 1 
                sb.append(tag);
                if (st.hasMoreTokens()) {
                    sb.append(st.nextToken());
                }
                sb.append("\n");

                // 2 ?
                while (st.hasMoreTokens()) {
                    sb.append(indent);
                    sb.append(st.nextToken());
                    sb.append("\n");
                }
            }
        }

        appendTo("@return  ", returns, sb, width);
        appendTo("@throws  ", throwses, sb, width);
        appendTo("@serialField", srcSerialFields, sb, width);
        appendTo("@serialData", srcSerialDatas, sb, width);
        appendTo("@see     ", sees, sb, width);
        appendTo("@since   ", sinces, sb, width);
        appendTo("@serial  ", srcSerials, sb, width);
        appendTo("@spec    ", srcSpecs, sb, width);

        String str = sb.toString();
        str = FastStringUtils.replaceFirst(str, "\n\n$", "\n");

        return str;
    }

    /**
     * ??????
     * ???????
     * <p>
     * @param tag 
     * @param tagList ?
     * @param sb ?
     * @param width 
     */
    private void appendTo(String tag, List<String> tagList, StringBuilder sb, int width) {

        if (tagList == null) {
            return;
        }

        for (String comment : tagList) {

            comment = adjustWidth(comment, width - tag.length());
            StringTokenizer st = new StringTokenizer(comment, "\n");

            sb.append(tag);
            if (st.hasMoreTokens()) {
                sb.append(st.nextToken());
            }
            sb.append("\n");

            while (st.hasMoreTokens()) {
                sb.append(FastStringUtils.replaceAll(tag, ".", " "));
                sb.append(st.nextToken());
                sb.append("\n");
            }
        }
    }

    /**
     * ?????
     * <p>
     * @param value ?
     * @param width ??
     * @return ??
     */
    private String adjustWidth(String value, int width) {

        // 1 ????
        if (value.getBytes().length < width) {
            return value + "\n";
        }

        List<String> lineValues = FastStringUtils.splitLine(value);
        StringBuilder resultBuf = new StringBuilder();
        boolean preTagArea = false;
        final int longWordWidth = width - 20;

        // ??????? pre ?
        // ?????? pre ??
        for (String lineValue : lineValues) {

            if (lineValue.equals("")) {
                resultBuf.append("\n");
                continue;
            }

            // pre 
            if (lineValue.startsWith("</pre>")) {
                preTagArea = false;
            } else if (lineValue.startsWith("<pre>")) {
                preTagArea = true;
            }
            if (preTagArea) {
                resultBuf.append(lineValue);
                resultBuf.append("\n");
                if (lineValue.endsWith("</pre>")) {
                    preTagArea = false;
                }
                continue;
            }
            if (lineValue.endsWith("<pre>")) {
                preTagArea = true;
            }

            // ????
            if (lineValue.getBytes().length < width) {
                resultBuf.append(lineValue);
                resultBuf.append("\n");
                continue;
            }

            // <table ???? summary ???
            if (lineValue.contains("<table")) {
                String s = FastStringUtils.replaceFirst(lineValue, "(?i)\\s(summary=)", "\n$1");
                resultBuf.append(s);
                resultBuf.append("\n");
                continue;
            }

            // ???
            String multiLineValue = FastStringUtils.replaceAll(lineValue,
                    "\\s?(\\p{Graph}{" + longWordWidth + ",})", "\n$1");

            StringTokenizer st = new StringTokenizer(multiLineValue, "\n");
            while (st.hasMoreTokens()) {
                lineValue = st.nextToken();
                wrap(lineValue, resultBuf, width);
            }

        }
        return resultBuf.toString();
    }

    /**
     * ????????????
     * ?????????
     * <p>
     * @param lineValue 
     * @param resultBuf ?????
     * @param width ??
     */
    private void wrap(String lineValue, StringBuilder resultBuf, int width) {

        final int minWidth = width - 10;
        final int maxWidth = width + 10;
        final int ADJUST_SKIP_WIDTH = width + 4;
        final int lastPos = lineValue.length() - 1;
        final String PUNCTS = "??)}";
        final String PARTICLES = "???????";

        StringBuilder buf = new StringBuilder();
        int bufLen = 0;
        for (int pos = 0; pos < lastPos; pos++) {

            if (bufLen == 0) {
                String after = lineValue.substring(pos, lastPos);
                int afterLen = after.getBytes().length;
                if (afterLen <= ADJUST_SKIP_WIDTH) {
                    buf.append(after);
                    break;
                }
            }

            char c = lineValue.charAt(pos);
            int cLen = String.valueOf(c).getBytes().length;
            bufLen += cLen;
            boolean isChangeLine = false;

            if (bufLen > minWidth) {
                // ???????????

                if (c == ' ') {

                    isChangeLine = true;
                    buf.append('\n');

                } else if (PUNCTS.indexOf(c) != -1 || PARTICLES.indexOf(c) != -1) {

                    char next = lineValue.charAt(pos + 1);
                    if (PUNCTS.indexOf(next) == -1 && next != ' ' && next != '.') {

                        isChangeLine = true;
                        buf.append(c);
                        buf.append('\n');
                    }

                } else if (bufLen > width) {
                    // ??????
                    // ???????????

                    if (c == '<' || cLen > 1) {

                        isChangeLine = true;
                        buf.append('\n');
                        buf.append(c);

                    } else if (bufLen > maxWidth) {
                        // ??????
                        // ????

                        for (int bPos = buf.length() - 1; bPos > 0; bPos--) {
                            char bc = buf.charAt(bPos);

                            if (bc == ' ') {
                                buf.replace(bPos, bPos + 1, "\n");
                                bufLen = buf.substring(bPos + 1).getBytes().length;
                                break;

                            } else {

                                int bcLen = String.valueOf(bc).getBytes().length;
                                if (bcLen > 1) {
                                    buf.insert(bPos + 1, '\n');
                                    bufLen = buf.substring(bPos + 2).getBytes().length;
                                    break;
                                }
                            }
                        }
                    }
                }
            }

            if (isChangeLine) {
                resultBuf.append(buf);
                buf = new StringBuilder();
                bufLen = 0;
            } else {
                buf.append(c);
            }
        }
        buf.append(lineValue.charAt(lastPos));

        resultBuf.append(buf);
        resultBuf.append('\n');
    }
}