Coverage Report - org.apache.maven.reporting.AbstractMavenReportRenderer
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractMavenReportRenderer
22%
50/224
32%
39/120
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  
 }