You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@poi.apache.org by jo...@apache.org on 2009/06/12 21:18:00 UTC

svn commit: r784240 - in /poi/trunk/src: documentation/content/xdocs/ java/org/apache/poi/hssf/model/ java/org/apache/poi/hssf/record/ java/org/apache/poi/hssf/record/aggregates/ java/org/apache/poi/hssf/usermodel/ testcases/org/apache/poi/hssf/model/ ...

Author: josh
Date: Fri Jun 12 19:17:59 2009
New Revision: 784240

URL: http://svn.apache.org/viewvc?rev=784240&view=rev
Log:
Bugzilla 47363 - Fixed HSSFSheet to allow addition of data validations after sheet protection

Added:
    poi/trunk/src/java/org/apache/poi/hssf/record/aggregates/WorksheetProtectionBlock.java
Modified:
    poi/trunk/src/documentation/content/xdocs/status.xml
    poi/trunk/src/java/org/apache/poi/hssf/model/RecordOrderer.java
    poi/trunk/src/java/org/apache/poi/hssf/model/Sheet.java
    poi/trunk/src/java/org/apache/poi/hssf/record/UnknownRecord.java
    poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java
    poi/trunk/src/testcases/org/apache/poi/hssf/model/TestSheet.java
    poi/trunk/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java

Modified: poi/trunk/src/documentation/content/xdocs/status.xml
URL: http://svn.apache.org/viewvc/poi/trunk/src/documentation/content/xdocs/status.xml?rev=784240&r1=784239&r2=784240&view=diff
==============================================================================
--- poi/trunk/src/documentation/content/xdocs/status.xml (original)
+++ poi/trunk/src/documentation/content/xdocs/status.xml Fri Jun 12 19:17:59 2009
@@ -35,6 +35,7 @@
         <release version="3.5-beta7" date="2009-??-??">
         </release>
         <release version="3.5-beta6" date="2009-06-22">
+           <action dev="POI-DEVELOPERS" type="fix">47363 - Fixed HSSFSheet to allow addition of data validations after sheet protection</action>
            <action dev="POI-DEVELOPERS" type="fix">47294 - Fixed XSSFWorkbook#setRepeatingRowsAndColumns to tolerate sheet names with quotes</action>
            <action dev="POI-DEVELOPERS" type="fix">47309 - Fixed logic in HSSFCell.getCellComment to handle sheets with more than 65536 comments</action>
            <action dev="POI-DEVELOPERS" type="fix">46776 - Added clone() method to MulBlankRecord to fix crash in Sheet.cloneSheet()</action>

Modified: poi/trunk/src/java/org/apache/poi/hssf/model/RecordOrderer.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/model/RecordOrderer.java?rev=784240&r1=784239&r2=784240&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/model/RecordOrderer.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/model/RecordOrderer.java Fri Jun 12 19:17:59 2009
@@ -27,6 +27,7 @@
 import org.apache.poi.hssf.record.CalcModeRecord;
 import org.apache.poi.hssf.record.DVALRecord;
 import org.apache.poi.hssf.record.DateWindow1904Record;
+import org.apache.poi.hssf.record.DefaultColWidthRecord;
 import org.apache.poi.hssf.record.DefaultRowHeightRecord;
 import org.apache.poi.hssf.record.DeltaRecord;
 import org.apache.poi.hssf.record.DimensionsRecord;
@@ -62,21 +63,23 @@
 import org.apache.poi.hssf.record.UnknownRecord;
 import org.apache.poi.hssf.record.WindowOneRecord;
 import org.apache.poi.hssf.record.WindowTwoRecord;
+import org.apache.poi.hssf.record.aggregates.ColumnInfoRecordsAggregate;
 import org.apache.poi.hssf.record.aggregates.ConditionalFormattingTable;
 import org.apache.poi.hssf.record.aggregates.DataValidityTable;
 import org.apache.poi.hssf.record.aggregates.MergedCellsTable;
 import org.apache.poi.hssf.record.aggregates.PageSettingsBlock;
