/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program 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.
*
* Copyright (c) 2001 - 2009 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.layout.process;
import java.util.HashMap;
import org.pentaho.reporting.engine.classic.core.layout.model.BlockRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.CanvasRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.FinishedRenderNode;
import org.pentaho.reporting.engine.classic.core.layout.model.LayoutNodeTypes;
import org.pentaho.reporting.engine.classic.core.layout.model.LogicalPageBox;
import org.pentaho.reporting.engine.classic.core.layout.model.ParagraphRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderNode;
import org.pentaho.reporting.engine.classic.core.states.ReportStateKey;
import org.pentaho.reporting.engine.classic.core.util.InstanceID;
import org.pentaho.reporting.libraries.base.util.FastStack;
/**
* Removed finished block-boxes. The boxes have to be marked as 'finished' by the flow output target or nothing will be
* removed at all. The boxes marked as finished will be replaced by 'FinishedRenderNodes'. This step preserves nodes
* that have pagebreaks.
*
* @author Thomas Morgner
*/
public final class CleanFlowBoxesStep extends IterateStructuralProcessStep
{
private HashMap finishContexts;
private InstanceID canvasProcessingId;
private FastStack blockContexts;
private ReportStateKey lastSeenStateKey;
public CleanFlowBoxesStep()
{
finishContexts = new HashMap();
blockContexts = new FastStack(50);
}
public void compute(final LogicalPageBox pageBox)
{
//Log.debug ("START CLEAR");
finishContexts.clear();
blockContexts.clear();
if (startBlockBox(pageBox))
{
// not processing the header and footer area: they are 'out-of-context' bands
processBoxChilds(pageBox);
}
finishBlockBox(pageBox);
finishContexts.clear();
blockContexts.clear();
}
protected void processParagraphChilds(final ParagraphRenderBox box)
{
// we do not process the paragraph lines. This should have been done
// in the startblock thing and they get re-added anyway as long as the
// paragraph is active.
}
protected boolean startRowBox(final RenderBox box)
{
// it is guaranteed that the finished flag is only set to true, if the box is closed.
if (box.isFinished() == false || box.isCommited() == false)
{
finishContexts.put(box.getInstanceId(), Boolean.FALSE);
}
else
{
finishContexts.put(box.getInstanceId(), Boolean.TRUE);
}
if (canvasProcessingId == null)
{
canvasProcessingId = box.getInstanceId();
}
final ReportStateKey stateKey = box.getStateKey();
if (stateKey != null)
{
lastSeenStateKey = stateKey;
}
return true;
}
protected void finishRowBox(final RenderBox box)
{
if (canvasProcessingId == box.getInstanceId())
{
canvasProcessingId = null;
}
if (box.isFinished() == false || box.isCommited() == false)
{
finishContexts.remove(box.getInstanceId());
return;
}
final Boolean finishedFlag = (Boolean) finishContexts.get(box.getInstanceId());
if (Boolean.FALSE.equals(finishedFlag))
{
if (box.getParent() != null)
{
finishContexts.put(box.getParent().getInstanceId(), Boolean.FALSE);
}
}
else
{
// The whole box and all childs are finished. We could now safely remove the box.
// (We only remove blocklevel boxes to avoid layouting-troubles, but *we could*.
box.setDeepFinished(true);
}
finishContexts.remove(box.getInstanceId());
}
protected boolean startCanvasBox(final CanvasRenderBox box)
{
// it is guaranteed that the finished flag is only set to true, if the box is closed.
if (box.isFinished() == false || box.isCommited() == false)
{
finishContexts.put(box.getInstanceId(), Boolean.FALSE);
}
else
{
finishContexts.put(box.getInstanceId(), Boolean.TRUE);
}
if (canvasProcessingId == null)
{
canvasProcessingId = box.getInstanceId();
}
final ReportStateKey stateKey = box.getStateKey();
if (stateKey != null)
{
lastSeenStateKey = stateKey;
}
return true;
}
protected void finishCanvasBox(final CanvasRenderBox box)
{
if (canvasProcessingId == box.getInstanceId())
{
canvasProcessingId = null;
}
if (box.isFinished() == false || box.isCommited() == false)
{
finishContexts.remove(box.getInstanceId());
return;
}
final Boolean finishedFlag = (Boolean) finishContexts.get(box.getInstanceId());
if (Boolean.FALSE.equals(finishedFlag))
{
if (box.getParent() != null)
{
finishContexts.put(box.getParent().getInstanceId(), Boolean.FALSE);
}
}
else
{
// The whole box and all childs are finished. We could now safely remove the box.
// (We only remove blocklevel boxes to avoid layouting-troubles, but *we could*.
box.setDeepFinished(true);
}
finishContexts.remove(box.getInstanceId());
}
// We cannot clear the box until we have verified that all childs of that box have been cleared.
protected boolean startBlockBox(final BlockRenderBox box)
{
if (box.isDeepFinished())
{
return false;
}
if (box.isFinished() == false)
{
finishContexts.put(box.getInstanceId(), Boolean.FALSE);
}
else
{
finishContexts.put(box.getInstanceId(), Boolean.TRUE);
}
final ReportStateKey stateKey = box.getStateKey();
if (stateKey != null)
{
lastSeenStateKey = stateKey;
}
return true;
}
protected void finishBlockBox(final BlockRenderBox box)
{
final Boolean finishedFlag = (Boolean) finishContexts.get(box.getInstanceId());
if (Boolean.FALSE.equals(finishedFlag))
{
if (box.getParent() != null)
{
finishContexts.put(box.getParent().getInstanceId(), Boolean.FALSE);
}
// DebugLog.log("Not removing box " + box + " as this box is not finished.");
box.setDeepFinished(false);
finishContexts.remove(box.getInstanceId());
return;
}
else
{
box.setDeepFinished(true);
}
finishContexts.remove(box.getInstanceId());
if (canvasProcessingId != null)
{
// DebugLog.log("Canvas Processing ID is active. Wont delete");
return;
}
final RenderNode first = box.getFirstChild();
if (first == null)
{
return;
}
if (first.isFinished() == false)
{
// DebugLog.log("First child is active. Wont delete");
return;
}
if ((first.getNodeType() & LayoutNodeTypes.MASK_BOX) == LayoutNodeTypes.MASK_BOX)
{
final RenderBox nextBox = (RenderBox) first;
if (nextBox.isDeepFinished() == false)
{
// DebugLog.log("First child is not deep-finished. Wont delete");
return;
}
}
RenderNode last = first;
while (true)
{
final RenderNode next = last.getNext();
if (next == null)
{
break;
}
if ((next.getNodeType() & LayoutNodeTypes.MASK_BOX) == LayoutNodeTypes.MASK_BOX &&
(next.getNodeType() & LayoutNodeTypes.MASK_BOX_INLINE) != LayoutNodeTypes.MASK_BOX_INLINE)
{
final RenderBox nextBox = (RenderBox) next;
if (next.isFinished() == false && nextBox.isDeepFinished() == false)
{
break;
}
}
if (next.isBreakAfter())
{
break;
}
last = next;
}
if (last == first &&
(first.getNodeType() == LayoutNodeTypes.TYPE_NODE_FINISHEDNODE))
{
// In this case, we can skip the replace-action below ..
// DebugLog.log("Already finish node");
return;
}
// So lets get started. We remove all nodes between (and inclusive)
// node and last.
final long nodeY = first.getY();
final long width = box.getContentAreaX2() - box.getContentAreaX1();
final long lastY2 = last.getY() + last.getHeight();
final long height = lastY2 - nodeY;
// make sure that the finished-box inherits the margins ..
final long marginsTop = first.getEffectiveMarginTop();
final long marginsBottom = last.getEffectiveMarginBottom();
final boolean breakAfter = last.isBreakAfter();
final FinishedRenderNode replacement =
new FinishedRenderNode(width, height, marginsTop, marginsBottom, breakAfter, lastSeenStateKey);
// DebugLog.log("Removing childs of " + box + " as this box is finished.");
// Log.debug (" (" + last.getInstanceId() + ") " +
// (box.getLastChild() == last) + " " + (box.getFirstChild() == first));
int counter = 0;
RenderNode removeNode = first;
while (removeNode != last)
{
final RenderNode next = removeNode.getNext();
if (removeNode.isOpen())
{
throw new IllegalStateException("A node is still open. We should not have come that far.");
}
box.remove(removeNode);
removeNode = next;
counter += 1;
}
if (last.isOpen())
{
throw new IllegalStateException("The last node is still open. We should not have come that far.");
}
counter += 1;
box.replaceChild(last, replacement);
if (replacement.getParent() != box)
{
throw new IllegalStateException("The replacement did not work.");
}
// DebugLog.log ("Removed " + counter + " nodes from " + box.getName() + " " + last.getName());
}
protected void processOtherNode(final RenderNode node)
{
if (node.getNodeType() == LayoutNodeTypes.TYPE_NODE_FINISHEDNODE)
{
final ReportStateKey stateKey = node.getStateKey();
if (stateKey != null)
{
lastSeenStateKey = stateKey;
}
}
}
}