Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
AbstractMavenReportRenderer |
|
| 4.115384615384615;4,115 |
1 | package org.apache.maven.reporting; | |
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 org.apache.commons.validator.EmailValidator; | |
23 | import org.apache.commons.validator.UrlValidator; | |
24 | ||
25 | import org.apache.maven.doxia.sink.Sink; | |
26 | import org.apache.maven.doxia.util.HtmlTools; | |
27 | ||
28 | import org.codehaus.plexus.util.StringUtils; | |
29 | ||
30 | import java.util.ArrayList; | |
31 | import java.util.Collections; | |
32 | import java.util.Iterator; | |
33 | import java.util.List; | |
34 | import java.util.Map; | |
35 | import java.util.Properties; | |
36 | ||
37 | /** | |
38 | * An abstract class to manage report generation, with many helper methods to ease the job: you just need to | |
39 | * implement getTitle() and renderBody(). | |
40 | * | |
41 | * @author <a href="mailto:jason@maven.org">Jason van Zyl</a> | |
42 | * @author <a href="evenisse@apache.org">Emmanuel Venisse</a> | |
43 | * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a> | |
44 | * @version $Id: AbstractMavenReportRenderer.java 1164753 2011-09-02 22:50:23Z hboutemy $ | |
45 | * @since 2.0 | |
46 | * @TODO Later it may be appropriate to create something like a VelocityMavenReportRenderer | |
47 | * that could take a velocity template and pipe that through Doxia rather than coding them | |
48 | * up like this. | |
49 | * @see #getTitle() | |
50 | * @see #renderBody() | |
51 | */ | |
52 | public abstract class AbstractMavenReportRenderer | |
53 | implements MavenReportRenderer | |
54 | { | |
55 | /** The current sink to use */ | |
56 | protected Sink sink; | |
57 | ||
58 | /** The current section number */ | |
59 | private int section; | |
60 | ||
61 | /** | |
62 | * Default constructor. | |
63 | * | |
64 | * @param sink the sink to use. | |
65 | */ | |
66 | public AbstractMavenReportRenderer( Sink sink ) | |
67 | 0 | { |
68 | 0 | this.sink = sink; |
69 | 0 | } |
70 | ||
71 | /** {@inheritDoc} */ | |
72 | public void render() | |
73 | { | |
74 | 0 | sink.head(); |
75 | ||
76 | 0 | sink.title(); |
77 | 0 | text( getTitle() ); |
78 | 0 | sink.title_(); |
79 | ||
80 | 0 | sink.head_(); |
81 | ||
82 | 0 | sink.body(); |
83 | 0 | renderBody(); |
84 | 0 | sink.body_(); |
85 | ||
86 | 0 | sink.flush(); |
87 | ||
88 | 0 | sink.close(); |
89 | 0 | } |
90 | ||
91 | // ---------------------------------------------------------------------- | |
92 | // Section handler | |
93 | // ---------------------------------------------------------------------- | |
94 | ||
95 | /** | |
96 | * Convenience method to wrap section creation in the current sink. An anchor will be add for the name. | |
97 | * | |
98 | * @param name the name of this section, could be null. | |
99 | * @see #text(String) | |
100 | * @see Sink#section1() | |
101 | * @see Sink#sectionTitle1() | |
102 | * @see Sink#sectionTitle1_() | |
103 | * @see Sink#section2() | |
104 | * @see Sink#sectionTitle2() | |
105 | * @see Sink#sectionTitle2_() | |
106 | * @see Sink#section3() | |
107 | * @see Sink#sectionTitle3() | |
108 | * @see Sink#sectionTitle3_() | |
109 | * @see Sink#section4() | |
110 | * @see Sink#sectionTitle4() | |
111 | * @see Sink#sectionTitle4_() | |
112 | * @see Sink#section5() | |
113 | * @see Sink#sectionTitle5() | |
114 | * @see Sink#sectionTitle5_() | |
115 | */ | |
116 | protected void startSection( String name ) | |
117 | { | |
118 | 0 | section = section + 1; |
119 | ||
120 | 0 | switch ( section ) |
121 | { | |
122 | case 1: | |
123 | 0 | sink.section1(); |
124 | 0 | sink.sectionTitle1(); |
125 | 0 | break; |
126 | case 2: | |
127 | 0 | sink.section2(); |
128 | 0 | sink.sectionTitle2(); |
129 | 0 | break; |
130 | case 3: | |
131 | 0 | sink.section3(); |
132 | 0 | sink.sectionTitle3(); |
133 | 0 | break; |
134 | case 4: | |
135 | 0 | sink.section4(); |
136 | 0 | sink.sectionTitle4(); |
137 | 0 | break; |
138 | case 5: | |
139 | 0 | sink.section5(); |
140 | 0 | sink.sectionTitle5(); |
141 | 0 | break; |
142 | ||
143 | default: | |
144 | // TODO: warning - just don't start a section | |
145 | break; | |
146 | } | |
147 | ||
148 | 0 | text( name ); |
149 | ||
150 | 0 | switch ( section ) |
151 | { | |
152 | case 1: | |
153 | 0 | sink.sectionTitle1_(); |
154 | 0 | break; |
155 | case 2: | |
156 | 0 | sink.sectionTitle2_(); |
157 | 0 | break; |
158 | case 3: | |
159 | 0 | sink.sectionTitle3_(); |
160 | 0 | break; |
161 | case 4: | |
162 | 0 | sink.sectionTitle4_(); |
163 | 0 | break; |
164 | case 5: | |
165 | 0 | sink.sectionTitle5_(); |
166 | 0 | break; |
167 | ||
168 | default: | |
169 | // TODO: warning - just don't start a section | |
170 | break; | |
171 | } | |
172 | ||
173 | 0 | sink.anchor( HtmlTools.encodeId( name ) ); |
174 | 0 | sink.anchor_(); |
175 | 0 | } |
176 | ||
177 | /** | |
178 | * Convenience method to wrap section ending in the current sink. | |
179 | * | |
180 | * @see Sink#section1_() | |
181 | * @see Sink#section2_() | |
182 | * @see Sink#section3_() | |
183 | * @see Sink#section4_() | |
184 | * @see Sink#section5_() | |
185 | * @IllegalStateException if too many closing sections. | |
186 | */ | |
187 | protected void endSection() | |
188 | { | |
189 | 0 | switch ( section ) |
190 | { | |
191 | case 1: | |
192 | 0 | sink.section1_(); |
193 | 0 | break; |
194 | case 2: | |
195 | 0 | sink.section2_(); |
196 | 0 | break; |
197 | case 3: | |
198 | 0 | sink.section3_(); |
199 | 0 | break; |
200 | case 4: | |
201 | 0 | sink.section4_(); |
202 | 0 | break; |
203 | case 5: | |
204 | 0 | sink.section5_(); |
205 | 0 | break; |
206 | ||
207 | default: | |
208 | // TODO: warning - just don't start a section | |
209 | break; | |
210 | } | |
211 | ||
212 | 0 | section = section - 1; |
213 | ||
214 | 0 | if ( section < 0 ) |
215 | { | |
216 | 0 | throw new IllegalStateException( "Too many closing sections" ); |
217 | } | |
218 | 0 | } |
219 | ||
220 | // ---------------------------------------------------------------------- | |
221 | // Table handler | |
222 | // ---------------------------------------------------------------------- | |
223 | ||
224 | /** | |
225 | * Convenience method to wrap the table start in the current sink. | |
226 | * | |
227 | * @see Sink#table() | |
228 | */ | |
229 | protected void startTable() | |
230 | { | |
231 | 0 | startTable( new int[] {Sink.JUSTIFY_LEFT}, false ); |
232 | 0 | } |
233 | ||
234 | /** | |
235 | * Convenience method to wrap the table start in the current sink. | |
236 | * | |
237 | * @param justification the justification of table cells. | |
238 | * @param grid whether to draw a grid around cells. | |
239 | * | |
240 | * @see Sink#table() | |
241 | * @see Sink#tableRows(int[],boolean) | |
242 | * @since 2.1 | |
243 | */ | |
244 | protected void startTable( int[] justification, boolean grid ) | |
245 | { | |
246 | 0 | sink.table(); |
247 | 0 | sink.tableRows( justification, grid ); |
248 | 0 | } |
249 | ||
250 | /** | |
251 | * Convenience method to wrap the table ending in the current sink. | |
252 | * | |
253 | * @see Sink#table_() | |
254 | */ | |
255 | protected void endTable() | |
256 | { | |
257 | 0 | sink.tableRows_(); |
258 | 0 | sink.table_(); |
259 | 0 | } |
260 | ||
261 | /** | |
262 | * Convenience method to wrap the table header cell start in the current sink. | |
263 | * | |
264 | * @param text the text to put in this cell, could be null. | |
265 | * @see #text(String) | |
266 | * @see Sink#tableHeaderCell() | |
267 | * @see Sink#tableHeaderCell_() | |
268 | */ | |
269 | protected void tableHeaderCell( String text ) | |
270 | { | |
271 | 0 | sink.tableHeaderCell(); |
272 | ||
273 | 0 | text( text ); |
274 | ||
275 | 0 | sink.tableHeaderCell_(); |
276 | 0 | } |
277 | ||
278 | /** | |
279 | * Convenience method to wrap a table cell start in the current sink. | |
280 | * <p>The text could be a link patterned text defined by <code>{text, url}</code></p> | |
281 | * | |
282 | * @param text the text to put in this cell, could be null. | |
283 | * @see #linkPatternedText(String) | |
284 | * @see #tableCell(String) | |
285 | */ | |
286 | protected void tableCell( String text ) | |
287 | { | |
288 | 0 | tableCell( text, false ); |
289 | 0 | } |
290 | ||
291 | /** | |
292 | * Convenience method to wrap a table cell start in the current sink. | |
293 | * <p>The text could be a link patterned text defined by <code>{text, url}</code></p> | |
294 | * <p>If <code>asHtml</code> is true, add the text as Html</p> | |
295 | * | |
296 | * @param text the text to put in this cell, could be null. | |
297 | * @param asHtml <tt>true</tt> to add the text as Html, <tt>false</tt> otherwise. | |
298 | * @see #linkPatternedText(String) | |
299 | * @see Sink#tableCell() | |
300 | * @see Sink#tableCell_() | |
301 | * @see Sink#rawText(String) | |
302 | */ | |
303 | protected void tableCell( String text, boolean asHtml ) | |
304 | { | |
305 | 0 | sink.tableCell(); |
306 | ||
307 | 0 | if ( asHtml ) |
308 | { | |
309 | 0 | sink.rawText( text ); |
310 | } | |
311 | else | |
312 | { | |
313 | 0 | linkPatternedText( text ); |
314 | } | |
315 | ||
316 | 0 | sink.tableCell_(); |
317 | 0 | } |
318 | ||
319 | /** | |
320 | * Convenience method to wrap a table row start in the current sink. | |
321 | * <p>The texts in the <code>content</code> could be link patterned texts defined by <code>{text, url}</code></p> | |
322 | * | |
323 | * @param content an array of text to put in the cells in this row, could be null. | |
324 | * @see #tableCell(String) | |
325 | * @see Sink#tableRow() | |
326 | * @see Sink#tableRow_() | |
327 | */ | |
328 | protected void tableRow( String[] content ) | |
329 | { | |
330 | 0 | sink.tableRow(); |
331 | ||
332 | 0 | if ( content != null ) |
333 | { | |
334 | 0 | for ( int i = 0; i < content.length; i++ ) |
335 | { | |
336 | 0 | tableCell( content[i] ); |
337 | } | |
338 | } | |
339 | ||
340 | 0 | sink.tableRow_(); |
341 | 0 | } |
342 | ||
343 | /** | |
344 | * Convenience method to wrap a table header row start in the current sink. | |
345 | * <p>The texts in the <code>content</code> could be link patterned texts defined by <code>{text, url}</code></p> | |
346 | * | |
347 | * @param content an array of text to put in the cells in this row header, could be null. | |
348 | * @see #tableHeaderCell(String) | |
349 | * @see Sink#tableRow() | |
350 | * @see Sink#tableRow_() | |
351 | */ | |
352 | protected void tableHeader( String[] content ) | |
353 | { | |
354 | 0 | sink.tableRow(); |
355 | ||
356 | 0 | if ( content != null ) |
357 | { | |
358 | 0 | for ( int i = 0; i < content.length; i++ ) |
359 | { | |
360 | 0 | tableHeaderCell( content[i] ); |
361 | } | |
362 | } | |
363 | ||
364 | 0 | sink.tableRow_(); |
365 | 0 | } |
366 | ||
367 | /** | |
368 | * Convenience method to wrap a table caption in the current sink. | |
369 | * | |
370 | * @param caption the caption of the table, could be null. | |
371 | * @see #text(String) | |
372 | * @see Sink#tableCaption() | |
373 | * @see Sink#tableCaption_() | |
374 | */ | |
375 | protected void tableCaption( String caption ) | |
376 | { | |
377 | 0 | sink.tableCaption(); |
378 | ||
379 | 0 | text( caption ); |
380 | ||
381 | 0 | sink.tableCaption_(); |
382 | 0 | } |
383 | ||
384 | // ---------------------------------------------------------------------- | |
385 | // Paragraph handler | |
386 | // ---------------------------------------------------------------------- | |
387 | ||
388 | /** | |
389 | * Convenience method to wrap a paragraph in the current sink. | |
390 | * | |
391 | * @param paragraph the paragraph to add, could be null. | |
392 | * @see #text(String) | |
393 | * @see Sink#paragraph() | |
394 | * @see Sink#paragraph_() | |
395 | */ | |
396 | protected void paragraph( String paragraph ) | |
397 | { | |
398 | 0 | sink.paragraph(); |
399 | ||
400 | 0 | text( paragraph ); |
401 | ||
402 | 0 | sink.paragraph_(); |
403 | 0 | } |
404 | ||
405 | /** | |
406 | * Convenience method to wrap a link in the current sink. | |
407 | * | |
408 | * @param href the link to add, cannot be null. | |
409 | * @param name the link name. | |
410 | * @see #text(String) | |
411 | * @see Sink#link(String) | |
412 | * @see Sink#link_() | |
413 | */ | |
414 | protected void link( String href, String name ) | |
415 | { | |
416 | 0 | sink.link( href ); |
417 | ||
418 | 0 | text( name ); |
419 | ||
420 | 0 | sink.link_(); |
421 | 0 | } |
422 | ||
423 | /** | |
424 | * Convenience method to wrap a text in the current sink. | |
425 | * <p>If text is empty or has a <code>null</code> value, add the <code>"-"</code> charater</p> | |
426 | * | |
427 | * @param text a text, could be null. | |
428 | * @see Sink#text(String) | |
429 | */ | |
430 | protected void text( String text ) | |
431 | { | |
432 | 0 | if ( StringUtils.isEmpty( text ) ) // Take care of spaces |
433 | { | |
434 | 0 | sink.text( "-" ); |
435 | } | |
436 | else | |
437 | { | |
438 | 0 | sink.text( text ); |
439 | } | |
440 | 0 | } |
441 | ||
442 | /** | |
443 | * Convenience method to wrap a text as verbatim style in the current sink . | |
444 | * | |
445 | * @param text a text, could be null. | |
446 | * @see #text(String) | |
447 | * @see Sink#verbatim(boolean) | |
448 | * @see Sink#verbatim_() | |
449 | */ | |
450 | protected void verbatimText( String text ) | |
451 | { | |
452 | 0 | sink.verbatim( true ); |
453 | ||
454 | 0 | text( text ); |
455 | ||
456 | 0 | sink.verbatim_(); |
457 | 0 | } |
458 | ||
459 | /** | |
460 | * Convenience method to wrap a text with a given link href as verbatim style in the current sink. | |
461 | * | |
462 | * @param text a string | |
463 | * @param href an href could be null | |
464 | * @see #link(String, String) | |
465 | * @see #verbatimText(String) | |
466 | * @see Sink#verbatim(boolean) | |
467 | * @see Sink#verbatim_() | |
468 | */ | |
469 | protected void verbatimLink( String text, String href ) | |
470 | { | |
471 | 0 | if ( StringUtils.isEmpty( href ) ) |
472 | { | |
473 | 0 | verbatimText( text ); |
474 | } | |
475 | else | |
476 | { | |
477 | 0 | sink.verbatim( true ); |
478 | ||
479 | 0 | link( href, text ); |
480 | ||
481 | 0 | sink.verbatim_(); |
482 | } | |
483 | 0 | } |
484 | ||
485 | /** | |
486 | * Convenience method to add a Javascript code in the current sink. | |
487 | * | |
488 | * @param jsCode a string of Javascript | |
489 | * @see Sink#rawText(String) | |
490 | */ | |
491 | protected void javaScript( String jsCode ) | |
492 | { | |
493 | 0 | sink.rawText( "<script type=\"text/javascript\">\n" + jsCode + "</script>" ); |
494 | 0 | } |
495 | ||
496 | /** | |
497 | * Convenience method to wrap a patterned text in the current link. | |
498 | * <p>The text variable should contained this given pattern <code>{text, url}</code> | |
499 | * to handle the link creation.</p> | |
500 | * | |
501 | * @param text a text with link pattern defined. | |
502 | * @see #text(String) | |
503 | * @see #link(String, String) | |
504 | * @see #applyPattern(String) | |
505 | */ | |
506 | public void linkPatternedText( String text ) | |
507 | { | |
508 | 0 | if ( StringUtils.isEmpty( text ) ) |
509 | { | |
510 | 0 | text( text ); |
511 | } | |
512 | else | |
513 | { | |
514 | 0 | List<String> segments = applyPattern( text ); |
515 | ||
516 | 0 | if ( segments == null ) |
517 | { | |
518 | 0 | text( text ); |
519 | } | |
520 | else | |
521 | { | |
522 | 0 | for ( Iterator<String> it = segments.iterator(); it.hasNext(); ) |
523 | { | |
524 | 0 | String name = it.next(); |
525 | 0 | String href = it.next(); |
526 | ||
527 | 0 | if ( href == null ) |
528 | { | |
529 | 0 | text( name ); |
530 | } | |
531 | else | |
532 | { | |
533 | 0 | if ( getValidHref( href ) != null ) |
534 | { | |
535 | 0 | link( getValidHref( href ), name ); |
536 | } | |
537 | else | |
538 | { | |
539 | 0 | text( href ); |
540 | } | |
541 | } | |
542 | 0 | } |
543 | } | |
544 | } | |
545 | 0 | } |
546 | ||
547 | /** | |
548 | * Create a link pattern text defined by <code>{text, url}</code>. | |
549 | * <p>This created pattern could be used by the method <code>linkPatternedText(String)</code> to | |
550 | * handle a text with link.</p> | |
551 | * | |
552 | * @param text | |
553 | * @param href | |
554 | * @return a link pattern | |
555 | * @see #linkPatternedText(String) | |
556 | */ | |
557 | protected static String createLinkPatternedText( String text, String href ) | |
558 | { | |
559 | 0 | if ( text == null ) |
560 | { | |
561 | 0 | return text; |
562 | } | |
563 | ||
564 | 0 | if ( href == null ) |
565 | { | |
566 | 0 | return text; |
567 | } | |
568 | ||
569 | 0 | return '{' + text + ", " + href + '}'; |
570 | } | |
571 | ||
572 | /** | |
573 | * Convenience method to display a <code>Properties</code> object as comma separated String. | |
574 | * | |
575 | * @param props the properties to display. | |
576 | * @return the properties object as comma separated String | |
577 | */ | |
578 | protected static String propertiesToString( Properties props ) | |
579 | { | |
580 | 0 | if ( props == null || props.isEmpty() ) |
581 | { | |
582 | 0 | return ""; |
583 | } | |
584 | ||
585 | 0 | StringBuilder sb = new StringBuilder(); |
586 | ||
587 | 0 | for ( Map.Entry<?, ?> entry : props.entrySet() ) |
588 | { | |
589 | 0 | if ( sb.length() > 0 ) |
590 | { | |
591 | 0 | sb.append( ", " ); |
592 | } | |
593 | ||
594 | 0 | sb.append( entry.getKey() ).append( "=" ).append( entry.getValue() ); |
595 | } | |
596 | ||
597 | 0 | return sb.toString(); |
598 | } | |
599 | ||
600 | // ---------------------------------------------------------------------- | |
601 | // Private methods | |
602 | // ---------------------------------------------------------------------- | |
603 | ||
604 | /** | |
605 | * Return a valid href. | |
606 | * <p>A valid href could start by <code>mailto:</code>.</p> | |
607 | * <p>For a relative path, the href should start by <code>./</code> to be valid.</p> | |
608 | * | |
609 | * @param href an href, could be null. | |
610 | * @return a valid href or <code>null</code> if the href is null or not valid. | |
611 | */ | |
612 | private static String getValidHref( String href ) | |
613 | { | |
614 | 0 | if ( StringUtils.isEmpty( href ) ) |
615 | { | |
616 | 0 | return null; |
617 | } | |
618 | ||
619 | 0 | href = href.trim(); |
620 | ||
621 | 0 | String[] schemes = {"http", "https"}; |
622 | 0 | UrlValidator urlValidator = new UrlValidator( schemes ); |
623 | 0 | EmailValidator emailValidator = EmailValidator.getInstance(); |
624 | ||
625 | 0 | if ( emailValidator.isValid( href ) |
626 | || ( href.contains( "?" ) && emailValidator.isValid( href.substring( 0, href.indexOf( "?" ) ) ) ) ) | |
627 | { | |
628 | 0 | return "mailto:" + href; |
629 | } | |
630 | 0 | else if ( href.toLowerCase().startsWith( "mailto:" ) ) |
631 | { | |
632 | 0 | return href; |
633 | } | |
634 | 0 | else if ( urlValidator.isValid( href ) ) |
635 | { | |
636 | 0 | return href; |
637 | } | |
638 | else | |
639 | { | |
640 | String hrefTmp; | |
641 | 0 | if ( !href.endsWith( "/" ) ) |
642 | { | |
643 | 0 | hrefTmp = href + "/index.html"; |
644 | } | |
645 | else | |
646 | { | |
647 | 0 | hrefTmp = href + "index.html"; |
648 | } | |
649 | ||
650 | 0 | if ( urlValidator.isValid( hrefTmp ) ) |
651 | { | |
652 | 0 | return href; |
653 | } | |
654 | ||
655 | 0 | if ( href.startsWith( "./" ) ) |
656 | { | |
657 | 0 | if ( href.length() > 2 ) |
658 | { | |
659 | 0 | return href.substring( 2, href.length() ); |
660 | } | |
661 | ||
662 | 0 | return "."; |
663 | } | |
664 | ||
665 | 0 | return null; |
666 | } | |
667 | } | |
668 | ||
669 | /** | |
670 | * The method parses a text and applies the given pattern <code>{text, url}</code> to create | |
671 | * a list of text/href. | |
672 | * | |
673 | * @param text a text with or without the pattern <code>{text, url}</code> | |
674 | * @return a map of text/href | |
675 | */ | |
676 | private static List<String> applyPattern( String text ) | |
677 | { | |
678 | 32 | if ( StringUtils.isEmpty( text ) ) |
679 | { | |
680 | 0 | return null; |
681 | } | |
682 | ||
683 | // Map defined by key/value name/href | |
684 | // If href == null, it means | |
685 | 32 | List<String> segments = new ArrayList<String>(); |
686 | ||
687 | // TODO Special case http://jira.codehaus.org/browse/MEV-40 | |
688 | 32 | if ( text.indexOf( "${" ) != -1 ) |
689 | { | |
690 | 2 | int lastComma = text.lastIndexOf( "," ); |
691 | 2 | int lastSemi = text.lastIndexOf( "}" ); |
692 | 2 | if ( lastComma != -1 && lastSemi != -1 && lastComma < lastSemi ) |
693 | { | |
694 | 0 | segments.add( text.substring( lastComma + 1, lastSemi ).trim() ); |
695 | 0 | segments.add( null ); |
696 | } | |
697 | else | |
698 | { | |
699 | 2 | segments.add( text ); |
700 | 2 | segments.add( null ); |
701 | } | |
702 | ||
703 | 2 | return segments; |
704 | } | |
705 | ||
706 | 30 | boolean inQuote = false; |
707 | 30 | int braceStack = 0; |
708 | 30 | int lastOffset = 0; |
709 | ||
710 | 540 | for ( int i = 0; i < text.length(); i++ ) |
711 | { | |
712 | 510 | char ch = text.charAt( i ); |
713 | ||
714 | 510 | if ( ch == '\'' && !inQuote && braceStack == 0 ) |
715 | { | |
716 | // handle: '' | |
717 | 18 | if ( i + 1 < text.length() && text.charAt( i + 1 ) == '\'' ) |
718 | { | |
719 | 10 | i++; |
720 | 10 | segments.add( text.substring( lastOffset, i ) ); |
721 | 10 | segments.add( null ); |
722 | 10 | lastOffset = i + 1; |
723 | } | |
724 | else | |
725 | { | |
726 | 8 | inQuote = true; |
727 | } | |
728 | } | |
729 | else | |
730 | { | |
731 | 492 | switch ( ch ) |
732 | { | |
733 | case '{': | |
734 | 22 | if ( !inQuote ) |
735 | { | |
736 | 16 | if ( braceStack == 0 ) |
737 | { | |
738 | 14 | if ( i != 0 ) // handle { at first character |
739 | { | |
740 | 8 | segments.add( text.substring( lastOffset, i ) ); |
741 | 8 | segments.add( null ); |
742 | } | |
743 | 14 | lastOffset = i + 1; |
744 | } | |
745 | 16 | braceStack++; |
746 | } | |
747 | break; | |
748 | case '}': | |
749 | 16 | if ( !inQuote ) |
750 | { | |
751 | 14 | braceStack--; |
752 | 14 | if ( braceStack == 0 ) |
753 | { | |
754 | 12 | String subString = text.substring( lastOffset, i ); |
755 | 12 | lastOffset = i + 1; |
756 | ||
757 | 12 | int lastComma = subString.lastIndexOf( "," ); |
758 | 12 | if ( lastComma != -1 ) |
759 | { | |
760 | 10 | segments.add( subString.substring( 0, lastComma ).trim() ); |
761 | 10 | segments.add( subString.substring( lastComma + 1 ).trim() ); |
762 | } | |
763 | else | |
764 | { | |
765 | 2 | segments.add( subString ); |
766 | 2 | segments.add( null ); |
767 | } | |
768 | 12 | } |
769 | } | |
770 | break; | |
771 | case '\'': | |
772 | 6 | inQuote = false; |
773 | 6 | break; |
774 | default: | |
775 | break; | |
776 | } | |
777 | } | |
778 | } | |
779 | ||
780 | 30 | if ( !StringUtils.isEmpty( text.substring( lastOffset ) ) ) |
781 | { | |
782 | 14 | segments.add( text.substring( lastOffset ) ); |
783 | 14 | segments.add( null ); |
784 | } | |
785 | ||
786 | 30 | if ( braceStack != 0 ) |
787 | { | |
788 | 2 | throw new IllegalArgumentException( "Unmatched braces in the pattern." ); |
789 | } | |
790 | ||
791 | 28 | if ( inQuote ) |
792 | { | |
793 | //throw new IllegalArgumentException( "Unmatched quote in the pattern." ); | |
794 | //TODO: warning... | |
795 | } | |
796 | ||
797 | 28 | return Collections.unmodifiableList( segments ); |
798 | } | |
799 | ||
800 | // ---------------------------------------------------------------------- | |
801 | // Abstract methods | |
802 | // ---------------------------------------------------------------------- | |
803 | ||
804 | /** {@inheritDoc} */ | |
805 | public abstract String getTitle(); | |
806 | ||
807 | /** | |
808 | * Renderer the body content of the report. | |
809 | */ | |
810 | protected abstract void renderBody(); | |
811 | } |