+import org.apache.poi.hssf.record.aggregates.WorksheetProtectionBlock;
 import org.apache.poi.hssf.record.pivottable.ViewDefinitionRecord;
 
 /**
  * Finds correct insert positions for records in workbook streams<p/>
- * 
+ *
  * See OOO excelfileformat.pdf sec. 4.2.5 'Record Order in a BIFF8 Workbook Stream'
- * 
+ *
  * @author Josh Micich
  */
 final class RecordOrderer {
-	
+
 	// TODO - simplify logic using a generalised record ordering
 
 	private RecordOrderer() {
@@ -84,7 +87,6 @@
 	}
 	/**
 	 * Adds the specified new record in the correct place in sheet records list
-	 * 
 	 */
 	public static void addNewSheetRecord(List<RecordBase> sheetRecords, RecordBase newRecord) {
 		int index = findSheetInsertPos(sheetRecords, newRecord.getClass());
@@ -107,9 +109,69 @@
 		if (recClass == PageSettingsBlock.class) {
 			return getPageBreakRecordInsertPos(records);
 		}
+		if (recClass == WorksheetProtectionBlock.class) {
+			return getWorksheetProtectionBlockInsertPos(records);
+		}
 		throw new RuntimeException("Unexpected record class (" + recClass.getName() + ")");
 	}
 
+	/**
+	 * Finds the index where the protection block should be inserted
+	 * @param records the records for this sheet
+	 * <pre>
+	 * + BOF
+	 * o INDEX
+	 * o Calculation Settings Block
+	 * o PRINTHEADERS
+	 * o PRINTGRIDLINES
+	 * o GRIDSET
+	 * o GUTS
+	 * o DEFAULTROWHEIGHT
+	 * o SHEETPR
+	 * o Page Settings Block
+	 * o Worksheet Protection Block
+	 * o DEFCOLWIDTH
+	 * oo COLINFO
+	 * o SORT
+	 * + DIMENSION
+	 * </pre>
+	 */
+	private static int getWorksheetProtectionBlockInsertPos(List<RecordBase> records) {
+		int i = getDimensionsIndex(records);
+		while (i > 0) {
+			i--;
+			Object rb = records.get(i);
+			if (!isProtectionSubsequentRecord(rb)) {
+				return i+1;
+			}
+		}
+		throw new IllegalStateException("did not find insert pos for protection block");
+	}
+
+
+	/**
+	 * These records may occur between the 'Worksheet Protection Block' and DIMENSION:
+	 * <pre>
+	 * o DEFCOLWIDTH
+	 * oo COLINFO
+	 * o SORT
+	 * </pre>
+	 */
+	private static boolean isProtectionSubsequentRecord(Object rb) {
+		if (rb instanceof ColumnInfoRecordsAggregate) {
+			return true; // oo COLINFO
+		}
+		if (rb instanceof Record) {
+			Record record = (Record) rb;
+			switch (record.getSid()) {
+				case DefaultColWidthRecord.sid:
+				case UnknownRecord.SORT_0090:
+					return true;
+			}
+		}
+		return false;
+	}
+
 	private static int getPageBreakRecordInsertPos(List<RecordBase> records) {
 		int dimensionsIndex = getDimensionsIndex(records);
 		int i = dimensionsIndex-1;
@@ -163,7 +225,7 @@
 			if (rb instanceof DataValidityTable) {
 				continue;
 			}
-			
+
 			Record rec = (Record) rb;
 			switch (rec.getSid()) {
 				case WindowTwoRecord.sid:
@@ -171,7 +233,7 @@
 				case PaneRecord.sid:
 				case SelectionRecord.sid:
 				case UnknownRecord.STANDARDWIDTH_0099:
-				// MergedCellsTable usually here 
+				// MergedCellsTable usually here
 				case UnknownRecord.LABELRANGES_015F:
 				case UnknownRecord.PHONETICPR_00EF:
 					// ConditionalFormattingTable goes here
@@ -187,13 +249,13 @@
 		for (int i = records.size() - 2; i >= 0; i--) { // -2 to skip EOF record
 			Object rb = records.get(i);
 			if (!(rb instanceof Record)) {
-				// DataValidityTable, ConditionalFormattingTable, 
+				// DataValidityTable, ConditionalFormattingTable,
 				// even PageSettingsBlock (which doesn't normally appear after 'View Settings')
-				continue; 
+				continue;
 			}
 			Record rec = (Record) rb;
 			switch (rec.getSid()) {
-				// 'View Settings' (4 records) 
+				// 'View Settings' (4 records)
 				case WindowTwoRecord.sid:
 				case SCLRecord.sid:
 				case PaneRecord.sid:
@@ -206,11 +268,11 @@
 		throw new RuntimeException("Did not find Window2 record");
 	}
 
-	
+
 	/**
 	 * Finds the index where the sheet validations header record should be inserted
 	 * @param records the records for this sheet
-	 * 
+	 *
 	 * + WINDOW2
 	 * o SCL
 	 * o PANE
@@ -340,7 +402,7 @@
 	}
 	/**
 	 * @return <code>true</code> if the specified record ID terminates a sequence of Row block records
-	 * It is assumed that at least one row or cell value record has been found prior to the current 
+	 * It is assumed that at least one row or cell value record has been found prior to the current
 	 * record
 	 */
 	public static boolean isEndOfRowBlock(int sid) {
@@ -359,7 +421,7 @@
 
 			case DVALRecord.sid:
 				return true;
-			case EOFRecord.sid: 
+			case EOFRecord.sid:
 				// WINDOW2 should always be present, so shouldn't have got this far
 				throw new RuntimeException("Found EOFRecord before WindowTwoRecord was encountered");
 		}
@@ -367,13 +429,13 @@
 	}
 
 	/**
-	 * @return <code>true</code> if the specified record id normally appears in the row blocks section 
+	 * @return <code>true</code> if the specified record id normally appears in the row blocks section
 	 * of the sheet records
 	 */
 	public static boolean isRowBlockRecord(int sid) {
 		switch (sid) {
 			case RowRecord.sid:
-				
+
 			case BlankRecord.sid:
 			case BoolErrRecord.sid:
 			case FormulaRecord.sid:

Modified: poi/trunk/src/java/org/apache/poi/hssf/model/Sheet.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/model/Sheet.java?rev=784240&r1=784239&r2=784240&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/model/Sheet.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/model/Sheet.java Fri Jun 12 19:17:59 2009
@@ -42,19 +42,15 @@
 import org.apache.poi.hssf.record.MergeCellsRecord;
 import org.apache.poi.hssf.record.NoteRecord;
 import org.apache.poi.hssf.record.ObjRecord;
-import org.apache.poi.hssf.record.ObjectProtectRecord;
 import org.apache.poi.hssf.record.PaneRecord;
-import org.apache.poi.hssf.record.PasswordRecord;
 import org.apache.poi.hssf.record.PrintGridlinesRecord;
 import org.apache.poi.hssf.record.PrintHeadersRecord;
-import org.apache.poi.hssf.record.ProtectRecord;
 import org.apache.poi.hssf.record.Record;
 import org.apache.poi.hssf.record.RecordBase;
 import org.apache.poi.hssf.record.RefModeRecord;
 import org.apache.poi.hssf.record.RowRecord;
 import org.apache.poi.hssf.record.SCLRecord;
 import org.apache.poi.hssf.record.SaveRecalcRecord;
-import org.apache.poi.hssf.record.ScenarioProtectRecord;
 import org.apache.poi.hssf.record.SelectionRecord;
 import org.apache.poi.hssf.record.UncalcedRecord;
 import org.apache.poi.hssf.record.WSBoolRecord;
@@ -68,6 +64,7 @@
 import org.apache.poi.hssf.record.aggregates.PageSettingsBlock;
 import org.apache.poi.hssf.record.aggregates.RecordAggregate;
 import org.apache.poi.hssf.record.aggregates.RowRecordsAggregate;
+import org.apache.poi.hssf.record.aggregates.WorksheetProtectionBlock;
 import org.apache.poi.hssf.record.aggregates.RecordAggregate.PositionTrackingVisitor;
 import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor;
 import org.apache.poi.hssf.record.formula.FormulaShifter;
@@ -113,11 +110,11 @@
     protected DefaultRowHeightRecord     defaultrowheight  =     null;
     private PageSettingsBlock _psBlock;
 
-    // 'Worksheet Protection Block'
-    protected ProtectRecord              protect           =     null;
-    protected ObjectProtectRecord        objprotect        =     null;
-    protected ScenarioProtectRecord      scenprotect       =     null;
-    protected PasswordRecord             _password          =     null;
+    /**
+     * 'Worksheet Protection Block'<br/>
+     *  Aggregate object is always present, but possibly empty.
+     */
+    private final WorksheetProtectionBlock _protectionBlock = new WorksheetProtectionBlock();
 
     protected WindowTwoRecord            windowTwo         =     null;
     protected SelectionRecord            _selection         =     null;
@@ -227,6 +224,11 @@
                 continue;
             }
 
+            if (WorksheetProtectionBlock.isComponentRecord(recSid)) {
+                _protectionBlock.addRecords(rs);
+                continue;
+            }
+
             if (recSid == MergeCellsRecord.sid) {
                 // when the MergedCellsTable is found in the right place, we expect those records to be contiguous
                 _mergedCellsTable.read(rs);
@@ -299,22 +301,6 @@
             {
                 windowTwo = (WindowTwoRecord) rec;
             }
-            else if ( recSid == ProtectRecord.sid )
-            {
-                protect = (ProtectRecord) rec;
-            }
-            else if ( recSid == ObjectProtectRecord.sid )
-            {
-                objprotect = (ObjectProtectRecord) rec;
-            }
-            else if ( recSid == ScenarioProtectRecord.sid )
-            {
-                scenprotect = (ScenarioProtectRecord) rec;
-            }
-            else if ( recSid == PasswordRecord.sid )
-            {
-                _password = (PasswordRecord) rec;
-            }
             else if ( recSid == GutsRecord.sid )
             {
                 _gutsRecord = (GutsRecord) rec;
@@ -348,6 +334,7 @@
         _rowsAggregate = rra;
         // put merged cells table in the right place (regardless of where the first MergedCellsRecord was found */
         RecordOrderer.addNewSheetRecord(records, _mergedCellsTable);
+        RecordOrderer.addNewSheetRecord(records, _protectionBlock);
         if (log.check( POILogger.DEBUG ))
             log.log(POILogger.DEBUG, "sheet createSheet (existing file) exited");
     }
@@ -432,7 +419,7 @@
         records.add(_psBlock);
 
         // 'Worksheet Protection Block' (after 'Page Settings Block' and before DEFCOLWIDTH)
-        // PROTECT record normally goes here, don't add yet since the flag is initially false
+        records.add(_protectionBlock); // initially empty
 
         defaultcolwidth = createDefaultColWidth();
         records.add( defaultcolwidth);
@@ -1407,62 +1394,11 @@
     }
 
     /**
-     * creates an ObjectProtect record with protect set to false.
-     */
-    private static ObjectProtectRecord createObjectProtect() {
-        if (log.check( POILogger.DEBUG ))
-            log.log(POILogger.DEBUG, "create protect record with protection disabled");
-        ObjectProtectRecord retval = new ObjectProtectRecord();
-
-        retval.setProtect(false);
-        return retval;
-    }
-
-    /**
-     * creates a ScenarioProtect record with protect set to false.
-     */
-    private static ScenarioProtectRecord createScenarioProtect() {
-        if (log.check( POILogger.DEBUG ))
-            log.log(POILogger.DEBUG, "create protect record with protection disabled");
-        ScenarioProtectRecord retval = new ScenarioProtectRecord();
-
-        retval.setProtect(false);
-        return retval;
-    }
-
-    /**
-     * @return the ProtectRecord. If one is not contained in the sheet, then one is created.
-     */
-    public ProtectRecord getProtect() {
-        if (protect == null) {
-            protect = new ProtectRecord(false);
-            // Insert the newly created protect record just before DefaultColWidthRecord
-            int loc = findFirstRecordLocBySid(DefaultColWidthRecord.sid);
-            _records.add(loc, protect);
-        }
-        return protect;
-    }
-
-    /**
-     * @return the PasswordRecord. If one is not contained in the sheet, then one is created.
+     * @return the {@link WorksheetProtectionBlock} for this sheet
      */
-    public PasswordRecord getPassword() {
-        if (_password == null) {
-            _password = createPassword();
-            //Insert the newly created password record at the end of the record (just before the EOF)
-            int loc = findFirstRecordLocBySid(EOFRecord.sid);
-            _records.add(loc, _password);
-        }
-        return _password;
+    public WorksheetProtectionBlock getProtectionBlock() {
+        return _protectionBlock;
     }
-
-    /**
-     * creates a Password record with password set to 0x0000.
-     */
-    private static PasswordRecord createPassword() {
-        return new PasswordRecord(0x0000);
-    }
-
     /**
      * Sets whether the gridlines are shown in a viewer.
      * @param show whether to show gridlines or not
@@ -1601,68 +1537,6 @@
         }
     }
 
-    /**
-     * protect a spreadsheet with a password (not encypted, just sets protect
-     * flags and the password.
-     * @param password to set
-     * @param objects are protected
-     * @param scenarios are protected
-     */
-    public void protectSheet( String password, boolean objects, boolean scenarios ) {
-        int protIdx = -1;
-        ProtectRecord prec = getProtect();
-        PasswordRecord pass = getPassword();
-        prec.setProtect(true);
-        pass.setPassword(PasswordRecord.hashPassword(password));
-        if((objprotect == null && objects) || (scenprotect != null && scenarios)) {
-            protIdx = _records.indexOf( protect );
-        }
-        if(objprotect == null && objects) {
-            ObjectProtectRecord rec = createObjectProtect();
-            rec.setProtect(true);
-            _records.add(protIdx+1,rec);
-            objprotect = rec;
-        }
-        if(scenprotect == null && scenarios) {
-            ScenarioProtectRecord srec = createScenarioProtect();
-            srec.setProtect(true);
-            _records.add(protIdx+2,srec);
-            scenprotect = srec;
-        }
-    }
-
-    /**
-     * unprotect objects in the sheet (will not protect them, but any set to false are
-     * unprotected.
-     * @param sheet is unprotected (false = unprotect)
-     * @param objects are unprotected (false = unprotect)
-     * @param scenarios are unprotected (false = unprotect)
-     */
-    public void unprotectSheet( boolean sheet, boolean objects, boolean scenarios ) {
-
-        if (!sheet) {
-           ProtectRecord prec = getProtect();
-           prec.setProtect(sheet);
-           PasswordRecord pass = getPassword();
-           pass.setPassword((short)00);
-        }
-        if(objprotect != null && !objects) {
-            objprotect.setProtect(false);
-        }
-        if(scenprotect != null && !scenarios) {
-            scenprotect.setProtect(false);
-        }
-    }
-
-    /**
-     * @return {sheet is protected, objects are proteced, scenarios are protected}
-     */
-    public boolean[] isProtected() {
-        return new boolean[] { (protect != null && protect.getProtect()),
-                             (objprotect != null && objprotect.getProtect()),
-                             (scenprotect != null && scenprotect.getProtect())};
-    }
-
 
     public void groupRowRange(int fromRow, int toRow, boolean indent)
     {

Modified: poi/trunk/src/java/org/apache/poi/hssf/record/UnknownRecord.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/UnknownRecord.java?rev=784240&r1=784239&r2=784240&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/UnknownRecord.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/UnknownRecord.java Fri Jun 12 19:17:59 2009
@@ -44,6 +44,7 @@
 	public static final int PRINTSIZE_0033       = 0x0033;
 	public static final int PLS_004D             = 0x004D;
 	public static final int SHEETPR_0081         = 0x0081;
+	public static final int SORT_0090            = 0x0090;
 	public static final int STANDARDWIDTH_0099   = 0x0099;
 	public static final int SCL_00A0             = 0x00A0;
 	public static final int BITMAP_00E9          = 0x00E9;
@@ -56,7 +57,7 @@
 	public static final int SHEETPROTECTION_0867 = 0x0867;
 	public static final int RANGEPROTECTION_0868 = 0x0868;
 	public static final int HEADER_FOOTER_089C   = 0x089C;
-	
+
 	private int _sid;
 	private byte[] _rawData;
 
@@ -65,8 +66,8 @@
 	 * @param data  the data
 	 */
 	public UnknownRecord(int id, byte[] data) {
-	  _sid = id & 0xFFFF;
-	  _rawData = data;
+		_sid = id & 0xFFFF;
+		_rawData = data;
 	}
 
 
@@ -122,12 +123,12 @@
 
 	/**
 	 * These BIFF record types are known but still uninterpreted by POI
-	 * 
+	 *
 	 * @return the documented name of this BIFF record type, <code>null</code> if unknown to POI
 	 */
 	public static String getBiffName(int sid) {
 		// Note to POI developers:
-		// Make sure you delete the corresponding entry from 
+		// Make sure you delete the corresponding entry from
 		// this method any time a new Record subclass is created.
 		switch (sid) {
 			case PRINTSIZE_0033: return "PRINTSIZE";
@@ -135,7 +136,7 @@
 			case 0x0050: return "DCON"; // Data Consolidation Information
 			case 0x007F: return "IMDATA";
 			case SHEETPR_0081: return "SHEETPR";
-			case 0x0090: return "SORT"; // Sorting Options
+			case SORT_0090: return "SORT"; // Sorting Options
 			case 0x0094: return "LHRECORD"; // .WK? File Conversion Information
 			case STANDARDWIDTH_0099: return "STANDARDWIDTH"; //Standard Column Width
 			case 0x009D: return "AUTOFILTERINFO"; // Drop-Down Arrow Count
@@ -145,7 +146,7 @@
 			case 0x00B2: return "SXVI";        // (pivot table) View Item
 			case 0x00B4: return "SXIVD";       // (pivot table) Row/Column Field IDs
 			case 0x00B5: return "SXLI";        // (pivot table) Line Item Array
-			
+
 			case 0x00D3: return "OBPROJ";
 			case 0x00DC: return "PARAMQRY";
 			case 0x00DE: return "OLESIZE";
@@ -181,7 +182,7 @@
 			case 0x087B: return "CFEX";
 			case 0x087C: return "XFCRC";
 			case 0x087D: return "XFEXT";
-			case 0x087F: return "CONTINUEFRT12";	
+			case 0x087F: return "CONTINUEFRT12";
 			case 0x088B: return "PLV";
 			case 0x088C: return "COMPAT12";
 			case 0x088D: return "DXF";
@@ -209,12 +210,11 @@
 	}
 
 	/**
-	 * 
 	 * @return <code>true</code> if the unknown record id has been observed in POI unit tests
 	 */
 	private static boolean isObservedButUnknown(int sid) {
 		switch (sid) {
-			case 0x0033: 
+			case 0x0033:
 				// contains 2 bytes of data: 0x0001 or 0x0003
 			case 0x0034:
 				// Seems to be written by MSAccess
@@ -222,7 +222,7 @@
 				// appears after last cell value record and before WINDOW2
 			case 0x01BD:
 			case 0x01C2:
-				// Written by Excel 2007 
+				// Written by Excel 2007
 				// rawData is multiple of 12 bytes long
 				// appears after last cell value record and before WINDOW2 or drawing records
 			case 0x089D:

Added: poi/trunk/src/java/org/apache/poi/hssf/record/aggregates/WorksheetProtectionBlock.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/aggregates/WorksheetProtectionBlock.java?rev=784240&view=auto
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/aggregates/WorksheetProtectionBlock.java (added)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/aggregates/WorksheetProtectionBlock.java Fri Jun 12 19:17:59 2009
@@ -0,0 +1,245 @@
+/* ====================================================================
+   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.
+==================================================================== */
+
+package org.apache.poi.hssf.record.aggregates;
+
+import org.apache.poi.hssf.model.RecordStream;
+import org.apache.poi.hssf.record.ObjectProtectRecord;
+import org.apache.poi.hssf.record.PasswordRecord;
+import org.apache.poi.hssf.record.ProtectRecord;
+import org.apache.poi.hssf.record.Record;
+import org.apache.poi.hssf.record.RecordFormatException;
+import org.apache.poi.hssf.record.ScenarioProtectRecord;
+
+/**
+ * Groups the sheet protection records for a worksheet.
+ * <p/>
+ *
+ * See OOO excelfileformat.pdf sec 4.18.2 'Sheet Protection in a Workbook
+ * (BIFF5-BIFF8)'
+ *
+ * @author Josh Micich
+ */
+public final class WorksheetProtectionBlock extends RecordAggregate {
+	// Every one of these component records is optional
+	// (The whole WorksheetProtectionBlock may not be present)
+	private ProtectRecord _protectRecord;
+	private ObjectProtectRecord _objectProtectRecord;
+	private ScenarioProtectRecord _scenarioProtectRecord;
+	private PasswordRecord _passwordRecord;
+
+	/**
+	 * Creates an empty WorksheetProtectionBlock
+	 */
+	public WorksheetProtectionBlock() {
+		// all fields empty
+	}
+
+	/**
+	 * @return <code>true</code> if the specified Record sid is one belonging to
+	 *         the 'Page Settings Block'.
+	 */
+	public static boolean isComponentRecord(int sid) {
+		switch (sid) {
+			case ProtectRecord.sid:
+			case ObjectProtectRecord.sid:
+			case ScenarioProtectRecord.sid:
+			case PasswordRecord.sid:
+				return true;
+		}
+		return false;
+	}
+
+	private boolean readARecord(RecordStream rs) {
+		switch (rs.peekNextSid()) {
+			case ProtectRecord.sid:
+				checkNotPresent(_protectRecord);
+				_protectRecord = (ProtectRecord) rs.getNext();
+				break;
+			case ObjectProtectRecord.sid:
+				checkNotPresent(_objectProtectRecord);
+				_objectProtectRecord = (ObjectProtectRecord) rs.getNext();
+				break;
+			case ScenarioProtectRecord.sid:
+				checkNotPresent(_scenarioProtectRecord);
+				_scenarioProtectRecord = (ScenarioProtectRecord) rs.getNext();
+				break;
+			case PasswordRecord.sid:
+				checkNotPresent(_passwordRecord);
+				_passwordRecord = (PasswordRecord) rs.getNext();
+				break;
+			default:
+				// all other record types are not part of the PageSettingsBlock
+				return false;
+		}
+		return true;
+	}
+
+	private void checkNotPresent(Record rec) {
+		if (rec != null) {
+			throw new RecordFormatException("Duplicate PageSettingsBlock record (sid=0x"
+					+ Integer.toHexString(rec.getSid()) + ")");
+		}
+	}
+
+	public void visitContainedRecords(RecordVisitor rv) {
+		// Replicates record order from Excel 2007, though this is not critical
+
+		visitIfPresent(_protectRecord, rv);
+		visitIfPresent(_objectProtectRecord, rv);
+		visitIfPresent(_scenarioProtectRecord, rv);
+		visitIfPresent(_passwordRecord, rv);
+	}
+
+	private static void visitIfPresent(Record r, RecordVisitor rv) {
+		if (r != null) {
+			rv.visitRecord(r);
+		}
+	}
+
+	public PasswordRecord getPasswordRecord() {
+		return _passwordRecord;
+	}
+
+	public ScenarioProtectRecord getHCenter() {
+		return _scenarioProtectRecord;
+	}
+
+	/**
+	 * This method reads {@link WorksheetProtectionBlock} records from the supplied RecordStream
+	 * until the first non-WorksheetProtectionBlock record is encountered. As each record is read,
+	 * it is incorporated into this WorksheetProtectionBlock.
+	 * <p/>
+	 * As per the OOO documentation, the protection block records can be expected to be written
+	 * together (with no intervening records), but earlier versions of POI (prior to Jun 2009)
+	 * didn't do this.  Workbooks with sheet protection created by those earlier POI versions
+	 * seemed to be valid (Excel opens them OK). So PO allows continues to support reading of files
+	 * with non continuous worksheet protection blocks.
+	 *
+	 * <p/>
+	 * <b>Note</b> - when POI writes out this WorksheetProtectionBlock, the records will always be
+	 * written in one consolidated block (in the standard ordering) regardless of how scattered the
+	 * records were when they were originally read.
+	 */
+	public void addRecords(RecordStream rs) {
+		while (true) {
+			if (!readARecord(rs)) {
+				break;
+			}
+		}
+	}
+
+	/**
+	 * @return the ProtectRecord. If one is not contained in the sheet, then one
+	 *         is created.
+	 */
+	private ProtectRecord getProtect() {
+		if (_protectRecord == null) {
+			_protectRecord = new ProtectRecord(false);
+		}
+		return _protectRecord;
+	}
+
+	/**
+	 * @return the PasswordRecord. If one is not contained in the sheet, then
+	 *         one is created.
+	 */
+	private PasswordRecord getPassword() {
+		if (_passwordRecord == null) {
+			_passwordRecord = createPassword();
+		}
+		return _passwordRecord;
+	}
+
+	/**
+	 * protect a spreadsheet with a password (not encrypted, just sets protect
+	 * flags and the password.
+	 *
+	 * @param password to set. Pass <code>null</code> to remove all protection
+	 * @param shouldProtectObjects are protected
+	 * @param shouldProtectScenarios are protected
+	 */
+	public void protectSheet(String password, boolean shouldProtectObjects,
+			boolean shouldProtectScenarios) {
+		if (password == null) {
+			_passwordRecord = null;
+			_protectRecord = null;
+			_objectProtectRecord = null;
+			_scenarioProtectRecord = null;
+			return;
+		}
+
+		ProtectRecord prec = getProtect();
+		PasswordRecord pass = getPassword();
+		prec.setProtect(true);
+		pass.setPassword(PasswordRecord.hashPassword(password));
+		if (_objectProtectRecord == null && shouldProtectObjects) {
+			ObjectProtectRecord rec = createObjectProtect();
+			rec.setProtect(true);
+			_objectProtectRecord = rec;
+		}
+		if (_scenarioProtectRecord == null && shouldProtectScenarios) {
+			ScenarioProtectRecord srec = createScenarioProtect();
+			srec.setProtect(true);
+			_scenarioProtectRecord = srec;
+		}
+	}
+
+	public boolean isSheetProtected() {
+		return _protectRecord != null && _protectRecord.getProtect();
+	}
+
+	public boolean isObjectProtected() {
+		return _objectProtectRecord != null && _objectProtectRecord.getProtect();
+	}
+
+	public boolean isScenarioProtected() {
+		return _scenarioProtectRecord != null && _scenarioProtectRecord.getProtect();
+	}
+
+	/**
+	 * creates an ObjectProtect record with protect set to false.
+	 */
+	private static ObjectProtectRecord createObjectProtect() {
+		ObjectProtectRecord retval = new ObjectProtectRecord();
+		retval.setProtect(false);
+		return retval;
+	}
+
+	/**
+	 * creates a ScenarioProtect record with protect set to false.
+	 */
+	private static ScenarioProtectRecord createScenarioProtect() {
+		ScenarioProtectRecord retval = new ScenarioProtectRecord();
+		retval.setProtect(false);
+		return retval;
+	}
+
+	/**
+	 * creates a Password record with password set to 0x0000.
+	 */
+	private static PasswordRecord createPassword() {
+		return new PasswordRecord(0x0000);
+	}
+
+	public int getPasswordHash() {
+		if (_passwordRecord == null) {
+			return 0;
+		}
+		return _passwordRecord.getPassword();
+	}
+}

Modified: poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java?rev=784240&r1=784239&r2=784240&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java Fri Jun 12 19:17:59 2009
@@ -44,6 +44,7 @@
 import org.apache.poi.hssf.record.WSBoolRecord;
 import org.apache.poi.hssf.record.WindowTwoRecord;
 import org.apache.poi.hssf.record.aggregates.DataValidityTable;
+import org.apache.poi.hssf.record.aggregates.WorksheetProtectionBlock;
 import org.apache.poi.hssf.record.formula.FormulaShifter;
 import org.apache.poi.hssf.util.PaneInformation;
 import org.apache.poi.hssf.util.Region;
@@ -126,7 +127,7 @@
      * @return the parent workbook
      */
     public HSSFWorkbook getWorkbook(){
-        return _workbook;    
+        return _workbook;
     }
 
     /**
@@ -162,7 +163,7 @@
                 hrow = getRow( cval.getRow() );
                 lastrow = hrow;
                 if (hrow == null) {
-                    // Some tools (like Perl module Spreadsheet::WriteExcel - bug 41187) skip the RowRecords 
+                    // Some tools (like Perl module Spreadsheet::WriteExcel - bug 41187) skip the RowRecords
                     // Excel, OpenOffice.org and GoogleDocs are all OK with this, so POI should be too.
                     if (rowRecordsAlreadyPresent) {
                         // if at least one row record is present, all should be present.
@@ -180,7 +181,7 @@
             if (log.check( POILogger.DEBUG ))
                 log.log( DEBUG, "record took ",
                     new Long( System.currentTimeMillis() - cellstart ) );
-            
+
         }
         if (log.check( POILogger.DEBUG ))
             log.log(DEBUG, "total sheet cell creation took ",
@@ -340,12 +341,12 @@
      * Gets the number last row on the sheet.
      * Owing to idiosyncrasies in the excel file
      *  format, if the result of calling this method
-     *  is zero, you can't tell if that means there 
+     *  is zero, you can't tell if that means there
      *  are zero rows on the sheet, or one at
      *  position zero. For that case, additionally
      *  call {@link #getPhysicalNumberOfRows()} to
      *  tell if there is a row at position zero
-     *  or not. 
+     *  or not.
      * @return the number of the last row contained in this sheet, zero based.
      */
     public int getLastRowNum() {
@@ -504,20 +505,20 @@
     {
         _sheet.setDefaultRowHeight((short) (height * 20));
     }
-    
+
     /**
      * Returns the HSSFCellStyle that applies to the given
      *  (0 based) column, or null if no style has been
      *  set for that column
      */
     public HSSFCellStyle getColumnStyle(int column) {
-    	short styleIndex = _sheet.getXFIndexForColAt((short)column);
-    	
-    	if(styleIndex == 0xf) {
-    		// None set
-    		return null;
-    	}
-    	
+        short styleIndex = _sheet.getXFIndexForColAt((short)column);
+
+        if(styleIndex == 0xf) {
+            // None set
+            return null;
+        }
+
         ExtendedFormatRecord xf = _book.getExFormatAt(styleIndex);
         return new HSSFCellStyle(styleIndex, xf, _book);
     }
@@ -660,8 +661,8 @@
      */
     public Region getMergedRegionAt(int index) {
         CellRangeAddress cra = getMergedRegion(index);
-        
-        return new Region(cra.getFirstRow(), (short)cra.getFirstColumn(), 
+
+        return new Region(cra.getFirstRow(), (short)cra.getFirstColumn(),
                 cra.getLastRow(), (short)cra.getLastColumn());
     }
     /**
@@ -858,7 +859,7 @@
      * @param value whether to display or hide all zero values on the worksheet
      */
     public void setDisplayZeros(boolean value){
-        _sheet.getWindowTwo().setDisplayZeros(value);    
+        _sheet.getWindowTwo().setDisplayZeros(value);
     }
 
     /**
@@ -967,19 +968,22 @@
         _sheet.getPageSettings().setMargin(margin, size);
     }
 
+    private WorksheetProtectionBlock getProtectionBlock() {
+        return _sheet.getProtectionBlock();
+    }
     /**
      * Answer whether protection is enabled or disabled
      * @return true => protection enabled; false => protection disabled
      */
     public boolean getProtect() {
-        return getSheet().isProtected()[0];
+        return getProtectionBlock().isSheetProtected();
     }
 
     /**
      * @return hashed password
      */
     public short getPassword() {
-        return (short)getSheet().getPassword().getPassword();
+        return (short)getProtectionBlock().getPasswordHash();
     }
 
     /**
@@ -987,7 +991,7 @@
      * @return true => protection enabled; false => protection disabled
      */
     public boolean getObjectProtect() {
-        return getSheet().isProtected()[1];
+        return getProtectionBlock().isObjectProtected();
     }
 
     /**
@@ -995,24 +999,14 @@
      * @return true => protection enabled; false => protection disabled
      */
     public boolean getScenarioProtect() {
-        return getSheet().isProtected()[2];
-    }
-
-    /**
-     * Sets the protection on enabled or disabled
-     * @param protect true => protection enabled; false => protection disabled
-     * @deprecated (Jul 2007) use {@link #protectSheet(String)}
-     */
-    public void setProtect(boolean protect) {
-        getSheet().getProtect().setProtect(protect);
+        return getProtectionBlock().isScenarioProtected();
     }
-
     /**
      * Sets the protection enabled as well as the password
-     * @param password to set for protection
+     * @param password to set for protection. Pass <code>null</code> to remove protection
      */
     public void protectSheet(String password) {
-            getSheet().protectSheet(password, true, true); //protect objs&scenarios(normal)
+        getProtectionBlock().protectSheet(password, true, true); //protect objs&scenarios(normal)
     }
 
     /**
@@ -1153,7 +1147,7 @@
     public void shiftRows( int startRow, int endRow, int n, boolean copyRowHeight, boolean resetOriginalRowHeight) {
         shiftRows(startRow, endRow, n, copyRowHeight, resetOriginalRowHeight, true);
     }
-    
+
     /**
      * Shifts rows between startRow and endRow n number of rows.
      * If you use a negative number, it will shift rows up.
@@ -1171,7 +1165,7 @@
      * @param resetOriginalRowHeight whether to set the original row's height to the default
      * @param moveComments whether to move comments at the same time as the cells they are attached to
      */
-    public void shiftRows(int startRow, int endRow, int n, 
+    public void shiftRows(int startRow, int endRow, int n,
             boolean copyRowHeight, boolean resetOriginalRowHeight, boolean moveComments) {
         int s, inc;
         if (n < 0) {
@@ -1183,7 +1177,7 @@
         }
         NoteRecord[] noteRecs;
         if (moveComments) {
-            noteRecs = _sheet.getNoteRecords(); 
+            noteRecs = _sheet.getNoteRecords();
         } else {
             noteRecs = NoteRecord.EMPTY_ARRAY;
         }
@@ -1197,10 +1191,10 @@
             if ( row2Replace == null )
                 row2Replace = createRow( rowNum + n );
 
-            
+
             // Remove all the old cells from the row we'll
-            //  be writing too, before we start overwriting 
-            //  any cells. This avoids issues with cells 
+            //  be writing too, before we start overwriting
+            //  any cells. This avoids issues with cells
             //  changing type, and records not being correctly
             //  overwritten
             row2Replace.removeAllCells();
@@ -1235,7 +1229,7 @@
             }
             // Now zap all the cells in the source row
             row.removeAllCells();
-            
+
             // Move comments from the source row to the
             //  destination row. Note that comments can
             //  exist for cells which are null
@@ -1476,10 +1470,10 @@
         for (Iterator<EscherRecord> iterator = escherRecords.iterator(); iterator.hasNext();) {
             EscherRecord escherRecord = iterator.next();
             if (fat) {
-				System.out.println(escherRecord.toString());
-			} else {
-				escherRecord.display(w, 0);
-			}
+                System.out.println(escherRecord.toString());
+            } else {
+                escherRecord.display(w, 0);
+            }
         }
         w.flush();
     }
@@ -1533,7 +1527,7 @@
         EscherAggregate agg = (EscherAggregate) _sheet.findFirstRecordBySid(EscherAggregate.sid);
         return agg;
     }
-    
+
     /**
      * Returns the top-level drawing patriach, if there is
      *  one.
@@ -1549,7 +1543,7 @@
     public HSSFPatriarch getDrawingPatriarch() {
         EscherAggregate agg = getDrawingEscherAggregate();
         if(agg == null) return null;
-        
+
         HSSFPatriarch patriarch = new HSSFPatriarch(this, agg);
         agg.setPatriarch(patriarch);
 
@@ -1649,7 +1643,7 @@
     public void autoSizeColumn(int column) {
         autoSizeColumn(column, false);
     }
-    
+
     /**
      * Adjusts the column width to fit the contents.
      *
@@ -1657,9 +1651,9 @@
      *  normally only be called once per column, at the end of your
      *  processing.
      *
-     * You can specify whether the content of merged cells should be considered or ignored.  
+     * You can specify whether the content of merged cells should be considered or ignored.
      *  Default is to ignore merged cells.
-     *   
+     *
      * @param column the column index
      * @param useMergedCells whether to use the contents of merged cells when calculating the width of the column
      */
@@ -1672,13 +1666,13 @@
          * '0' looks to be a good choice.
          */
         char defaultChar = '0';
-       
+
         /**
          * This is the multiple that the font height is scaled by when determining the
          * boundary of rotated text.
          */
         double fontHeightMultiple = 2.0;
-       
+
         FontRenderContext frc = new FontRenderContext(null, true, true);
 
         HSSFWorkbook wb = new HSSFWorkbook(_book);
@@ -1704,7 +1698,7 @@
                 CellRangeAddress region = getMergedRegion(i);
                 if (containsCell(region, row.getRowNum(), column)) {
                     if (!useMergedCells) {
-                        // If we're not using merged cells, skip this one and move on to the next. 
+                        // If we're not using merged cells, skip this one and move on to the next.
                         continue rows;
                     }
                     cell = row.getCell(region.getFirstColumn());
@@ -1715,7 +1709,7 @@
             HSSFCellStyle style = cell.getCellStyle();
             int cellType = cell.getCellType();
             if(cellType == HSSFCell.CELL_TYPE_FORMULA) cellType = cell.getCachedFormulaResultType();
-            
+
             HSSFFont font = wb.getFontAt(style.getFontIndex());
 
             if (cellType == HSSFCell.CELL_TYPE_STRING) {
@@ -1836,9 +1830,9 @@
             if(c != null) {
                 return c.getCellComment();
             }
-			// No cell, so you will get new
-			//  objects every time, sorry...
-			return HSSFCell.findCellComment(_sheet, row, column);
+            // No cell, so you will get new
+            //  objects every time, sorry...
+            return HSSFCell.findCellComment(_sheet, row, column);
         }
         return null;
     }

Modified: poi/trunk/src/testcases/org/apache/poi/hssf/model/TestSheet.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/hssf/model/TestSheet.java?rev=784240&r1=784239&r2=784240&view=diff
==============================================================================
--- poi/trunk/src/testcases/org/apache/poi/hssf/model/TestSheet.java (original)
+++ poi/trunk/src/testcases/org/apache/poi/hssf/model/TestSheet.java Fri Jun 12 19:17:59 2009
@@ -644,15 +644,14 @@
 		Sheet sheet = Sheet.createSheet();
 
 		List<RecordBase> sheetRecs = sheet.getRecords();
-		assertEquals(22, sheetRecs.size());
+		assertEquals(23, sheetRecs.size());
 
 		FormulaShifter shifter = FormulaShifter.createForRowShift(0, 0, 0, 1);
 		sheet.updateFormulasAfterCellShift(shifter, 0);
-		if (sheetRecs.size() == 23 && sheetRecs.get(21) instanceof ConditionalFormattingTable) {
+		if (sheetRecs.size() == 24 && sheetRecs.get(22) instanceof ConditionalFormattingTable) {
 			throw new AssertionFailedError("Identified bug 46547a");
 		}
-		assertEquals(22, sheetRecs.size());
-
+		assertEquals(23, sheetRecs.size());
 	}
 	/**
 	 * Bug 46547 happened when attempting to add conditional formatting to a sheet

Modified: poi/trunk/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java?rev=784240&r1=784239&r2=784240&view=diff
==============================================================================
--- poi/trunk/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java (original)
+++ poi/trunk/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java Fri Jun 12 19:17:59 2009
@@ -28,7 +28,10 @@
 import org.apache.poi.hssf.model.Sheet;
 import org.apache.poi.hssf.model.DrawingManager2;
 import org.apache.poi.hssf.record.*;
+import org.apache.poi.hssf.record.aggregates.WorksheetProtectionBlock;
+import org.apache.poi.hssf.usermodel.RecordInspector.RecordCollector;
 import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.ss.util.CellRangeAddressList;
 import org.apache.poi.ss.usermodel.BaseTestSheet;
 import org.apache.poi.ddf.EscherDgRecord;
 
@@ -125,7 +128,7 @@
         s.setRowSumsRight(true);
 
         // Check
-        assertEquals(true, record.getAlternateExpression()); //sheet.setRowSumsBelow alters this field too 
+        assertEquals(true, record.getAlternateExpression()); //sheet.setRowSumsBelow alters this field too
         assertEquals(false, record.getAlternateFormula());
         assertEquals(true, record.getAutobreaks());
         assertEquals(true, record.getDialog());
@@ -300,37 +303,104 @@
     /**
      * Test that the ProtectRecord is included when creating or cloning a sheet
      */
-    public void testProtect() {
+    public void testCloneWithProtect() {
+        String passwordA = "secrect";
+        int expectedHashA = -6810;
+        String passwordB = "admin";
+        int expectedHashB = -14556;
         HSSFWorkbook workbook = new HSSFWorkbook();
         HSSFSheet hssfSheet = workbook.createSheet();
-        Sheet sheet = hssfSheet.getSheet();
-        ProtectRecord protect = sheet.getProtect();
+        hssfSheet.protectSheet(passwordA);
 
-        assertFalse(protect.getProtect());
+        assertEquals(expectedHashA, hssfSheet.getSheet().getProtectionBlock().getPasswordHash());
 
-        // This will tell us that cloneSheet, and by extension,
-        // the list forms of createSheet leave us with an accessible
-        // ProtectRecord.
-        hssfSheet.protectSheet("secret");
-        Sheet cloned = sheet.cloneSheet();
-        assertNotNull(cloned.getProtect());
-        assertTrue(hssfSheet.getProtect());
+        // Clone the sheet, and make sure the password hash is preserved
+        HSSFSheet sheet2 = workbook.cloneSheet(0);
+        assertEquals(expectedHashA, sheet2.getSheet().getProtectionBlock().getPasswordHash());
+
+        // change the password on the first sheet
+        hssfSheet.protectSheet(passwordB);
+        assertEquals(expectedHashB, hssfSheet.getSheet().getProtectionBlock().getPasswordHash());
+        // but the cloned sheet's password should remain unchanged
+        assertEquals(expectedHashA, sheet2.getSheet().getProtectionBlock().getPasswordHash());
     }
 
     public void testProtectSheet() {
-        short expected = (short)0xfef1;
+        int expectedHash = (short)0xfef1;
         HSSFWorkbook wb = new HSSFWorkbook();
         HSSFSheet s = wb.createSheet();
         s.protectSheet("abcdefghij");
-        Sheet sheet = s.getSheet();
-        ProtectRecord protect = sheet.getProtect();
-        PasswordRecord pass = sheet.getPassword();
-        assertTrue("protection should be on",protect.getProtect());
-        assertTrue("object protection should be on",sheet.isProtected()[1]);
-        assertTrue("scenario protection should be on",sheet.isProtected()[2]);
-        assertEquals("well known value for top secret hash should be "+Integer.toHexString(expected).substring(4),expected,pass.getPassword());
+        WorksheetProtectionBlock pb = s.getSheet().getProtectionBlock();
+        assertTrue("protection should be on", pb.isSheetProtected());
+        assertTrue("object protection should be on",pb.isObjectProtected());
+        assertTrue("scenario protection should be on",pb.isScenarioProtected());
+        assertEquals("well known value for top secret hash should be "+Integer.toHexString(expectedHash).substring(4), expectedHash, pb.getPasswordHash());
     }
 
+    /**
+     * {@link PasswordRecord} belongs with the rest of the Worksheet Protection Block
+     * (which should be before {@link DimensionsRecord}).
+     */
+    public void testProtectSheetRecordOrder_bug47363a() {
+        HSSFWorkbook wb = new HSSFWorkbook();
+        HSSFSheet s = wb.createSheet();
+        s.protectSheet("secret");
+        RecordCollector rc = new RecordCollector();
+        s.getSheet().visitContainedRecords(rc, 0);
+        Record[] recs = rc.getRecords();
+        int nRecs = recs.length;
+        if (recs[nRecs-2] instanceof PasswordRecord && recs[nRecs-5] instanceof DimensionsRecord) {
+           throw new AssertionFailedError("Identified bug 47363a - PASSWORD after DIMENSION");
+        }
+        // Check that protection block is together, and before DIMENSION
+        confirmRecordClass(recs, nRecs-4, DimensionsRecord.class);
+        confirmRecordClass(recs, nRecs-9, ProtectRecord.class);
+        confirmRecordClass(recs, nRecs-8, ObjectProtectRecord.class);
+        confirmRecordClass(recs, nRecs-7, ScenarioProtectRecord.class);
+        confirmRecordClass(recs, nRecs-6, PasswordRecord.class);
+    }
+
+    private static void confirmRecordClass(Record[] recs, int index, Class<? extends Record> cls) {
+        if (recs.length <= index) {
+            throw new AssertionFailedError("Expected (" + cls.getName() + ") at index "
+                    + index + " but array length is " + recs.length + ".");
+        }
+        assertEquals(cls, recs[index].getClass());
+    }
+
+    /**
+     * There should be no problem with adding data validations after sheet protection
+     */
+    public void testDvProtectionOrder_bug47363b() {
+        HSSFWorkbook workbook = new HSSFWorkbook();
+        HSSFSheet sheet = workbook.createSheet("Sheet1");
+        sheet.protectSheet("secret");
+
+        DVConstraint dvc = DVConstraint.createNumericConstraint(DVConstraint.ValidationType.INTEGER,
+                                                DVConstraint.OperatorType.BETWEEN, "10", "100");
+        CellRangeAddressList numericCellAddressList = new CellRangeAddressList(0, 0, 1, 1);
+        HSSFDataValidation dv = new HSSFDataValidation(numericCellAddressList, dvc);
+        try {
+            sheet.addValidationData(dv);
+        } catch (IllegalStateException e) {
+            String expMsg = "Unexpected (org.apache.poi.hssf.record.PasswordRecord) while looking for DV Table insert pos";
+            if (expMsg.equals(e.getMessage())) {
+                throw new AssertionFailedError("Identified bug 47363b");
+            }
+            throw e;
+        }
+        RecordCollector rc;
+        rc = new RecordCollector();
+        sheet.getSheet().visitContainedRecords(rc, 0);
+        int nRecsWithProtection = rc.getRecords().length;
+
+        sheet.protectSheet(null);
+        rc = new RecordCollector();
+        sheet.getSheet().visitContainedRecords(rc, 0);
+        int nRecsWithoutProtection = rc.getRecords().length;
+
+        assertEquals(4, nRecsWithProtection - nRecsWithoutProtection);
+    }
 
     public void testZoom() {
         HSSFWorkbook wb = new HSSFWorkbook();
@@ -674,16 +744,16 @@
 
         //TODO: check shapeId in the cloned sheet
     }
-    
+
     /**
      * POI now (Sep 2008) allows sheet names longer than 31 chars (for other apps besides Excel).
      * Since Excel silently truncates to 31, make sure that POI enforces uniqueness on the first
-     * 31 chars. 
+     * 31 chars.
      */
     public void testLongSheetNames() {
         HSSFWorkbook wb = new HSSFWorkbook();
         final String SAME_PREFIX = "A123456789B123456789C123456789"; // 30 chars
-        
+
         wb.createSheet(SAME_PREFIX + "Dxxxx");
         try {
             wb.createSheet(SAME_PREFIX + "Dyyyy"); // identical up to the 32nd char
@@ -693,7 +763,7 @@
         }
         wb.createSheet(SAME_PREFIX + "Exxxx"); // OK - differs in the 31st char
     }
-    
+
     /**
      * Tests that we can read existing column styles
      */
@@ -701,17 +771,17 @@
         HSSFWorkbook wbNone = HSSFTestDataSamples.openSampleWorkbook("ColumnStyleNone.xls");
         HSSFWorkbook wbSimple = HSSFTestDataSamples.openSampleWorkbook("ColumnStyle1dp.xls");
         HSSFWorkbook wbComplex = HSSFTestDataSamples.openSampleWorkbook("ColumnStyle1dpColoured.xls");
-    	
+
         // Presence / absence checks
         assertNull(wbNone.getSheetAt(0).getColumnStyle(0));
         assertNull(wbNone.getSheetAt(0).getColumnStyle(1));
-        
+
         assertNull(wbSimple.getSheetAt(0).getColumnStyle(0));
         assertNotNull(wbSimple.getSheetAt(0).getColumnStyle(1));
-        
+
         assertNull(wbComplex.getSheetAt(0).getColumnStyle(0));
         assertNotNull(wbComplex.getSheetAt(0).getColumnStyle(1));
-        
+
         // Details checks
         HSSFCellStyle bs = wbSimple.getSheetAt(0).getColumnStyle(1);
         assertEquals(62, bs.getIndex());
@@ -721,8 +791,8 @@
         assertEquals(8, bs.getFont(wbSimple).getColor());
         assertFalse(bs.getFont(wbSimple).getItalic());
         assertEquals(HSSFFont.BOLDWEIGHT_NORMAL, bs.getFont(wbSimple).getBoldweight());
-        
-        
+
+
         HSSFCellStyle cs = wbComplex.getSheetAt(0).getColumnStyle(1);
         assertEquals(62, cs.getIndex());
         assertEquals("#,##0.0_ ;\\-#,##0.0\\ ", cs.getDataFormatString());
@@ -732,5 +802,4 @@
         assertFalse(cs.getFont(wbComplex).getItalic());
         assertEquals(HSSFFont.BOLDWEIGHT_BOLD, cs.getFont(wbComplex).getBoldweight());
     }
-
 }



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@poi.apache.org
For additional commands, e-mail: commits-help@poi.apache.org