FOP-2925: Change in IPD on empty block NPE

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1875645 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/fop-core/src/main/java/org/apache/fop/fo/FObj.java b/fop-core/src/main/java/org/apache/fop/fo/FObj.java
index f57e59b..d938ede 100644
--- a/fop-core/src/main/java/org/apache/fop/fo/FObj.java
+++ b/fop-core/src/main/java/org/apache/fop/fo/FObj.java
@@ -78,6 +78,8 @@
     private String layer;
     // End of property values
 
+    private boolean forceKeepTogether;
+
     /**
      * Create a new formatting object.
      *
@@ -775,6 +777,14 @@
         return (super.toString() + "[@id=" + this.id + "]");
     }
 
+    public boolean isForceKeepTogether() {
+        return forceKeepTogether;
+    }
+
+    public void setForceKeepTogether(boolean b) {
+        forceKeepTogether = b;
+    }
+
     /** Basic {@link FONode.FONodeIterator} implementation */
     public static class FObjIterator implements FONodeIterator {
 
diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/AbstractBreaker.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/AbstractBreaker.java
index d571482..625b520 100644
--- a/fop-core/src/main/java/org/apache/fop/layoutmgr/AbstractBreaker.java
+++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/AbstractBreaker.java
@@ -19,16 +19,12 @@
 
 package org.apache.fop.layoutmgr;
 
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
-import org.apache.fop.events.EventBroadcaster;
 import org.apache.fop.fo.Constants;
 import org.apache.fop.layoutmgr.BreakingAlgorithm.KnuthNode;
 import org.apache.fop.traits.MinOptMax;
@@ -42,9 +38,9 @@
     /** logging instance */
     protected static final Log log = LogFactory.getLog(AbstractBreaker.class);
 
-    private LayoutManager originalRestartAtLM;
-    private Position positionAtBreak;
-    private List firstElementsForRestart;
+    protected LayoutManager originalRestartAtLM;
+    protected Position positionAtBreak;
+    protected List firstElementsForRestart;
     protected PageSequenceLayoutManager pslm;
 
     /**
@@ -433,12 +429,16 @@
                     }
 
                     firstElementsForRestart = null;
-                    LayoutManager restartAtLM = getRestartAtLM(alg, ipdChangesOnNextPage, onLastPageAndIPDChanges,
-                            visitedBefore, blockList, 1);
+                    RestartAtLM restartAtLMClass = new RestartAtLM();
+                    LayoutManager restartAtLM = restartAtLMClass.getRestartAtLM(this, alg, ipdChangesOnNextPage,
+                            onLastPageAndIPDChanges, visitedBefore, blockList, 1);
+                    if (restartAtLMClass.invalidPosition) {
+                        return false;
+                    }
                     if (restartAtLM == null || restartAtLM.getChildLMs().isEmpty()) {
                         firstElementsForRestart = null;
-                        LayoutManager restartAtLM2 = getRestartAtLM(alg, ipdChangesOnNextPage, onLastPageAndIPDChanges,
-                                visitedBefore, blockList, 0);
+                        LayoutManager restartAtLM2 = new RestartAtLM().getRestartAtLM(this, alg, ipdChangesOnNextPage,
+                                onLastPageAndIPDChanges, visitedBefore, blockList, 0);
                         if (restartAtLM2 != null) {
                             restartAtLM = restartAtLM2;
                         }
@@ -465,101 +465,6 @@
         return true;
     }
 
-    private LayoutManager getRestartAtLM(PageBreakingAlgorithm alg, boolean ipdChangesOnNextPage,
-                                         boolean onLastPageAndIPDChanges, boolean visitedBefore,
-                                         BlockSequence blockList, int start) {
-        KnuthNode optimalBreak = ipdChangesOnNextPage ? alg.getBestNodeBeforeIPDChange() : alg
-                .getBestNodeForLastPage();
-        if (onLastPageAndIPDChanges && visitedBefore && this.originalRestartAtLM == null) {
-            optimalBreak = null;
-        }
-        int positionIndex = findPositionIndex(optimalBreak, alg, start);
-        if (ipdChangesOnNextPage || (positionAtBreak != null && positionAtBreak.getIndex() > -1)) {
-            firstElementsForRestart = Collections.EMPTY_LIST;
-            if (ipdChangesOnNextPage) {
-                if (containsNonRestartableLM(positionAtBreak)) {
-                    if (alg.getIPDdifference() > 0) {
-                        EventBroadcaster eventBroadcaster = getCurrentChildLM().getFObj()
-                                .getUserAgent().getEventBroadcaster();
-                        BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider
-                                .get(eventBroadcaster);
-                        eventProducer.nonRestartableContentFlowingToNarrowerPage(this);
-                    }
-                    firstElementsForRestart = new LinkedList();
-                    boolean boxFound = false;
-                    Iterator iter = blockList.listIterator(positionIndex + 1);
-                    Position position = null;
-                    while (iter.hasNext()
-                            && (position == null || containsNonRestartableLM(position))) {
-                        positionIndex++;
-                        KnuthElement element = (KnuthElement) iter.next();
-                        position = element.getPosition();
-                        if (element.isBox()) {
-                            boxFound = true;
-                            firstElementsForRestart.add(element);
-                        } else if (boxFound) {
-                            firstElementsForRestart.add(element);
-                        }
-                    }
-                    if (position instanceof SpaceResolver.SpaceHandlingBreakPosition) {
-                                    /* Retrieve the original position wrapped into this space position */
-                        positionAtBreak = position.getPosition();
-                    } else {
-                        positionAtBreak = null;
-                    }
-                }
-            }
-        }
-        LayoutManager restartAtLM = null;
-        if (ipdChangesOnNextPage || !(positionAtBreak != null && positionAtBreak.getIndex() > -1)) {
-            if (positionAtBreak != null && positionAtBreak.getIndex() == -1) {
-                Position position;
-                Iterator iter = blockList.listIterator(positionIndex + 1);
-                do {
-                    KnuthElement nextElement = (KnuthElement) iter.next();
-                    position = nextElement.getPosition();
-                } while (position == null
-                        || position instanceof SpaceResolver.SpaceHandlingPosition
-                        || position instanceof SpaceResolver.SpaceHandlingBreakPosition
-                        && position.getPosition().getIndex() == -1);
-                LayoutManager surroundingLM = positionAtBreak.getLM();
-                while (position.getLM() != surroundingLM) {
-                    position = position.getPosition();
-                }
-                restartAtLM = position.getPosition().getLM();
-            }
-            if (onLastPageAndIPDChanges && restartAtLM != null) {
-                if (originalRestartAtLM == null) {
-                    originalRestartAtLM = restartAtLM;
-                } else {
-                    restartAtLM = originalRestartAtLM;
-                }
-                firstElementsForRestart = Collections.EMPTY_LIST;
-            }
-        }
-        if (onLastPageAndIPDChanges && !visitedBefore && positionAtBreak.getPosition() != null) {
-            restartAtLM = positionAtBreak.getPosition().getLM();
-        }
-        return restartAtLM;
-    }
-
-    private int findPositionIndex(KnuthNode optimalBreak, PageBreakingAlgorithm alg, int start) {
-        int positionIndex = (optimalBreak != null) ? optimalBreak.position : start;
-        for (int i = positionIndex; i < alg.par.size(); i++) {
-            KnuthElement elementAtBreak = alg.getElement(i);
-            if (elementAtBreak.getPosition() == null) {
-                elementAtBreak = alg.getElement(0);
-            }
-            positionAtBreak = elementAtBreak.getPosition();
-            /* Retrieve the original position wrapped into this space position */
-            positionAtBreak = positionAtBreak.getPosition();
-            if (positionAtBreak != null) {
-                return i;
-            }
-        }
-        return positionIndex;
-    }
-
     /**
      * Returns {@code true} if the given position or one of its descendants
      * corresponds to a non-restartable LM.
@@ -567,7 +472,7 @@
      * @param position a position
      * @return {@code true} if there is a non-restartable LM in the hierarchy
      */
