View Javadoc

1   package org.apache.torque.generator.outlet.java;
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.HashSet;
23  import java.util.Set;
24  import java.util.StringTokenizer;
25  
26  import org.apache.commons.lang.StringUtils;
27  import org.apache.torque.generator.GeneratorException;
28  import org.apache.torque.generator.control.ControllerState;
29  import org.apache.torque.generator.outlet.OutletImpl;
30  import org.apache.torque.generator.outlet.OutletResult;
31  import org.apache.torque.generator.qname.QualifiedName;
32  
33  /**
34   * An outlet for creating correctly formatted javadoc.
35   *
36   * @version $Id: JavadocOutlet.java 1388635 2012-09-21 19:27:28Z tfischer $
37   */
38  public class JavadocOutlet extends OutletImpl
39  {
40      /** The name of the mergepoint which output is used as javadoc body. */
41      public static final String BODY_MERGEPOINT_NAME = "body";
42  
43      /** The name of the mergepoint which contains the javadoc attributes. */
44      public static final String ATTRIBUTES_MERGEPOINT_NAME = "attributes";
45  
46      /** The first line of the javadoc. */
47      private static final String START_LINE = "/**";
48  
49      /** The mid line start of the javadoc. */
50      private static final String MID_LINE_START = " * ";
51  
52      /** The last line of the javadoc. */
53      private static final String END_LINE = " */";
54  
55      /** The character which starts a javadoc attribute. */
56      private static final String JAVADOC_ATTRIBUTE_START = "@";
57  
58      /** The default maximum line width. */
59      private static final int DEFAULT_MAX_LINEWIDTH = 80;
60  
61      /** The line break string. */
62      private String lineBreak = "\n";
63  
64      /** How long a line can be before it will be wrapped, -1 for no wrapping */
65      private int maxLineLength = DEFAULT_MAX_LINEWIDTH;
66  
67      /** The indent to use in front of each line. */
68      private String indent = "    ";
69  
70      /**
71       * At which characters a line can be wrapped
72       * with the character removed.
73       */
74      private String removableWrapCharacters = " ";
75  
76      /**
77       * After which characters a line can be wrapped
78       * without the character removed.
79       */
80      private String wrapAfterCharacters = ".;,-";
81  
82      /**
83       * Constructor.
84       *
85       * @param qualifiedName the qualified name of the outlet, not null.
86       */
87      public JavadocOutlet(QualifiedName qualifiedName)
88      {
89          super(qualifiedName);
90      }
91  
92      @Override
93      public OutletResult execute(ControllerState controllerState)
94              throws GeneratorException
95      {
96          StringBuilder result = new StringBuilder();
97          result.append(indent).append(START_LINE).append(lineBreak);
98          String body = mergepoint(BODY_MERGEPOINT_NAME, controllerState);
99          result.append(wrapLinesAndIndent(body));
100         {
101             String attributes = mergepoint(
102                     ATTRIBUTES_MERGEPOINT_NAME,
103                     controllerState);
104             if (!StringUtils.isEmpty(attributes)
105                 && !StringUtils.isEmpty(body))
106             {
107                 result.append(indent).append(" *").append(lineBreak);
108             }
109             if (!StringUtils.isEmpty(attributes))
110             {
111                  result.append(wrapLinesAndIndent(attributes));
112             }
113         }
114 
115         result.append(indent).append(END_LINE).append(lineBreak);
116         return new OutletResult(result.toString());
117     }
118 
119     /**
120      * Wraps the content string such that the line length is not longer than
121      * maxLineLength characters, if that can be achieved by wrapping at the
122      * wrap characters. All resulting lines are also indented.
123      *
124      * @param content The content to wrap, may be null.
125      *
126      * @return the wrapped and indented content, not null.
127      */
128     String wrapLinesAndIndent(String content)
129     {
130         if (StringUtils.isBlank(content))
131         {
132             return "";
133         }
134         StringTokenizer tokenizer
135                 = new StringTokenizer(
136                         content.trim(),
137                         removableWrapCharacters
138                             + wrapAfterCharacters
139                             + "\r\n"
140                             + JAVADOC_ATTRIBUTE_START,
141                         true);
142         StringBuilder result = new StringBuilder();
143         result.append(indent).append(MID_LINE_START);
144         int currentLineLength = indent.length() + MID_LINE_START.length();
145         boolean lineBreakPossible = false;
146         // last char is space so it can be removed
147         boolean lastCharRemovable = true;
148         String token = null;
149         String currentIndent = indent + MID_LINE_START;
150         String lastJavadocAttribute = null;
151         while (tokenizer.hasMoreTokens() || token != null)
152         {
153             if (token == null)
154             {
155                 // token has not been prefetched
156                 token = tokenizer.nextToken();
157             }
158             if ("\r".equals(token))
159             {
160                 // Assumption: no \r without line breaks
161                 // always remove, will be added again if linebreak is \r\n
162                 token = null;
163             }
164             else if ("\n".equals(token))
165             {
166                 // line break does not count towards line length
167                 result.append(lineBreak);
168                 lineBreakPossible = false;
169                 // because currentIndent ends with a space
170                 // we can remove the last char
171                 lastCharRemovable = true;
172                 if (tokenizer.hasMoreTokens())
173                 {
174                     result.append(currentIndent);
175                     currentLineLength = currentIndent.length();
176                 }
177                 token = null;
178             }
179             else if (JAVADOC_ATTRIBUTE_START.equals(token))
180             {
181                 if (tokenizer.hasMoreTokens())
182                 {
183 
184                     token = tokenizer.nextToken();
185                     // + 2 because of "@" + space after attribute
186                     currentIndent = StringUtils.rightPad(
187                             indent + MID_LINE_START,
188                             indent.length() + MID_LINE_START.length()
189                                 + 2 + token.length());
190                     if (result.length()
191                             > indent.length() + MID_LINE_START.length())
192                     {
193                         // we could already be indented by a line break
194                         // in a previous param
195                         removeEnd(result, " \r\n");
196                         boolean alreadyIndented = false;
197                         if (result.toString().endsWith(indent + " *"))
198                         {
199                             alreadyIndented = true;
200                         }
201                         boolean doubleLineBreak = false;
202                         if (!token.equals(lastJavadocAttribute))
203                         {
204                             doubleLineBreak = true;
205                         }
206                         if (!alreadyIndented)
207                         {
208                             result.append(lineBreak).append(indent).append(" *");
209                         }
210                         if (doubleLineBreak)
211                         {
212                             result.append(lineBreak).append(indent).append(" *");
213                         }
214                         result.append(" ");
215                     }
216                     //+ 3 because " * "
217                     currentLineLength
218                             = indent.length() + MID_LINE_START.length();
219                     lastJavadocAttribute = token;
220                 }
221                 result.append(JAVADOC_ATTRIBUTE_START);
222                 ++currentLineLength;
223                 lineBreakPossible = false;
224                 lastCharRemovable = false;
225             }
226             else if (maxLineLength == -1)
227             {
228                 result.append(token);
229                 token = null;
230             }
231             else if (removableWrapCharacters.indexOf(token) != -1)
232             {
233                 if (currentLineLength + 1 > maxLineLength)
234                 {
235                     result.append(lineBreak);
236                     if (tokenizer.hasMoreTokens())
237                     {
238                         result.append(currentIndent);
239                         currentLineLength = currentIndent.length();
240                     }
241                     lineBreakPossible = false;
242                     lastCharRemovable = false;
243                 }
244                 else
245                 {
246                     result.append(token);
247                     currentLineLength++;
248                     lineBreakPossible = true;
249                     lastCharRemovable = true;
250                 }
251                 token = null;
252             }
253             else if (lineBreakPossible)
254             {
255                 // we must check next token
256                 String nextToken = null;
257                 if (tokenizer.hasMoreTokens())
258                 {
259                     nextToken = tokenizer.nextToken();
260                 }
261                 int unbreakableChunkSize;
262                 if (nextToken == null
263                     || "\r".equals(nextToken)
264                     || "\n".equals(nextToken)
265                     || wrapAfterCharacters.contains(token)
266                     || JAVADOC_ATTRIBUTE_START.equals(nextToken)
267                     || removableWrapCharacters.contains(nextToken))
268                 {
269                     unbreakableChunkSize = token.length();
270                 }
271                 else
272                 {
273                     unbreakableChunkSize = token.length() + nextToken.length();
274                 }
275                 if (currentLineLength + unbreakableChunkSize > maxLineLength)
276                 {
277                     // break now
278                     if (lastCharRemovable)
279                     {
280                         result.replace(
281                                 result.length() - 1,
282                                 result.length(),
283                                 "");
284                     }
285                     result.append(lineBreak)
286                         .append(currentIndent)
287                         .append(token);
288                     currentLineLength
289                             = currentIndent.length() + token.length();
290                 }
291                 else
292                 {
293                     // no break necessary
294                     result.append(token);
295                     currentLineLength += token.length();
296                 }
297                 lastCharRemovable = false;
298                 lineBreakPossible = wrapAfterCharacters.contains(token);
299                 token = nextToken;
300             }
301             else
302             {
303                 result.append(token);
304                 currentLineLength += token.length();
305                 lastCharRemovable = false;
306                 lineBreakPossible = wrapAfterCharacters.contains(token);
307                 token = null;
308             }
309         }
310         if (!result.toString().endsWith(lineBreak))
311         {
312             result.append(lineBreak);
313         }
314         return result.toString();
315     }
316 
317     public String getLineBreak()
318     {
319         return lineBreak;
320     }
321 
322     public void setLineBreak(String lineBreak)
323     {
324         if (!("\r".equals(lineBreak)) && !("\r\n".equals(lineBreak)))
325         {
326             throw new IllegalArgumentException(
327                     "lineBreak must be either \\r or \\r\\n");
328         }
329         this.lineBreak = lineBreak;
330     }
331 
332     public int getMaxLineLength()
333     {
334         return maxLineLength;
335     }
336 
337     public void setMaxLineLength(int maxLineLength)
338     {
339         this.maxLineLength = maxLineLength;
340     }
341 
342     public String getIndent()
343     {
344         return indent;
345     }
346 
347     public void setIndent(String indent)
348     {
349         this.indent = indent;
350     }
351 
352     public String getRemovableWrapCharacters()
353     {
354         return removableWrapCharacters;
355     }
356 
357     public void setRemovableWrapCharacters(String removableWrapCharacters)
358     {
359         this.removableWrapCharacters = removableWrapCharacters;
360     }
361 
362     public String getWrapAfterCharacters()
363     {
364         return wrapAfterCharacters;
365     }
366 
367     public void setWrapAfterCharacters(String wrapAfterCharacters)
368     {
369         this.wrapAfterCharacters = wrapAfterCharacters;
370     }
371 
372     /**
373      * Removes the trailing characters from a string builder.
374      * The characters to be removed are passed in as parameter.
375      *
376      * @param stringBuilder the string builder to remove the end from, not null.
377      * @param removeChars The characters to remove if they appear at the end,
378      *        not null.
379      */
380     static void removeEnd(StringBuilder stringBuilder, String removeChars)
381     {
382         Set<Character> removeCharSet = new HashSet<Character>();
383         for (char character : removeChars.toCharArray())
384         {
385             removeCharSet.add(character);
386         }
387         int index = stringBuilder.length();
388         while (index > 0)
389         {
390             if (!removeCharSet.contains(stringBuilder.charAt(index - 1)))
391             {
392                 break;
393             }
394             index--;
395         }
396         // index is now last char in String which does not match pattern
397         // maybe -1 if all the string matches or the input is empty
398         stringBuilder.replace(index, stringBuilder.length(), "");
399     }
400 }