001    package org.apache.maven.tools.plugin.javadoc;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import java.util.ArrayList;
023    import java.util.Arrays;
024    import java.util.Collections;
025    import java.util.Enumeration;
026    import java.util.List;
027    import java.util.StringTokenizer;
028    
029    import javax.swing.text.AttributeSet;
030    import javax.swing.text.MutableAttributeSet;
031    import javax.swing.text.SimpleAttributeSet;
032    
033    import com.sun.javadoc.Tag;
034    import com.sun.tools.doclets.Taglet;
035    
036    /**
037     * Abstract <code>Taglet</code> for <a href="http://maven.codehaus.org/"/>Maven</a> Mojo annotations.
038     * <br/>
039     * A Mojo annotation is defined like the following:
040     * <pre>
041     * &#64;annotation &lt;annotationValue&gt; &lt;parameterName="parameterValue"&gt;
042     * </pre>
043     *
044     * @see <a href="package-summary.html#package_description">package-summary.html</a>
045     *
046     * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
047     * @version $Id: AbstractMojoTaglet.java 1338289 2012-05-14 16:53:36Z olamy $
048     */
049    public abstract class AbstractMojoTaglet
050        implements Taglet
051    {
052        /** {@inheritDoc} */
053        public String toString( Tag tag )
054        {
055            if ( tag == null )
056            {
057                return null;
058            }
059    
060            String tagValue = getTagValue( tag );
061            MutableAttributeSet tagAttributes = getTagAttributes( tag );
062    
063            StringBuilder sb = new StringBuilder();
064    
065            appendTag( sb, tag, tagAttributes, tagValue );
066    
067            return sb.toString();
068        }
069    
070        /** {@inheritDoc} */
071        public String toString( Tag[] tags )
072        {
073            if ( tags.length == 0 )
074            {
075                return null;
076            }
077    
078            StringBuilder sb = new StringBuilder();
079            for ( int i = 0; i < tags.length; i++ )
080            {
081                String tagValue = getTagValue( tags[i] );
082                MutableAttributeSet tagAttributes = getTagAttributes( tags[i] );
083    
084                appendTag( sb, tags[i], tagAttributes, tagValue );
085            }
086    
087            return sb.toString();
088        }
089    
090        /**
091         * @return the header, i.e. the message, to display
092         */
093        public abstract String getHeader();
094    
095        /**
096         * @return the given annotation value, or <code>null</code> if the given Mojo annotation/tag does't allow
097         * annotation value.
098         * <br/>
099         * <b>Note</b>: the value could be a pattern value, i.e.: <code>*</code> for every values, <code>a|b|c</code>
100         * for <code>a OR b OR c</code>.
101         */
102        public abstract String getAllowedValue();
103    
104        /**
105         * @return an array of the allowed parameter names for the given Mojo annotation/tag, or <code>null</code>
106         * if the annotation/tag doesn't allow parameter.
107         */
108        public abstract String[] getAllowedParameterNames();
109    
110        /**
111         * @return <code>true</code> if taglet has annotation value, <code>false</code> otherwise.
112         * @see #getAllowedValue()
113         */
114        public boolean hasAnnotationValue()
115        {
116            return getAllowedValue() != null;
117        }
118    
119        /**
120         * @return <code>true</code> if taglet has parameters, <code>false</code> otherwise.
121         * @see #getAllowedParameterNames()
122         */
123        public boolean hasAnnotationParameters()
124        {
125            return getAllowedParameterNames() != null;
126        }
127    
128        /**
129         * @param tag not null.
130         * @return a not null String or <code>null</code> if no annotation value was found.
131         */
132        private String getTagValue( Tag tag )
133        {
134            if ( tag == null )
135            {
136                throw new IllegalArgumentException( "tag should be not null" );
137            }
138    
139            String text = tag.text();
140            if ( isEmpty( text ) )
141            {
142                // using pattern: @annotation
143                return null;
144            }
145    
146            String tagValue = null;
147            StringTokenizer token = new StringTokenizer( text, " " );
148            while ( token.hasMoreTokens() )
149            {
150                String nextToken = token.nextToken();
151    
152                if ( nextToken.indexOf( "=" ) == -1 )
153                {
154                    // using pattern: @annotation <annotationValue>
155                    tagValue = nextToken;
156                }
157            }
158    
159            return tagValue;
160        }
161    
162        /**
163         * @param tag not null.
164         * @return a not null MutableAttributeSet.
165         */
166        private MutableAttributeSet getTagAttributes( Tag tag )
167        {
168            if ( tag == null )
169            {
170                throw new IllegalArgumentException( "tag should be not null" );
171            }
172    
173            String text = tag.text();
174    
175            StringTokenizer token = new StringTokenizer( text, " " );
176            MutableAttributeSet tagAttributes = new SimpleAttributeSet();
177            while ( token.hasMoreTokens() )
178            {
179                String nextToken = token.nextToken();
180    
181                if ( nextToken.indexOf( "=" ) == -1 )
182                {
183                    // using pattern: @annotation <annotationValue>
184                    continue;
185                }
186    
187                StringTokenizer token2 = new StringTokenizer( nextToken, "=" );
188                if ( token2.countTokens() != 2 )
189                {
190                    System.err.println( "The annotation '" + tag.name() + "' has no name/value pairs parameter: "
191                        + tag.name() + " " + text + " (" + tag.position().file() + ":" + tag.position().line() + ":"
192                        + tag.position().column() + ")" );
193                    tagAttributes.addAttribute( token2.nextToken(), "" );
194                    continue;
195                }
196    
197                String name = token2.nextToken();
198                String value = token2.nextToken().replaceAll( "\"", "" );
199    
200                if ( getAllowedParameterNames() != null && !Arrays.asList( getAllowedParameterNames() ).contains( name ) )
201                {
202                    System.err.println( "The annotation '" + tag.name() + "' has wrong parameter name: " + tag.name() + " "
203                        + text + " (" + tag.position().file() + ":" + tag.position().line() + ":" + tag.position().column()
204                        + ")" );
205                }
206    
207                tagAttributes.addAttribute( name, value );
208            }
209    
210            return tagAttributes;
211        }
212    
213        /**
214         * Append a tag
215         *
216         * @param sb not null
217         * @param tag not null
218         * @param tagAttributes not null
219         * @param tagValue not null
220         */
221        private void appendTag( StringBuilder sb, Tag tag, MutableAttributeSet tagAttributes, String tagValue )
222        {
223            if ( !hasAnnotationParameters() )
224            {
225                if ( tagAttributes.getAttributeCount() > 0 )
226                {
227                    System.err.println( "The annotation '@" + getName() + "' should have no attribute ("
228                        + tag.position().file() + ":" + tag.position().line() + ":" + tag.position().column() + ")" );
229                }
230    
231                if ( hasAnnotationValue() )
232                {
233                    sb.append( "<DT><B>" ).append( getHeader() ).append( ":</B></DT>" );
234                    if ( isEveryValues( getAllowedValue() ) )
235                    {
236                        if ( isNotEmpty( tagValue ) )
237                        {
238                            sb.append( "<DD>" ).append( tagValue ).append( "</DD>" );
239                        }
240                        else
241                        {
242                            System.err.println( "The annotation '@" + getName() + "' is specified to have a value but "
243                                + "no value is defined (" + tag.position().file() + ":" + tag.position().line() + ":"
244                                + tag.position().column() + ")" );
245                            sb.append( "<DD>" ).append( "NOT DEFINED" ).append( "</DD>" );
246                        }
247                    }
248                    else
249                    {
250                        List<String> l = getOnlyValues( getAllowedValue() );
251                        if ( isNotEmpty( tagValue ) )
252                        {
253                            if ( l.contains( tagValue ) )
254                            {
255                                sb.append( "<DD>" ).append( tagValue ).append( "</DD>" );
256                            }
257                            else
258                            {
259                                System.err.println( "The annotation '@" + getName() + "' is specified to be a value of "
260                                    + l + " (" + tag.position().file() + ":" + tag.position().line() + ":"
261                                    + tag.position().column() + ")" );
262                                sb.append( "<DD>" ).append( tagValue ).append( "</DD>" );
263                            }
264                        }
265                        else
266                        {
267                            sb.append( "<DD>" ).append( l.get( 0 ) ).append( "</DD>" );
268                        }
269                    }
270                }
271                else
272                {
273                    if ( isNotEmpty( tagValue ) )
274                    {
275                        System.err.println( "The annotation '@" + getName() + "' should have no value ("
276                            + tag.position().file() + ":" + tag.position().line() + ":" + tag.position().column() + ")" );
277                    }
278                    sb.append( "<DT><B>" ).append( getHeader() ).append( "</B></DT>" );
279                    sb.append( "<DD></DD>" );
280                }
281            }
282            else
283            {
284                if ( hasAnnotationValue() )
285                {
286                    sb.append( "<DT><B>" ).append( getHeader() ).append( ":</B></DT>" );
287                    if ( isEveryValues( getAllowedValue() ) )
288                    {
289                        if ( isNotEmpty( tagValue ) )
290                        {
291                            sb.append( "<DD>" ).append( tagValue );
292                        }
293                        else
294                        {
295                            System.err.println( "The annotation '@" + getName() + "' is specified to have a value but "
296                                + "no value is defined (" + tag.position().file() + ":" + tag.position().line() + ":"
297                                + tag.position().column() + ")" );
298                            sb.append( "<DD>" ).append( "NOT DEFINED" );
299                        }
300                    }
301                    else
302                    {
303                        List<String> l = getOnlyValues( getAllowedValue() );
304                        if ( isNotEmpty( tagValue ) )
305                        {
306                            if ( l.contains( tagValue ) )
307                            {
308                                sb.append( "<DD>" ).append( tagValue );
309                            }
310                            else
311                            {
312                                System.err.println( "The annotation '@" + getName() + "' is specified to be a value in "
313                                    + l + " (" + tag.position().file() + ":" + tag.position().line() + ":"
314                                    + tag.position().column() + ")" );
315                                sb.append( "<DD>" ).append( tagValue );
316                            }
317                        }
318                        else
319                        {
320                            sb.append( "<DD>" ).append( l.get( 0 ) );
321                        }
322                    }
323                }
324                else
325                {
326                    if ( isNotEmpty( tagValue ) )
327                    {
328                        System.err.println( "The annotation '@" + getName() + "' should have no value ("
329                            + tag.position().file() + ":" + tag.position().line() + ":" + tag.position().column() + ")" );
330                    }
331                    sb.append( "<DT><B>" ).append( getHeader() ).append( ":</B></DT>" );
332                    sb.append( "<DD>" );
333                }
334    
335                appendAnnotationParameters( sb, tagAttributes );
336                sb.append( "</DD>" );
337            }
338        }
339    
340        /**
341         * Append the annotation parameters as a definition list.
342         *
343         * @param sb not null
344         * @param att not null
345         */
346        private static void appendAnnotationParameters( StringBuilder sb, MutableAttributeSet att )
347        {
348            sb.append( "<DL>" );
349    
350            Enumeration<?> names = att.getAttributeNames();
351            while ( names.hasMoreElements() )
352            {
353                Object key = names.nextElement();
354                Object value = att.getAttribute( key );
355    
356                if ( value instanceof AttributeSet )
357                {
358                    // ignored
359                }
360                else
361                {
362                    sb.append( "<DT><B>" ).append( key ).append( ":</B></DT>" );
363                    sb.append( "<DD>" ).append( value ).append( "</DD>" );
364                }
365            }
366    
367            sb.append( "</DL>" );
368        }
369    
370        /**
371         * @param text not null
372         * @return <code>true</code> if text contains <code>*</code>, <code>false</code> otherwise.
373         */
374        private static boolean isEveryValues( String text )
375        {
376            return text.trim().equals( "*" );
377        }
378    
379        /**
380         * Splits the provided text into a array, using pipe as the separator.
381         *
382         * @param text not null
383         * @return a list of parsed Strings or <code>Collections.EMPTY_LIST</code>.
384         * By convention, the default value is the first element.
385         */
386        private static List<String> getOnlyValues( String text )
387        {
388            if ( text.indexOf( "|" ) == -1 )
389            {
390                return Collections.emptyList();
391            }
392    
393            List<String> l = new ArrayList<String>();
394            StringTokenizer token = new StringTokenizer( text, "|" );
395            while ( token.hasMoreTokens() )
396            {
397                l.add( token.nextToken() );
398            }
399    
400            return l;
401        }
402    
403        /**
404         * <p>Checks if a String is non <code>null</code> and is
405         * not empty (<code>length > 0</code>).</p>
406         *
407         * @param str the String to check
408         * @return true if the String is non-null, and not length zero
409         */
410        private static boolean isNotEmpty( String str )
411        {
412            return ( str != null && str.length() > 0 );
413        }
414    
415        /**
416         * <p>Checks if a (trimmed) String is <code>null</code> or empty.</p>
417         *
418         * @param str the String to check
419         * @return <code>true</code> if the String is <code>null</code>, or
420         *  length zero once trimmed
421         */
422        private static boolean isEmpty( String str )
423        {
424            return ( str == null || str.trim().length() == 0 );
425        }
426    }