Home » displaytag-1.1.1-src » org » displaytag » decorator » [javadoc | source]

    1   /**
    2    * Licensed under the Artistic License; you may not use this file
    3    * except in compliance with the License.
    4    * You may obtain a copy of the License at
    5    *
    6    *      http://displaytag.sourceforge.net/license.html
    7    *
    8    * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
    9    * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
   10    * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
   11    */
   12   package org.displaytag.decorator;
   13   
   14   import java.util;
   15   import java.text.MessageFormat;
   16   
   17   import javax.servlet.jsp.PageContext;
   18   
   19   import org.apache.commons.lang.ObjectUtils;
   20   import org.apache.commons.lang.StringUtils;
   21   import org.apache.commons.logging.Log;
   22   import org.apache.commons.logging.LogFactory;
   23   import org.displaytag.exception.DecoratorException;
   24   import org.displaytag.exception.ObjectLookupException;
   25   import org.displaytag.model.Column;
   26   import org.displaytag.model.ColumnIterator;
   27   import org.displaytag.model.HeaderCell;
   28   import org.displaytag.model.Row;
   29   import org.displaytag.model.TableModel;
   30   import org.displaytag.util.TagConstants;
   31   
   32   
   33   /**
   34    * A TableDecorator that, in conjunction with totaled and grouped columns, produces multi level subtotals on arbitrary
   35    * String groupings.  Use it directly, subclass it, or use it as an example to better meet your local needs.
   36    * @author rapruitt
   37    * @author Fabrizio Giustina
   38    */
   39   public class MultilevelTotalTableDecorator extends TableDecorator
   40   {
   41   
   42       /**
   43        * If there are no columns that are totaled, we should not issue a totals row.
   44        */
   45       private boolean containsTotaledColumns = false;
   46   
   47       /**
   48        * No current reset group.
   49        */
   50       private static final int NO_RESET_GROUP = 4200;
   51   
   52       /**
   53        * Maps the groups to their current totals.
   54        */
   55       private Map groupNumberToGroupTotal = new HashMap();
   56   
   57       /**
   58        * The deepest reset group. Resets on an outer group will force any deeper groups to reset as well.
   59        */
   60       private int deepestResetGroup = NO_RESET_GROUP;
   61   
   62       /**
   63        * Controls when the subgroup is ended.
   64        */
   65       protected int innermostGroup;
   66   
   67       /**
   68        * Logger.
   69        */
   70       private Log logger = LogFactory.getLog(MultilevelTotalTableDecorator.class);
   71   
   72       /**
   73        * CSS class applied to grand total totals.
   74        */
   75       protected String grandTotalSum = "grandtotal-sum";
   76   
   77       /**
   78        * CSS class applied to grand total cells where the column is not totaled.
   79        */
   80       protected String grandTotalNoSum = "grandtotal-nosum";
   81   
   82       /**
   83        * CSS class applied to grand total lablels.
   84        */
   85       protected String grandTotalLabel = "grandtotal-label";
   86   
   87       /**
   88        * Grandtotal description.
   89        */
   90       protected String grandTotalDescription = "Grand Total";
   91   
   92   
   93       /**
   94        * CSS class appplied to subtotal headers.
   95        */
   96       private String subtotalHeaderClass = "subtotal-header";
   97   
   98       /**
   99        * CSS class applied to subtotal labels.
  100        */
  101       private String subtotalLabelClass = "subtotal-label";
  102   
  103       /**
  104        * Message format for subtotal descriptions.
  105        */
  106       private MessageFormat subtotalDesc = new MessageFormat("{0} Total");
  107   
  108       /**
  109        * CSS class applied to subtotal totals.
  110        */
  111       private String subtotalValueClass = "subtotal-sum";
  112   
  113   
  114       /**
  115        * Holds the header rows and their content for a particular group.
  116        */
  117       private List headerRows = new ArrayList(5);
  118   
  119       public void init(PageContext context, Object decorated, TableModel model)
  120       {
  121           super.init(context, decorated, model);
  122           List headerCells = model.getHeaderCellList();
  123           // go through each column, looking for grouped columns; add them to the group number map
  124           for (Iterator iterator = headerCells.iterator(); iterator.hasNext();)
  125           {
  126               HeaderCell headerCell = (HeaderCell) iterator.next();
  127               containsTotaledColumns = containsTotaledColumns || headerCell.isTotaled();
  128               if (headerCell.getGroup() > 0)
  129               {
  130                   groupNumberToGroupTotal.put(new Integer(headerCell.getGroup()), new GroupTotals(headerCell
  131                       .getColumnNumber()));
  132                   if (headerCell.getGroup() > innermostGroup)
  133                   {
  134                       innermostGroup = headerCell.getGroup();
  135                   }
  136               }
  137           }
  138       }
  139   
  140       public String getGrandTotalDescription()
  141       {
  142           return grandTotalDescription;
  143       }
  144   
  145       public void setGrandTotalDescription(String grandTotalDescription)
  146       {
  147           this.grandTotalDescription = grandTotalDescription;
  148       }
  149   
  150       /**
  151        * The pattern to use to generate the subtotal labels.  The grouping value of the cell will be the first arg.
  152        * The default value is "{0} Total".
  153        * @param pattern
  154        * @param locale
  155        */
  156       public void setSubtotalLabel(String pattern, Locale locale)
  157       {
  158           this.subtotalDesc = new MessageFormat(pattern, locale);
  159       }
  160   
  161       public String getGrandTotalLabel()
  162       {
  163           return grandTotalLabel;
  164       }
  165   
  166       public String getGrandTotalSum()
  167       {
  168           return grandTotalSum;
  169       }
  170   
  171       public String getGrandTotalNoSum()
  172       {
  173           return grandTotalNoSum;
  174       }
  175   
  176       public void setGrandTotalNoSum(String grandTotalNoSum) 
  177       {
  178           this.grandTotalNoSum = grandTotalNoSum;
  179       }
  180   
  181       public void setGrandTotalSum(String grandTotalSum)
  182       {
  183           this.grandTotalSum = grandTotalSum;
  184       }
  185   
  186       public void setGrandTotalLabel(String grandTotalLabel)
  187       {
  188           this.grandTotalLabel = grandTotalLabel;
  189       }
  190   
  191       public String getSubtotalValueClass()
  192       {
  193           return subtotalValueClass;
  194       }
  195   
  196       public void setSubtotalValueClass(String subtotalValueClass)
  197       {
  198           this.subtotalValueClass = subtotalValueClass;
  199       }
  200   
  201       public String getSubtotalLabelClass()
  202       {
  203           return subtotalLabelClass;
  204       }
  205   
  206       public void setSubtotalLabelClass(String subtotalLabelClass)
  207       {
  208           this.subtotalLabelClass = subtotalLabelClass;
  209       }
  210   
  211       public String getSubtotalHeaderClass()
  212       {
  213           return subtotalHeaderClass;
  214       }
  215   
  216       public void setSubtotalHeaderClass(String subtotalHeaderClass)
  217       {
  218           this.subtotalHeaderClass = subtotalHeaderClass;
  219       }
  220   
  221       public void startOfGroup(String value, int group)
  222       {
  223           if (containsTotaledColumns)
  224           {
  225               StringBuffer tr = new StringBuffer();
  226               tr.append("<tr>");
  227               GroupTotals groupTotals = (GroupTotals) groupNumberToGroupTotal.get(new Integer(group));
  228               int myColumnNumber = groupTotals.columnNumber;
  229               for (int i = 0; i < myColumnNumber; i++)
  230               {
  231                   tr.append("<td></td>\n");
  232               }
  233               tr.append("<td class=\"").append(getSubtotalHeaderClass()).append(" group-").append(group).append("\" >");
  234               tr.append(value).append("</td>\n");
  235               List headerCells = tableModel.getHeaderCellList();
  236               for (int i = myColumnNumber; i < headerCells.size() - 1; i++)
  237               {
  238                   tr.append("<td></td>\n");
  239               }
  240               tr.append("</tr>\n");
  241               headerRows.add(tr);
  242           }
  243       }
  244   
  245       public String displayGroupedValue(String value, short groupingStatus, int columnNumber)
  246       {
  247   //        if (groupingStatus == TableWriterTemplate.GROUP_START_AND_END && columnNumber > 1)
  248   //        {
  249   //            return value;
  250   //        }
  251   //        else
  252   //        {
  253               return "";
  254   //        }
  255       }
  256   
  257       public String startRow()
  258       {
  259           StringBuffer sb = new StringBuffer();
  260           for (Iterator iterator = headerRows.iterator(); iterator.hasNext();)
  261           {
  262               StringBuffer stringBuffer = (StringBuffer) iterator.next();
  263               sb.append(stringBuffer);
  264           }
  265           return sb.toString();
  266       }
  267   
  268       public void endOfGroup(String value, int groupNumber)
  269       {
  270           if (deepestResetGroup > groupNumber)
  271           {
  272               deepestResetGroup = groupNumber;
  273           }
  274       }
  275   
  276       public String finishRow()
  277       {
  278           String returnValue = "";
  279           if (containsTotaledColumns)
  280           {
  281               if (innermostGroup > 0 && deepestResetGroup != NO_RESET_GROUP)
  282               {
  283                   StringBuffer out = new StringBuffer();
  284                   // Starting with the deepest group, print the current total and reset. Do not reset unaffected groups.
  285                   for (int i = innermostGroup; i >= deepestResetGroup; i--)
  286                   {
  287                       Integer groupNumber = new Integer(i);
  288   
  289                       GroupTotals totals = (GroupTotals) groupNumberToGroupTotal.get(groupNumber);
  290                       if (totals == null)
  291                       {
  292                           logger.warn("There is a gap in the defined groups - no group defined for " + groupNumber);
  293                           continue;
  294                       }
  295                       totals.printTotals(getListIndex(), out);
  296                       totals.setStartRow(getListIndex() + 1);
  297                   }
  298                   returnValue = out.toString();
  299               }
  300               else
  301               {
  302                   returnValue = null;
  303               }
  304               deepestResetGroup = NO_RESET_GROUP;
  305               headerRows.clear();
  306               if (isLastRow())
  307               {
  308                   returnValue = StringUtils.defaultString(returnValue);
  309                   returnValue += totalAllRows();
  310               }
  311           }
  312           return returnValue;
  313       }
  314   
  315       /**
  316        * Issue a grand total row at the bottom.
  317        * @return the suitable string
  318        */
  319       protected String totalAllRows()
  320       {
  321           if (containsTotaledColumns)
  322           {
  323               List headerCells = tableModel.getHeaderCellList();
  324               StringBuffer output = new StringBuffer();
  325               int currentRow = getListIndex();
  326               output.append(TagConstants.TAG_OPEN + TagConstants.TAGNAME_ROW
  327                       + " class=\"grandtotal-row\"" + TagConstants.TAG_CLOSE);
  328               boolean first = true;
  329               for (Iterator iterator = headerCells.iterator(); iterator.hasNext();)
  330               {
  331                   HeaderCell headerCell = (HeaderCell) iterator.next();
  332                   if (first)
  333                   {
  334                       output.append(getTotalsTdOpen(headerCell, getGrandTotalLabel()));
  335                       output.append(getGrandTotalDescription());
  336                       first = false;
  337                   }
  338                   else if (headerCell.isTotaled())
  339                   {
  340                       // a total if the column should be totaled
  341                       Object total = getTotalForColumn(headerCell.getColumnNumber(), 0, currentRow);
  342                       output.append(getTotalsTdOpen(headerCell, getGrandTotalSum()));
  343                       output.append(formatTotal(headerCell, total));
  344                   }
  345                   else
  346                   {
  347                       // blank, if it is not a totals column
  348                       output.append(getTotalsTdOpen(headerCell, getGrandTotalNoSum()));
  349                   }
  350                   output.append(TagConstants.TAG_OPENCLOSING + TagConstants.TAGNAME_COLUMN + TagConstants.TAG_CLOSE);
  351               }
  352               output.append("\n</tr>\n");
  353   
  354               return output.toString();
  355           }
  356           else
  357           {
  358               return "";
  359           }
  360       }
  361   
  362       protected String getCellValue(int columnNumber, int rowNumber)
  363       {
  364           List fullList = tableModel.getRowListFull();
  365           Row row = (Row) fullList.get(rowNumber);
  366           ColumnIterator columnIterator = row.getColumnIterator(tableModel.getHeaderCellList());
  367           while (columnIterator.hasNext())
  368           {
  369               Column column = columnIterator.nextColumn();
  370               if (column.getHeaderCell().getColumnNumber() == columnNumber)
  371               {
  372                   try
  373                   {
  374                       column.initialize();
  375                       return column.getChoppedAndLinkedValue();
  376                   }
  377                   catch (ObjectLookupException e)
  378                   {
  379                       logger.error("Error: " + e.getMessage(), e);
  380                       throw new RuntimeException("Error: " + e.getMessage(), e);
  381                   }
  382                   catch (DecoratorException e)
  383                   {
  384                       logger.error("Error: " + e.getMessage(), e);
  385                       throw new RuntimeException("Error: " + e.getMessage(), e);
  386                   }
  387               }
  388           }
  389           throw new RuntimeException("Unable to find column " + columnNumber + " in the list of columns");
  390       }
  391   
  392       protected Object getTotalForColumn(int columnNumber, int startRow, int stopRow)
  393       {
  394           List fullList = tableModel.getRowListFull();
  395           List window = fullList.subList(startRow, stopRow + 1);
  396           Object total = null;
  397           for (Iterator iterator = window.iterator(); iterator.hasNext();)
  398           {
  399               Row row = (Row) iterator.next();
  400               ColumnIterator columnIterator = row.getColumnIterator(tableModel.getHeaderCellList());
  401               while (columnIterator.hasNext())
  402               {
  403                   Column column = columnIterator.nextColumn();
  404                   if (column.getHeaderCell().getColumnNumber() == columnNumber)
  405                   {
  406                       Object value = null;
  407                       try
  408                       {
  409                           value = column.getValue(false);
  410                       }
  411                       catch (ObjectLookupException e)
  412                       {
  413                           logger.error(e);
  414                       }
  415                       catch (DecoratorException e)
  416                       {
  417                           logger.error(e);
  418                       }
  419                       if (value != null && ! TagConstants.EMPTY_STRING.equals(value))
  420                       {
  421                           total = add(column, total, value);
  422                       }
  423                   }
  424               }
  425           }
  426           return total;
  427       }
  428   
  429       protected Object add(Column column, Object total, Object value) {
  430           if (value == null)
  431           {
  432               return total;
  433           }
  434           else if (value instanceof Number)
  435           {
  436               Number oldTotal = new Double(0);
  437               if (total != null)
  438               {
  439                   oldTotal = (Number)total;
  440               }
  441               return new Double(oldTotal.doubleValue() + ((Number) value).doubleValue());
  442           }
  443           else
  444           {
  445               throw new UnsupportedOperationException("Cannot add a value of " + value + " in column " + column.getHeaderCell().getTitle());
  446           }
  447       }
  448   
  449       public String getTotalsTdOpen(HeaderCell header, String totalClass)
  450       {
  451   
  452           String cssClass = ObjectUtils.toString(header.getHtmlAttributes().get("class"));
  453   
  454           StringBuffer buffer = new StringBuffer();
  455           buffer.append(TagConstants.TAG_OPEN);
  456           buffer.append(TagConstants.TAGNAME_COLUMN);
  457           if (cssClass != null || totalClass != null)
  458           {
  459               buffer.append(" class=\"");
  460   
  461               if (cssClass != null)
  462               {
  463                   buffer.append(cssClass);
  464                   if (totalClass != null)
  465                   {
  466                       buffer.append(" ");
  467                   }
  468               }
  469               if (totalClass != null)
  470               {
  471                   buffer.append(totalClass);
  472               }
  473               buffer.append("\"");
  474           }
  475           buffer.append(TagConstants.TAG_CLOSE);
  476           return buffer.toString();
  477       }
  478   
  479       public String getTotalsRowOpen()
  480       {
  481           return TagConstants.TAG_OPEN + TagConstants.TAGNAME_ROW + " class=\"subtotal\"" + TagConstants.TAG_CLOSE;
  482       }
  483   
  484       public String getTotalRowLabel(String groupingValue)
  485       {
  486           return subtotalDesc.format(new Object[]{groupingValue});
  487       }
  488   
  489       public String formatTotal(HeaderCell header, Object total)
  490       {
  491           Object displayValue = total;
  492           if (header.getColumnDecorators().length > 0)
  493           {
  494               for (int i = 0; i < header.getColumnDecorators().length; i++)
  495               {
  496                   DisplaytagColumnDecorator decorator = header.getColumnDecorators()[i];
  497                   try
  498                   {
  499                       displayValue = decorator.decorate(total, this.getPageContext(), tableModel.getMedia());
  500                   }
  501                   catch (DecoratorException e)
  502                   {
  503                       logger.warn(e.getMessage(), e);
  504                       // ignore, use undecorated value for totals
  505                   }
  506               }
  507           }
  508           return displayValue != null ? displayValue.toString() : "";
  509       }
  510   
  511       class GroupTotals
  512       {
  513   
  514           /**
  515            * The label class.
  516            */
  517           protected String totalLabelClass = getSubtotalLabelClass();
  518           /**
  519            * The row opener
  520            */
  521           protected String totalsRowOpen = getTotalsRowOpen();
  522   
  523           /**
  524            * The value class.
  525            */
  526           protected String totalValueClass = getSubtotalValueClass();
  527   
  528           private int columnNumber;
  529   
  530           private int firstRowOfCurrentSet;
  531   
  532           public GroupTotals(int headerCellColumn)
  533           {
  534               this.columnNumber = headerCellColumn;
  535               this.firstRowOfCurrentSet = 0;
  536           }
  537   
  538           public void printTotals(int currentRow, StringBuffer out)
  539           {
  540   
  541               // For each column, output:
  542               List headerCells = tableModel.getHeaderCellList();
  543               if (firstRowOfCurrentSet < currentRow) // If there is more than one row, show a total
  544               {
  545                   out.append(totalsRowOpen);
  546                   for (Iterator iterator = headerCells.iterator(); iterator.hasNext();)
  547                   {
  548                       HeaderCell headerCell = (HeaderCell) iterator.next();
  549   
  550                       if (columnNumber == headerCell.getColumnNumber())
  551                       {
  552                           // a totals label if it is the column for the current group
  553                           String currentLabel = getCellValue(columnNumber, firstRowOfCurrentSet);
  554                           out.append(getTotalsTdOpen(headerCell, getTotalLabelClass() + " group-" + (columnNumber + 1)));
  555                           out.append(getTotalRowLabel(currentLabel));
  556                       }
  557                       else if (headerCell.isTotaled())
  558                       {
  559                           // a total if the column should be totaled
  560                           Object total = getTotalForColumn(headerCell.getColumnNumber(),
  561                                   firstRowOfCurrentSet, currentRow);
  562                           out.append(getTotalsTdOpen(headerCell, getTotalValueClass() + " group-" + (columnNumber + 1)));
  563                           out.append(formatTotal(headerCell, total));
  564                       }
  565                       else
  566                       {
  567                           // blank, if it is not a totals column
  568                           String style = "group-" + (columnNumber + 1);
  569                           if (headerCell.getColumnNumber() < innermostGroup)
  570                           {
  571                               style += " " + getTotalLabelClass() + " ";
  572                           }
  573                           out.append(getTotalsTdOpen(headerCell, style));
  574                       }
  575                       out.append(TagConstants.TAG_OPENCLOSING + TagConstants.TAGNAME_COLUMN + TagConstants.TAG_CLOSE);
  576                   }
  577                   out.append("\n</tr>\n");
  578               }
  579           }
  580   
  581           public void setStartRow(int i)
  582           {
  583               firstRowOfCurrentSet = i;
  584           }
  585   
  586           public String getTotalLabelClass()
  587           {
  588               return totalLabelClass;
  589           }
  590   
  591           public void setTotalsRowOpen(String totalsRowOpen)
  592           {
  593               this.totalsRowOpen = totalsRowOpen;
  594           }
  595   
  596           public void setTotalLabelClass(String totalLabelClass)
  597           {
  598               this.totalLabelClass = totalLabelClass;
  599           }
  600   
  601           public String getTotalValueClass()
  602           {
  603               return totalValueClass;
  604           }
  605   
  606           public void setTotalValueClass(String totalValueClass)
  607           {
  608               this.totalValueClass = totalValueClass;
  609           }
  610       }
  611   }

Home » displaytag-1.1.1-src » org » displaytag » decorator » [javadoc | source]