Coverage report

  %line %branch
org.apache.jetspeed.portlets.layout.ColumnLayout
0% 
0% 

 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  
  * contributor license agreements.  See the NOTICE file distributed with
 4  
  * this work for additional information regarding copyright ownership.
 5  
  * The ASF licenses this file to You under the Apache License, Version 2.0
 6  
  * (the "License"); you may not use this file except in compliance with
 7  
  * the License.  You may obtain a copy of the License at
 8  
  * 
 9  
  *      http://www.apache.org/licenses/LICENSE-2.0
 10  
  * 
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 package org.apache.jetspeed.portlets.layout;
 18  
 
 19  
 import java.io.Serializable;
 20  
 import java.text.DecimalFormat;
 21  
 import java.text.DecimalFormatSymbols;
 22  
 import java.util.ArrayList;
 23  
 import java.util.Collection;
 24  
 import java.util.Collections;
 25  
 import java.util.HashMap;
 26  
 import java.util.Iterator;
 27  
 import java.util.List;
 28  
 import java.util.Locale;
 29  
 import java.util.Map;
 30  
 import java.util.SortedMap;
 31  
 import java.util.TreeMap;
 32  
 
 33  
 import org.apache.jetspeed.om.page.Fragment;
 34  
 
 35  
 /**
 36  
  * <h2>Basics</h2>
 37  
  * <p>
 38  
  * <code>ColumnLayout</code> is the model used to support any 1 to <i>n</i>
 39  
  * column-based layout. <code>ColumnLayout</code> is constrained by a number
 40  
  * columns that will not be exceeded, even if a fragment specifies a column
 41  
  * outside of this constraint. Any fragment exceeded the specified column
 42  
  * constraint will be deposited into the right-most column.
 43  
  * </p>
 44  
  * 
 45  
  * <h2>Characteristics:</h2>
 46  
  * <ul>
 47  
  *   <li>Columns and rows always start at 0.</li>
 48  
  *   <li>Unless otherwise noted, assume all Collections returned are immutable.</li>
 49  
  *   <li>Unless otherwise noted, assume that no public method will ever return <code>null</code>.</li>
 50  
  * </ul>
 51  
  *  
 52  
  * 
 53  
  * <h2>Layout Events</h2>
 54  
  * <p>
 55  
  * When any move*() method is invoked and a portlet is actually moved (see indvidual
 56  
  * methods for what causes these circumstances), an initial LayoutEvent is dispatched.  
 57  
  * This may cause a cascade of LayoutEvents to be fired in turn if the movement of the
 58  
  * target fragment cause other fragments to be repositioned.  In this case a LayoutEvent
 59  
  * is dispatched for each portlet moved, which in turn may our may not cause another
 60  
  * LayoutEvent to be fired. 
 61  
  * </p>
 62  
  * @see org.apache.jetspeed.portlets.layout.LayoutEvent
 63  
  * @see org.apache.jetspeed.portlets.layout.LayoutEventListener
 64  
  * @see org.apache.jetspeed.portlets.layout.LayoutCoordinate
 65  
  * @see org.apache.jetspeed.om.page.Fragment
 66  
  * 
 67  
  * @author <href a="mailto:weaver@apache.org">Scott T. Weaver</a>
 68  
  * 
 69  
  */
 70  
 public class ColumnLayout implements Serializable
 71  
 {
 72  
     /** Percentage widths gutter width */
 73  
     private final static double PERCENTAGE_WIDTH_GUTTER = 0.01;
 74  
 
 75  
     /** Percentage widths format */
 76  0
     private final static DecimalFormat PERCENTAGE_WIDTH_FORMAT = new DecimalFormat("0.00'%'", new DecimalFormatSymbols(
 77  
                                                                                Locale.ENGLISH));
 78  
 
 79  
     /** Constrains the columns for this layout */
 80  
     private final int numberOfColumns;
 81  
     
 82  
     /** SortedMap of Columns (which are also sorted maps */
 83  
     private final SortedMap columns;
 84  
     
 85  
     /** Width settings for eacah column */
 86  
     private final String[] columnWidths;
 87  
     
 88  
     /** Efficent way to always be aware of the next available row in a column */
 89  
     private final int[] nextRowNumber;
 90  
     
 91  
     /** maps Fragments (key) to it's current LayoutCoordinate (value) in this layout */
 92  
     private final Map coordinates;
 93  
     
 94  
     /** All of the LayoutEventListeners registered to this layout */
 95  
     private final List eventListeners;
 96  
 
 97  
     /**
 98  
      * 
 99  
      * @param numberOfColumns
 100  
      *            the maximum number of columns this layout will have.
 101  
      * @param layoutType
 102  
      *            this value corresponds to the property settings of the
 103  
      *            fragments within your psml. Layout type allows segration of
 104  
      *            property settings based on the type of layout in use. This
 105  
      *            effectively allows for the interchange of multiple layout
 106  
      *            formats without one format effecting the settings of another.
 107  
      * @param columnWidths
 108  
      *            widths for each column that accumulate to 100% if percentages
 109  
      *            are used.
 110  
      * @see org.apache.jetspeed.om.page.Fragment#getType()
 111  
      */
 112  
     public ColumnLayout(int numberOfColumns, String layoutType, String[] columnWidths)
 113  0
     {
 114  0
         this.numberOfColumns = numberOfColumns;
 115  0
         this.columnWidths = columnWidths;
 116  0
         eventListeners = new ArrayList();
 117  
 
 118  0
         columns = new TreeMap();
 119  0
         coordinates = new HashMap();
 120  
 
 121  0
         for (int i = 0; i < numberOfColumns; i++)
 122  
         {
 123  0
             columns.put(new Integer(i), class="keyword">new TreeMap());
 124  
         }
 125  
 
 126  0
         nextRowNumber = new int[numberOfColumns];
 127  
 
 128  0
         for (int i = 0; i < numberOfColumns; i++)
 129  
         {
 130  0
             nextRowNumber[i] = 0;
 131  
         }
 132  0
     }
 133  
     
 134  
     /**
 135  
      * Same as ColumnLayout(int numberOfColumns, String layoutType) but also
 136  
      * supplies a Collection of fragmetns to initially populate the layout
 137  
      * with.  Adding these fragments <strong>WILL NOT</strong> cause
 138  
      * a LayoutEvent to be dispatched.
 139  
      * 
 140  
      * @see ColumnLayout(int numberOfColumns, String layoutType)
 141  
      * @param numberOfColumns
 142  
      *            the maximum number of columns this layout will have.
 143  
      * @param layoutType
 144  
      *            this value corresponds to the property settings of the
 145  
      *            fragments within your psml. Layout type allows segration of
 146  
      *            property settings based on the type of layout in use. This
 147  
      *            effectively allows for the interchange of multiple layout
 148  
      *            formats without one format effecting the settings of another.
 149  
      * @param fragments Initial set of fragments to add to this layout.
 150  
      * @param columnWidths
 151  
      *            widths for each column that accumulate to 100% if percentages
 152  
      *            are used.
 153  
      * @throws LayoutEventException
 154  
      */
 155  
     public ColumnLayout(int numberOfColumns, String layoutType, Collection fragments, String[] columnWidths) throws LayoutEventException
 156  
     {
 157  0
         this(numberOfColumns, layoutType, columnWidths);
 158  0
         Iterator fragmentsItr = fragments.iterator();
 159  
         try
 160  
         {
 161  0
             while (fragmentsItr.hasNext())
 162  
             {
 163  0
                 Fragment fragment = (Fragment) fragmentsItr.next();
 164  0
                 doAdd(getColumn(fragment), getRow(getColumn(fragment), fragment), fragment);
 165  0
             }
 166  
         }
 167  0
         catch (InvalidLayoutLocationException e)
 168  
         {
 169  
             // This should NEVER happen as getColumn() should
 170  
             // automatically constrain any fragments who's column
 171  
             // setting would cause this exception.
 172  0
             throw new LayoutError("A malformed fragment could not be adjusted.", e);
 173  0
         }
 174  0
     }
 175  
 
 176  
     /**
 177  
      * <p>
 178  
      * Adds a fragment to the layout using fragment properties of
 179  
      * <code> row  </code> and <code> column  </code> as hints on where to put
 180  
      * this fragment. The following rules apply to malformed fragment
 181  
      * definitions:
 182  
      * </p>
 183  
      * <ul>
 184  
      * <li>Fragments without a row defined are placed at the bottom of their
 185  
      * respective column </li>
 186  
      * <li>Fragments without a column are placed in the right-most column.
 187  
      * </li>
 188  
      * <li> Fragments with overlapping row numbers. The last fragment has
 189  
      * priority pushing the fragment in that row down one row. </li>
 190  
      * </ul>
 191  
      * 
 192  
      * @param fragment
 193  
      *            Fragment to add to this layout.
 194  
      * @throws LayoutEventException 
 195  
      * @see org.apache.jetspeed.om.page.Fragment
 196  
      * 
 197  
      */
 198  
     public void addFragment(Fragment fragment) throws LayoutEventException
 199  
     {
 200  
         try
 201  
         {
 202  0
             doAdd(getColumn(fragment), getRow(getColumn(fragment), fragment), fragment);
 203  0
             LayoutCoordinate coordinate = getCoordinate(fragment);
 204  0
             processEvent(new LayoutEvent(LayoutEvent.ADDED, fragment, coordinate, coordinate));
 205  
         }
 206  0
         catch (InvalidLayoutLocationException e)
 207  
         {
 208  
             // This should NEVER happen as getColumn() should
 209  
             // automatically constrain any fragments who's column
 210  
             // setting would cause this exception.
 211  0
             throw new LayoutError("A malformed fragment could not be adjusted.", e);
 212  
         }
 213  0
         catch (FragmentNotInLayoutException e)
 214  
         {        
 215  0
             throw new LayoutError("Failed to add coordinate to this ColumnLayout.", e);
 216  0
         }
 217  0
     }
 218  
     
 219  
     /**
 220  
      * Adds a LayoutEventListener to this layout that will be fired any time
 221  
      * a LayoutEvent is disaptched.
 222  
      * 
 223  
      * @param eventListener
 224  
      * @see LayoutEventListener
 225  
      * @see LayoutEventListener
 226  
      */
 227  
     public void addLayoutEventListener(LayoutEventListener eventListener)
 228  
     {
 229  0
         eventListeners.add(eventListener);
 230  0
     }
 231  
 
 232  
     /**
 233  
      * 
 234  
      * @param columnNumber
 235  
      *            Number of column to retreive
 236  
      * @return requested column (as a immutable Collection). Never returns
 237  
      *         <code>null.</code>
 238  
      * @throws InvalidLayoutLocationException
 239  
      *             if the column is outisde of the constraints of this layout
 240  
      */
 241  
     public Collection getColumn(int columnNumber) throws InvalidLayoutLocationException
 242  
     {
 243  0
         return Collections.unmodifiableCollection(getColumnMap(columnNumber).values());
 244  
     }
 245  
 
 246  
     /**
 247  
      * returns the width to be used with the specified column.  If
 248  
      * there is no specific column setting sfor the specified column
 249  
      * 0 is returned.
 250  
      * 
 251  
      * @param columnNumber whose width has been requested.
 252  
      * @return the width to be used with the specified column.  Or 0 if no value
 253  
      * has been specified.
 254  
      */
 255  
     public String getColumnWidth(int columnNumber)
 256  
     {
 257  0
         if ((columnWidths != null) && (columnNumber < numberOfColumns))
 258  
         {
 259  0
             String columnWidth = columnWidths[columnNumber];
 260  
 
 261  
             // subtract "gutter" width from last percentage
 262  
             // column to prevent wrapping on rounding errors
 263  
             // of column widths when rendered in the browser
 264  0
             if ((numberOfColumns > 1) && (columnNumber == (numberOfColumns - 1)))
 265  
             {
 266  0
                 int percentIndex = columnWidth.lastIndexOf('%');
 267  0
                 if (percentIndex > 0)
 268  
                 {
 269  
                     try
 270  
                     {
 271  0
                         double width = Double.parseDouble(columnWidth.substring(0,percentIndex).trim());
 272  0
                         synchronized (PERCENTAGE_WIDTH_FORMAT)
 273  
                         {
 274  0
                             columnWidth = PERCENTAGE_WIDTH_FORMAT.format(width - PERCENTAGE_WIDTH_GUTTER);
 275  0
                         }
 276  
                     }
 277  0
                     catch (NumberFormatException nfe)
 278  
                     {
 279  0
                     }
 280  
                 }
 281  
             }
 282  0
             return columnWidth;
 283  
         }
 284  0
         return "0";
 285  
     }
 286  
     
 287  
     /**
 288  
      * returns the float to be used with the specified column.
 289  
      * 
 290  
      * @param columnNumber whose width has been requested.
 291  
      * @return "right" for the last column, "left" if more than one
 292  
      *         column, or "none" otherwise.
 293  
      */
 294  
     public String getColumnFloat(int columnNumber)
 295  
     {
 296  0
         if ((numberOfColumns > 1) && (columnNumber < numberOfColumns))
 297  
         {
 298  0
             if (columnNumber == (numberOfColumns - 1))
 299  
             {
 300  0
                 return "right";
 301  
             }
 302  
             else
 303  
             {
 304  0
                 return "left";
 305  
             }
 306  
         }
 307  0
         return "none";
 308  
     }
 309  
     
 310  
     /**
 311  
      * @return <code>java.util.Collection</code> all of columns (also
 312  
      *         Collection objects) in order within this layout. All Collections
 313  
      *         are immutable.
 314  
      */
 315  
     public Collection getColumns()
 316  
     {
 317  0
         ArrayList columnList = new ArrayList(getNumberOfColumns());
 318  0
         Iterator itr = columns.values().iterator();
 319  0
         while (itr.hasNext())
 320  
         {
 321  0
             columnList.add(Collections.unmodifiableCollection(((Map) itr.next()).values()));
 322  
         }
 323  
 
 324  0
         return Collections.unmodifiableCollection(columnList);
 325  
     }
 326  
     
 327  
     /**
 328  
      * 
 329  
      * Returns the index of the last row in the specified column.
 330  
      * 
 331  
      * @param columnNumber column form whom we ant to identify the
 332  
      * last row.
 333  
      * @return the index of the last row in the specified column.
 334  
      */
 335  
     public int getLastRowNumber(class="keyword">int columnNumber)
 336  
     {
 337  0
         return nextRowNumber[columnNumber] - 1;
 338  
     }
 339  
     
 340  
     /**
 341  
      * Returns an immutable Collection of all the Fragments contained within
 342  
      * this ColumnLayout in no sepcific order.
 343  
      * @return Immutable Collection of Fragments.
 344  
      */
 345  
     public Collection getFragments()
 346  
     {
 347  0
         return Collections.unmodifiableCollection(coordinates.keySet());
 348  
     }
 349  
 
 350  
     /**
 351  
      * Retrieves the fragment at the specified loaction.
 352  
      * 
 353  
      * @param columnNumber Column coordinate (first column starts at 0)
 354  
      * @param rowNumber Row coordinate (first row starts at 0)
 355  
      * @return Fragment at the specified coordinate.  Never returns <code>null</code>.
 356  
      * @throws EmptyLayoutLocationException if there is no fragment currently located at the specified coordinate.
 357  
      * @throws InvalidLayoutLocationException if the coordinate lies outside the confines of this layout, i.e., the
 358  
      * <code>columnNumber</code> exceeds the max columns setting for this layout.
 359  
      */
 360  
     public Fragment getFragmentAt(int columnNumber, class="keyword">int rowNumber) throws EmptyLayoutLocationException,
 361  
             InvalidLayoutLocationException
 362  
     {
 363  0
         SortedMap column = getColumnMap(columnNumber);
 364  0
         Integer rowInteger = new Integer(rowNumber);
 365  0
         if (column.containsKey(rowInteger))
 366  
         {
 367  0
             return (Fragment) column.get(rowInteger);
 368  
         }
 369  
         else
 370  
         {
 371  0
             throw new EmptyLayoutLocationException(columnNumber, rowNumber);
 372  
         }
 373  
     }
 374  
     
 375  
     /**
 376  
      * 
 377  
      * Retrieves the fragment at the specified loaction.
 378  
      * 
 379  
      * @param coodinate LayoutCoordinate object that will be used to located a fragment in this
 380  
      * layout.
 381  
      * @see LayoutCoordinate
 382  
      * @return Fragment at the specified coordinate.  Never returns <code>null</code>.
 383  
      * @throws EmptyLayoutLocationException if there is no fragment currently located at the specified coordinate.
 384  
      * @throws InvalidLayoutLocationException if the coordinate lies outside the confines of this layout, i.e., the
 385  
      * <code>columnNumber</code> exceeds the max columns setting for this layout.
 386  
      * @see LayoutCoordinate
 387  
      */
 388  
     public Fragment getFragmentAt(LayoutCoordinate coodinate) throws EmptyLayoutLocationException,
 389  
             InvalidLayoutLocationException
 390  
     {
 391  0
         return getFragmentAt(coodinate.getX(), coodinate.getY());
 392  
     }
 393  
 
 394  
     /**
 395  
      * 
 396  
      * @return The total number of columns in this layout.
 397  
      */
 398  
     public int getNumberOfColumns()
 399  
     {
 400  0
         return numberOfColumns;
 401  
     }
 402  
 
 403  
     /**
 404  
      * 
 405  
      * @return The last column in this layout.  The Collection is immutable.
 406  
      */
 407  
     public Collection getLastColumn() 
 408  
     {
 409  
         try
 410  
         {
 411  0
             return Collections.unmodifiableCollection(getColumnMap(numberOfColumns - 1).values());
 412  
         }
 413  0
         catch (InvalidLayoutLocationException e)
 414  
         {
 415  
             // This should NEVER happen as getLastColumn() is
 416  
             // always correctly constrained and should always exists.
 417  0
             throw new LayoutError("It appears this layout is corrupt and cannot correctly identify its last column.", e);
 418  
         }
 419  
     }
 420  
 
 421  
     /**
 422  
      * 
 423  
      * @return The last column in this layout.  The Collection is immutable.
 424  
      */
 425  
     public Collection getFirstColumn()
 426  
     {
 427  
         try
 428  
         {
 429  0
             return Collections.unmodifiableCollection(getColumnMap(0).values());
 430  
         }
 431  0
         catch (InvalidLayoutLocationException e)
 432  
         {
 433  
             // This should NEVER happen as getLastColumn() is
 434  
             // always correctly constrained and should always exists.
 435  0
             throw new LayoutError("It appears this layout is corrupt and cannot correctly identify its first column.", e);
 436  
         }
 437  
     }
 438  
 
 439  
     /**
 440  
      * 
 441  
      * Moves a fragment one column to the right.  A LayoutEvent is triggered by
 442  
      * this action.
 443  
      * 
 444  
      * <p>
 445  
      * If the fragment currently
 446  
      * resides in right-most column, no action is taking and no event LayoutEvent
 447  
      * is fired.
 448  
      * </p>
 449  
      * 
 450  
      * @param fragment fragment to move.
 451  
      * @throws FragmentNotInLayoutException if the specified fragment is not currently in the layout.
 452  
      * @throws LayoutEventException If a triggered LayoutEvent fails.
 453  
      */
 454  
     public void moveRight(Fragment fragment) throws FragmentNotInLayoutException, LayoutEventException
 455  
     {
 456  0
         LayoutCoordinate coordinate = getCoordinate(fragment);
 457  0
         LayoutCoordinate newCoordinate = new LayoutCoordinate(coordinate.getX() + 1, coordinate.getY());
 458  
 
 459  0
         if (newCoordinate.getX() < numberOfColumns)
 460  
         {
 461  
 
 462  
             try
 463  
             {
 464  0
                 doMove(fragment, coordinate, newCoordinate);
 465  0
                 processEvent(new LayoutEvent(LayoutEvent.MOVED_RIGHT, fragment, coordinate, class="keyword">newCoordinate));
 466  
                 // now move the fragment below up one level.
 467  
                 try
 468  
                 {
 469  0
                     Fragment fragmentBelow = getFragmentAt(new LayoutCoordinate(coordinate.getX(), coordinate.getY() + 1));
 470  0
                     moveUp(fragmentBelow);
 471  
                 }
 472  0
                 catch (EmptyLayoutLocationException e)
 473  
                 {
 474  
                     // indicates no fragment below
 475  0
                 }
 476  
             }
 477  0
             catch (InvalidLayoutLocationException e)
 478  
             {
 479  
                 // This should NEVER happen as the location has already been verfied to be valid
 480  0
                 throw new LayoutError("It appears this layout is corrupt and cannot correctly identify valid column locations.", e);
 481  0
             }      
 482  
         }
 483  0
     }
 484  
 
 485  
     /**
 486  
      * Moves a fragment one column to the left.  A LayoutEvent is triggered by
 487  
      * this action.
 488  
      * 
 489  
      * <p>
 490  
      * If the fragment currently
 491  
      * resides in left-most column, no action is taking and no event LayoutEvent
 492  
      * is fired.
 493  
      * </p>
 494  
      * 
 495  
      * @param fragment
 496  
      * @throws FragmentNotInLayoutException if the specified fragment is not currently in the layout.
 497  
      * @throws LayoutEventException If a triggered LayoutEvent fails.
 498  
      */
 499  
     public void moveLeft(Fragment fragment) throws FragmentNotInLayoutException, LayoutEventException
 500  
     {
 501  0
         LayoutCoordinate coordinate = getCoordinate(fragment);
 502  0
         LayoutCoordinate newCoordinate = new LayoutCoordinate(coordinate.getX() - 1, coordinate.getY());
 503  
 
 504  0
         if (newCoordinate.getX() >= 0)
 505  
         {
 506  
             try
 507  
             {
 508  0
                 doMove(fragment, coordinate, newCoordinate);
 509  0
                 processEvent(new LayoutEvent(LayoutEvent.MOVED_LEFT, fragment, coordinate, class="keyword">newCoordinate));
 510  
                 // now move the fragment below up one level.
 511  
                 try
 512  
                 {
 513  0
                     Fragment fragmentBelow = getFragmentAt(new LayoutCoordinate(coordinate.getX(), coordinate.getY() + 1));
 514  0
                     moveUp(fragmentBelow);
 515  
                 }
 516  0
                 catch (EmptyLayoutLocationException e)
 517  
                 {
 518  
                     // indicates no fragment below
 519  0
                 }
 520  
             }
 521  0
             catch (InvalidLayoutLocationException e)
 522  
             {
 523  
                 // This should NEVER happen as the location has already been verfied to be valid
 524  0
                 throw new LayoutError("It appears this layout is corrupt and cannot correctly identify valid column locations.", e);
 525  0
             }
 526  
          
 527  
         }
 528  
 
 529  0
     }
 530  
 
 531  
     /**
 532  
      * Moves a fragment one row to the up.  A LayoutEvent is triggered by
 533  
      * this action.
 534  
      * 
 535  
      * <p>
 536  
      * If the fragment currently
 537  
      * resides in top-most row, no action is taking and no event LayoutEvent
 538  
      * is fired.
 539  
      * </p>
 540  
      * @param fragment
 541  
      * @throws FragmentNotInLayoutException if the specified fragment is not currently in the layout.
 542  
      * @throws LayoutEventException If a triggered LayoutEvent fails.
 543  
      */
 544  
     public void moveUp(Fragment fragment) throws FragmentNotInLayoutException, LayoutEventException
 545  
     {
 546  0
         LayoutCoordinate coordinate = getCoordinate(fragment);
 547  0
         LayoutCoordinate aboveLayoutCoordinate = new LayoutCoordinate(coordinate.getX(), coordinate.getY() - 1);
 548  0
         LayoutCoordinate newCoordinate = aboveLayoutCoordinate;
 549  
 
 550  
         // never go "above" 0.
 551  0
         if (newCoordinate.getY() >= 0)
 552  
         {
 553  
             try
 554  
             {
 555  
                 try
 556  
                 {
 557  
                     // now move the fragment above down one level.
 558  0
                     /*Fragment fragmentAbove =*/ getFragmentAt(aboveLayoutCoordinate);
 559  0
                     doMove(fragment, coordinate, newCoordinate);
 560  0
                     processEvent(new LayoutEvent(LayoutEvent.MOVED_UP, fragment, coordinate, class="keyword">newCoordinate));                
 561  
                 }
 562  0
                 catch (EmptyLayoutLocationException e)
 563  
                 {
 564  
                     // Nothing above??? Then scoot all elements below up one level.
 565  0
                     doMove(fragment, coordinate, newCoordinate);
 566  0
                     processEvent(new LayoutEvent(LayoutEvent.MOVED_UP, fragment, coordinate, class="keyword">newCoordinate));
 567  
                     
 568  
                     // If this the last row, make sure to update the next row pointer accordingly.
 569  0
                     if(coordinate.getY() == (nextRowNumber[coordinate.getX()] - 1))
 570  
                     {
 571  0
                         nextRowNumber[coordinate.getX()] = coordinate.getX();
 572  
                     }
 573  
                     
 574  
                     try
 575  
                     {
 576  0
                         Fragment fragmentBelow = getFragmentAt(new LayoutCoordinate(coordinate.getX(),
 577  
                                 coordinate.getY() + 1));
 578  0
                         moveUp(fragmentBelow);
 579  
                     }
 580  0
                     catch (EmptyLayoutLocationException e1)
 581  
                     {
 582  
 
 583  0
                     }
 584  0
                 }
 585  
             }
 586  0
             catch (InvalidLayoutLocationException e)
 587  
             {
 588  
                 // This should NEVER happen as the location has already been verfied to be valid
 589  0
                 throw new LayoutError("It appears this layout is corrupt and cannot correctly identify valid column locations.", e);
 590  0
             }
 591  
         }
 592  0
     }
 593  
 
 594  
     /**
 595  
      * 
 596  
      * @param fragment
 597  
      * @throws FragmentNotInLayoutException if the specified fragment is not currently in the layout.
 598  
      * @throws LayoutEventException If a triggered LayoutEvent fails.
 599  
      */
 600  
     public void moveDown(Fragment fragment) throws FragmentNotInLayoutException, LayoutEventException
 601  
     {
 602  0
         LayoutCoordinate coordinate = getCoordinate(fragment);
 603  0
         LayoutCoordinate newCoordinate = new LayoutCoordinate(coordinate.getX(), coordinate.getY() + 1);
 604  
 
 605  
         // never move past the current bottom row
 606  0
         if (newCoordinate.getY() < nextRowNumber[coordinate.getX()])
 607  
         {
 608  
             try
 609  
             {
 610  
                 try
 611  
                 {
 612  
                     // the best approach to move a fragment down is to actually move
 613  
                     // its neighbor underneath up
 614  0
                     LayoutCoordinate aboveCoord = new LayoutCoordinate(coordinate.getX(), coordinate.getY() + 1);
 615  0
                     Fragment fragmentBelow = getFragmentAt(aboveCoord);
 616  0
                     doMove(fragmentBelow, aboveCoord, coordinate);
 617  0
                     processEvent(new LayoutEvent(LayoutEvent.MOVED_UP, fragmentBelow, aboveCoord, coordinate));
 618  
                     // Since this logic path is a somewhat special case, the processing of the  MOVED_DOWN
 619  
                     // event happens within the doAdd() method.
 620  
                 }
 621  0
                 catch (EmptyLayoutLocationException e)
 622  
                 {
 623  0
                     doMove(fragment, coordinate, newCoordinate);
 624  0
                     processEvent(new LayoutEvent(LayoutEvent.MOVED_DOWN, fragment, coordinate, class="keyword">newCoordinate));
 625  0
                 }
 626  
             }
 627  0
             catch (InvalidLayoutLocationException e)
 628  
             {
 629  
                 // This should NEVER happen as the location has already been verfied to be valid
 630  0
                 throw new LayoutError("It appears this layout is corrupt and cannot correctly identify valid column locations.", e);
 631  0
             }
 632  
  
 633  
         }
 634  0
     }
 635  
 
 636  
     /**
 637  
      * Performs the actual movement of a fragment.
 638  
      * 
 639  
      * 
 640  
      * @param fragment
 641  
      * @param oldCoordinate
 642  
      * @param newCoordinate
 643  
      * @throws InvalidLayoutLocationException
 644  
      * @throws LayoutEventException 
 645  
      */
 646  
     protected void doMove(Fragment fragment, LayoutCoordinate oldCoordinate, LayoutCoordinate newCoordinate)
 647  
             throws InvalidLayoutLocationException, LayoutEventException
 648  
     {
 649  0
         SortedMap oldColumn = getColumnMap(oldCoordinate.getX());
 650  0
         oldColumn.remove(new Integer(oldCoordinate.getY()));
 651  0
         coordinates.remove(fragment);
 652  
 
 653  0
         doAdd(newCoordinate.getX(), newCoordinate.getY(), fragment);
 654  0
     }
 655  
 
 656  
     /**
 657  
      *
 658  
      * 
 659  
      * @param fragment fragment whose LayoutCoordinate we ant.
 660  
      * @return LayoutCoordinate representing the current location of this
 661  
      * Fragment within this layout.
 662  
      * @throws FragmentNotInLayoutException if the Fragment is not present in this layout.
 663  
      * @see LayoutCoordinate
 664  
      */
 665  
     public LayoutCoordinate getCoordinate(Fragment fragment) throws FragmentNotInLayoutException
 666  
     {
 667  0
         if (coordinates.containsKey(fragment))
 668  
         {
 669  0
             return (LayoutCoordinate) coordinates.get(fragment);
 670  
         }
 671  
         else
 672  
         {
 673  0
             throw new FragmentNotInLayoutException(fragment);
 674  
         }
 675  
     }
 676  
 
 677  
     /**
 678  
      * Adds a fragment at the indicated <code>columnNumber</code>
 679  
      * and <code>rowNumber</code>.
 680  
      * 
 681  
      * @param columnNumber
 682  
      * @param rowNumber
 683  
      * @param fragment
 684  
      * @throws InvalidLayoutLocationException if the coordinates are outside the bounds of this layout.
 685  
      * @throws LayoutEventException id a LayoutEvent fails
 686  
      */
 687  
     protected void doAdd(int columnNumber, class="keyword">int rowNumber, Fragment fragment) throws InvalidLayoutLocationException, LayoutEventException
 688  
     {
 689  0
         SortedMap column = getColumnMap(columnNumber);
 690  
     
 691  0
         Integer rowInteger = new Integer(rowNumber);
 692  0
         LayoutCoordinate targetCoordinate = new LayoutCoordinate(columnNumber, rowNumber);
 693  0
         if (column.containsKey(rowInteger))
 694  
         {
 695  
             // If the row has something in it, push everythin down 1
 696  0
             Fragment existingFragment = (Fragment) column.get(rowInteger);
 697  0
             column.put(rowInteger, fragment);
 698  0
             coordinates.put(fragment, targetCoordinate);
 699  0
             doAdd(columnNumber, ++rowNumber, existingFragment);
 700  
             
 701  0
             LayoutCoordinate oneDownCoordinate = new LayoutCoordinate(targetCoordinate.getX(), targetCoordinate.getY() + 1);
 702  0
             processEvent(new LayoutEvent(LayoutEvent.MOVED_DOWN, existingFragment, targetCoordinate, oneDownCoordinate));
 703  0
         }
 704  
         else
 705  
         {
 706  0
             column.put(rowInteger, fragment);
 707  0
             coordinates.put(fragment, targetCoordinate);
 708  0
             rowNumber++;
 709  0
             if(rowNumber > nextRowNumber[columnNumber])
 710  
             {
 711  0
                 nextRowNumber[columnNumber] = rowNumber;
 712  
             }
 713  
         }
 714  
     
 715  0
     }
 716  
 
 717  
     /**
 718  
      * Retrieves this specified <code>columnNumber</code> as a
 719  
      * SortedMap.
 720  
      * 
 721  
      * @param columnNumber
 722  
      * @return
 723  
      * @throws InvalidLayoutLocationException if the <code>columnNumber</code> resides
 724  
      * outside the bounds of this layout.
 725  
      */
 726  
     protected final SortedMap getColumnMap(int columnNumber) throws InvalidLayoutLocationException
 727  
     {
 728  0
         Integer columnNumberIneteger = new Integer(columnNumber);
 729  
 
 730  0
         if (columns.containsKey(columnNumberIneteger))
 731  
         {
 732  0
             return ((SortedMap) columns.get(columnNumberIneteger));
 733  
         }
 734  
         else
 735  
         {
 736  0
             throw new InvalidLayoutLocationException(columnNumber);
 737  
         }
 738  
 
 739  
     }
 740  
 
 741  
     /**
 742  
      * Gets the row number of this fragment to looking the <code>layoutType</code>
 743  
      * property <i>row</i>.  If this property is undefined, the bottom-most row
 744  
      * number of <code>currentColumn</code> is returned.
 745  
      * 
 746  
      * @param currentColumn
 747  
      * @param fragment
 748  
      * @return valid row for this fragment within this layout.
 749  
      */
 750  
     protected final int getRow(class="keyword">int currentColumn, Fragment fragment)
 751  
     {
 752  0
         String propertyValue = fragment.getProperty(Fragment.ROW_PROPERTY_NAME);
 753  0
         if (propertyValue != null)
 754  
         {
 755  0
             return Integer.parseInt(propertyValue);
 756  
         }
 757  
         else
 758  
         {
 759  0
             return nextRowNumber[currentColumn];
 760  
         }
 761  
 
 762  
     }
 763  
 
 764  
     /**
 765  
      * Gets the row number of this fragment to looking the <code>layoutType</code>
 766  
      * property <i>column</i>. 
 767  
      * 
 768  
      * If the <i>column</i> is undefined or exceeds the constriants of this
 769  
      * layout, the value returned is <code>numberOfColumns - 1</code>.  If the
 770  
      * value is less than 0, 0 is returned.
 771  
      * 
 772  
      * 
 773  
      * @param fragment
 774  
      * @return
 775  
      */
 776  
     protected final int getColumn(Fragment fragment)
 777  
     {
 778  0
         String propertyValue = fragment.getProperty(Fragment.COLUMN_PROPERTY_NAME);
 779  0
         if (propertyValue != null)
 780  
         {
 781  0
             int columnNumber = Integer.parseInt(propertyValue);
 782  
 
 783  
             // Exceeded columns get put into the last column
 784  0
             if (columnNumber >= numberOfColumns)
 785  
             {
 786  0
                 columnNumber = (numberOfColumns - 1);
 787  
             }
 788  
             // Columns less than 1 go in the first column
 789  0
             else if (columnNumber < 0)
 790  
             {
 791  0
                 columnNumber = 0;
 792  
             }
 793  
 
 794  0
             return columnNumber;
 795  
         }
 796  
         else
 797  
         {
 798  0
             return (numberOfColumns - 1);
 799  
         }
 800  
     }
 801  
     
 802  
     /**
 803  
      * Dispatches a LayoutEvent to all LayoutEventListeners registered to this layout.
 804  
      * 
 805  
      * @param event
 806  
      * @throws LayoutEventException if an error occurs while processing a the LayoutEvent.
 807  
      */
 808  
     protected final void processEvent(LayoutEvent event) throws LayoutEventException
 809  
     {
 810  0
         Iterator itr = eventListeners.iterator();
 811  0
         while(itr.hasNext())
 812  
         {
 813  0
             LayoutEventListener eventListener = (LayoutEventListener) itr.next();
 814  0
             eventListener.handleEvent(event);
 815  0
         }
 816  
         
 817  0
     }
 818  
 
 819  
 }

This report is generated by jcoverage, Maven and Maven JCoverage Plugin.