-    private boolean containsNonRestartableLM(Position position) {
+    protected boolean containsNonRestartableLM(Position position) {
         LayoutManager lm = position.getLM();
         if (lm != null && !lm.isRestartable()) {
             return true;
diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java
index 3e8bd99..4dddbcf 100644
--- a/fop-core/src/main/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java
+++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/BlockStackingLayoutManager.java
@@ -833,6 +833,9 @@
     public Keep getKeepTogether() {
         Keep keep = Keep.getKeep(getKeepTogetherProperty());
         keep = keep.compare(getParentKeepTogether());
+        if (getFObj().isForceKeepTogether()) {
+            keep = Keep.KEEP_ALWAYS;
+        }
         return keep;
     }
 
diff --git a/fop-core/src/main/java/org/apache/fop/layoutmgr/RestartAtLM.java b/fop-core/src/main/java/org/apache/fop/layoutmgr/RestartAtLM.java
new file mode 100644
index 0000000..9c8aa82
--- /dev/null
+++ b/fop-core/src/main/java/org/apache/fop/layoutmgr/RestartAtLM.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+package org.apache.fop.layoutmgr;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+import org.apache.fop.events.EventBroadcaster;
+
+/**
+ * Class to find the restart layoutmanager for changing IPD
+ */
+class RestartAtLM {
+    protected boolean invalidPosition;
+
+    protected LayoutManager getRestartAtLM(AbstractBreaker breaker, PageBreakingAlgorithm alg,
+                                           boolean ipdChangesOnNextPage, boolean onLastPageAndIPDChanges,
+                                           boolean visitedBefore, AbstractBreaker.BlockSequence blockList, int start) {
+        BreakingAlgorithm.KnuthNode optimalBreak = ipdChangesOnNextPage ? alg.getBestNodeBeforeIPDChange() : alg
+                .getBestNodeForLastPage();
+        if (onLastPageAndIPDChanges && visitedBefore && breaker.originalRestartAtLM == null) {
+            optimalBreak = null;
+        }
+        int positionIndex = findPositionIndex(breaker, optimalBreak, alg, start);
+        if (ipdChangesOnNextPage || (breaker.positionAtBreak != null && breaker.positionAtBreak.getIndex() > -1)) {
+            breaker.firstElementsForRestart = Collections.EMPTY_LIST;
+            if (ipdChangesOnNextPage) {
+                if (breaker.containsNonRestartableLM(breaker.positionAtBreak)) {
+                    if (alg.getIPDdifference() > 0) {
+                        EventBroadcaster eventBroadcaster = breaker.getCurrentChildLM().getFObj()
+                                .getUserAgent().getEventBroadcaster();
+                        BlockLevelEventProducer eventProducer = BlockLevelEventProducer.Provider
+                                .get(eventBroadcaster);
+                        eventProducer.nonRestartableContentFlowingToNarrowerPage(this);
+                    }
+                    breaker.firstElementsForRestart = new LinkedList();
+                    boolean boxFound = false;
+                    Iterator iter = blockList.listIterator(positionIndex + 1);
+                    Position position = null;
+                    while (iter.hasNext()
+                            && (position == null || breaker.containsNonRestartableLM(position))) {
+                        positionIndex++;
+                        KnuthElement element = (KnuthElement) iter.next();
+                        position = element.getPosition();
+                        if (element.isBox()) {
+                            boxFound = true;
+                            breaker.firstElementsForRestart.add(element);
+                        } else if (boxFound) {
+                            breaker.firstElementsForRestart.add(element);
+                        }
+                    }
+                    if (position instanceof SpaceResolver.SpaceHandlingBreakPosition) {
+                        /* Retrieve the original position wrapped into this space position */
+                        breaker.positionAtBreak = position.getPosition();
+                    } else {
+                        breaker.positionAtBreak = null;
+                    }
+                }
+            }
+        }
+        LayoutManager restartAtLM = null;
+        if (ipdChangesOnNextPage || !(breaker.positionAtBreak != null && breaker.positionAtBreak.getIndex() > -1)) {
+            if (breaker.positionAtBreak != null && breaker.positionAtBreak.getIndex() == -1) {
+                Position position;
+                Iterator iter = blockList.listIterator(positionIndex + 1);
+                do {
+                    KnuthElement nextElement = (KnuthElement) iter.next();
+                    position = nextElement.getPosition();
+                } while (position == null
+                        || position instanceof SpaceResolver.SpaceHandlingPosition
+                        || position instanceof SpaceResolver.SpaceHandlingBreakPosition
+                        && position.getPosition().getIndex() == -1);
+                LayoutManager surroundingLM = breaker.positionAtBreak.getLM();
+                while (position.getLM() != surroundingLM) {
+                    position = position.getPosition();
+                }
+                if (position.getPosition() == null) {
+                    position.getLM().getFObj().setForceKeepTogether(true);
+                    invalidPosition = true;
+                    return null;
+                }
+                restartAtLM = position.getPosition().getLM();
+            }
+            if (onLastPageAndIPDChanges && restartAtLM != null) {
+                if (breaker.originalRestartAtLM == null) {
+                    breaker.originalRestartAtLM = restartAtLM;
+                } else {
+                    restartAtLM = breaker.originalRestartAtLM;
+                }
+                breaker.firstElementsForRestart = Collections.EMPTY_LIST;
+            }
+        }
+        if (onLastPageAndIPDChanges && !visitedBefore && breaker.positionAtBreak.getPosition() != null) {
+            restartAtLM = breaker.positionAtBreak.getPosition().getLM();
+        }
+        return restartAtLM;
+    }
+
+    private int findPositionIndex(AbstractBreaker breaker, BreakingAlgorithm.KnuthNode optimalBreak,
+                                  PageBreakingAlgorithm alg, int start) {
+        int positionIndex = (optimalBreak != null) ? optimalBreak.position : start;
+        for (int i = positionIndex; i < alg.par.size(); i++) {
+            KnuthElement elementAtBreak = alg.getElement(i);
+            if (elementAtBreak.getPosition() == null) {
+                elementAtBreak = alg.getElement(0);
+            }
+            breaker.positionAtBreak = elementAtBreak.getPosition();
+            /* Retrieve the original position wrapped into this space position */
+            breaker.positionAtBreak = breaker.positionAtBreak.getPosition();
+            if (breaker.positionAtBreak != null) {
+                return i;
+            }
+        }
+        return positionIndex;
+    }
+}
diff --git a/fop/test/layoutengine/standard-testcases/flow_changing-ipd_last-page_15.xml b/fop/test/layoutengine/standard-testcases/flow_changing-ipd_last-page_15.xml
new file mode 100755
index 0000000..6640fdb
--- /dev/null
+++ b/fop/test/layoutengine/standard-testcases/flow_changing-ipd_last-page_15.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<!-- $Id$ -->
+<testcase>
+  <info>
+    <p>
+      This test checks that the definition of a special page-master for the last page with a
+      different width that the previous "rest" page causes FOP to redo the line breaking layout.
+    </p>
+  </info>
+  <fo>
+<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:fox="http://xmlgraphics.apache.org/fop/extensions">
+  <fo:layout-master-set>
+    <fo:simple-page-master margin-bottom="8mm" master-name="PageFront" page-width="210mm" page-height="297mm" margin-right="20mm" margin-top="15mm" margin-left="20mm">
+      <fo:region-body margin-right="58mm" margin-top="35mm" margin-bottom="20mm" margin-left="0mm" region-name="PageBody"/>
+    </fo:simple-page-master>
+    <fo:simple-page-master margin-bottom="8mm" master-name="PageRest" page-width="210mm" page-height="297mm" margin-right="20mm" margin-top="15mm" margin-left="20mm">
+      <fo:region-body margin-bottom="0mm" margin-top="0mm" margin-right="0mm" margin-left="0mm" region-name="PageBody"/>
+    </fo:simple-page-master>
+    <fo:page-sequence-master master-name="LetterPages">
+      <fo:repeatable-page-master-alternatives>
+        <fo:conditional-page-master-reference page-position="first" master-reference="PageFront"/>
+        <fo:conditional-page-master-reference page-position="rest" master-reference="PageRest"/>
+        <fo:conditional-page-master-reference page-position="last" master-reference="PageRest"/>
+      </fo:repeatable-page-master-alternatives>
+    </fo:page-sequence-master>
+  </fo:layout-master-set>
+  <fo:page-sequence format="1" id="th_default_sequence1" initial-page-number="auto" force-page-count="auto" master-reference="LetterPages">
+    <fo:flow flow-name="PageBody">
+      <fo:block-container height="210mm">
+        <fo:block>test</fo:block>
+      </fo:block-container>
+      <fo:block font-size="9pt" line-height="11pt">
+        <fo:inline>Step 2 – Finance<fo:block/>
+        </fo:inline>
+        If there is any Hire Purchase (HP) / Finance agreement associated with this vehicle, please contact the finance company to provide your authorisation for Bank Box to liaise with them in order to obtain the early settlement figure.</fo:block>
+    </fo:flow>
+  </fo:page-sequence>
+</fo:root>
+</fo>
+  <checks>
+    <eval expected="test" xpath="//pageViewport[1]//lineArea[1]//text[1]/word[1]"/>
+    <eval expected="Step" xpath="//pageViewport[2]//lineArea[1]//text[1]/word[1]"/>
+    <eval expected="2" xpath="count(//pageViewport)"/>
+  </checks>
+</testcase>