View Javadoc
1   package org.apache.maven.tools.plugin.javadoc;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collections;
25  import java.util.Enumeration;
26  import java.util.List;
27  import java.util.StringTokenizer;
28  
29  import javax.swing.text.AttributeSet;
30  import javax.swing.text.MutableAttributeSet;
31  import javax.swing.text.SimpleAttributeSet;
32  
33  import com.sun.javadoc.Tag;
34  import com.sun.tools.doclets.Taglet;
35  
36  /**
37   * Abstract <code>Taglet</code> for <a href="http://maven.codehaus.org/"/>Maven</a> Mojo annotations.
38   * <br/>
39   * A Mojo annotation is defined like the following:
40   * <pre>
41   * &#64;annotation &lt;annotationValue&gt; &lt;parameterName="parameterValue"&gt;
42   * </pre>
43   *
44   * @see <a href="package-summary.html#package_description">package-summary.html</a>
45   *
46   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
47   * @version $Id: AbstractMojoTaglet.html 1024032 2018-01-19 18:16:30Z hboutemy $
48   */
49  public abstract class AbstractMojoTaglet
50      implements Taglet
51  {
52      /** {@inheritDoc} */
53      public String toString( Tag tag )
54      {
55          if ( tag == null )
56          {
57              return null;
58          }
59  
60          String tagValue = getTagValue( tag );
61          MutableAttributeSet tagAttributes = getTagAttributes( tag );
62  
63          StringBuilder sb = new StringBuilder();
64  
65          appendTag( sb, tag, tagAttributes, tagValue );
66  
67          return sb.toString();
68      }
69  
70      /** {@inheritDoc} */
71      public String toString( Tag[] tags )
72      {
73          if ( tags.length == 0 )
74          {
75              return null;
76          }
77  
78          StringBuilder sb = new StringBuilder();
79          for ( int i = 0; i < tags.length; i++ )
80          {
81              String tagValue = getTagValue( tags[i] );
82              MutableAttributeSet tagAttributes = getTagAttributes( tags[i] );
83  
84              appendTag( sb, tags[i], tagAttributes, tagValue );
85          }
86  
87          return sb.toString();
88      }
89  
90      /**
91       * @return the header, i.e. the message, to display
92       */
93      public abstract String getHeader();
94  
95      /**
96       * @return the given annotation value, or <code>null</code> if the given Mojo annotation/tag does't allow
97       * annotation value.
98       * <br/>
99       * <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 }