gt: 				if (keepLeft || keepRight || keepTop || keepBottom)
context: /*
   * JasperReports - Free Java Reporting Library.
   * Copyright (C) 2001 - 2014 TIBCO Software Inc. All rights reserved.
   * http://www.jaspersoft.com
   *
   * Unless you have purchased a commercial license agreement from Jaspersoft,
   * the following license terms apply:
   *
   * This program is part of JasperReports.
   *
   * JasperReports is free software: you can redistribute it and/or modify
   * it under the terms of the GNU Lesser General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * JasperReports is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   * GNU Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public License
   * along with JasperReports. If not, see <http://www.gnu.org/licenses/>.
   */
  
  /*
   * Contributors:
   * Greg Hilton
   */
  
  package net.sf.jasperreports.engine.export;
  
  import java.awt.Color;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.ListIterator;
  import java.util.Map;
  
  import net.sf.jasperreports.engine.JRBoxContainer;
  import net.sf.jasperreports.engine.JRLineBox;
  import net.sf.jasperreports.engine.JRPrintElement;
  import net.sf.jasperreports.engine.JRPrintFrame;
  import net.sf.jasperreports.engine.JRPrintPage;
  import net.sf.jasperreports.engine.JasperPrint;
  import net.sf.jasperreports.engine.type.ModeEnum;
  import net.sf.jasperreports.engine.util.JRBoxUtil;
  import net.sf.jasperreports.engine.util.Pair;
  
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  
  /**
   * Utility class used by grid exporters to create a grid for page layout.
   *
   * @author Lucian Chirita (lucianc@users.sourceforge.net)
   */
  public class JRGridLayout
  {
  	private static final Log log = LogFactory.getLog(JRGridLayout.class);
  	
  	private final ExporterNature nature;
  	private final List<JRPrintElement> elementList;
  	
  	private final Map<GridCellSize, GridCellSize> cellSizes;
  	private final Map<GridCellStyle, GridCellStyle> cellStyles;
  	private final Map<Pair<GridCellSize, GridCellStyle>, EmptyGridCell> emptyCells;
  
  	private int width;
  	private int height;
  	private int offsetX;
  	private int offsetY;
  
  	private CutsInfo xCuts;
  	private CutsInfo yCuts;
  	private Grid grid;
  
  	private Map<BoxKey,JRLineBox> boxesCache;
  	private boolean hasTopMargin = true;
  	private boolean hasBottomMargin = true;
  	private boolean hasLeftMargin = true;
  	private boolean hasRightMargin = true;
  	
  	private boolean isNested;
  	
  	/**
  	 * Constructor.
  	 *
  	 * @param elements the elements that should arranged in a grid
  	 * @param width the width available for the grid
  	 * @param height the height available for the grid
  	 * @param offsetX horizontal element position offset
  	 * @param offsetY vertical element position offset
  	 */
  	public JRGridLayout(
  		ExporterNature nature,
  		List<JRPrintElement> elements,
  		int width,
  		int height,
  		int offsetX,
  		int offsetY
  		)
  	{
  		this(
  			nature,
  			elements,
  			width,
  			height,
  			offsetX,
  			offsetY,
  			null //xCuts
  			);
  	}
  
  	/**
  	 * Constructor.
  	 *
  	 * @param elements the elements that should arranged in a grid
  	 * @param width the width available for the grid
  	 * @param height the height available for the grid
  	 * @param offsetX horizontal element position offset
  	 * @param offsetY vertical element position offset
  	 * @param xCuts An optional list of pre-calculated X cuts.
  	 */
  	public JRGridLayout(
  		ExporterNature nature,
  		List<JRPrintElement> elements,
  		int width,
  		int height,
  		int offsetX,
  		int offsetY,
  		CutsInfo xCuts
  		)
  	{
  		this.nature = nature;
  		this.elementList = elements;
  		
  		// TODO lucianc cache these across report pages?
  		this.cellSizes = new HashMap<GridCellSize, GridCellSize>();
  		this.cellStyles = new HashMap<GridCellStyle, GridCellStyle>();
  		this.emptyCells = new HashMap<Pair<GridCellSize,GridCellStyle>, EmptyGridCell>();
  		
  		this.height = height;
  		this.width = width;
  		this.offsetX = offsetX;
  		this.offsetY = offsetY;
  		this.xCuts = xCuts;
  
  		boxesCache = new HashMap<BoxKey,JRLineBox>();
  
  		layoutGrid(null, elements);
  	}
  
  	/**
  	 * Constructor.
  	 *
  	 * @param width the width available for the grid
  	 * @param height the height available for the grid
  	 * @param offsetX horizontal element position offset
  	 * @param offsetY vertical element position offset
  	 */
  	protected JRGridLayout(
  		JRGridLayout parent,
  		List<JRPrintElement> elements,
  		int width,
  		int height,
  		int offsetX,
  		int offsetY,
  		PrintElementIndex parentElementIndex
  		)
  	{
  		this.nature = parent.nature;
  		this.elementList = parent.elementList;
  		
  		this.cellSizes = parent.cellSizes;
  		this.cellStyles = parent.cellStyles;
  		this.emptyCells = parent.emptyCells;
  		
  		this.height = height;
  		this.width = width;
  		this.offsetX = offsetX;
  		this.offsetY = offsetY;
  
  		//this constructor is called only in nested grids:
  		this.isNested = true;
  
  		boxesCache = new HashMap<BoxKey,JRLineBox>();
  		
  		layoutGrid(parentElementIndex, elements);
  	}
  	
  	public JRPrintElement getElement(PrintElementIndex parentIndex, int index)
  	{
  		// TODO lucianc keep a cache of current element position?
  		JRPrintElement element;
  		if (parentIndex == null)
  		{
  			element = elementList.get(index);
  		}
  		else
  		{
  			JRPrintFrame parentFrame = (JRPrintFrame) getElement(parentIndex.getParentIndex(), parentIndex.getIndex());
  			element = parentFrame.getElements().get(index);
  		}
  		return element;
  	}
  
  	/**
  	 * Constructs the element grid.
  	 * @param parentElementIndex 
  	 */
  	protected void layoutGrid(PrintElementIndex parentElementIndex, List<JRPrintElement> elements)
  	{
  
  		boolean createXCuts = (xCuts == null);
  
  		xCuts = createXCuts ? new CutsInfo() : xCuts;
  		yCuts = nature.isIgnoreLastRow() ? new CutsInfo(0) : new CutsInfo(height);
  
  		if(!isNested && nature.isIgnorePageMargins()) //FIXMEXLS left and right margins are not ignored when all pages on a single sheet
  		{
  			// TODO lucianc this is an extra virtualization iteration
  			setMargins(elements);
  
  			if(createXCuts)
  			{
  				if(hasLeftMargin)
  				{
  					xCuts.removeCutOffset(0);
  				}
  			}
  
  			if(hasTopMargin)
  			{
  				yCuts.removeCutOffset(0);
  			}
  			if(hasBottomMargin)
  			{
  				yCuts.removeCutOffset(height);
  			}
  		}
  
  		createCuts(elements, offsetX, offsetY, createXCuts);
  
  		// add a cut at the width if it's a nested grid, or if the right margin
  		// is not to be removed and no element goes beyond the width
  		if (createXCuts && (isNested
  				|| (!(nature.isIgnorePageMargins() && hasRightMargin)
  				&& !(xCuts.hasCuts() && xCuts.getLastCutOffset() >= width))))
  		{
  			xCuts.addCutOffset(width);
  		}
  		
  		xCuts.use();
  		yCuts.use();
  
  		int colCount = Math.max(xCuts.size() - 1, 0);
  		int rowCount = Math.max(yCuts.size() - 1, 0);
  
  		grid = new Grid(rowCount, colCount);
  
  		for(int row = 0; row < rowCount; row++)
  		{
  			for(int col = 0; col < colCount; col++)
  			{
  				GridCellSize size = cellSize(
  					xCuts.getCutOffset(col + 1) - xCuts.getCutOffset(col),
  					yCuts.getCutOffset(row + 1) - yCuts.getCutOffset(row),
  					1,
  					1
  					);
  				grid.set(row, col, emptyCell(size, null));
  			}
  		}
  
  		setGridElements(parentElementIndex, elements, 
  				offsetX, offsetY,
  				0, 0, rowCount, colCount);
  
  		width = xCuts.getTotalLength();
  		height = yCuts.getTotalLength();
  	}
  	
  	protected GridCellSize cellSize(int width, int height, int colSpan, int rowSpan)
  	{
  		GridCellSize key = new GridCellSize(width, height, colSpan, rowSpan);
  		GridCellSize size = cellSizes.get(key);
  		if (size == null)
  		{
  			size = key;
  			cellSizes.put(key, size);
  			
  			if (log.isTraceEnabled())
  			{
  				log.trace(this + " added cell size " + size);
  			}
  		}
  		return size;
  	}
  
  	protected void createCuts(List<JRPrintElement> elements, int elementOffsetX, int elementOffsetY, boolean createXCuts)
  	{
  		for (Iterator<JRPrintElement> it = elements.iterator(); it.hasNext();)
  		{
  			JRPrintElement element = it.next();
  			if (nature.isToExport(element))
  			{
  				if (createXCuts)
  				{
  					xCuts.addCutOffset(element.getX() + elementOffsetX);
  					xCuts.addCutOffset(element.getX() + element.getWidth() + elementOffsetX);
  				}
  
  				yCuts.addCutOffset(element.getY() + elementOffsetY);
  				yCuts.addCutOffset(element.getY() + element.getHeight() + elementOffsetY);
  				
  				JRPrintFrame frame = element instanceof JRPrintFrame ? (JRPrintFrame)element : null;
  				if (frame != null && nature.isDeep(frame))
  				{
  					createCuts(
  						frame.getElements(),
  						element.getX() + elementOffsetX + frame.getLineBox().getLeftPadding().intValue(),
  						element.getY() + elementOffsetY + frame.getLineBox().getTopPadding().intValue(),
  						createXCuts
  						);
  				}
  			}
  		}
  	}
  
  	protected void setMargins(List<JRPrintElement> elements)
  	{
  		for (Iterator<JRPrintElement> it = elements.iterator(); it.hasNext();)
  		{
  			JRPrintElement element = it.next();
  			if (nature.isToExport(element))
  			{
  				if(hasLeftMargin && element.getX() <= 0)
  				{
  					hasLeftMargin = false;
  				}
  
  				if(hasRightMargin && element.getX() >= width - element.getWidth())
  				{
  					hasRightMargin = false;
  				}
  
  				if(hasTopMargin && element.getY() <= 0)
  				{
  					hasTopMargin = false;
  				}
  
  				if(hasBottomMargin && element.getY() >= height - element.getHeight())
  				{
  					hasBottomMargin = false;
  				}
  			}
  		}
  	}
  
  
  	protected void setGridElements(PrintElementIndex parentIndex, List<JRPrintElement> elements, 
  			int elementOffsetX, int elementOffsetY,
  			int startRow, int startCol, int endRow, int endCol)
  	{
  		for (ListIterator<JRPrintElement> it = elements.listIterator(elements.size()); it.hasPrevious();)
  		{
  			JRPrintElement element = it.previous();
  			int elementIndex = it.nextIndex();
  			
  			if (nature.isToExport(element))
  			{
  				int x = element.getX() + elementOffsetX;
  				int y = element.getY() + elementOffsetY;
  
  				int col1 = xCuts.indexOfCutOffset(x);
  				int row1 = yCuts.indexOfCutOffset(y);
  				int col2 = xCuts.indexOfCutOffset(x + element.getWidth());
  				int row2 = yCuts.indexOfCutOffset(y + element.getHeight());
  
  				if (!isOverlap(row1, col1, row2, col2))
  				{
  					JRPrintFrame frame = element instanceof JRPrintFrame ? (JRPrintFrame)element : null;
  					if (frame != null && nature.isDeep(frame))
  					{
  						PrintElementIndex frameIndex = new PrintElementIndex(parentIndex, elementIndex);
  						setGridElements(
  							frameIndex, frame.getElements(),
  							x + frame.getLineBox().getLeftPadding().intValue(),
  							y + frame.getLineBox().getTopPadding().intValue(),
  							row1, col1, row2, col2
  							);
  						
  						setFrameCellsStyle(frame, row1, col1, row2, col2);
  					}
  					else
  					{
  						setGridElement(element, parentIndex, elementIndex, row1, col1, row2, col2);
  					}
  				}
  			}
  		}
  
  		if (nature.isHorizontallyMergeEmptyCells())
  		{
  			horizontallyMergeEmptyCells(startRow, startCol, endRow, endCol);
  		}
  	}
  
  	protected EmptyGridCell emptyCell(GridCellSize size, GridCellStyle style)
  	{
  		Pair<GridCellSize, GridCellStyle> key = new Pair<GridCellSize, GridCellStyle>(size, style);
  		EmptyGridCell cell = emptyCells.get(key);
  		if (cell == null)
  		{
  			cell = new EmptyGridCell(size, style);
  			emptyCells.put(key, cell);
  			
  			if (log.isDebugEnabled())
  			{
  				log.debug(this + " created empty cell for " + size + " and " + style);
  			}
  		}
  		return cell;
  	}
  	
  	protected void horizontallyMergeEmptyCells(int startRow, int startCol, int endRow, int endCol)
  	{
  		for (int row = startRow; row < endRow; ++row)
  		{
  			int startSpan = -1;
  			int spanWidth = 0;
  			int col = startCol;
  			for (; col < endCol; ++col)
  			{
  				JRExporterGridCell cell = grid.get(row, col);
  				if (isEmpty(cell))
  				{
  					if (startSpan == -1)
  					{
  						startSpan = col;
  					}
  					spanWidth += cell.getWidth();
  				}
  				else
  				{
  					if (startSpan != -1 && col - startSpan > 1)
  					{
  						spanEmptyCell(row, startSpan, spanWidth, col - startSpan);
  					}
  					startSpan = -1;
  					spanWidth = 0;
  				}
  			}
  			if (startSpan != -1 && col - startSpan > 1)
  			{
  				spanEmptyCell(row, startSpan, spanWidth, col - startSpan);
  			}
  		}
  	}
  
  	protected void spanEmptyCell(int row, int col, int spanWidth, int colSpan)
  	{
  		EmptyGridCell spanCell = (EmptyGridCell) grid.get(row, col);
  		GridCellSize newSize = cellSize(spanWidth, spanCell.getHeight(), 
  				colSpan, spanCell.getRowSpan());
  		grid.set(row, col, emptyCell(newSize, spanCell.getStyle()));
  		//TODO set OCCUPIED_CELL?
  	}
  	
  	protected boolean isEmpty(JRExporterGridCell cell)
  	{
  		return cell.getType() == JRExporterGridCell.TYPE_EMPTY_CELL && ((EmptyGridCell) cell).isEmpty();
  	}
  
  	protected boolean isOverlap(int row1, int col1, int row2, int col2)
  	{
  		boolean isOverlap = false;
  		if (nature.isSpanCells())
  		{
  			is_overlap_out:
  			for (int row = row1; row < row2; row++)
  			{
  				for (int col = col1; col < col2; col++)
  				{
  					if (!isEmpty(grid.get(row, col)))
  					{
  						isOverlap = true;
  						break is_overlap_out;
  					}
  				}
  			}
  		}
  		else
  		{
  			isOverlap = !isEmpty(grid.get(row1, col1));
  		}
  		return isOverlap;
  	}
  
  
  	protected void setGridElement(JRPrintElement element, PrintElementIndex parentIndex, int elementIndex,
  			int row1, int col1, int row2, int col2)
  	{
  		yCuts.addUsage(row1, Cut.USAGE_NOT_EMPTY);
  		xCuts.addUsage(col1, Cut.USAGE_NOT_EMPTY);
  
  		int rowSpan = nature.isSpanCells() ? row2 - row1 : 1;
  		int colSpan = nature.isSpanCells() ? col2 - col1 : 1;
  
  		JRExporterGridCell gridCell = new ElementGridCell(
  				this,
  				parentIndex,
  				elementIndex,
  				cellSize(
  						element.getWidth(),
  						element.getHeight(),
  						colSpan,
  						rowSpan
  				));
  
  		nature.setXProperties(xCuts, element, row1, col1, row2, col2);
  		nature.setYProperties(yCuts, element, row1, col1, row2, col2);
  
  		if (nature.isSpanCells())
  		{
  			OccupiedGridCell occupiedGridCell = new OccupiedGridCell(gridCell);
  			for (int row = row1; row < row2; row++)
  			{
  				for (int col = col1; col < col2; col++)
  				{
  					grid.set(row, col, occupiedGridCell);
  				}
  				yCuts.addUsage(row, Cut.USAGE_SPANNED);
  			}
  
  			for (int col = col1; col < col2; col++)
  			{
  				xCuts.addUsage(col, Cut.USAGE_SPANNED);
  			}
  		}
  
  		if (col2 - col1 != 0 && row2 - row1 != 0)
  		{
  			JRLineBox box = (element instanceof JRBoxContainer)?((JRBoxContainer)element).getLineBox():null;
  			gridCell.setStyle(cellStyle(null, null, box));
  
  			if (nature.isBreakBeforeRow(element))
  			{
  				yCuts.addUsage(row1,  Cut.USAGE_BREAK);
  			}
  			if (nature.isBreakAfterRow(element))
  			{
  				yCuts.addUsage(row1 + rowSpan,  Cut.USAGE_BREAK);
  			}
  
  			grid.set(row1, col1, gridCell);
  		}
  	}
  	
  	protected GridCellStyle cellStyle(Color backcolor, Color forecolor, JRLineBox box)
  	{
  		if (backcolor == null && forecolor == null && box == null)
  		{
  			return null;
  		}
  
  		GridCellStyle key = new GridCellStyle(backcolor, forecolor, box);
  		GridCellStyle style = cellStyles.get(key);
  		if (style == null)
  		{
  			style = key;
  			cellStyles.put(key, style);
  			
  			if (log.isTraceEnabled())
  			{
  				log.trace(this + " added cell style " + style);
  			}
  		}
  		return style;
  	}
  
  
  	protected void setFrameCellsStyle(JRPrintFrame frame, int row1, int col1, int row2, int col2)
  	{
  		Color backcolor = frame.getModeValue() == ModeEnum.OPAQUE ? frame.getBackcolor() : null;
  
  		for (int row = row1; row < row2; row++)
  		{
  			for (int col = col1; col < col2; col++)
  			{
  				JRExporterGridCell cell = grid.get(row, col);
  
  				boolean modifiedStyle = false;
  				Color cellBackcolor = cell.getBackcolor();
  				if (cellBackcolor == null)
  				{
  					if (frame.getModeValue() == ModeEnum.OPAQUE)
  					{
  						cellBackcolor = backcolor;
  						modifiedStyle = true;
  					}
  				}
  
  				Color cellForecolor = cell.getForecolor();
  				if (cellForecolor == null)
  				{
  					cellForecolor = frame.getForecolor();
  					modifiedStyle = true;
  				}
  
  				boolean keepLeft = col == col1;
  				boolean keepRight = col == col2 - cell.getColSpan();
  				boolean keepTop = row == row1;
  				boolean keepBottom = row == row2 - cell.getRowSpan();
  
  				JRLineBox cellBox = cell.getBox();
  