From 0f29ae8e4dbf1a19093d08a4da15cdfe83948d8c Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Sun, 8 Dec 2019 23:29:50 +0000 Subject: Sonar fixes - type: bugs / severity: major git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1871064 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/poi/hssf/record/FontRecord.java | 57 ++++--- .../apache/poi/hssf/usermodel/HSSFCellStyle.java | 30 ++-- .../poi/poifs/crypt/ChunkedCipherInputStream.java | 9 +- .../apache/poi/poifs/crypt/CryptoFunctions.java | 186 +++++++++++---------- .../cryptoapi/CryptoAPIDocumentInputStream.java | 8 +- .../cryptoapi/CryptoAPIDocumentOutputStream.java | 12 +- .../poi/poifs/filesystem/DocumentInputStream.java | 32 ++-- src/java/org/apache/poi/sl/draw/DrawFactory.java | 32 ++-- src/java/org/apache/poi/sl/draw/DrawShape.java | 94 +++++------ .../org/apache/poi/ss/formula/functions/Delta.java | 45 +++-- .../org/apache/poi/ss/formula/functions/Fixed.java | 11 +- .../org/apache/poi/ss/formula/functions/Irr.java | 26 ++- .../org/apache/poi/ss/usermodel/DataFormatter.java | 128 +++++++------- .../poi/ss/usermodel/ExcelGeneralNumberFormat.java | 3 +- .../apache/poi/ss/usermodel/FractionFormat.java | 29 ++-- 15 files changed, 348 insertions(+), 354 deletions(-) (limited to 'src/java') diff --git a/src/java/org/apache/poi/hssf/record/FontRecord.java b/src/java/org/apache/poi/hssf/record/FontRecord.java index 5296d5e3ce..cf673397d6 100644 --- a/src/java/org/apache/poi/hssf/record/FontRecord.java +++ b/src/java/org/apache/poi/hssf/record/FontRecord.java @@ -17,6 +17,8 @@ package org.apache.poi.hssf.record; +import java.util.Objects; + import org.apache.poi.util.BitField; import org.apache.poi.util.BitFieldFactory; import org.apache.poi.util.HexDump; @@ -30,7 +32,8 @@ import org.apache.poi.util.StringUtil; * REFERENCE: PG 315 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2) */ public final class FontRecord extends StandardRecord { - public final static short sid = 0x0031; // docs are wrong (0x231 Microsoft Support site article Q184647) + // docs are wrong (0x231 Microsoft Support site article Q184647) + public final static short sid = 0x0031; public final static short SS_NONE = 0; public final static short SS_SUPER = 1; public final static short SS_SUB = 2; @@ -39,7 +42,8 @@ public final class FontRecord extends StandardRecord { public final static byte U_DOUBLE = 2; public final static byte U_SINGLE_ACCOUNTING = 0x21; public final static byte U_DOUBLE_ACCOUNTING = 0x22; - private short field_1_font_height; // in units of .05 of a point + // in units of .05 of a point + private short field_1_font_height; private short field_2_attributes; // 0 0x01 - Reserved bit must be 0 @@ -359,24 +363,22 @@ public final class FontRecord extends StandardRecord { } public String toString() { - StringBuilder sb = new StringBuilder(); - - sb.append("[FONT]\n"); - sb.append(" .fontheight = ").append(HexDump.shortToHex(getFontHeight())).append("\n"); - sb.append(" .attributes = ").append(HexDump.shortToHex(getAttributes())).append("\n"); - sb.append(" .italic = ").append(isItalic()).append("\n"); - sb.append(" .strikout = ").append(isStruckout()).append("\n"); - sb.append(" .macoutlined= ").append(isMacoutlined()).append("\n"); - sb.append(" .macshadowed= ").append(isMacshadowed()).append("\n"); - sb.append(" .colorpalette = ").append(HexDump.shortToHex(getColorPaletteIndex())).append("\n"); - sb.append(" .boldweight = ").append(HexDump.shortToHex(getBoldWeight())).append("\n"); - sb.append(" .supersubscript= ").append(HexDump.shortToHex(getSuperSubScript())).append("\n"); - sb.append(" .underline = ").append(HexDump.byteToHex(getUnderline())).append("\n"); - sb.append(" .family = ").append(HexDump.byteToHex(getFamily())).append("\n"); - sb.append(" .charset = ").append(HexDump.byteToHex(getCharset())).append("\n"); - sb.append(" .fontname = ").append(getFontName()).append("\n"); - sb.append("[/FONT]\n"); - return sb.toString(); + return + "[FONT]\n" + + " .fontheight = " + HexDump.shortToHex(getFontHeight()) + "\n" + + " .attributes = " + HexDump.shortToHex(getAttributes()) + "\n" + + " .italic = " + isItalic() + "\n" + + " .strikout = " + isStruckout() + "\n" + + " .macoutlined= " + isMacoutlined() + "\n" + + " .macshadowed= " + isMacshadowed() + "\n" + + " .colorpalette = " + HexDump.shortToHex(getColorPaletteIndex()) + "\n" + + " .boldweight = " + HexDump.shortToHex(getBoldWeight()) + "\n" + + " .supersubscript= " + HexDump.shortToHex(getSuperSubScript()) + "\n" + + " .underline = " + HexDump.byteToHex(getUnderline()) + "\n" + + " .family = " + HexDump.byteToHex(getFamily()) + "\n" + + " .charset = " + HexDump.byteToHex(getCharset()) + "\n" + + " .fontname = " + getFontName() + "\n" + + "[/FONT]\n"; } public void serialize(LittleEndianOutput out) { @@ -421,7 +423,7 @@ public final class FontRecord extends StandardRecord { * Clones all the font style information from another * FontRecord, onto this one. This * will then hold all the same font style options. - * + * * @param source the record to clone the properties from */ public void cloneStyleFrom(FontRecord source) { @@ -464,12 +466,13 @@ public final class FontRecord extends StandardRecord { * for exact contents, because normally the * font record's position makes a big * difference too. - * + * * @param other the record to compare with - * + * * @return true, if the properties match */ public boolean sameProperties(FontRecord other) { + return field_1_font_height == other.field_1_font_height && field_2_attributes == other.field_2_attributes && @@ -480,15 +483,11 @@ public final class FontRecord extends StandardRecord { field_7_family == other.field_7_family && field_8_charset == other.field_8_charset && field_9_zero == other.field_9_zero && - stringEquals(this.field_11_font_name, other.field_11_font_name) + Objects.equals(this.field_11_font_name, other.field_11_font_name) ; } public boolean equals(Object o) { - return (o instanceof FontRecord) ? sameProperties((FontRecord)o) : false; - } - - private static boolean stringEquals(String s1, String s2) { - return (s1 == s2 || (s1 != null && s1.equals(s2))); + return (o instanceof FontRecord) && sameProperties((FontRecord) o); } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java index a3879a9194..68cbc96c9e 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java @@ -108,7 +108,7 @@ public final class HSSFCellStyle implements CellStyle { } // we keep the cached data in ThreadLocal members in order to - // avoid multi-threading issues when different workbooks are accessed in + // avoid multi-threading issues when different workbooks are accessed in // multiple threads at the same time private static final ThreadLocal lastDateFormat = new ThreadLocal() { @Override @@ -264,7 +264,7 @@ public final class HSSFCellStyle implements CellStyle { public void setQuotePrefixed(boolean quotePrefix) { _format.set123Prefix(quotePrefix); } - + /** * Is "Quote Prefix" or "123 Prefix" enabled for the cell? */ @@ -272,7 +272,7 @@ public final class HSSFCellStyle implements CellStyle { public boolean getQuotePrefixed() { return _format.get123Prefix(); } - + /** * set the type of horizontal alignment for the cell * @param align - the type of alignment @@ -340,8 +340,8 @@ public final class HSSFCellStyle implements CellStyle { /** * set the degree of rotation for the text in the cell * - * Note: HSSF uses values from -90 to 90 degrees, whereas XSSF - * uses values from 0 to 180 degrees. The implementations of this method will map between these two value-ranges + * Note: HSSF uses values from -90 to 90 degrees, whereas XSSF + * uses values from 0 to 180 degrees. The implementations of this method will map between these two value-ranges * accordingly, however the corresponding getter is returning values in the range mandated by the current type * of Excel file-format that this CellStyle is applied to. * @@ -352,7 +352,7 @@ public final class HSSFCellStyle implements CellStyle { { if (rotation == 0xff) { // Special cases for vertically aligned text - } + } else if ((rotation < 0)&&(rotation >= -90)) { //Take care of the funny 4th quadrant issue //The 4th quadrant (-1 to -90) is stored as (91 to 180) @@ -407,7 +407,7 @@ public final class HSSFCellStyle implements CellStyle { { return _format.getIndent(); } - + /** * set the type of border to use for the left border of the cell * @param border type @@ -575,7 +575,7 @@ public final class HSSFCellStyle implements CellStyle { { return _format.getBottomBorderPaletteIdx(); } - + /** * setting to one fills the cell with the foreground color... No idea about * other values @@ -674,7 +674,7 @@ public final class HSSFCellStyle implements CellStyle { } return result; } - + @Override public HSSFColor getFillBackgroundColorColor() { HSSFPalette pallette = new HSSFPalette( @@ -769,7 +769,7 @@ public final class HSSFCellStyle implements CellStyle { public boolean getShrinkToFit() { return _format.getShrinkToFit(); } - + /** * Get the reading order, for RTL/LTR ordering of * the text. @@ -792,7 +792,7 @@ public final class HSSFCellStyle implements CellStyle { public void setReadingOrder(short order) { _format.setReadingOrder(order); } - + /** * Verifies that this style belongs to the supplied Workbook. * Will throw an exception if it belongs to a different one. @@ -836,9 +836,9 @@ public final class HSSFCellStyle implements CellStyle { if(_workbook != source._workbook) { lastDateFormat.set(Short.MIN_VALUE); - lastFormats.set(null); - getDataFormatStringCache.set(null); - + lastFormats.remove(); + getDataFormatStringCache.remove(); + // Then we need to clone the format string, // and update the format record for this short fmt = (short)_workbook.createFormat(source.getDataFormatString() ); @@ -894,5 +894,5 @@ public final class HSSFCellStyle implements CellStyle { } return false; } - + } diff --git a/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java b/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java index 5ad77ddd62..827ee4c0a7 100644 --- a/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java +++ b/src/java/org/apache/poi/poifs/crypt/ChunkedCipherInputStream.java @@ -21,10 +21,7 @@ import java.io.IOException; import java.io.InputStream; import java.security.GeneralSecurityException; -import javax.crypto.BadPaddingException; import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.ShortBufferException; import org.apache.poi.EncryptedDocumentException; import org.apache.poi.util.IOUtils; @@ -82,7 +79,7 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream { @Override public int read() throws IOException { byte[] b = { 0 }; - return (read(b) == 1) ? b[0] : -1; + return (read(b) == 1) ? (b[0] & 0xFF) : -1; } // do not implement! -> recursion @@ -222,7 +219,7 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream { /** * Used when BIFF header fields (sid, size) are being read. The internal * {@link Cipher} instance must step even when unencrypted bytes are read - * + * */ @Override public void readPlain(byte[] b, int off, int len) { @@ -236,7 +233,7 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream { readBytes = read(b, off, len, true); total += Math.max(0, readBytes); } while (readBytes > -1 && total < len); - + if (total < len) { throw new EOFException("buffer underrun"); } diff --git a/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java b/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java index 28e2bd7537..46cd58128a 100644 --- a/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java +++ b/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java @@ -16,7 +16,7 @@ ==================================================================== */ package org.apache.poi.poifs.crypt; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.security.DigestException; import java.security.GeneralSecurityException; import java.security.Key; @@ -44,59 +44,62 @@ import org.apache.poi.util.StringUtil; * Helper functions used for standard and agile encryption */ @Internal -public class CryptoFunctions { +public final class CryptoFunctions { //arbitrarily selected; may need to increase private static final int MAX_RECORD_LENGTH = 100_000; + private CryptoFunctions() { + } + /** *

2.3.4.7 ECMA-376 Document Encryption Key Generation (Standard Encryption)
* 2.3.4.11 Encryption Key Generation (Agile Encryption)

- * + * *

The encryption key for ECMA-376 document encryption [ECMA-376] using agile * encryption MUST be generated by using the following method, which is derived from PKCS #5: * Password-Based Cryptography Version 2.0 [RFC2898].

- * + * *

Let H() be a hashing algorithm as determined by the PasswordKeyEncryptor.hashAlgorithm * element, H_n be the hash data of the n-th iteration, and a plus sign (+) represent concatenation. * The password MUST be provided as an array of Unicode characters. Limitations on the length of the * password and the characters used by the password are implementation-dependent. * The initial password hash is generated as follows:

- * - * + * + * *
H_0 = H(salt + password)
- * + * *

The salt used MUST be generated randomly. The salt MUST be stored in the * PasswordKeyEncryptor.saltValue element contained within the \EncryptionInfo stream as * specified in section 2.3.4.10. The hash is then iterated by using the following approach:

- * + * *
H_n = H(iterator + H_n-1)
- * + * *

where iterator is an unsigned 32-bit value that is initially set to 0x00000000 and then incremented * monotonically on each iteration until PasswordKey.spinCount iterations have been performed. * The value of iterator on the last iteration MUST be one less than PasswordKey.spinCount.

- * + * *

For POI, H_final will be calculated by {@link #generateKey(byte[],HashAlgorithm,byte[],int)}

* - * @param password - * @param hashAlgorithm - * @param salt - * @param spinCount + * @param password the password + * @param hashAlgorithm the hash algorithm + * @param salt the initial salt value + * @param spinCount the repetition count * @return the hashed password */ public static byte[] hashPassword(String password, HashAlgorithm hashAlgorithm, byte[] salt, int spinCount) { return hashPassword(password, hashAlgorithm, salt, spinCount, true); } - + /** * Generalized method for read and write protection hash generation. * The difference is, read protection uses the order iterator then hash in the hash loop, whereas write protection * uses first the last hash value and then the current iterator value * - * @param password - * @param hashAlgorithm - * @param salt - * @param spinCount + * @param password the pasword + * @param hashAlgorithm the hash algorighm + * @param salt the initial salt value + * @param spinCount the repetition count * @param iteratorFirst if true, the iterator is hashed before the n-1 hash value, * if false the n-1 hash value is applied first * @return the hashed password @@ -107,16 +110,16 @@ public class CryptoFunctions { if (password == null) { password = Decryptor.DEFAULT_PASSWORD; } - + MessageDigest hashAlg = getMessageDigest(hashAlgorithm); - + hashAlg.update(salt); byte[] hash = hashAlg.digest(StringUtil.getToUnicodeLE(password)); byte[] iterator = new byte[LittleEndianConsts.INT_SIZE]; byte[] first = (iteratorFirst ? iterator : hash); byte[] second = (iteratorFirst ? hash : iterator); - + try { for (int i = 0; i < spinCount; i++) { LittleEndian.putInt(iterator, 0, i); @@ -128,13 +131,13 @@ public class CryptoFunctions { } catch (DigestException e) { throw new EncryptedDocumentException("error in password hashing"); } - + return hash; - } + } /** *

2.3.4.12 Initialization Vector Generation (Agile Encryption)

- * + * *

Initialization vectors are used in all cases for agile encryption. An initialization vector MUST be * generated by using the following method, where H() is a hash function that MUST be the same as * specified in section 2.3.4.11 and a plus sign (+) represents concatenation:

@@ -147,7 +150,7 @@ public class CryptoFunctions { * corresponding to the cipherAlgorithm attribute, pad the array of bytes by appending 0x36 until * the array is blockSize bytes. If the array of bytes is larger than blockSize bytes, truncate the * array to blockSize bytes. - * + * **/ public static byte[] generateIv(HashAlgorithm hashAlgorithm, byte[] salt, byte[] blockKey, int blockSize) { byte[] iv = salt; @@ -161,23 +164,23 @@ public class CryptoFunctions { /** *

2.3.4.11 Encryption Key Generation (Agile Encryption)

- * + * *

The final hash data that is used for an encryption key is then generated by using the following * method:

- * + * *
H_final = H(H_n + blockKey)
- * + * *

where blockKey represents an array of bytes used to prevent two different blocks from encrypting * to the same cipher text.

- * + * *

If the size of the resulting H_final is smaller than that of PasswordKeyEncryptor.keyBits, the key * MUST be padded by appending bytes with a value of 0x36. If the hash value is larger in size than - * PasswordKeyEncryptor.keyBits, the key is obtained by truncating the hash value.

+ * PasswordKeyEncryptor.keyBits, the key is obtained by truncating the hash value.

* - * @param passwordHash - * @param hashAlgorithm - * @param blockKey - * @param keySize + * @param passwordHash the hashed password byte + * @param hashAlgorithm the hash algorithm + * @param blockKey the block key + * @param keySize the key size * @return intermediate key */ public static byte[] generateKey(byte[] passwordHash, HashAlgorithm hashAlgorithm, byte[] blockKey, int keySize) { @@ -198,9 +201,8 @@ public class CryptoFunctions { * @param vec the initialization vector (IV), can be null * @param cipherMode Cipher.DECRYPT_MODE or Cipher.ENCRYPT_MODE * @return the requested cipher - * @throws GeneralSecurityException * @throws EncryptedDocumentException if the initialization failed or if an algorithm was specified, - * which depends on a missing bouncy castle provider + * which depends on a missing bouncy castle provider */ public static Cipher getCipher(SecretKey key, CipherAlgorithm cipherAlgorithm, ChainingMode chain, byte[] vec, int cipherMode) { return getCipher(key, cipherAlgorithm, chain, vec, cipherMode, null); @@ -218,14 +220,13 @@ public class CryptoFunctions { * @param cipherMode Cipher.DECRYPT_MODE or Cipher.ENCRYPT_MODE * @param padding the padding (null = NOPADDING, ANSIX923Padding, PKCS5Padding, PKCS7Padding, ISO10126Padding, ...) * @return the requested cipher - * @throws GeneralSecurityException * @throws EncryptedDocumentException if the initialization failed or if an algorithm was specified, - * which depends on a missing bouncy castle provider + * which depends on a missing bouncy castle provider */ public static Cipher getCipher(Key key, CipherAlgorithm cipherAlgorithm, ChainingMode chain, byte[] vec, int cipherMode, String padding) { int keySizeInBytes = key.getEncoded().length; if (padding == null) padding = "NoPadding"; - + try { // Ensure the JCE policies files allow for this sized key if (Cipher.getMaxAllowedKeyLength(cipherAlgorithm.jceId) < keySizeInBytes*8) { @@ -241,7 +242,7 @@ public class CryptoFunctions { } else { cipher = Cipher.getInstance(cipherAlgorithm.jceId + "/" + chain.jceId + "/" + padding); } - + if (vec == null) { cipher.init(cipherMode, key); } else { @@ -257,10 +258,10 @@ public class CryptoFunctions { } catch (GeneralSecurityException e) { throw new EncryptedDocumentException(e); } - } - + } + /** - * Returns a new byte array with a truncated to the given size. + * Returns a new byte array with a truncated to the given size. * If the hash has less then size bytes, it will be filled with 0x36-bytes * * @param hash the to be truncated/filled hash byte array @@ -272,7 +273,7 @@ public class CryptoFunctions { } /** - * Returns a new byte array with a truncated to the given size. + * Returns a new byte array with a truncated to the given size. * If the hash has less then size bytes, it will be filled with 0-bytes * * @param hash the to be truncated/filled hash byte array @@ -282,16 +283,16 @@ public class CryptoFunctions { public static byte[] getBlock0(byte[] hash, int size) { return getBlockX(hash, size, (byte)0); } - + private static byte[] getBlockX(byte[] hash, int size, byte fill) { if (hash.length == size) return hash; - + byte[] result = IOUtils.safelyAllocate(size, MAX_RECORD_LENGTH); Arrays.fill(result, fill); System.arraycopy(hash, 0, result, 0, Math.min(result.length, hash.length)); return result; } - + public static MessageDigest getMessageDigest(HashAlgorithm hashAlgorithm) { try { if (hashAlgorithm.needsBouncyCastle) { @@ -304,7 +305,7 @@ public class CryptoFunctions { throw new EncryptedDocumentException("hash algo not supported", e); } } - + public static Mac getMac(HashAlgorithm hashAlgorithm) { try { if (hashAlgorithm.needsBouncyCastle) { @@ -323,7 +324,7 @@ public class CryptoFunctions { if (Security.getProvider("BC") != null) { return; } - + try { ClassLoader cl = CryptoFunctions.class.getClassLoader(); String bcProviderName = "org.bouncycastle.jce.provider.BouncyCastleProvider"; @@ -370,7 +371,7 @@ public class CryptoFunctions { * @see 2.3.7.1 Binary Document Password Verifier Derivation Method 1 * @see 2.3.7.4 Binary Document Password Verifier Derivation Method 2 * @see Part 4 - Markup Language Reference - Ecma International - 3.2.12 fileSharing - * + * * @param password the password * @return the verifier (actually a short value) */ @@ -380,7 +381,7 @@ public class CryptoFunctions { } byte[] arrByteChars = toAnsiPassword(password); - + // SET Verifier TO 0x0000 short verifier = 0; @@ -391,27 +392,27 @@ public class CryptoFunctions { verifier = rotateLeftBase15Bit(verifier); verifier ^= arrByteChars[i]; } - + // as we haven't prepended the password length into the input array // we need to do it now separately ... verifier = rotateLeftBase15Bit(verifier); verifier ^= arrByteChars.length; - + // RETURN Verifier BITWISE XOR 0xCE4B verifier ^= 0xCE4B; // (0x8000 | ('N' << 8) | 'K') } - + return verifier & 0xFFFF; } - + /** * This method generates the xor verifier for word documents < 2007 (method 2). * Its output will be used as password input for the newer word generations which * utilize a real hashing algorithm like sha1. - * + * * @param password the password * @return the hashed password - * + * * @see 2.3.7.4 Binary Document Password Verifier Derivation Method 2 * @see How to set the editing restrictions in Word using Open XML SDK 2.0 * @see Funny: How the new powerful cryptography implemented in Word 2007 turns it into a perfect tool for document password removal. @@ -425,42 +426,43 @@ public class CryptoFunctions { byte[] generatedKey = new byte[4]; //Maximum length of the password is 15 chars. - final int maxPasswordLength = 15; - + final int maxPasswordLength = 15; + if (!password.isEmpty()) { // Truncate the password to 15 characters password = password.substring(0, Math.min(password.length(), maxPasswordLength)); byte[] arrByteChars = toAnsiPassword(password); - + // Compute the high-order word of the new key: - // --> Initialize from the initial code array (see below), depending on the passwords length. + // --> Initialize from the initial code array (see below), depending on the passwords length. int highOrderWord = INITIAL_CODE_ARRAY[arrByteChars.length - 1]; // --> For each character in the password: - // --> For every bit in the character, starting with the least significant and progressing to (but excluding) - // the most significant, if the bit is set, XOR the keys high-order word with the corresponding word from + // --> For every bit in the character, starting with the least significant and progressing to (but excluding) + // the most significant, if the bit is set, XOR the keys high-order word with the corresponding word from // the Encryption Matrix - for (int i = 0; i < arrByteChars.length; i++) { - int tmp = maxPasswordLength - arrByteChars.length + i; - for (int intBit = 0; intBit < 7; intBit++) { - if ((arrByteChars[i] & (0x0001 << intBit)) != 0) { - highOrderWord ^= ENCRYPTION_MATRIX[tmp][intBit]; + int line = maxPasswordLength - arrByteChars.length; + for (byte ch : arrByteChars) { + for (int xor : ENCRYPTION_MATRIX[line++]) { + if ((ch & 1) == 1) { + highOrderWord ^= xor; } + ch >>>= 1; } } - + // Compute the low-order word of the new key: int verifier = createXorVerifier1(password); // The byte order of the result shall be reversed [password "Example": 0x64CEED7E becomes 7EEDCE64], // and that value shall be hashed as defined by the attribute values. - + LittleEndian.putShort(generatedKey, 0, (short)verifier); LittleEndian.putShort(generatedKey, 2, (short)highOrderWord); } - + return LittleEndian.getInt(generatedKey); } @@ -471,16 +473,16 @@ public class CryptoFunctions { int hashedPassword = createXorVerifier2(password); return String.format(Locale.ROOT, "%1$08X", hashedPassword); } - + /** - * Convenience function which returns the reversed xored-hashed password for further + * Convenience function which returns the reversed xored-hashed password for further * processing in word documents 2007 and newer, which utilize a real hashing algorithm like sha1. */ public static String xorHashPasswordReversed(String password) { int hashedPassword = createXorVerifier2(password); - + return String.format(Locale.ROOT, "%1$02X%2$02X%3$02X%4$02X" - , ( hashedPassword >>> 0 ) & 0xFF + , (hashedPassword) & 0xFF , ( hashedPassword >>> 8 ) & 0xFF , ( hashedPassword >>> 16 ) & 0xFF , ( hashedPassword >>> 24 ) & 0xFF @@ -492,7 +494,7 @@ public class CryptoFunctions { * * @see 2.3.7.2 Binary Document XOR Array Initialization Method 1 * @see 2.3.7.4 Binary Document Password Verifier Derivation Method 2 - * + * * @param password the password * @return the xor key */ @@ -503,7 +505,7 @@ public class CryptoFunctions { } /** - * Creates an byte array for xor obfuscation (method 1) + * Creates an byte array for xor obfuscation (method 1) * * @see 2.3.7.2 Binary Document XOR Array Initialization Method 1 * @see Libre Office implementation @@ -515,17 +517,17 @@ public class CryptoFunctions { if (password.length() > 15) { password = password.substring(0, 15); } - byte[] passBytes = password.getBytes(Charset.forName("ASCII")); - + byte[] passBytes = password.getBytes(StandardCharsets.US_ASCII); + // this code is based on the libre office implementation. - // The MS-OFFCRYPTO misses some infos about the various rotation sizes + // The MS-OFFCRYPTO misses some infos about the various rotation sizes byte[] obfuscationArray = new byte[16]; System.arraycopy(passBytes, 0, obfuscationArray, 0, passBytes.length); System.arraycopy(PAD_ARRAY, 0, obfuscationArray, passBytes.length, PAD_ARRAY.length-passBytes.length+1); - + int xorKey = createXorKey1(password); - - // rotation of key values is application dependent - Excel = 2 / Word = 7 + + // rotation of key values is application dependent - Excel = 2 / Word = 7 int nRotateSize = 2; byte[] baseKeyLE = {(byte) (xorKey & 0xFF), (byte) ((xorKey >>> 8) & 0xFF)}; @@ -533,26 +535,26 @@ public class CryptoFunctions { obfuscationArray[i] ^= baseKeyLE[i&1]; obfuscationArray[i] = rotateLeft(obfuscationArray[i], nRotateSize); } - + return obfuscationArray; } - + /** * The provided Unicode password string is converted to a ANSI string * * @param password the password * @return the ansi bytes - * + * * @see Part 4 - Markup Language Reference - Ecma International - section 3.2.29 (workbookProtection) */ private static byte[] toAnsiPassword(String password) { - // TODO: charset conversion (see ecma spec) - + // TODO: charset conversion (see ecma spec) + // Get the single-byte values by iterating through the Unicode characters. // For each character, if the low byte is not equal to 0, take it. // Otherwise, take the high byte. byte[] arrByteChars = new byte[password.length()]; - + for (int i = 0; i < password.length(); i++) { int intTemp = password.charAt(i); byte lowByte = (byte)(intTemp & 0xFF); @@ -562,11 +564,11 @@ public class CryptoFunctions { return arrByteChars; } - + private static byte rotateLeft(byte bits, int shift) { return (byte)(((bits & 0xff) << shift) | ((bits & 0xff) >>> (8 - shift))); } - + private static short rotateLeftBase15Bit(short verifier) { /* * IF (Verifier BITWISE AND 0x4000) is 0x0000 diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentInputStream.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentInputStream.java index 361578e967..c2391b1680 100644 --- a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentInputStream.java +++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentInputStream.java @@ -27,19 +27,19 @@ import org.apache.poi.util.Internal; /** * A seekable InputStream, which is used to decrypt/extract the document entries - * within the encrypted stream + * within the encrypted stream */ @Internal /* package */ class CryptoAPIDocumentInputStream extends ByteArrayInputStream { private Cipher cipher; private final CryptoAPIDecryptor decryptor; private byte[] oneByte = {0}; - + public void seek(int newpos) { if (newpos > count) { throw new ArrayIndexOutOfBoundsException(newpos); } - + this.pos = newpos; mark = newpos; } @@ -60,7 +60,7 @@ import org.apache.poi.util.Internal; } catch (ShortBufferException e) { throw new EncryptedDocumentException(e); } - return oneByte[0]; + return oneByte[0] & 0xFF; } @Override diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentOutputStream.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentOutputStream.java index fb6fb93e18..3a9cae9b04 100644 --- a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentOutputStream.java +++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDocumentOutputStream.java @@ -37,21 +37,21 @@ import org.apache.poi.util.Internal; this.encryptor = encryptor; cipher = encryptor.initCipherForBlock(null, 0); } - + public byte[] getBuf() { return buf; } - + public void setSize(int count) { this.count = count; } - + public void setBlock(int block) throws GeneralSecurityException { encryptor.initCipherForBlock(cipher, block); } - + @Override - public void write(int b) { + public synchronized void write(int b) { try { oneByte[0] = (byte)b; cipher.update(oneByte, 0, 1, oneByte, 0); @@ -62,7 +62,7 @@ import org.apache.poi.util.Internal; } @Override - public void write(byte[] b, int off, int len) { + public synchronized void write(byte[] b, int off, int len) { try { cipher.update(b, off, len, b, off); super.write(b, off, len); diff --git a/src/java/org/apache/poi/poifs/filesystem/DocumentInputStream.java b/src/java/org/apache/poi/poifs/filesystem/DocumentInputStream.java index e0ad436e2a..26769a94ea 100644 --- a/src/java/org/apache/poi/poifs/filesystem/DocumentInputStream.java +++ b/src/java/org/apache/poi/poifs/filesystem/DocumentInputStream.java @@ -68,9 +68,9 @@ public final class DocumentInputStream extends InputStream implements LittleEndi /** * Create an InputStream from the specified DocumentEntry - * + * * @param document the DocumentEntry to be read - * + * * @exception IOException if the DocumentEntry cannot be opened (like, maybe it has * been deleted?) */ @@ -91,7 +91,7 @@ public final class DocumentInputStream extends InputStream implements LittleEndi DocumentNode doc = (DocumentNode)document; DocumentProperty property = (DocumentProperty)doc.getProperty(); _document = new POIFSDocument( - property, + property, ((DirectoryNode)doc.getParent()).getFileSystem() ); _data = _document.getBlockIterator(); @@ -99,7 +99,7 @@ public final class DocumentInputStream extends InputStream implements LittleEndi /** * Create an InputStream from the specified Document - * + * * @param document the Document to be read */ public DocumentInputStream(POIFSDocument document) { @@ -146,7 +146,7 @@ public final class DocumentInputStream extends InputStream implements LittleEndi } @Override - public void mark(int ignoredReadlimit) { + public synchronized void mark(int ignoredReadlimit) { _marked_offset = _current_offset; _marked_offset_count = Math.max(0, _current_block_count - 1); } @@ -159,13 +159,7 @@ public final class DocumentInputStream extends InputStream implements LittleEndi } byte[] b = new byte[1]; int result = read(b, 0, 1); - if(result >= 0) { - if(b[0] < 0) { - return b[0]+256; - } - return b[0]; - } - return result; + return (result == EOF) ? EOF : (b[0] & 0xFF); } @Override @@ -199,7 +193,7 @@ public final class DocumentInputStream extends InputStream implements LittleEndi * method repositions the stream to its beginning. */ @Override - public void reset() { + public synchronized void reset() { // Special case for reset to the start if(_marked_offset == 0 && _marked_offset_count == 0) { _current_block_count = _marked_offset_count; @@ -216,15 +210,15 @@ public final class DocumentInputStream extends InputStream implements LittleEndi _buffer = _data.next(); _current_offset += _buffer.remaining(); } - + _current_block_count = _marked_offset_count; - + // Do we need to position within it? if(_current_offset != _marked_offset) { // Grab the right block _buffer = _data.next(); _current_block_count++; - + // Skip to the right place in it // (It should be positioned already at the start of the block, // we need to move further inside the block) @@ -250,9 +244,9 @@ public final class DocumentInputStream extends InputStream implements LittleEndi } else if (new_offset > _document_size) { new_offset = _document_size; } - + long rval = new_offset - _current_offset; - + // TODO Do this better byte[] skip = IOUtils.safelyAllocate(rval, Integer.MAX_VALUE); readFully(skip); @@ -298,7 +292,7 @@ public final class DocumentInputStream extends InputStream implements LittleEndi _current_block_count++; _buffer = _data.next(); } - + int limit = Math.min(len-read, _buffer.remaining()); _buffer.get(buf, off+read, limit); _current_offset += limit; diff --git a/src/java/org/apache/poi/sl/draw/DrawFactory.java b/src/java/org/apache/poi/sl/draw/DrawFactory.java index 6a032d6c6e..6b63274455 100644 --- a/src/java/org/apache/poi/sl/draw/DrawFactory.java +++ b/src/java/org/apache/poi/sl/draw/DrawFactory.java @@ -47,17 +47,21 @@ public class DrawFactory { * This is a fallback, for operations where usercode can't set a graphics context. * Preferably use the rendering hint {@link Drawable#DRAW_FACTORY} to set the factory. * - * @param factory the custom factory + * @param factory the custom factory or {@code null} to reset/remove the default factory */ @SuppressWarnings("unused") public static void setDefaultFactory(DrawFactory factory) { - defaultFactory.set(factory); + if (factory == null) { + defaultFactory.remove(); + } else { + defaultFactory.set(factory); + } } /** * Returns the DrawFactory, preferably via a graphics instance. * If graphics is null, the current thread local is checked or - * if it is not set, a new factory is created. + * if it is not set, a new factory is created. * * @param graphics the current graphics context or null * @return the draw factory @@ -112,7 +116,7 @@ public class DrawFactory { } else if (shape.getClass().isAnnotationPresent(DrawNotImplemented.class)) { return new DrawNothing(shape); } - + throw new IllegalArgumentException("Unsupported shape type: "+shape.getClass()); } @@ -139,11 +143,11 @@ public class DrawFactory { public DrawConnectorShape getDrawable(ConnectorShape shape) { return new DrawConnectorShape(shape); } - + public DrawTableShape getDrawable(TableShape shape) { return new DrawTableShape(shape); } - + public DrawTextShape getDrawable(TextShape shape) { return new DrawTextShape(shape); } @@ -151,15 +155,15 @@ public class DrawFactory { public DrawGroupShape getDrawable(GroupShape shape) { return new DrawGroupShape(shape); } - + public DrawPictureShape getDrawable(PictureShape shape) { return new DrawPictureShape(shape); } - + public DrawGraphicalFrame getDrawable(GraphicalFrame shape) { return new DrawGraphicalFrame(shape); } - + public DrawTextParagraph getDrawable(TextParagraph paragraph) { return new DrawTextParagraph(paragraph); } @@ -167,12 +171,12 @@ public class DrawFactory { public DrawBackground getDrawable(Background shape) { return new DrawBackground(shape); } - + @SuppressWarnings("WeakerAccess") public DrawTextFragment getTextFragment(TextLayout layout, AttributedString str) { return new DrawTextFragment(layout, str); } - + public DrawPaint getPaint(PlaceableShape shape) { return new DrawPaint(shape); } @@ -183,7 +187,7 @@ public class DrawFactory { * * @param graphics the graphics context to draw to * @param shape the shape - * @param bounds the bounds within the graphics context to draw to + * @param bounds the bounds within the graphics context to draw to */ public void drawShape(Graphics2D graphics, Shape shape, Rectangle2D bounds) { Rectangle2D shapeBounds = shape.getAnchor(); @@ -202,7 +206,7 @@ public class DrawFactory { tx.translate(-shapeBounds.getCenterX(), -shapeBounds.getCenterY()); } graphics.setRenderingHint(Drawable.GROUP_TRANSFORM, tx); - + Drawable d = getDrawable(shape); d.applyTransform(graphics); d.draw(graphics); @@ -210,7 +214,7 @@ public class DrawFactory { graphics.setRenderingHint(Drawable.GROUP_TRANSFORM, txg); } } - + /** * Return a FontManager, either registered beforehand or a default implementation diff --git a/src/java/org/apache/poi/sl/draw/DrawShape.java b/src/java/org/apache/poi/sl/draw/DrawShape.java index 67162ac4a8..28ac58b011 100644 --- a/src/java/org/apache/poi/sl/draw/DrawShape.java +++ b/src/java/org/apache/poi/sl/draw/DrawShape.java @@ -60,56 +60,52 @@ public class DrawShape implements Drawable { return; } - final PlaceableShape ps = (PlaceableShape)shape; - final boolean isHSLF = isHSLF(shape); + final Rectangle2D anchor = getAnchor(graphics, (PlaceableShape)shape); + if (anchor == null) { + return; + } - final Rectangle2D anchor = getAnchor(graphics, ps); - - char[] cmds = isHSLF ? new char[]{'h', 'v', 'r'} : new char[]{'r', 'h', 'v'}; - for (char ch : cmds) { - switch (ch) { - case 'h': - //flip horizontal - if (ps.getFlipHorizontal()) { - graphics.translate(anchor.getX() + anchor.getWidth(), anchor.getY()); - graphics.scale(-1, 1); - graphics.translate(-anchor.getX(), -anchor.getY()); - } - break; - case 'v': - //flip vertical - if (ps.getFlipVertical()) { - graphics.translate(anchor.getX(), anchor.getY() + anchor.getHeight()); - graphics.scale(1, -1); - graphics.translate(-anchor.getX(), -anchor.getY()); - } - break; - case 'r': - // rotation - double rotation = ps.getRotation(); - if (rotation != 0.) { - // PowerPoint rotates shapes relative to the geometric center - double centerX = anchor.getCenterX(); - double centerY = anchor.getCenterY(); - - - // transformation is applied reversed ... - graphics.translate(centerX, centerY); - graphics.rotate(Math.toRadians(rotation)); - graphics.translate(-centerX, -centerY); - } - break; - default: - throw new RuntimeException("unexpected transform code " + ch); - } + if (isHSLF(shape)) { + flipHorizontal(graphics, anchor); + flipVertical(graphics, anchor); + rotate(graphics, anchor); + } else { + rotate(graphics, anchor); + flipHorizontal(graphics, anchor); + flipVertical(graphics, anchor); } } - private static double safeScale(double dim1, double dim2) { - if (dim1 == 0.) { - return 1; + private void flipHorizontal(Graphics2D graphics, Rectangle2D anchor) { + assert(shape instanceof PlaceableShape && anchor != null); + if (((PlaceableShape)shape).getFlipHorizontal()) { + graphics.translate(anchor.getX() + anchor.getWidth(), anchor.getY()); + graphics.scale(-1, 1); + graphics.translate(-anchor.getX(), -anchor.getY()); } - return (dim2 == 0.) ? 1 : dim1/dim2; + } + + private void flipVertical(Graphics2D graphics, Rectangle2D anchor) { + assert(shape instanceof PlaceableShape && anchor != null); + if (((PlaceableShape)shape).getFlipVertical()) { + graphics.translate(anchor.getX(), anchor.getY() + anchor.getHeight()); + graphics.scale(1, -1); + graphics.translate(-anchor.getX(), -anchor.getY()); + } + } + + private void rotate(Graphics2D graphics, Rectangle2D anchor) { + assert(shape instanceof PlaceableShape && anchor != null); + double rotation = ((PlaceableShape)shape).getRotation(); + if (rotation != 0.) { + // PowerPoint rotates shapes relative to the geometric center + graphics.rotate(Math.toRadians(rotation), anchor.getCenterX(), anchor.getCenterY()); + } + + } + + private static double safeScale(double dim1, double dim2) { + return (dim1 == 0. || dim2 == 0.) ? 1 : dim1/dim2; } @Override @@ -160,15 +156,11 @@ public class DrawShape implements Drawable { // this handling is only based on try and error ... not sure why h/xslf is handled differently. if (!isHSLF) { - txs2.translate(centerX, centerY); - txs2.quadrantRotate(1); - txs2.translate(-centerX, -centerY); + txs2.quadrantRotate(1, centerX, centerY); txs2.concatenate(tx); } - txs2.translate(centerX, centerY); - txs2.quadrantRotate(3); - txs2.translate(-centerX, -centerY); + txs2.quadrantRotate(3, centerX, centerY); if (isHSLF) { txs2.concatenate(tx); diff --git a/src/java/org/apache/poi/ss/formula/functions/Delta.java b/src/java/org/apache/poi/ss/formula/functions/Delta.java index 0ad65c375e..c26ca2755c 100644 --- a/src/java/org/apache/poi/ss/formula/functions/Delta.java +++ b/src/java/org/apache/poi/ss/formula/functions/Delta.java @@ -18,9 +18,11 @@ package org.apache.poi.ss.formula.functions; import org.apache.poi.ss.formula.OperationEvaluationContext; -import org.apache.poi.ss.formula.eval.*; - -import java.math.BigDecimal; +import org.apache.poi.ss.formula.eval.ErrorEval; +import org.apache.poi.ss.formula.eval.EvaluationException; +import org.apache.poi.ss.formula.eval.NumberEval; +import org.apache.poi.ss.formula.eval.OperandResolver; +import org.apache.poi.ss.formula.eval.ValueEval; /** * Implementation for Excel DELTA() function.

@@ -46,33 +48,20 @@ public final class Delta extends Fixed2ArgFunction implements FreeRefFunction { private final static NumberEval ZERO = new NumberEval(0); public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg1, ValueEval arg2) { - ValueEval veText1; try { - veText1 = OperandResolver.getSingleValue(arg1, srcRowIndex, srcColumnIndex); - } catch (EvaluationException e) { - return e.getErrorEval(); - } - String strText1 = OperandResolver.coerceValueToString(veText1); - Double number1 = OperandResolver.parseDouble(strText1); - if (number1 == null) { - return ErrorEval.VALUE_INVALID; - } + Double number1 = evaluateValue(arg1, srcRowIndex, srcColumnIndex); + if (number1 == null) { + return ErrorEval.VALUE_INVALID; + } + Double number2 = evaluateValue(arg2, srcRowIndex, srcColumnIndex); + if (number2 == null) { + return ErrorEval.VALUE_INVALID; + } - ValueEval veText2; - try { - veText2 = OperandResolver.getSingleValue(arg2, srcRowIndex, srcColumnIndex); + return (number1.compareTo(number2) == 0) ? ONE : ZERO; } catch (EvaluationException e) { return e.getErrorEval(); } - - String strText2 = OperandResolver.coerceValueToString(veText2); - Double number2 = OperandResolver.parseDouble(strText2); - if (number2 == null) { - return ErrorEval.VALUE_INVALID; - } - - int result = new BigDecimal(number1.doubleValue()).compareTo(new BigDecimal(number2.doubleValue())); - return result == 0 ? ONE : ZERO; } public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) { @@ -82,4 +71,10 @@ public final class Delta extends Fixed2ArgFunction implements FreeRefFunction { return ErrorEval.VALUE_INVALID; } + + private static Double evaluateValue(ValueEval arg, int srcRowIndex, int srcColumnIndex) throws EvaluationException { + ValueEval veText = OperandResolver.getSingleValue(arg, srcRowIndex, srcColumnIndex); + String strText1 = OperandResolver.coerceValueToString(veText); + return OperandResolver.parseDouble(strText1); + } } \ No newline at end of file diff --git a/src/java/org/apache/poi/ss/formula/functions/Fixed.java b/src/java/org/apache/poi/ss/formula/functions/Fixed.java index e18bb062ea..364a05824c 100644 --- a/src/java/org/apache/poi/ss/formula/functions/Fixed.java +++ b/src/java/org/apache/poi/ss/formula/functions/Fixed.java @@ -64,7 +64,8 @@ public final class Fixed implements Function1Arg, Function2Arg, Function3Arg { } return ErrorEval.VALUE_INVALID; } - + + @SuppressWarnings("squid:S2111") private ValueEval fixed( ValueEval numberParam, ValueEval placesParam, ValueEval skipThousandsSeparatorParam, @@ -85,16 +86,16 @@ public final class Fixed implements Function1Arg, Function2Arg, Function3Arg { Boolean skipThousandsSeparator = OperandResolver.coerceValueToBoolean( skipThousandsSeparatorValueEval, false); - + // Round number to respective places. number = number.setScale(places, RoundingMode.HALF_UP); - + // Format number conditionally using a thousands separator. NumberFormat nf = NumberFormat.getNumberInstance(Locale.US); DecimalFormat formatter = (DecimalFormat)nf; formatter.setGroupingUsed(!(skipThousandsSeparator != null && skipThousandsSeparator)); - formatter.setMinimumFractionDigits(places >= 0 ? places : 0); - formatter.setMaximumFractionDigits(places >= 0 ? places : 0); + formatter.setMinimumFractionDigits(Math.max(places, 0)); + formatter.setMaximumFractionDigits(Math.max(places, 0)); String numberString = formatter.format(number.doubleValue()); // Return the result as a StringEval. diff --git a/src/java/org/apache/poi/ss/formula/functions/Irr.java b/src/java/org/apache/poi/ss/formula/functions/Irr.java index 5d91be9a74..7430335a0a 100644 --- a/src/java/org/apache/poi/ss/formula/functions/Irr.java +++ b/src/java/org/apache/poi/ss/formula/functions/Irr.java @@ -31,6 +31,8 @@ import org.apache.poi.ss.formula.eval.ValueEval; * @see Excel IRR */ public final class Irr implements Function { + private static final int MAX_ITERATION_COUNT = 20; + private static final double ABSOLUTE_ACCURACY = 1E-7; public ValueEval evaluate(final ValueEval[] args, final int srcRowIndex, final int srcColumnIndex) { @@ -89,27 +91,24 @@ public final class Irr implements Function { * http://en.wikipedia.org/wiki/Newton%27s_method */ public static double irr(double[] values, double guess) { - final int maxIterationCount = 20; - final double absoluteAccuracy = 1E-7; double x0 = guess; - double x1; - int i = 0; - while (i < maxIterationCount) { + for (int i = 0; i < MAX_ITERATION_COUNT; i++) { // the value of the function (NPV) and its derivate can be calculated in the same loop final double factor = 1.0 + x0; - int k = 0; - double fValue = values[k]; + double denominator = factor; + if (denominator == 0) { + return Double.NaN; + } + + double fValue = values[0]; double fDerivative = 0; - for (double denominator = factor; ++k < values.length; ) { + for (int k = 1; k < values.length; k++) { final double value = values[k]; fValue += value / denominator; denominator *= factor; - if (denominator == 0) { - return Double.NaN; - } fDerivative -= k * value / denominator; } @@ -117,14 +116,13 @@ public final class Irr implements Function { if (fDerivative == 0) { return Double.NaN; } - x1 = x0 - fValue/fDerivative; + double x1 = x0 - fValue/fDerivative; - if (Math.abs(x1 - x0) <= absoluteAccuracy) { + if (Math.abs(x1 - x0) <= ABSOLUTE_ACCURACY) { return x1; } x0 = x1; - ++i; } // maximum number of iterations is exceeded return Double.NaN; diff --git a/src/java/org/apache/poi/ss/usermodel/DataFormatter.java b/src/java/org/apache/poi/ss/usermodel/DataFormatter.java index 15bbef0fbe..19c0afaa7c 100644 --- a/src/java/org/apache/poi/ss/usermodel/DataFormatter.java +++ b/src/java/org/apache/poi/ss/usermodel/DataFormatter.java @@ -16,7 +16,7 @@ 2012 - Alfresco Software, Ltd. Alfresco Software has modified source of this file - The details of changes as svn diff can be found in svn at location root/projects/3rd-party/src + The details of changes as svn diff can be found in svn at location root/projects/3rd-party/src ==================================================================== */ package org.apache.poi.ss.usermodel; @@ -111,12 +111,12 @@ import org.apache.poi.util.POILogger; *

* Some formats are automatically "localized" by Excel, eg show as mm/dd/yyyy when * loaded in Excel in some Locales but as dd/mm/yyyy in others. These are always - * returned in the "default" (US) format, as stored in the file. - * Some format strings request an alternate locale, eg + * returned in the "default" (US) format, as stored in the file. + * Some format strings request an alternate locale, eg * [$-809]d/m/yy h:mm AM/PM which explicitly requests UK locale. * These locale directives are (currently) ignored. * You can use {@link DateFormatConverter} to do some of this localisation if - * you need it. + * you need it. */ public class DataFormatter implements Observer { private static final String defaultFractionWholePartFormat = "#"; @@ -129,13 +129,13 @@ public class DataFormatter implements Observer { /** Pattern to find "AM/PM" marker */ private static final Pattern amPmPattern = Pattern.compile("(([AP])[M/P]*)", Pattern.CASE_INSENSITIVE); - + /** Pattern to find formats with condition ranges e.g. [>=100] */ private static final Pattern rangeConditionalPattern = Pattern.compile(".*\\[\\s*(>|>=|<|<=|=)\\s*[0-9]*\\.*[0-9].*"); - /** + /** * A regex to find locale patterns like [$$-1009] and [$?-452]. - * Note that we don't currently process these into locales + * Note that we don't currently process these into locales */ private static final Pattern localePatternGroup = Pattern.compile("(\\[\\$[^-\\]]*-[0-9A-Z]+])"); @@ -144,14 +144,14 @@ public class DataFormatter implements Observer { * Allowed colours are: Black, Blue, Cyan, Green, * Magenta, Red, White, Yellow, "Color n" (1<=n<=56) */ - private static final Pattern colorPattern = + private static final Pattern colorPattern = Pattern.compile("(\\[BLACK])|(\\[BLUE])|(\\[CYAN])|(\\[GREEN])|" + "(\\[MAGENTA])|(\\[RED])|(\\[WHITE])|(\\[YELLOW])|" + "(\\[COLOR\\s*\\d])|(\\[COLOR\\s*[0-5]\\d])", Pattern.CASE_INSENSITIVE); /** * A regex to identify a fraction pattern. - * This requires that replaceAll("\\?", "#") has already been called + * This requires that replaceAll("\\?", "#") has already been called */ private static final Pattern fractionPattern = Pattern.compile("(?:([#\\d]+)\\s+)?(#+)\\s*/\\s*([#\\d]+)"); @@ -162,10 +162,10 @@ public class DataFormatter implements Observer { /** * A regex to detect if an alternate grouping character is used - * in a numeric format + * in a numeric format */ private static final Pattern alternateGrouping = Pattern.compile("([#0]([^.#0])[#0]{3})"); - + /** * Cells formatted with a date or time format and which contain invalid date or time values * show 255 pound signs ("#"). @@ -191,7 +191,7 @@ public class DataFormatter implements Observer { * A default date format, if no date format was given */ private DateFormat defaultDateformat; - + /** General format for numbers. */ private Format generalNumberFormat; @@ -208,10 +208,10 @@ public class DataFormatter implements Observer { /** stores the locale valid it the last formatting call */ private Locale locale; - + /** stores if the locale should change according to {@link LocaleUtil#getUserLocale()} */ private boolean localeIsAdapting; - + private class LocaleChangeObservable extends Observable { void checkForLocaleChange() { checkForLocaleChange(LocaleUtil.getUserLocale()); @@ -223,13 +223,13 @@ public class DataFormatter implements Observer { notifyObservers(newLocale); } } - + /** the Observable to notify, when the locale has been changed */ private final LocaleChangeObservable localeChangedObservable = new LocaleChangeObservable(); - + /** For logging any problems we find */ private static POILogger logger = POILogFactory.getLogger(DataFormatter.class); - + /** * Creates a formatter using the {@link Locale#getDefault() default locale}. */ @@ -281,7 +281,7 @@ public class DataFormatter implements Observer { /** * Return a Format for the given cell if one exists, otherwise try to - * create one. This method will return null if the any of the + * create one. This method will return null if any of the * following is true: *

    *
  • the cell's style is null
  • @@ -294,9 +294,9 @@ public class DataFormatter implements Observer { */ private Format getFormat(Cell cell, ConditionalFormattingEvaluator cfEvaluator) { if (cell == null) return null; - + ExcelNumberFormat numFmt = ExcelNumberFormat.from(cell, cfEvaluator); - + if ( numFmt == null) { return null; } @@ -316,25 +316,25 @@ public class DataFormatter implements Observer { } return false; } - + private Format getFormat(double cellValue, int formatIndex, String formatStrIn, boolean use1904Windowing) { localeChangedObservable.checkForLocaleChange(); // Might be better to separate out the n p and z formats, falling back to p when n and z are not set. // That however would require other code to be re factored. // String[] formatBits = formatStrIn.split(";"); - // int i = cellValue > 0.0 ? 0 : cellValue < 0.0 ? 1 : 2; + // int i = cellValue > 0.0 ? 0 : cellValue < 0.0 ? 1 : 2; // String formatStr = (i < formatBits.length) ? formatBits[i] : formatBits[0]; String formatStr = formatStrIn; - + // Excel supports 2+ part conditional data formats, eg positive/negative/zero, // or (>1000),(>0),(0),(negative). As Java doesn't handle these kinds - // of different formats for different ranges, just +ve/-ve, we need to + // of different formats for different ranges, just +ve/-ve, we need to // handle these ourselves in a special way. // For now, if we detect 2+ parts, we call out to CellFormat to handle it // TODO Going forward, we should really merge the logic between the two classes - if (formatStr.contains(";") && + if (formatStr.contains(";") && (formatStr.indexOf(';') != formatStr.lastIndexOf(';') || rangeConditionalPattern.matcher(formatStr).matches() ) ) { @@ -343,8 +343,8 @@ public class DataFormatter implements Observer { CellFormat cfmt = CellFormat.getInstance(locale, formatStr); // CellFormat requires callers to identify date vs not, so do so Object cellValueO = Double.valueOf(cellValue); - if (DateUtil.isADateFormat(formatIndex, formatStr) && - // don't try to handle Date value 0, let a 3 or 4-part format take care of it + if (DateUtil.isADateFormat(formatIndex, formatStr) && + // don't try to handle Date value 0, let a 3 or 4-part format take care of it ((Double)cellValueO).doubleValue() != 0.0) { cellValueO = DateUtil.getJavaDate(cellValue, use1904Windowing); } @@ -354,23 +354,23 @@ public class DataFormatter implements Observer { logger.log(POILogger.WARN, "Formatting failed for format " + formatStr + ", falling back", e); } } - + // Excel's # with value 0 will output empty where Java will output 0. This hack removes the # from the format. if (emulateCSV && cellValue == 0.0 && formatStr.contains("#") && !formatStr.contains("0")) { formatStr = formatStr.replaceAll("#", ""); } - + // See if we already have it cached Format format = formats.get(formatStr); if (format != null) { return format; } - + // Is it one of the special built in types, General or @? if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) { return generalNumberFormat; } - + // Build a formatter, and cache it format = createFormat(cellValue, formatIndex, formatStr); formats.put(formatStr, format); @@ -393,14 +393,14 @@ public class DataFormatter implements Observer { private Format createFormat(double cellValue, int formatIndex, String sFormat) { localeChangedObservable.checkForLocaleChange(); - + String formatStr = sFormat; - + // Remove colour formatting if present Matcher colourM = colorPattern.matcher(formatStr); while(colourM.find()) { String colour = colourM.group(); - + // Paranoid replacement... int at = formatStr.indexOf(colour); if(at == -1) break; @@ -431,7 +431,7 @@ public class DataFormatter implements Observer { if(formatStr == null || formatStr.trim().length() == 0) { return getDefaultFormat(cellValue); } - + if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) { return generalNumberFormat; } @@ -455,12 +455,12 @@ public class DataFormatter implements Observer { return new FractionFormat(wholePart, fractionMatcher.group(3)); } } - + // Strip custom text in quotes and escaped characters for now as it can cause performance problems in fractions. //String strippedFormatStr = formatStr.replaceAll("\\\\ ", " ").replaceAll("\\\\.", "").replaceAll("\"[^\"]*\"", " ").replaceAll("\\?", "#"); return new FractionFormat(defaultFractionWholePartFormat, defaultFractionFractionPartFormat); } - + if (numPattern.matcher(formatStr).find()) { return createNumberFormat(formatStr, cellValue); } @@ -471,8 +471,8 @@ public class DataFormatter implements Observer { // TODO - when does this occur? return null; } - - + + private Format createDateFormat(String pFormatStr, double cellValue) { String formatStr = pFormatStr; @@ -480,7 +480,7 @@ public class DataFormatter implements Observer { formatStr = formatStr.replaceAll("\\\\,",","); formatStr = formatStr.replaceAll("\\\\\\.","."); // . is a special regexp char formatStr = formatStr.replaceAll("\\\\ "," "); - formatStr = formatStr.replaceAll("\\\\/","/"); // weird: m\\/d\\/yyyy + formatStr = formatStr.replaceAll("\\\\/","/"); // weird: m\\/d\\/yyyy formatStr = formatStr.replaceAll(";@", ""); formatStr = formatStr.replaceAll("\"/\"", "/"); // "/" is escaped for no reason in: mm"/"dd"/"yyyy formatStr = formatStr.replace("\"\"", "'"); // replace Excel quoting with Java style quoting @@ -675,7 +675,7 @@ public class DataFormatter implements Observer { } } - // Now, handle the other aspects like + // Now, handle the other aspects like // quoting and scientific notation for(int i = 0; i < sb.length(); i++) { char c = sb.charAt(i); @@ -748,7 +748,7 @@ public class DataFormatter implements Observer { private Format createNumberFormat(String formatStr, double cellValue) { String format = cleanFormatForNumber(formatStr); DecimalFormatSymbols symbols = decimalSymbols; - + // Do we need to change the grouping character? // eg for a format like #'##0 which wants 12'345 not 12,345 Matcher agm = alternateGrouping.matcher(format); @@ -766,7 +766,7 @@ public class DataFormatter implements Observer { format = format.replace(oldPart, newPart); } } - + try { return new InternalDecimalFormatWithScale(format, symbols); } catch(IllegalArgumentException iae) { @@ -787,7 +787,7 @@ public class DataFormatter implements Observer { } private Format getDefaultFormat(double cellValue) { localeChangedObservable.checkForLocaleChange(); - + // for numeric cells try user supplied default if (defaultNumFormat != null) { return defaultNumFormat; @@ -826,6 +826,16 @@ public class DataFormatter implements Observer { return null; } Format dateFormat = getFormat(cell, cfEvaluator); + if (dateFormat == null) { + if (defaultDateformat == null) { + DateFormatSymbols sym = DateFormatSymbols.getInstance(LocaleUtil.getUserLocale()); + SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", sym); + sdf.setTimeZone(LocaleUtil.getUserTimeZone()); + dateFormat = sdf; + } else { + dateFormat = defaultNumFormat; + } + } synchronized (dateFormat) { if(dateFormat instanceof ExcelStyleDateFormatter) { // Hint about the raw excel value @@ -846,7 +856,7 @@ public class DataFormatter implements Observer { *

    * Format comes from either the highest priority conditional format rule with a * specified format, or from the cell style. - * + * * @param cell The cell * @param cfEvaluator if available, or null * @return a formatted number string @@ -879,7 +889,7 @@ public class DataFormatter implements Observer { */ public String formatRawCellContents(double value, int formatIndex, String formatString, boolean use1904Windowing) { localeChangedObservable.checkForLocaleChange(); - + // Is it a date? if(DateUtil.isADateFormat(formatIndex,formatString)) { if(DateUtil.isValidExcelDate(value)) { @@ -896,13 +906,13 @@ public class DataFormatter implements Observer { return invalidDateTimeString; } } - + // else Number Format numberFormat = getFormat(value, formatIndex, formatString, use1904Windowing); if (numberFormat == null) { return String.valueOf(value); } - + // When formatting 'value', double to text to BigDecimal produces more // accurate results than double to Double in JDK8 (as compared to // previous versions). However, if the value contains E notation, this @@ -960,7 +970,7 @@ public class DataFormatter implements Observer { public String formatCellValue(Cell cell, FormulaEvaluator evaluator) { return formatCellValue(cell, evaluator, null); } - + /** *

    * Returns the formatted value of a cell as a String regardless @@ -990,7 +1000,7 @@ public class DataFormatter implements Observer { */ public String formatCellValue(Cell cell, FormulaEvaluator evaluator, ConditionalFormattingEvaluator cfEvaluator) { localeChangedObservable.checkForLocaleChange(); - + if (cell == null) { return ""; } @@ -1077,9 +1087,9 @@ public class DataFormatter implements Observer { result.setParseIntegerOnly(true); return result; } - + /** - * Enables excel style rounding mode (round half up) on the + * Enables excel style rounding mode (round half up) on the * Decimal Format given. */ public static void setExcelStyleRoundingMode(DecimalFormat format) { @@ -1119,16 +1129,16 @@ public class DataFormatter implements Observer { if (!(localeObj instanceof Locale)) return; Locale newLocale = (Locale)localeObj; if (!localeIsAdapting || newLocale.equals(locale)) return; - + locale = newLocale; - + dateSymbols = DateFormatSymbols.getInstance(locale); decimalSymbols = DecimalFormatSymbols.getInstance(locale); generalNumberFormat = new ExcelGeneralNumberFormat(locale); // taken from Date.toString() defaultDateformat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", dateSymbols); - defaultDateformat.setTimeZone(LocaleUtil.getUserTimeZone()); + defaultDateformat.setTimeZone(LocaleUtil.getUserTimeZone()); // init built-in formats @@ -1262,10 +1272,10 @@ public class DataFormatter implements Observer { return df.parseObject(source, pos); } } - - - + + + /** * Format class that does nothing and always returns a constant string. * @@ -1294,7 +1304,7 @@ public class DataFormatter implements Observer { } /** * Workaround until we merge {@link DataFormatter} with {@link CellFormat}. - * Constant, non-cachable wrapper around a {@link CellFormatResult} + * Constant, non-cachable wrapper around a {@link CellFormatResult} */ @SuppressWarnings("serial") private final class CellFormatResultWrapper extends Format { diff --git a/src/java/org/apache/poi/ss/usermodel/ExcelGeneralNumberFormat.java b/src/java/org/apache/poi/ss/usermodel/ExcelGeneralNumberFormat.java index 2efbb3ff03..891c63e8d1 100644 --- a/src/java/org/apache/poi/ss/usermodel/ExcelGeneralNumberFormat.java +++ b/src/java/org/apache/poi/ss/usermodel/ExcelGeneralNumberFormat.java @@ -16,7 +16,7 @@ 2012 - Alfresco Software, Ltd. Alfresco Software has modified source of this file - The details of changes as svn diff can be found in svn at location root/projects/3rd-party/src + The details of changes as svn diff can be found in svn at location root/projects/3rd-party/src ==================================================================== */ package org.apache.poi.ss.usermodel; @@ -55,6 +55,7 @@ public class ExcelGeneralNumberFormat extends Format { DataFormatter.setExcelStyleRoundingMode(decimalFormat); } + @SuppressWarnings("squid:S2111") public StringBuffer format(Object number, StringBuffer toAppendTo, FieldPosition pos) { final double value; if (number instanceof Number) { diff --git a/src/java/org/apache/poi/ss/usermodel/FractionFormat.java b/src/java/org/apache/poi/ss/usermodel/FractionFormat.java index 66f5e7c535..eb6fa7bf6b 100644 --- a/src/java/org/apache/poi/ss/usermodel/FractionFormat.java +++ b/src/java/org/apache/poi/ss/usermodel/FractionFormat.java @@ -31,11 +31,11 @@ import org.apache.poi.util.POILogger; /** *

    Format class that handles Excel style fractions, such as "# #/#" and "#/###"

    - * + * *

    As of this writing, this is still not 100% accurate, but it does a reasonable job * of trying to mimic Excel's fraction calculations. It does not currently * maintain Excel's spacing.

    - * + * *

    This class relies on a method lifted nearly verbatim from org.apache.math.fraction. * If further uses for Commons Math are found, we will consider adding it as a dependency. * For now, we have in-lined the one method to keep things simple.

    @@ -43,7 +43,7 @@ import org.apache.poi.util.POILogger; @SuppressWarnings("serial") public class FractionFormat extends Format { - private static final POILogger LOGGER = POILogFactory.getLogger(FractionFormat.class); + private static final POILogger LOGGER = POILogFactory.getLogger(FractionFormat.class); private static final Pattern DENOM_FORMAT_PATTERN = Pattern.compile("(?:(#+)|(\\d+))"); //this was chosen to match the earlier limitation of max denom power @@ -78,7 +78,7 @@ public class FractionFormat extends Format { try{ tmpExact = Integer.parseInt(m.group(2)); //if the denom is 0, fall back to the default: tmpExact=100 - + if (tmpExact == 0){ tmpExact = -1; } @@ -104,10 +104,11 @@ public class FractionFormat extends Format { maxDenom = tmpMax; } + @SuppressWarnings("squid:S2111") public String format(Number num) { final BigDecimal doubleValue = new BigDecimal(num.doubleValue()); - + final boolean isNeg = doubleValue.compareTo(BigDecimal.ZERO) < 0; final BigDecimal absValue = doubleValue.abs(); @@ -117,7 +118,7 @@ public class FractionFormat extends Format { if (wholePart.add(decPart).compareTo(BigDecimal.ZERO) == 0) { return "0"; } - + // if the absolute value is smaller than 1 over the exact or maxDenom // you can stop here and return "0" // reciprocal is result of an int devision ... and so it's nearly always 0 @@ -125,10 +126,10 @@ public class FractionFormat extends Format { // if (absDoubleValue < reciprocal) { // return "0"; // } - + //this is necessary to prevent overflow in the maxDenom calculation if (decPart.compareTo(BigDecimal.ZERO) == 0){ - + StringBuilder sb = new StringBuilder(); if (isNeg){ sb.append("-"); @@ -136,7 +137,7 @@ public class FractionFormat extends Format { sb.append(wholePart); return sb.toString(); } - + final SimpleFraction fract; try { //this should be the case because of the constructor @@ -151,12 +152,12 @@ public class FractionFormat extends Format { } StringBuilder sb = new StringBuilder(); - + //now format the results if (isNeg){ sb.append("-"); } - + //if whole part has to go into the numerator if (wholePartFormatString == null || wholePartFormatString.isEmpty()){ final int fden = fract.getDenominator(); @@ -165,8 +166,8 @@ public class FractionFormat extends Format { sb.append(trueNum.toBigInteger()).append("/").append(fden); return sb.toString(); } - - + + //short circuit if fraction is 0 or 1 if (fract.getNumerator() == 0){ sb.append(wholePart); @@ -190,5 +191,5 @@ public class FractionFormat extends Format { public Object parseObject(String source, ParsePosition pos) { throw new NotImplementedException("Reverse parsing not supported"); } - + } -- cgit v1.2.3