View Javadoc

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.layout.impl;
18  
19  import java.util.HashMap;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.ArrayList;
23  import java.util.Set;
24  import java.util.Map;
25  import java.util.Collections;
26  import java.util.Comparator;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.jetspeed.components.portletregistry.PortletRegistry;
31  import org.apache.jetspeed.layout.Coordinate;
32  import org.apache.jetspeed.layout.PortletPlacementException;
33  import org.apache.jetspeed.layout.PortletPlacementContext;
34  import org.apache.jetspeed.om.page.Fragment;
35  import org.apache.jetspeed.om.page.Page;
36  import org.apache.pluto.om.common.Parameter;
37  import org.apache.pluto.om.common.ParameterSet;
38  import org.apache.pluto.om.portlet.PortletDefinition;
39  
40  
41  /***
42   * Portal Placement Context
43   * 
44   * The purpose of the object is to provide an API that
45   * can be used to move a portlet fragment on the page.
46   * This includes moving, adding, removing and getting
47   * information about portlets that are on the page and
48   * portlets that are available to be added to the page.
49   * 
50   * This object represents the fragment contents of a
51   * single layout fragment (i.e. nested depth cannot 
52   * be captured by this object).
53   * 
54   * An important note about this object:
55   * This object is really only intended to be used to do
56   * a single operation such as "moveabs" or "add".  After
57   * performing the operation, the hashmap data structures
58   * are not correct and should not be used for subsequent
59   * operations.  The reason they are incorrect is that when
60   * a fragment is moved, the coordinate of fragments below
61   * it are now different.  These could be updated, but it
62   * really doesn't serve a purpose since this is a short
63   * lived object.
64   * 
65   * @author <a>David Gurney</a>
66   * @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
67   * @author <a href="mailto:smilek@apache.org">Steve Milek</a>
68   * @version $Id: $
69   */
70  public class PortletPlacementContextImpl implements PortletPlacementContext 
71  {
72      private static Log log = LogFactory.getLog( PortletPlacementContextImpl.class );
73      protected static final String eol = System.getProperty( "line.separator" );
74  
75  	// Columns are reference by index, the rows are held
76  	// in the columnsList as shown below:
77  	//
78  	// [0]        [1]          [2]
79  	// ArrayList  ArrayList    ArrayList
80  	//  Row0Frag   Row0Frag     Row0Frag
81  	//  Row1Frag   Row1Frag     Row1Frag
82  	//  Row2Frag   Row2Frag     Row2Frag
83  	//  ...
84  	//
85  	protected ArrayList[] columnsList = null;
86  	
87  	// Used as a convience when looking up a particular fragment
88  	//
89  	// key is Fragment id (String), value is a Coordinate object
90  	protected Map fragmentCoordinateMap = new HashMap();
91  	
92  	// Used as a convience when looking up a particular fragment by id
93  	//
94  	// key is the Fragment id (String), value is the Fragment
95  	protected Map fragmentMap = new HashMap();
96  	
97  	// Number of columns
98  	protected int numberOfColumns = -1;
99  	
100     protected Page page;
101     private PortletRegistry registry;
102     protected Fragment layoutContainerFragment;
103         
104 	public PortletPlacementContextImpl( Page page, PortletRegistry registry ) 
105         throws PortletPlacementException 
106     {
107 		this( page, registry, null );
108 	}
109         
110     public PortletPlacementContextImpl( Page page, PortletRegistry registry, Fragment container ) 
111         throws PortletPlacementException 
112     {
113     	if ( page == null )
114     		throw new NullPointerException( "PortletPlacementContext cannot be instantiated with a null Page argument" );
115     	if ( registry == null )
116     		throw new NullPointerException( "PortletPlacementContext cannot be instantiated with a null PortletRegistry argument" );
117     	
118     	this.page = page;
119     	this.registry = registry;
120     	
121     	init( container );
122     }
123 	
124     protected void init( Fragment container )
125         throws PortletPlacementException 
126     {
127         if ( container == null )
128         {
129             container = page.getRootFragment();
130             if ( container == null )
131             	throw new PortletPlacementException( "PortletPlacementContext cannot acquire root layout fragment from page" );
132         }        
133         if ( ! "layout".equals( container.getType() ) )
134         {
135         	throw new PortletPlacementException( "PortletPlacementContext specified container fragment (" + container.getId() + ") is not a layout fragment, but is type: " + container.getType() );
136         }
137         this.layoutContainerFragment = container;
138         
139         int columnCount = PortletPlacementContextImpl.getColumnCountAndSizes( container, registry, null );
140         if ( columnCount <= 0 )
141         {
142         	throw new PortletPlacementException( "PortletPlacementContext cannot detemine number of columns in layout fragment (" + container.getId() + ")" );
143         }
144         this.numberOfColumns = columnCount;
145         
146         initProcessLayoutContainerFragment();
147         
148         //debugFragments( "init" );
149 	}
150 	
151 	private void initProcessLayoutContainerFragment() 
152         throws PortletPlacementException 
153     {
154         List fragChildren = this.layoutContainerFragment.getFragments();
155         int fragChildCount = fragChildren.size();
156         
157         int columnCount = this.numberOfColumns;
158         
159         // sort the fragments in the same manner as /portal and /desktop rendering
160         FragmentLinkedListEntry[][] colLinkedLists = new FragmentLinkedListEntry[columnCount][fragChildCount];
161         FragmentLinkedListInfo[] colLinkedListsInfo = new FragmentLinkedListInfo[columnCount];
162         for ( int colIndex = 0 ; colIndex < columnCount ; colIndex++ )
163         {
164         	colLinkedListsInfo[ colIndex ] = new FragmentLinkedListInfo();
165         }
166         for( int fragChildIndex = 0; fragChildIndex < fragChildCount; fragChildIndex++ ) 
167         {
168             Fragment fragment = (Fragment)fragChildren.get( fragChildIndex );		
169             if ( fragment != null )
170             {
171             	int col = getColumnFromFragment( fragment );            	
172             	
173             	FragmentLinkedListEntry[] ll = colLinkedLists[col];
174             	FragmentLinkedListInfo llInfo = colLinkedListsInfo[col];
175             	
176             	Integer rowObj = getRowFromFragment( fragment );
177             	int row;
178             	if ( rowObj != null )
179             		row = rowObj.intValue();
180             	else
181             		row = llInfo.getHigh() + 1;   // fragment with unspecified row property is assigned 
182                                                   //    the value of current fragment in the highest row + 1
183             		                              //    - this is one of the reasons we are not using sort here
184             	
185             	FragmentLinkedListEntry fragLLentry = new FragmentLinkedListEntry( fragChildIndex, row );
186             	int llLen = llInfo.useNextAvailableIndex();
187             	ll[ llLen ] = fragLLentry;
188             	if ( llLen == 0 )
189             	{
190             		llInfo.setHead( 0 );
191             		llInfo.setTail( 0 );
192             		llInfo.setHigh( row );
193             	}
194             	else
195             	{
196             		if ( row > llInfo.getHigh() )
197             		{
198             			ll[ llInfo.getTail() ].setNextEntry( llLen );
199             			llInfo.setHigh( row );
200             			llInfo.setTail( llLen );
201             		}
202             		else
203             		{
204             			int llEntryIndex = llInfo.getHead();
205             			int llPrevEntryIndex = -1;
206             			while ( ll[llEntryIndex].getRow() < row )
207             			{
208             				llPrevEntryIndex = llEntryIndex;
209             				llEntryIndex = ll[llEntryIndex].getNextEntry();
210             			}
211             			if ( ll[llEntryIndex].getRow() == row )
212             			{   // a subsequent fragment (in the document) with a row value equal to that 
213             				//    of a previous fragment is inserted before the previous fragment
214             				//    - this is one of the reasons we are not using sort here
215             				int incrementedRow = row + 1;
216             				ll[llEntryIndex].setRow( incrementedRow );
217             				if ( llInfo.getTail() == llEntryIndex )
218             					llInfo.setHigh( incrementedRow );
219             			}
220             			fragLLentry.setNextEntry( llEntryIndex );
221             			if ( llPrevEntryIndex == -1 )
222             				llInfo.setHead( llLen );
223             			else
224             				ll[llPrevEntryIndex].setNextEntry( llLen );
225             		}
226             	}
227             }
228         }
229 
230         ArrayList[] columnFragments = new ArrayList[ columnCount ];
231         for ( int colIndex = 0 ; colIndex < columnCount ; colIndex++ )
232         {
233         	ArrayList fragmentsInColumn = new ArrayList();
234         	columnFragments[ colIndex ] = fragmentsInColumn;
235         	
236         	FragmentLinkedListEntry[] ll = colLinkedLists[colIndex];
237         	FragmentLinkedListInfo llInfo = colLinkedListsInfo[colIndex];
238             
239         	int rowIndex = 0;
240             int nextEntryIndex = llInfo.getHead();
241             while ( nextEntryIndex != -1 )
242             {
243                 FragmentLinkedListEntry fragLLentry = ll[nextEntryIndex];
244                 Fragment fragment = (Fragment)fragChildren.get( fragLLentry.getFragmentIndex() );
245                 
246                 fragmentsInColumn.add( fragment );
247                 CoordinateImpl coordinate = new CoordinateImpl( colIndex, rowIndex );
248         		this.fragmentCoordinateMap.put( fragment.getId(), coordinate );
249         		this.fragmentMap.put( fragment.getId(), fragment );
250         		
251                 nextEntryIndex = fragLLentry.getNextEntry();
252         		rowIndex++;
253             }
254         }
255         this.columnsList = columnFragments;
256 	}
257 	
258 	private int getColumnFromFragment( Fragment fragment )
259 	{
260 		// get column value in the same manner as /portal and /desktop rendering
261 		
262 		// get column from properties to distinguish between null and -1 (fragment.getLayoutColumn() is -1 when column is not specified)
263 		String colStr = (String)fragment.getProperties().get( "column" );
264         int columnCount = this.numberOfColumns;
265 		int col = columnCount - 1;
266 		if ( colStr != null )
267 		{
268 			try
269     		{
270 				col = Integer.parseInt( colStr );
271 				if ( col < 0 )
272 					col = 0;
273 				else if ( col >= columnCount )
274 					col = columnCount - 1;
275     		}
276     		catch ( NumberFormatException ex )
277     		{
278     			col = columnCount - 1;
279     		}
280 		}
281 		return col;
282 	}
283 	private Integer getRowFromFragment( Fragment fragment )
284 	{
285 		// get row value in the same manner as /portal and /desktop rendering
286 		
287 		// get row from properties to distinguish between null and -1 (fragment.getLayoutRow() is -1 when row is not specified)
288 		String rowStr = (String)fragment.getProperties().get( "row" );
289 		if ( rowStr != null )
290 		{
291 			try
292     		{
293 				int row = Integer.parseInt( rowStr );
294 				if ( row < 0 )
295 					row = 0;
296 				return new Integer( row );
297     		}
298     		catch ( NumberFormatException ex )
299     		{
300     		}
301 		}
302 		return null;
303 	}
304 	
305 	private int normalizeColumnIndex( int col, ArrayList[] columnFragments, int defaultForUnspecifiedCol )
306 	{
307 		int columnCount = this.numberOfColumns;
308 		if ( col >= columnCount )
309     		col = (columnCount -1);
310     	else if ( col < 0 && defaultForUnspecifiedCol >= 0 && defaultForUnspecifiedCol < columnCount )
311     		col = defaultForUnspecifiedCol;
312     	else if ( col < 0 )
313     		col = 0;
314 		return col;
315 	}
316 
317 	class FragmentLinkedListInfo
318 	{
319 		private int head = -1;
320 		private int tail = -1;
321 		private int high = -1;
322 		private int availableNextIndex = 0;
323 		
324 		FragmentLinkedListInfo()
325 		{
326 		}
327 		
328 		public int getHead()
329 		{
330         	return head;
331         }
332 		public void setHead( int newOne )
333 		{
334         	this.head = newOne;
335         }
336 		public int getTail()
337 		{
338         	return tail;
339         }
340 		public void setTail( int newOne )
341 		{
342         	this.tail = newOne;
343         }
344 		public int getHigh()
345 		{
346         	return high;
347         }
348 		public void setHigh( int newOne )
349 		{
350         	this.high = newOne;
351         }
352 		public int useNextAvailableIndex()
353 		{
354 			return this.availableNextIndex++;
355 		}
356 	}
357 	
358 	class FragmentLinkedListEntry
359 	{
360 		private int fragmentIndex;
361 		private int row;
362 		private int nextEntry = -1;
363 		
364 		FragmentLinkedListEntry( int fragmentIndex, int row )
365 		{
366 			this.fragmentIndex = fragmentIndex;
367 			this.row = row;
368 		}
369 		
370 		public int getFragmentIndex()
371 		{
372 			return this.fragmentIndex;
373 		}
374 		public int getRow()
375 		{
376 			return this.row;
377 		}
378 		public void setRow( int newOne )
379 		{
380 			this.row = newOne;
381 		}
382 		public int getNextEntry()
383 		{
384 			return this.nextEntry;
385 		}
386 		public void setNextEntry( int newOne )
387 		{
388 			this.nextEntry = newOne;
389 		}
390 	}	
391     
392 	public String dumpFragments( String debug )
393     {       
394         StringBuffer out = new StringBuffer();
395         out.append( "PortletPlacementContext - " );
396         if ( debug != null )
397         	out.append( debug ).append( " - " );
398         out.append( "container: " ).append( this.layoutContainerFragment == null ? "<null>" : ( this.layoutContainerFragment.getId() + " / " + this.layoutContainerFragment.getType() ) ).append( " column-count=" ).append( this.numberOfColumns ).append( eol );
399         for (int ix = 0; ix < this.columnsList.length; ix++)
400         {
401             ArrayList column = this.columnsList[ix];
402             out.append( "   column " ).append( ix ).append( eol );
403             Iterator frags = column.iterator();
404             while ( frags.hasNext() )
405             {
406                 Fragment f = (Fragment)frags.next();
407                 out.append( "      frag " ).append( f == null ? "<null>" : ( f.getId() + " / " + f.getType() ) ).append( eol );
408             }
409         }
410         return out.toString();
411     }
412     public Fragment debugFragments( String debug )
413     {
414         log.info( dumpFragments( debug ) );
415         return layoutContainerFragment;
416     }
417 
418     /***
419      * Takes the internal portlet placement state and stores back
420      * out to fragment state
421      * 
422      * @return the managed page layout with updated fragment state. 
423      */
424     public Page syncPageFragments()
425     {
426     	syncFragments( true, -1 );
427     	//debugFragments( "syncPage" );
428     	return this.page;
429     }
430     
431     protected int getLatestColumn( Coordinate coordinate )
432     {
433     	int col = -1;
434     	if ( coordinate != null )
435     	{
436     		col = coordinate.getNewCol();
437     		if ( col == -1 )
438     			col = coordinate.getOldCol();
439     	}
440     	return col;
441     }
442     protected int getLatestRow( Coordinate coordinate )
443     {
444     	int row = -1;
445     	if ( coordinate != null )
446     	{
447     		row = coordinate.getNewRow();
448     		if ( row == -1 )
449     			row = coordinate.getOldRow();
450     	}
451     	return row;
452     }
453     
454     protected void syncFragments( boolean updateFragmentObjects, int onlyForColumnIndex )
455     {
456         for ( int colIndex = 0; colIndex < this.columnsList.length; colIndex++ )
457         {
458         	if ( onlyForColumnIndex == -1 || onlyForColumnIndex == colIndex )
459         	{
460 	            ArrayList column = this.columnsList[colIndex];
461 	            int colRowCount = column.size();
462 	        	for ( int rowIndex= 0; rowIndex < colRowCount; rowIndex++ )
463 	        	{
464 	        		Fragment fragment = (Fragment)column.get( rowIndex );
465 	                Coordinate coordinate = (Coordinate)this.fragmentCoordinateMap.get( fragment.getId() );
466 	                boolean needsReplacementCoordinate = false;
467 	                
468 	                if ( getLatestColumn( coordinate ) != colIndex || getLatestRow( coordinate ) != rowIndex )
469 	                	needsReplacementCoordinate = true;
470 
471 	                if ( needsReplacementCoordinate )
472 	        		{
473 	        			Coordinate replacementCoordinate = new CoordinateImpl( coordinate.getOldCol(), coordinate.getOldRow(), colIndex, rowIndex );
474 	        			this.fragmentCoordinateMap.put( fragment.getId(), replacementCoordinate );
475 	        		}
476 	        		if ( updateFragmentObjects )
477 	                {
478 	                	fragment.setLayoutColumn( colIndex );
479 	                	fragment.setLayoutRow( rowIndex );
480 	                }
481 	            }
482         	}
483         }
484     }
485     
486     public int getFragmentRow( Fragment fragment )
487     {
488     	if ( fragment == null ) return -1;
489 		Coordinate coordinate = (Coordinate)this.fragmentCoordinateMap.get( fragment.getId() );
490     	
491 		if ( coordinate == null )
492 			return -1;
493 		if ( coordinate.getNewRow() >= 0  )
494 			return coordinate.getNewRow();
495 		return coordinate.getOldRow();
496     }
497     
498     public int getFragmentCol( Fragment fragment )
499     {
500     	if ( fragment == null ) return -1;
501 		Coordinate coordinate = (Coordinate)this.fragmentCoordinateMap.get( fragment.getId() );
502     	
503 		if ( coordinate == null )
504 			return -1;
505 		if ( coordinate.getNewCol() >= 0  )
506 			return coordinate.getNewCol();
507 		return coordinate.getOldCol();
508     }
509 			
510 	public Fragment getFragment( String fragmentId ) throws PortletPlacementException 
511     {
512 		return (Fragment)this.fragmentMap.get( fragmentId );
513 	}
514 	
515 	public Fragment getFragmentAtOldCoordinate( Coordinate coordinate ) throws PortletPlacementException 
516     {
517 		return getFragmentAtCoordinate( coordinate, true, false );
518 	}
519 
520 	public Fragment getFragmentAtNewCoordinate( Coordinate coordinate ) throws PortletPlacementException 
521     {
522 		return getFragmentAtCoordinate( coordinate, false, false );
523 	}
524 
525 	protected Fragment getFragmentAtCoordinate( Coordinate coordinate, boolean useOldCoordinateValues, boolean suppressExceptions ) throws PortletPlacementException 
526     {
527 		int col = -1;
528 		int row = -1;
529 		if ( useOldCoordinateValues ) 
530         {
531 			col = coordinate.getOldCol();
532 			row = coordinate.getOldRow();
533 		}
534 		else 
535         {
536 			col = coordinate.getNewCol();
537 			row = coordinate.getNewRow();
538 		}
539 		
540 		// Do some sanity checking about the request
541 		if ( col < 0 || col >= this.numberOfColumns )
542         {
543 			if ( suppressExceptions )
544 				return null;
545 			throw new PortletPlacementException( "Requested column (" + col + ") is out of bounds (column-count=" + this.numberOfColumns + ")" );
546 		}
547 		
548 		// Get the array list associated with the column
549 		ArrayList columnArray = this.columnsList[col];
550 		if ( row < 0 || row >= columnArray.size() )
551         {
552 			if ( suppressExceptions )
553 				return null;
554 			throw new PortletPlacementException( "Requested row (" + row + ") is out of bounds (col[" + col + "].row-count=" + columnArray.size() + ")" );
555 		}
556 		
557 		return (Fragment)columnArray.get( row );
558 	}
559 	
560 	public Fragment getFragmentById( String fragmentId ) throws PortletPlacementException 
561     {
562 		return (Fragment)this.fragmentMap.get( fragmentId );
563 	}
564 
565 	public int getNumberColumns() throws PortletPlacementException 
566     {
567         return this.numberOfColumns;
568 	}
569 
570 	public int getNumberRows( int col ) throws PortletPlacementException 
571     {
572 		// Sanity check the column
573 		if ( col < 0 || col >= this.numberOfColumns )
574         {
575 			throw new PortletPlacementException( "Requested column (" + col + ") is out of bounds (column-count=" + this.numberOfColumns + ")" );
576 		}
577 		return this.columnsList[col].size();
578 	}
579 	
580 	public Coordinate add( Fragment fragment, Coordinate coordinate ) throws PortletPlacementException 
581     {
582 		return moveAbsolute( fragment, coordinate, true );
583 	}
584 
585 	public Coordinate moveAbsolute( Fragment fragment, Coordinate newCoordinate )
586         throws PortletPlacementException 
587     {
588 		return moveAbsolute( fragment, newCoordinate, false );
589     }
590 	public Coordinate moveAbsolute( Fragment fragment, Coordinate newCoordinate, boolean okToAddFragment )
591         throws PortletPlacementException 
592     {
593 		if ( fragment == null )
594     		throw new NullPointerException( "PortletPlacementContext moveAbsolute() cannot accept a null Fragment argument" );
595 
596 		Coordinate currentCoordinate = (Coordinate)this.fragmentCoordinateMap.get( fragment.getId() );
597 		int currentCol = getLatestColumn( currentCoordinate );
598 		int currentRow = getLatestRow( currentCoordinate );
599 		
600 		int newCol = normalizeColumnIndex( getLatestColumn( newCoordinate ), this.columnsList, currentCol );
601 		int newRow = getLatestRow( newCoordinate );
602 
603 		if ( currentCoordinate == null )
604 		{
605 			if ( ! okToAddFragment )
606 				throw new NullPointerException( "PortletPlacementContext moveAbsolute() cannot add fragment (" + fragment.getId() + ") unless the okToAddFragment argument is set to true" );
607 			
608 			// add fragment
609 			ArrayList newColumn = this.columnsList[newCol];
610 			if ( newRow < 0 || newRow >= newColumn.size() )
611 				newRow = newColumn.size();
612 			newColumn.add( newRow, fragment );
613 			
614 			CoordinateImpl coordinate = new CoordinateImpl( newCol, newRow );
615         	this.fragmentCoordinateMap.put( fragment.getId(), coordinate );
616 			this.fragmentMap.put( fragment.getId(), fragment );
617 			syncFragments( false, newCol );
618 		}
619 		else
620 		{
621 			boolean columnChanged = ( currentCol != newCol );
622 			boolean rowChanged = ( currentRow != newRow );
623 
624 			if ( columnChanged || rowChanged )
625 			{
626 				verifyFragmentAtExpectedCoordinate( currentCol, currentRow, fragment, "moveAbsolute()" );
627 				
628 				ArrayList currentColumn = this.columnsList[currentCol];
629 				currentColumn.remove( currentRow );
630 				
631 				ArrayList newColumn = currentColumn;
632 				if ( columnChanged )
633 					newColumn = this.columnsList[newCol];
634 
635 				if ( newRow < 0 || newRow >= newColumn.size() )
636 					newColumn.add( fragment );
637 				else
638 					newColumn.add( newRow, fragment );
639 				
640 				this.fragmentMap.put( fragment.getId(), fragment );
641 				
642 				syncFragments( false, currentCol );
643 				if ( columnChanged )
644 					syncFragments( false, newCol );
645 			}
646 		}
647 		return (Coordinate)this.fragmentCoordinateMap.get( fragment.getId() );
648 	}
649 
650 	protected Coordinate moveDirection( Fragment fragment, int deltaCol, int deltaRow ) 
651         throws PortletPlacementException 
652     {
653 		if ( fragment == null )
654     		throw new NullPointerException( "PortletPlacementContext moveDirection() cannot accept a null Fragment argument" );
655 
656 		if ( deltaCol != 0 || deltaRow != 0 )
657 		{
658 			Coordinate currentCoordinate = (Coordinate)this.fragmentCoordinateMap.get( fragment.getId() );
659 			if ( currentCoordinate == null )
660 				throw new NullPointerException( "PortletPlacementContext moveDirection() cannot locate target fragment (" + fragment.getId() + ")" );
661 	
662 			int currentCol = getLatestColumn( currentCoordinate );
663 			int currentRow = getLatestRow( currentCoordinate );
664 			
665 			verifyFragmentAtExpectedCoordinate( currentCol, currentRow, fragment, "moveDirection()" );
666 			
667 			int newCol = currentCol + deltaCol;
668 			int newRow = currentRow + deltaRow;
669 			if ( newCol >= 0 && newCol < this.numberOfColumns )
670 			{
671 				ArrayList currentColumn = this.columnsList[currentCol];
672 				ArrayList newColumn = currentColumn;
673 				if ( newCol != currentCol )
674 					newColumn = this.columnsList[newCol];
675 				
676 				currentColumn.remove( currentRow );
677 					
678 				if ( newRow < 0 || newRow >= newColumn.size() )
679 					newColumn.add( fragment );
680 				else
681 					newColumn.add( newRow, fragment );
682 				
683 				this.fragmentMap.put( fragment.getId(), fragment );
684 				
685 				syncFragments( false, currentCol );
686 				if ( newCol != currentCol )
687 					syncFragments( false, newCol );
688 			}
689 		}
690 		return (Coordinate)this.fragmentCoordinateMap.get( fragment.getId() );
691 	}
692 	
693 	public Coordinate moveDown( Fragment fragment ) throws PortletPlacementException 
694     {
695 		return moveDirection( fragment, 0, 1 );
696 	}
697 
698 	public Coordinate moveUp( Fragment fragment ) throws PortletPlacementException 
699     {
700 		return moveDirection( fragment, 0, -1 );
701 	}
702 
703 	public Coordinate moveLeft( Fragment fragment ) throws PortletPlacementException 
704     {
705 		return moveDirection( fragment, -1, 0 );
706 	}
707 
708 	public Coordinate moveRight( Fragment fragment ) throws PortletPlacementException 
709     {
710 		return moveDirection( fragment, 1, 0 );
711 	}
712 
713 	public Coordinate remove( Fragment fragment ) throws PortletPlacementException 
714     {
715 		if ( fragment == null )
716     		throw new NullPointerException( "PortletPlacementContext remove() cannot accept a null Fragment argument" );
717 		
718 		Coordinate currentCoordinate = (Coordinate)this.fragmentCoordinateMap.get( fragment.getId() );
719 		if ( currentCoordinate == null )
720 			throw new NullPointerException( "PortletPlacementContext remove() cannot locate target fragment (" + fragment.getId() + ")" );
721 
722 		int currentCol = getLatestColumn( currentCoordinate );
723 		int currentRow = getLatestRow( currentCoordinate );
724 		
725 		verifyFragmentAtExpectedCoordinate( currentCol, currentRow, fragment, "moveDirection()" );
726 
727 		ArrayList currentColumn = this.columnsList[currentCol];
728 		
729 		currentColumn.remove( currentRow );
730 		
731 		this.fragmentCoordinateMap.remove( fragment.getId() );
732 		this.fragmentMap.remove( fragment.getId() );
733 		
734 		syncFragments( false, currentCol );
735 		
736 		return currentCoordinate;
737 	}
738 	
739 	protected Fragment verifyFragmentAtExpectedCoordinate( int colIndex, int rowIndex, Fragment fragment, String sourceDesc )
740 		throws PortletPlacementException
741 	{
742 		CoordinateImpl coordinate = new CoordinateImpl( colIndex, rowIndex );
743 		
744 		boolean suppressExceptions = ( fragment != null );
745 		Fragment foundFragment = getFragmentAtCoordinate( coordinate, true, suppressExceptions );
746 		
747 		if ( fragment != null )
748 		{
749 			if ( foundFragment == null || foundFragment.getId() != fragment.getId() )
750 			{
751 				sourceDesc = ( sourceDesc == null ? "getFragmentAtExpectedCoordinate" : sourceDesc );
752 				
753 				ArrayList column = null;
754 				int colFragCount = -1;
755 				if ( colIndex >= 0 && colIndex < this.numberOfColumns )
756 				{
757 					column = this.columnsList[colIndex];
758 					colFragCount = column.size();
759 				}
760 				StringBuffer out = new StringBuffer();
761 				out.append( "PortletPlacementContext " ).append( sourceDesc ).append( " has encountered unexpected results");
762 				out.append( " using the current instance state to locate fragment " ).append( fragment.getId() ).append( " (" );
763 				if ( foundFragment == null )
764 					out.append( "no fragment" );
765 				else
766 					out.append( "different fragment" );
767 				out.append( " in row " ).append( rowIndex ).append( " of column " ).append( colIndex );
768 				if ( column == null )
769 				{
770 					out.append( " - column is out of bounds, column-count=" ).append( this.numberOfColumns );
771 				}
772 				else
773 				{
774 					out.append( " - " );
775 					if ( rowIndex < 0 || rowIndex >= colFragCount )
776 						out.append( "row is out of bounds, " );
777 					out.append( "row-count=" ).append( colFragCount );
778 				}
779 				out.append( ")" );
780 				throw new PortletPlacementException( out.toString() );
781 			}
782 		}
783 		return fragment;
784 	}
785 
786 	static public int getColumnCountAndSizes( Fragment layoutFragment, PortletRegistry registry, Map fragSizes )
787 	{
788 		return PortletPlacementContextImpl.getColumnCountAndSizes( layoutFragment, registry, fragSizes, false );
789 	}
790     static public int getColumnCountAndSizes( Fragment layoutFragment, PortletRegistry registry, Map fragSizes, boolean suppressErrorLogging )
791 	{
792     	if ( layoutFragment == null )
793     		throw new NullPointerException( "getColumnCountAndSizes cannot accept a null Fragment argument" );
794     	if ( registry == null )
795     		throw new NullPointerException( "getColumnCountAndSizes cannot accept a null PortletRegistry argument" );
796     	
797 		int columnCount = -1;
798 		if ( ! "layout".equals( layoutFragment.getType() ) )
799 		{
800 			if ( ! suppressErrorLogging )
801 				log.error( "getColumnCountAndSizes not a layout fragment - " + layoutFragment.getId() + " type=" + layoutFragment.getType() );
802 		}
803 		else
804     	{   // get layout fragment sizes
805     		String sizesVal = layoutFragment.getProperty( "sizes" );
806     		String layoutName = layoutFragment.getName();
807     		layoutName = ( (layoutName != null && layoutName.length() > 0) ? layoutName : (String)null );
808     		ParameterSet paramSet = null;
809     		PortletDefinition portletDef = null;
810     		if ( sizesVal == null || sizesVal.length() == 0 )
811     		{
812     			if ( layoutName != null )
813     			{
814     				// logic below is copied from org.apache.jetspeed.portlets.MultiColumnPortlet
815     				portletDef = registry.getPortletDefinitionByUniqueName( layoutName );
816                     if ( portletDef != null )
817                     {
818                     	paramSet = portletDef.getInitParameterSet();
819                     	if ( paramSet != null )
820                     	{
821                     		Parameter sizesParam = paramSet.get( "sizes" );
822                     		sizesVal = ( sizesParam == null ) ? null : sizesParam.getValue();
823                     	}
824                     }
825     			}
826     		}
827     		if ( sizesVal != null && sizesVal.length() > 0 )
828     		{
829     			if ( fragSizes != null )
830     				fragSizes.put( layoutFragment.getId(), sizesVal );
831 					
832 				int sepPos = -1, startPos = 0, sizesLen = sizesVal.length();
833 				columnCount = 0;
834 				do
835 				{
836 					sepPos = sizesVal.indexOf( ',', startPos );
837 					if ( sepPos != -1 )
838 					{
839 						if ( sepPos > startPos )
840 							columnCount++;
841 						startPos = sepPos +1;
842 					}
843 					else if ( startPos < sizesLen )
844 					{
845 						columnCount++;
846 					}
847 				} while ( startPos < sizesLen && sepPos != -1 );
848 				if ( ! suppressErrorLogging && columnCount <= 0 )
849 					log.error( "getColumnCountAndSizes invalid columnCount - " + layoutFragment.getId() + " / " + layoutName + " count=" + columnCount + " sizes=" + sizesVal );
850 			}
851 			else if ( paramSet == null )
852 			{
853 				if ( ! suppressErrorLogging )
854 				{
855 					if ( layoutName == null )
856 						log.error( "getColumnCountAndSizes null sizes, null layoutName - " + layoutFragment.getId() );
857 					else if ( portletDef == null )
858 						log.error( "getColumnCountAndSizes null sizes, null PortletDefinition - " + layoutFragment.getId() + " / " + layoutName );
859 					else
860 						log.error( "getColumnCountAndSizes null sizes, null ParameterSet - " + layoutFragment.getId() + " / " + layoutName );
861 				}
862 			}
863 			else
864 			{
865 				Parameter colsParam = paramSet.get( "columns" );
866 				String colsParamVal = ( colsParam == null ) ? null : colsParam.getValue();
867 				if ( colsParamVal != null && colsParamVal.length() > 0 )
868 				{
869 					try
870 					{
871 						columnCount = Integer.parseInt( colsParamVal );
872 					}
873 					catch ( NumberFormatException ex )
874 					{
875 					}
876 					if ( columnCount < 1 )
877 					{
878 						columnCount = 2;
879 					}
880 					switch ( columnCount )
881 					{
882 	            		case 1: sizesVal = "100%"; break;
883 	            		case 2: sizesVal = "50%,50%"; break;
884 	            		case 3: sizesVal = "34%,33%,33%"; break;
885 	            		case 4: sizesVal = "25%,25%,25%,25%"; break;
886 	            		default: 
887 	            		{
888 	            			sizesVal = "50%,50%";
889 	            			columnCount = 2;
890 	            			break;
891 	            		}
892 					}
893 					if ( fragSizes != null )
894 						fragSizes.put( layoutFragment.getId(), sizesVal );
895 					//log.info( "getColumnCountAndSizes " + layoutFragment.getId() + " count=" + columnCount + " defaulted-sizes=" + sizesParamVal );
896 				}
897 				else
898 				{
899 					if ( ! suppressErrorLogging )
900 						log.error( "getColumnCountAndSizes null sizes, columns not defined in ParameterSet - " + layoutFragment.getId() + " / " + layoutName );
901 				}
902 			}
903     	}
904 		return columnCount;
905 	}	
906 }