001// Copyright 2007, 2008, 2011, 2012 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007// http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry5.javadoc;
016
017import com.sun.source.doctree.*;
018import org.apache.commons.lang.StringEscapeUtils;
019import org.apache.commons.lang.StringUtils;
020import org.apache.tapestry5.commons.util.CollectionFactory;
021
022import javax.lang.model.element.VariableElement;
023import java.io.IOException;
024import java.util.Locale;
025import java.util.Set;
026import java.util.regex.Matcher;
027import java.util.regex.Pattern;
028
029public class ParameterDescription
030{
031    public final VariableElement field;
032
033    public final String name;
034
035    public final String type;
036
037    public final String defaultValue;
038
039    public final String defaultPrefix;
040
041    public final boolean required;
042
043    public final boolean allowNull;
044
045    public final boolean cache;
046
047    public final String since;
048
049    public final boolean deprecated;
050
051    private final DocCommentTreeProvider docCommentTreeProvider;
052
053    private static final Pattern SPECIAL_CONTENT = Pattern.compile("(?:</?(\\p{Alpha}+)>)|(?:&\\p{Alpha}+;)");
054    private static final Set<String> PASS_THROUGH_TAGS = CollectionFactory.newSet("b", "em", "i", "code", "strong");
055
056
057    public ParameterDescription(final VariableElement fieldDoc, final String name, final String type, final String defaultValue, final String defaultPrefix,
058                                final boolean required, final boolean allowNull, final boolean cache, final String since, final boolean deprecated,
059                                final DocCommentTreeProvider docCommentTreeProvider)
060    {
061        this.field = fieldDoc;
062        this.name = name;
063        this.type = type;
064        this.defaultValue = defaultValue;
065        this.defaultPrefix = defaultPrefix;
066        this.required = required;
067        this.allowNull = allowNull;
068        this.cache = cache;
069        this.since = since;
070        this.deprecated = deprecated;
071        this.docCommentTreeProvider = docCommentTreeProvider;
072    }
073
074    /**
075     * Extracts the description, converting Text and @link nodes as needed into markup text.
076     *
077     * @return markup text, ready for writing
078     * @throws IOException if some error occurs.
079     */
080    public String extractDescription() throws IOException
081    {
082        final DocCommentTree tree = docCommentTreeProvider.getDocCommentTree(field);
083
084        if (tree == null)
085        {
086            return "";
087        }
088
089        StringBuilder builder = new StringBuilder();
090
091        for (com.sun.source.doctree.DocTree tag : tree.getFullBody())
092        {
093            if (tag.getKind() == DocTree.Kind.TEXT)
094            {
095                TextTree textTree = (TextTree) tag;
096                appendContentSafe(builder, textTree.getBody());
097                continue;
098            }
099
100            if (tag.getKind() == DocTree.Kind.LINK)
101            {
102                LinkTree seeTag = (LinkTree) tag;
103                String label = seeTag.getLabel().toString();
104                if (StringUtils.isNotEmpty(label))
105                {
106                    builder.append(StringEscapeUtils.escapeHtml(label));
107                    continue;
108                }
109
110                if (seeTag.getReference() != null)
111                    builder.append(StringEscapeUtils.escapeHtml(seeTag.getReference().getSignature()));
112            }
113            else if (tag.getKind() == DocTree.Kind.CODE)
114            {
115                LiteralTree codeTag = (LiteralTree) tag;
116                builder.append("<code>");
117                builder.append(StringEscapeUtils.escapeHtml(codeTag.getBody().getBody()));
118                builder.append("</code>");
119            }
120        }
121
122        String text = builder.toString();
123
124        // Fix it up a little.
125
126        // Remove any simple open or close tags found in the text, as well as any XML entities.
127
128        return text.trim();
129    }
130
131    private static void appendContentSafe(final StringBuilder sb, final String string){
132        Matcher m = SPECIAL_CONTENT.matcher(string);
133        int index = 0;
134        while (index < string.length()){
135            boolean match = m.find(index);
136            if (match){
137                if (index != m.start()){
138                    sb.append(StringEscapeUtils.escapeHtml(string.substring(index, m.start())));
139                }
140                String tagName = m.group(1);
141                if (tagName!= null){
142                    if(PASS_THROUGH_TAGS.contains(tagName.toLowerCase(Locale.US))){
143                        sb.append(m.group());
144                    }
145                }else{
146                    sb.append(m.group());
147                }
148                index = m.end();
149            }else{
150                sb.append(StringEscapeUtils.escapeHtml(string.substring(index)));
151                break;
152            }
153        }
154    }
155}