diff options
84 files changed, 1009 insertions, 244 deletions
diff --git a/build.gradle b/build.gradle index cf5f0d7222..d3f3d5a490 100644 --- a/build.gradle +++ b/build.gradle @@ -34,10 +34,10 @@ plugins { id 'com.dorongold.task-tree' version '4.0.1' id 'org.nosphere.apache.rat' version '0.8.1' id 'distribution' - id "com.github.spotbugs" version '6.1.10' + id 'com.github.spotbugs' version '6.1.13' // 6.2.0+ requires JDK 11 id 'de.thetaphi.forbiddenapis' version '3.9' id 'org.sonarqube' version '4.0.0.2929' - id 'org.cyclonedx.bom' version '2.3.0' + id 'org.cyclonedx.bom' version '2.3.1' id 'com.adarshr.test-logger' version '3.2.0' } @@ -67,7 +67,7 @@ configurations { } dependencies { - antLibs("org.junit.jupiter:junit-jupiter:5.12.2") + antLibs("org.junit.jupiter:junit-jupiter:5.13.2") antLibs("org.apache.ant:ant-junitlauncher:1.10.15") } @@ -77,7 +77,7 @@ ant.taskdef(name: "junit", wrapper { - gradleVersion = '8.14' + gradleVersion = '8.14.2' } group = 'org.apache.poi' @@ -107,12 +107,12 @@ subprojects { apply plugin: 'com.adarshr.test-logger' ext { - bouncyCastleVersion = '1.80' + bouncyCastleVersion = '1.81' commonsCodecVersion = '1.18.0' commonsCompressVersion = '1.27.1' commonsIoVersion = '2.19.0' commonsMathVersion = '3.6.1' - junitVersion = '5.12.2' + junitVersion = '5.13.2' log4jVersion = '2.24.3' mockitoVersion = '4.11.0' hamcrestVersion = '3.0' @@ -120,8 +120,8 @@ subprojects { batikVersion = '1.19' graphics2dVersion = '3.0.3' pdfboxVersion = '3.0.5' - saxonVersion = '12.5' - xmlSecVersion = '3.0.5' + saxonVersion = '12.7' + xmlSecVersion = '3.0.6' apiGuardianVersion = '1.1.2' jdkVersion = (project.properties['jdkVersion'] ?: '8') as int @@ -143,7 +143,7 @@ subprojects { resolutionStrategy { force "commons-io:commons-io:${commonsIoVersion}" force 'org.slf4j:slf4j-api:2.0.17' - force 'com.fasterxml.woodstox:woodstox-core:7.1.0' + force 'com.fasterxml.woodstox:woodstox-core:7.1.1' } } } @@ -171,8 +171,9 @@ subprojects { } dependencies { - testImplementation "org.junit.jupiter:junit-jupiter:${junitVersion}" - testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.12.2' + testImplementation platform("org.junit:junit-bom:${junitVersion}") + testImplementation 'org.junit.jupiter:junit-jupiter' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testImplementation "org.mockito:mockito-core:${mockitoVersion}" testImplementation "org.hamcrest:hamcrest:${hamcrestVersion}" testImplementation "org.apache.logging.log4j:log4j-core:${log4jVersion}" @@ -422,7 +423,7 @@ subprojects { jvmArgs += [ // see https://github.com/java9-modularity/gradle-modules-plugin/issues/97 // opposed to the recommendation there, it doesn't work to add ... to the dependencies - // testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.12.2' + // testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.13.2' // gradles gradle-worker.jar is still not a JPMS module and thus runs as unnamed module '--add-exports','org.junit.platform.commons/org.junit.platform.commons.util=org.apache.poi.poi', '--add-exports','org.junit.platform.commons/org.junit.platform.commons.util=ALL-UNNAMED', @@ -444,7 +445,7 @@ subprojects { } jacoco { - toolVersion = '0.8.12' + toolVersion = '0.8.13' } jacocoTestReport { @@ -270,20 +270,20 @@ under the License. <dependency prefix="main.com.zaxxer" artifact="com.zaxxer:SparseBitSet:1.3" usage="main"/> <dependency prefix="main.log4j-api" artifact="org.apache.logging.log4j:log4j-api:2.24.3" usage="main"/> - <dependency prefix="main.junit-api" artifact="org.junit.jupiter:junit-jupiter-api:5.12.2" usage="main-tests"/> - <dependency prefix="main.junit-jengine" artifact="org.junit.jupiter:junit-jupiter-engine:5.12.2" usage="main-tests"/> - <dependency prefix="main.junit-params" artifact="org.junit.jupiter:junit-jupiter-params:5.12.2" usage="main-tests"/> + <dependency prefix="main.junit-api" artifact="org.junit.jupiter:junit-jupiter-api:5.13.2" usage="main-tests"/> + <dependency prefix="main.junit-jengine" artifact="org.junit.jupiter:junit-jupiter-engine:5.13.2" usage="main-tests"/> + <dependency prefix="main.junit-params" artifact="org.junit.jupiter:junit-jupiter-params:5.13.2" usage="main-tests"/> <dependency prefix="main.junit-opentest4j" artifact="org.opentest4j:opentest4j:1.2.0" usage="main-tests"/> <dependency prefix="main.junit-apiguardian" artifact="org.apiguardian:apiguardian-api:1.1.2" usage="main-tests"/> - <dependency prefix="main.junit-pcommons" artifact="org.junit.platform:junit-platform-commons:1.12.2" usage="main-tests"/> - <dependency prefix="main.junit-pengine" artifact="org.junit.platform:junit-platform-engine:1.12.2" usage="main-tests"/> - <dependency prefix="main.junit-plauncher" artifact="org.junit.platform:junit-platform-launcher:1.12.2" usage="main-tests"/> + <dependency prefix="main.junit-pcommons" artifact="org.junit.platform:junit-platform-commons:1.13.2" usage="main-tests"/> + <dependency prefix="main.junit-pengine" artifact="org.junit.platform:junit-platform-engine:1.13.2" usage="main-tests"/> + <dependency prefix="main.junit-plauncher" artifact="org.junit.platform:junit-platform-launcher:1.13.2" usage="main-tests"/> <dependency prefix="main.jmh" artifact="org.openjdk.jmh:jmh-core:1.35" usage="main-tests"/> <dependency prefix="main.jmhAnnotation" artifact="org.openjdk.jmh:jmh-generator-annprocess:1.35" usage="main-tests"/> <dependency prefix="main.hamcrest" artifact="org.hamcrest:hamcrest:3.0" usage="main-tests"/> - <dependency prefix="main.xmlunit" artifact="org.xmlunit:xmlunit-core:2.10.0" usage="main-tests"/> + <dependency prefix="main.xmlunit" artifact="org.xmlunit:xmlunit-core:2.10.3" usage="main-tests"/> <dependency prefix="main.mockito" artifact="org.mockito:mockito-core:4.11.0" usage="main-tests"/> <dependency prefix="main.byte-buddy" artifact="net.bytebuddy:byte-buddy:1.17.5" usage="main-tests"/> <dependency prefix="main.byte-buddy-agent" artifact="net.bytebuddy:byte-buddy-agent:1.17.5" usage="main-tests"/> @@ -295,17 +295,17 @@ under the License. <dependency prefix="main.antlauncher" artifact="org.apache.ant:ant-launcher:1.10.15" usage="excelant"/> <!-- xml signature libs - not part of the distribution --> - <dependency prefix="dsig.xmlsec" artifact="org.apache.santuario:xmlsec:3.0.5" usage="ooxml-provided"/> - <dependency prefix="dsig.bouncycastle-prov" artifact="org.bouncycastle:bcprov-jdk18on:1.80" usage="ooxml-provided"/> - <dependency prefix="dsig.bouncycastle-pkix" artifact="org.bouncycastle:bcpkix-jdk18on:1.80" usage="ooxml-provided"/> - <dependency prefix="dsig.bouncycastle-util" artifact="org.bouncycastle:bcutil-jdk18on:1.80" usage="ooxml-provided"/> + <dependency prefix="dsig.xmlsec" artifact="org.apache.santuario:xmlsec:3.0.6" usage="ooxml-provided"/> + <dependency prefix="dsig.bouncycastle-prov" artifact="org.bouncycastle:bcprov-jdk18on:1.81" usage="ooxml-provided"/> + <dependency prefix="dsig.bouncycastle-pkix" artifact="org.bouncycastle:bcpkix-jdk18on:1.81" usage="ooxml-provided"/> + <dependency prefix="dsig.bouncycastle-util" artifact="org.bouncycastle:bcutil-jdk18on:1.81" usage="ooxml-provided"/> <!-- only used for signing the release - not used with the ooxml signatures --> - <dependency prefix="dsig.bouncycastle-bcpg" artifact="org.bouncycastle:bcpg-jdk18on:1.80" usage="util"/> + <dependency prefix="dsig.bouncycastle-bcpg" artifact="org.bouncycastle:bcpg-jdk18on:1.81" usage="util"/> <dependency prefix="ooxml.test.stax2" artifact="org.codehaus.woodstox:stax2-api:4.2.1" usage="ooxml-provided"/> <!-- svg/batik/pdf libs - not part of the distribution - move batik to its own directory because of JPMS module-path issues --> <dependency prefix="svg.xml-apis-ext" artifact="xml-apis:xml-apis-ext:1.3.04" usage="ooxml-batik"/> - <dependency prefix="svg.xmlgraphics-commons" artifact="org.apache.xmlgraphics:xmlgraphics-commons:2.9" usage="ooxml-batik"/> + <dependency prefix="svg.xmlgraphics-commons" artifact="org.apache.xmlgraphics:xmlgraphics-commons:2.11" usage="ooxml-batik"/> <dependency prefix="svg.batik-anim" artifact="org.apache.xmlgraphics:batik-anim:1.19" usage="ooxml-batik"/> <dependency prefix="svg.batik-awt-util" artifact="org.apache.xmlgraphics:batik-awt-util:1.19" usage="ooxml-batik"/> <dependency prefix="svg.batik-bridge" artifact="org.apache.xmlgraphics:batik-bridge:1.19" usage="ooxml-batik"/> @@ -338,13 +338,14 @@ under the License. <!-- jars in the ooxml-test-lib directory, see the fetch-ooxml-jars target--> <dependency prefix="ooxml.test.reflections" artifact="org.reflections:reflections:0.10.2" usage="ooxml-tests"/> - <dependency prefix="ooxml.test.guava" artifact="com.google.guava:guava:33.3.0-jre" usage="ooxml-tests"/> + <dependency prefix="ooxml.test.guava" artifact="com.google.guava:guava:33.4.8-jre" usage="ooxml-tests"/> + <dependency prefix="ooxml.test.guava.failureaccess" artifact="com.google.guava:failureaccess:1.0.3" usage="ooxml-tests"/> <dependency prefix="ooxml.test.javassist" artifact="org.javassist:javassist:3.27.0-GA" usage="ooxml-tests"/> <dependency prefix="ooxml.test.slf4j-api" artifact="org.slf4j:slf4j-api:2.0.17" usage="ooxml-tests"/> <dependency prefix="ooxml.test.opczip" artifact="com.github.rzymek:opczip:1.2.0" usage="ooxml-tests"/> <!-- coverage libs --> - <dependency prefix="jacoco" artifact="org.jacoco:jacoco:0.8.12" usage="util" packaging="zip"/> + <dependency prefix="jacoco" artifact="org.jacoco:jacoco:0.8.13" usage="util" packaging="zip"/> <dependency prefix="asm" artifact="org.ow2.asm:asm:9.5" usage="util"/> <dependency prefix="asm-commons" artifact="org.ow2.asm:asm-commons:9.5" usage="util"/> <dependency prefix="asm-tree" artifact="org.ow2.asm:asm-tree:9.5" usage="util"/> @@ -463,6 +464,7 @@ under the License. <path refid="main.classpath"/> <pathelement location="${main.output.dir}"/> <pathelement location="${ooxml.test.guava.jar}"/> + <pathelement location="${ooxml.test.guava.failureaccess.jar}"/> <pathelement location="${ooxml.test.opczip.jar}"/> <!-- classes are omitted on test cases outside the xml-dsign area to avoid classpath poisoning --> <!--path refid="ooxml.xmlsec.classpath"/--> @@ -516,6 +518,7 @@ under the License. <path id="test.ooxml.reflections.classpath"> <pathelement location="${ooxml.test.reflections.jar}"/> <pathelement location="${ooxml.test.guava.jar}"/> + <pathelement location="${ooxml.test.guava.failureaccess.jar}"/> <pathelement location="${ooxml.test.javassist.jar}"/> </path> @@ -834,6 +837,7 @@ under the License. <available file="${ooxml.commons-lang3.jar}"/> <available file="${ooxml.test.reflections.jar}"/> <available file="${ooxml.test.guava.jar}"/> + <available file="${ooxml.test.guava.failureaccess.jar}"/> <available file="${ooxml.test.javassist.jar}"/> <available file="${ooxml.test.slf4j-api.jar}"/> <available file="${ooxml.test.opczip.jar}"/> @@ -877,6 +881,7 @@ under the License. <downloadfile src="${ooxml.commons-lang3.url}" dest="${ooxml.commons-lang3.jar}"/> <downloadfile src="${ooxml.test.reflections.url}" dest="${ooxml.test.reflections.jar}"/> <downloadfile src="${ooxml.test.guava.url}" dest="${ooxml.test.guava.jar}"/> + <downloadfile src="${ooxml.test.guava.failureaccess.url}" dest="${ooxml.test.guava.failureaccess.jar}"/> <downloadfile src="${ooxml.test.javassist.url}" dest="${ooxml.test.javassist.jar}"/> <downloadfile src="${ooxml.test.slf4j-api.url}" dest="${ooxml.test.slf4j-api.jar}"/> <downloadfile src="${ooxml.test.opczip.url}" dest="${ooxml.test.opczip.jar}"/> diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ca025c83a7..ff23a68d70 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/jenkins/create_jobs.groovy b/jenkins/create_jobs.groovy index 6522e32de9..d112999d2c 100644 --- a/jenkins/create_jobs.groovy +++ b/jenkins/create_jobs.groovy @@ -35,10 +35,13 @@ def poijobs = [ disabled: true ], [ name: 'POI-DSL-1.23', jdk: '1.23', trigger: triggerSundays, skipcigame: true, + // let's save some CPU cycles here, 22 is not an LTS and JDK 23/24 is out + disabled: true ], - // Jenkins on ci-builds.apache.org does not support spotbugs with a new enough version of asm for Java24+ - [ name: 'POI-DSL-1.24', jdk: '1.24', trigger: triggerSundays, skipcigame: true, skipSpotbugs: true, - // use Ant for building until Gradle supports JDK 24 + [ name: 'POI-DSL-1.24', jdk: '1.24', trigger: triggerSundays, skipcigame: true, + ], + [ name: 'POI-DSL-1.25', jdk: '1.25', trigger: triggerSundays, skipcigame: true, skipSpotbugs: true, + // use Ant for building until Gradle supports JDK 25 // see https://docs.gradle.org/current/userguide/compatibility.html // and https://github.com/gradle/gradle/issues/31625 useAnt: true @@ -87,9 +90,14 @@ def poijobs = [ // let's save some CPU cycles here, 22 is not an LTS and JDK 23/24 is out disabled: true ], - [ name: 'POI-DSL-Windows-1.23', jdk: '1.23', trigger: triggerSundays, windows: true, slaves: 'Windows', skipcigame: true + [ name: 'POI-DSL-Windows-1.23', jdk: '1.23', trigger: triggerSundays, windows: true, slaves: 'Windows', skipcigame: true, + // let's save some CPU cycles here, 22 is not an LTS and JDK 23/24 is out + disabled: true ], [ name: 'POI-DSL-Windows-1.24', jdk: '1.24', trigger: triggerSundays, windows: true, slaves: 'Windows', skipcigame: true, + ], + [ name: 'POI-DSL-Windows-1.25', jdk: '1.25', trigger: triggerSundays, windows: true, slaves: 'Windows', skipcigame: true, + skipSpotbugs: true, // use Ant for building until Gradle supports JDK 24 // see https://docs.gradle.org/current/userguide/compatibility.html // and https://github.com/gradle/gradle/issues/31625 @@ -116,6 +124,8 @@ def xmlbeansjobs = [ ], [ name: 'POI-XMLBeans-DSL-1.24', jdk: '1.24', trigger: triggerSundays, skipcigame: true, ], + [ name: 'POI-XMLBeans-DSL-1.25', jdk: '1.25', trigger: triggerSundays, skipcigame: true, + ], [ name: 'POI-XMLBeans-DSL-Sonar', jdk: '1.17', trigger: triggerSundays, skipcigame: true, sonar: true ] @@ -144,6 +154,7 @@ def jdkMapping = [ '1.22': [ jenkinsJdk: 'jdk_22_latest', jdkVersion: 22, jdkVendor: '' ], '1.23': [ jenkinsJdk: 'jdk_23_latest', jdkVersion: 23, jdkVendor: '' ], '1.24': [ jenkinsJdk: 'jdk_24_latest', jdkVersion: 24, jdkVendor: '' ], + '1.25': [ jenkinsJdk: 'jdk_25_latest', jdkVersion: 25, jdkVendor: '' ], 'OpenJDK 1.8': [ jenkinsJdk: 'adoptopenjdk_hotspot_8u282', jdkVersion: 8, jdkVendor: 'adoptopenjdk' ], 'IBMJDK': [ jenkinsJdk: 'ibmjdk_1.8.0_261', jdkVersion: 8, jdkVendor: 'ibm' ] ] @@ -612,6 +623,8 @@ Unfortunately we often see builds break because of changes/new machines...''') 'jdk_22_latest', 'jdk_23_latest', 'jdk_24_latest', + 'jdk_25_latest', + 'jdk_26_latest', 'adoptopenjdk_hotspot_8u282', 'ibmjdk_1.8.0_261' ) diff --git a/osgi/README.md b/osgi/README.md index 00a93ada8a..d6042126b6 100644 --- a/osgi/README.md +++ b/osgi/README.md @@ -25,11 +25,11 @@ Available in Maven Central: https://mvnrepository.com/artifact/net.sf.saxon/Saxo 3. Apache XML Security for Java, Bouncy Castle and XML Commons Resolver These are required to sign or validate signed Office documents. The OSGi bundles are available in Maven Central: - - Apache XML Security for Java: https://mvnrepository.com/artifact/org.apache.santuario/xmlsec/3.0.5 + - Apache XML Security for Java: https://mvnrepository.com/artifact/org.apache.santuario/xmlsec/3.0.6 - XML Commons Resolver: https://mvnrepository.com/artifact/xml-resolver/xml-resolver/1.2-osgi - - Bouncy Castle: https://mvnrepository.com/artifact/org.bouncycastle/bcprov-ext-jdk18on/1.77, https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk18on/1.77 + - Bouncy Castle: https://mvnrepository.com/artifact/org.bouncycastle/bcprov-ext-jdk18on/1.81, https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk18on/1.81 4. PDFBox and PDFBox Graphics2D Required to render to PDF documents. The required jars can be downloaded from: diff --git a/poi-examples/src/main/java/org/apache/poi/examples/hwpf/Word2Forrest.java b/poi-examples/src/main/java/org/apache/poi/examples/hwpf/Word2Forrest.java index 937da16a33..6547d2e8cc 100644 --- a/poi-examples/src/main/java/org/apache/poi/examples/hwpf/Word2Forrest.java +++ b/poi-examples/src/main/java/org/apache/poi/examples/hwpf/Word2Forrest.java @@ -59,7 +59,7 @@ public final class Word2Forrest { Paragraph p = r.getParagraph (x); String text = p.text (); - if (text.trim ().length () == 0) + if (text.trim().isEmpty()) { continue; } diff --git a/poi-examples/src/main/java/org/apache/poi/examples/ss/ConditionalFormats.java b/poi-examples/src/main/java/org/apache/poi/examples/ss/ConditionalFormats.java index a0a8177e87..5e4d96f0c5 100644 --- a/poi-examples/src/main/java/org/apache/poi/examples/ss/ConditionalFormats.java +++ b/poi-examples/src/main/java/org/apache/poi/examples/ss/ConditionalFormats.java @@ -203,7 +203,7 @@ public final class ConditionalFormats { if (rn%10 == 0) { str = str + "x10 "; } - if (str.length() == 0) { + if (str.isEmpty()) { str = "nothing special..."; } r.createCell(1).setCellValue("It is " + str); diff --git a/poi-excelant/build.gradle b/poi-excelant/build.gradle index 2d52734a2e..a82cc6d4cf 100644 --- a/poi-excelant/build.gradle +++ b/poi-excelant/build.gradle @@ -41,7 +41,7 @@ dependencies { testImplementation(project(path: ':poi-ooxml', configuration: 'tests')) { exclude group: 'org.apache.poi', module: 'poi-scratchpad' } - testImplementation 'com.google.guava:guava:33.3.0-jre' + testImplementation 'com.google.guava:guava:33.4.8-jre' testImplementation "org.apache.logging.log4j:log4j-slf4j2-impl:${log4jVersion}" testImplementation 'org.slf4j:slf4j-simple:2.0.17' testRuntimeOnly "org.apiguardian:apiguardian-api:${apiGuardianVersion}" diff --git a/poi-integration/build.gradle b/poi-integration/build.gradle index e36681a723..35e588e3cc 100644 --- a/poi-integration/build.gradle +++ b/poi-integration/build.gradle @@ -41,7 +41,7 @@ sourceSets { dependencies { testImplementation 'org.apache.ant:ant:1.10.15' testImplementation 'org.apache.commons:commons-collections4:4.5.0' - testImplementation 'com.google.guava:guava:33.3.0-jre' + testImplementation 'com.google.guava:guava:33.4.8-jre' misc(project(':poi-ooxml')) { capabilities { diff --git a/poi-ooxml/build.gradle b/poi-ooxml/build.gradle index 375ae4f5ba..a09056cfdb 100644 --- a/poi-ooxml/build.gradle +++ b/poi-ooxml/build.gradle @@ -114,11 +114,11 @@ dependencies { testImplementation project(path:':poi', configuration:'tests') testImplementation project(path:':poi-ooxml-lite-agent', configuration: 'archives') testRuntimeOnly "org.apiguardian:apiguardian-api:${apiGuardianVersion}" - testImplementation 'org.xmlunit:xmlunit-core:2.10.0' + testImplementation 'org.xmlunit:xmlunit-core:2.10.3' testImplementation 'org.reflections:reflections:0.10.2' testImplementation 'org.openjdk.jmh:jmh-core:1.36' testImplementation 'org.openjdk.jmh:jmh-generator-annprocess:1.36' - testImplementation 'com.google.guava:guava:33.3.0-jre' + testImplementation 'com.google.guava:guava:33.4.8-jre' testImplementation 'com.github.rzymek:opczip:1.2.0' // prevent slf4j warnings coming from xmlsec -> slf4j-api 1.7.x dependency diff --git a/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/PackagePartName.java b/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/PackagePartName.java index 14a1ee56cc..971064db69 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/PackagePartName.java +++ b/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/PackagePartName.java @@ -356,7 +356,7 @@ public final class PackagePartName implements Comparable<PackagePartName> { private static void throwExceptionIfPartNameNotStartsWithForwardSlashChar( URI partUri) throws InvalidFormatException { String uriPath = partUri.getPath(); - if (uriPath.length() > 0 + if (!uriPath.isEmpty() && uriPath.charAt(0) != PackagingURIHelper.FORWARD_SLASH_CHAR) { throw new InvalidFormatException( "A part name shall start with a forward slash ('/') character [M1.4]: " @@ -377,7 +377,7 @@ public final class PackagePartName implements Comparable<PackagePartName> { private static void throwExceptionIfPartNameEndsWithForwardSlashChar( URI partUri) throws InvalidFormatException { String uriPath = partUri.getPath(); - if (uriPath.length() > 0 + if (!uriPath.isEmpty() && uriPath.charAt(uriPath.length() - 1) == PackagingURIHelper.FORWARD_SLASH_CHAR) { throw new InvalidFormatException( "A part name shall not have a forward slash as the last character [M1.5]: " @@ -422,7 +422,7 @@ public final class PackagePartName implements Comparable<PackagePartName> { */ public String getExtension() { String fragment = this.partNameURI.getPath(); - if (fragment.length() > 0) { + if (!fragment.isEmpty()) { int i = fragment.lastIndexOf('.'); if (i > -1) { return fragment.substring(i + 1); diff --git a/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/PackagingURIHelper.java b/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/PackagingURIHelper.java index bb58523580..58718a6f32 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/PackagingURIHelper.java +++ b/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/PackagingURIHelper.java @@ -287,7 +287,7 @@ public final class PackagingURIHelper { // form must actually be an absolute URI if(sourceURI.toString().equals("/")) { String path = targetURI.getPath(); - if(msCompatible && path.length() > 0 && path.charAt(0) == '/') { + if(msCompatible && !path.isEmpty() && path.charAt(0) == '/') { try { targetURI = new URI(path.substring(1)); } catch (Exception e) { @@ -701,7 +701,7 @@ public final class PackagingURIHelper { } // trailing white spaces must be url-encoded, see Bugzilla 53282 - if(value.length() > 0 ){ + if(!value.isEmpty()){ StringBuilder b = new StringBuilder(); int idx = value.length() - 1; for(; idx >= 0; idx--){ diff --git a/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java b/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java index 072fc6e283..3b5303d206 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java +++ b/poi-ooxml/src/main/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java @@ -151,7 +151,7 @@ public abstract class ContentTypeManager { public void addContentType(PackagePartName partName, String contentType) { boolean defaultCTExists = this.defaultContentType.containsValue(contentType); String extension = partName.getExtension().toLowerCase(Locale.ROOT); - if ((extension.length() == 0) || + if (extension.isEmpty() || // check if content-type and extension do match in both directions // some applications create broken files, e.g. extension "jpg" instead of "jpeg" (this.defaultContentType.containsKey(extension) && !defaultCTExists) || diff --git a/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java b/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java index 4b487fcc9e..2e6f7aa99f 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java +++ b/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java @@ -154,7 +154,7 @@ import org.w3c.dom.events.MutationEvent; * <p>To use SignatureInfo and its sibling classes, you'll need to have the following libs * in the classpath:</p> * <ul> - * <li>BouncyCastle bcpkix and bcprov (tested against 1.80)</li> + * <li>BouncyCastle bcpkix and bcprov (tested against 1.81)</li> * <li>Apache Santuario "xmlsec" (tested against 3.0.x)</li> * <li>and log4j-api (tested against 2.22.x)</li> * </ul> diff --git a/poi-ooxml/src/main/java/org/apache/poi/xdgf/usermodel/XDGFText.java b/poi-ooxml/src/main/java/org/apache/poi/xdgf/usermodel/XDGFText.java index c823bb343d..c2fc839d39 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xdgf/usermodel/XDGFText.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xdgf/usermodel/XDGFText.java @@ -110,7 +110,7 @@ public class XDGFText { public void draw(Graphics2D graphics) { String textContent = getTextContent(); - if (textContent.length() == 0) { + if (textContent.isEmpty()) { return; } @@ -148,7 +148,7 @@ public class XDGFText { float nextY = 0; for (String line : lines) { - if (line.length() == 0) { + if (line.isEmpty()) { continue; } diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/binary/XSSFBHeaderFooter.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/binary/XSSFBHeaderFooter.java index 4b0a599974..5a67148221 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/binary/XSSFBHeaderFooter.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/binary/XSSFBHeaderFooter.java @@ -51,16 +51,16 @@ class XSSFBHeaderFooter { String left = HEADER_FOOTER_HELPER.getLeftSection(rawString); String center = HEADER_FOOTER_HELPER.getCenterSection(rawString); String right = HEADER_FOOTER_HELPER.getRightSection(rawString); - if (left != null && left.length() > 0) { + if (left != null && !left.isEmpty()) { sb.append(left); } - if (center != null && center.length() > 0) { + if (center != null && !center.isEmpty()) { if (sb.length() > 0) { sb.append(' '); } sb.append(center); } - if (right != null && right.length() > 0) { + if (right != null && !right.isEmpty()) { if (sb.length() > 0) { sb.append(' '); } diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/binary/XSSFBHyperlinksTable.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/binary/XSSFBHyperlinksTable.java index b89e0ac943..e77308db6e 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/binary/XSSFBHyperlinksTable.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/binary/XSSFBHyperlinksTable.java @@ -152,7 +152,7 @@ public class XSSFBHyperlinksTable { CellRangeAddress cellRangeAddress = new CellRangeAddress(hyperlinkCellRange.firstRow, hyperlinkCellRange.lastRow, hyperlinkCellRange.firstCol, hyperlinkCellRange.lastCol); String url = relIdToHyperlink.get(relId); - if (location.length() == 0) { + if (location.isEmpty()) { location = url; } diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/eventusermodel/XSSFReader.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/eventusermodel/XSSFReader.java index 0910795230..c9822434e9 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/eventusermodel/XSSFReader.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/eventusermodel/XSSFReader.java @@ -359,7 +359,7 @@ public class XSSFReader { for (XSSFSheetRef xssfSheetRef : xmlSheetRefReader.getSheetRefs()) { //if there's no relationship id, silently skip the sheet String sheetId = xssfSheetRef.getId(); - if (sheetId != null && sheetId.length() > 0) { + if (sheetId != null && !sheetId.isEmpty()) { validSheets.add(xssfSheetRef); } } diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/eventusermodel/XSSFSheetXMLHandler.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/eventusermodel/XSSFSheetXMLHandler.java index b5cc39728b..dbe3bcdee6 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/eventusermodel/XSSFSheetXMLHandler.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/eventusermodel/XSSFSheetXMLHandler.java @@ -417,7 +417,7 @@ public class XSSFSheetXMLHandler extends DefaultHandler { case SST_STRING: String sstIndex = value.toString(); - if (sstIndex.length() > 0) { + if (!sstIndex.isEmpty()) { try { int idx = Integer.parseInt(sstIndex); RichTextString rtss = sharedStringsTable.getItemAt(idx); @@ -430,7 +430,7 @@ public class XSSFSheetXMLHandler extends DefaultHandler { case NUMBER: String n = value.toString(); - if (this.formatString != null && n.length() > 0) { + if (this.formatString != null && !n.isEmpty()) { try { thisStr = formatter.formatRawCellContents( Double.parseDouble(n), this.formatIndex, this.formatString); diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/extractor/XSSFEventBasedExcelExtractor.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/extractor/XSSFEventBasedExcelExtractor.java index 2cf729307f..aabd090432 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/extractor/XSSFEventBasedExcelExtractor.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/extractor/XSSFEventBasedExcelExtractor.java @@ -302,7 +302,7 @@ public class XSSFEventBasedExcelExtractor for (XSSFShape shape : shapes) { if (shape instanceof XSSFSimpleShape) { String sText = ((XSSFSimpleShape) shape).getText(); - if (sText != null && sText.length() > 0) { + if (sText != null && !sText.isEmpty()) { text.append(sText).append('\n'); } } @@ -384,7 +384,7 @@ public class XSSFEventBasedExcelExtractor */ private void appendHeaderFooterText(StringBuilder buffer, String name) { String text = headerFooterMap.get(name); - if (text != null && text.length() > 0) { + if (text != null && !text.isEmpty()) { // this is a naive way of handling the left, center, and right // header and footer delimiters, but it seems to be as good as // the method used by XSSFExcelExtractor diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/extractor/XSSFExcelExtractor.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/extractor/XSSFExcelExtractor.java index da03c669d5..855c5ee3ba 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/extractor/XSSFExcelExtractor.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/extractor/XSSFExcelExtractor.java @@ -192,7 +192,7 @@ public class XSSFExcelExtractor for (XSSFShape shape : drawing.getShapes()){ if (shape instanceof XSSFSimpleShape){ String boxText = ((XSSFSimpleShape)shape).getText(); - if (boxText.length() > 0){ + if (!boxText.isEmpty()){ text.append(boxText); text.append('\n'); } diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/SheetDataWriter.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/SheetDataWriter.java index 70b2b68d04..dcd8294271 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/SheetDataWriter.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/SheetDataWriter.java @@ -382,7 +382,7 @@ public class SheetDataWriter implements Closeable { * need to be preserved with the xml:space=\"preserve\" attribute */ boolean hasLeadingTrailingSpaces(String str) { - if (str != null && str.length() > 0) { + if (str != null && !str.isEmpty()) { char firstChar = str.charAt(0); char lastChar = str.charAt(str.length() - 1); return Character.isWhitespace(firstChar) || Character.isWhitespace(lastChar) ; @@ -391,7 +391,7 @@ public class SheetDataWriter implements Closeable { } protected void outputEscapedString(String s) throws IOException { - if (s == null || s.length() == 0) { + if (s == null || s.isEmpty()) { return; } diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/BaseXSSFEvaluationWorkbook.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/BaseXSSFEvaluationWorkbook.java index c21f0593de..206ed84e49 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/BaseXSSFEvaluationWorkbook.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/BaseXSSFEvaluationWorkbook.java @@ -428,7 +428,7 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork // TODO - no idea if this is right CTDefinedName ctn = _nameRecord.getCTName(); String strVal = ctn.getStringValue(); - return !ctn.getFunction() && strVal != null && strVal.length() > 0; + return !ctn.getFunction() && strVal != null && !strVal.isEmpty(); } @Override diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFName.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFName.java index 1e9047009e..476671a48a 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFName.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFName.java @@ -390,7 +390,7 @@ public final class XSSFName implements Name { */ private static void validateName(String name) { - if (name.length() == 0) { + if (name.isEmpty()) { throw new IllegalArgumentException("Name cannot be blank"); } if (name.length() > 255) { diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFRichTextString.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFRichTextString.java index d1fdf6b4b6..f8c1c54a24 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFRichTextString.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFRichTextString.java @@ -485,7 +485,7 @@ public class XSSFRichTextString implements RichTextString { */ protected static void preserveSpaces(STXstring xs) { String text = xs.getStringValue(); - if (text != null && text.length() > 0) { + if (text != null && !text.isEmpty()) { char firstChar = text.charAt(0); char lastChar = text.charAt(text.length() - 1); if(Character.isWhitespace(firstChar) || Character.isWhitespace(lastChar)) { diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFRow.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFRow.java index 5144729fa3..5e6bd25c46 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFRow.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFRow.java @@ -608,7 +608,7 @@ public class XSSFRow implements Row, Comparable<XSSFRow> { } // remove any remaining illegal references in _rows.cArray - while(_row.getCArray().length > _cells.size()) { + while(_row.sizeOfCArray() > _cells.size()) { _row.removeC(_cells.size()); } } diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFSheet.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFSheet.java index b8166a880e..c851eda3ec 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFSheet.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFSheet.java @@ -3926,7 +3926,15 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet, OoxmlSheetEx if(minCell != Integer.MAX_VALUE) { cellRangeAddress = new CellRangeAddress(getFirstRowNum(), getLastRowNum(), minCell, maxCell); } + } else { + // sort columns + for(Map.Entry<Integer, XSSFRow> entry : _rows.entrySet()) { + XSSFRow row = entry.getValue(); + // sorting happens in XSSFRow.fixupCTCells + row.onDocumentWrite(); + } } + if (cellRangeAddress != null) { if (worksheet.isSetDimension()) { worksheet.getDimension().setRef(cellRangeAddress.formatAsString()); diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFSimpleShape.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFSimpleShape.java index bc60130c76..a95d3659a3 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFSimpleShape.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFSimpleShape.java @@ -199,7 +199,8 @@ public class XSSFSimpleShape extends XSSFShape implements Iterable<XSSFTextParag } XSSFTextParagraph p = _paragraphs.get(i); - if (p.isBullet() && p.getText().length() > 0) { + final String pText = p.getText(); + if (p.isBullet() && !pText.isEmpty()) { int level = Math.min(p.getLevel(), MAX_LEVELS - 1); @@ -211,11 +212,11 @@ public class XSSFSimpleShape extends XSSFShape implements Iterable<XSSFTextParag out.append('\t'); } String character = p.getBulletCharacter(); - out.append(character.length() > 0 ? character + " " : "- "); - out.append(p.getText()); + out.append(!character.isEmpty() ? character + " " : "- "); + out.append(pText); } } else { - out.append(p.getText()); + out.append(pText); // this paragraph is not a bullet, so reset the count array for (int k = 0; k < MAX_LEVELS; k++) { @@ -254,9 +255,10 @@ public class XSSFSimpleShape extends XSSFShape implements Iterable<XSSFTextParag for (int j = 0; j < level; j++) { out.append('\t'); } - if (p.getText().length() > 0) { + final String pText = p.getText(); + if (!pText.isEmpty()) { out.append(getBulletPrefix(scheme, levelCount.get(level))); - out.append(p.getText()); + out.append(pText); } while (true) { XSSFTextParagraph nextp = (index + 1) == _paragraphs.size() ? null : _paragraphs.get(index + 1); @@ -291,11 +293,12 @@ public class XSSFSimpleShape extends XSSFShape implements Iterable<XSSFTextParag } // check for empty text - only output a bullet if there is text, // but it is still part of the group - if (nextp.getText().length() > 0) { + final String npText = nextp.getText(); + if (!npText.isEmpty()) { // increment the count for this level levelCount.set(level, levelCount.get(level) + 1); out.append(getBulletPrefix(nextScheme, levelCount.get(level))); - out.append(nextp.getText()); + out.append(npText); } } else { // something doesn't match so stop diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFTextParagraph.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFTextParagraph.java index d18f562d46..db696efbf2 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFTextParagraph.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFTextParagraph.java @@ -65,7 +65,7 @@ public class XSSFTextParagraph implements Iterable<XSSFTextRun>{ } } - public String getText(){ + public String getText() { StringBuilder out = new StringBuilder(); for (XSSFTextRun r : _runs) { out.append(r.getText()); diff --git a/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java b/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java index 36d1350d59..88a603e160 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java @@ -1155,7 +1155,7 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody { * @return the LastParagraph of the document */ public XWPFParagraph getLastParagraph() { - int lastPos = paragraphs.toArray().length - 1; + final int lastPos = paragraphs.size() - 1; return paragraphs.get(lastPos); } diff --git a/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFParagraph.java b/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFParagraph.java index 044ce9a400..7f3ab37ff6 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFParagraph.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFParagraph.java @@ -109,14 +109,13 @@ public class XWPFParagraph implements IBodyElement, IRunBody, ISDTContents, Para * sub-paragraph that correspond to character text * runs, and builds the appropriate runs for these. */ - @SuppressWarnings("deprecation") private void buildRunsInOrderFromXml(XmlObject object) { try (XmlCursor c = object.newCursor()) { c.selectPath("child::*"); while (c.toNextSelection()) { XmlObject o = c.getObject(); if (o instanceof CTR) { - XWPFRun r = new XWPFRun((CTR) o, this); + XWPFRun r = new XWPFRun((CTR) o, (IRunBody) this); runs.add(r); iruns.add(r); } @@ -145,23 +144,22 @@ public class XWPFParagraph implements IBodyElement, IRunBody, ISDTContents, Para iruns.add(cc); } if (o instanceof CTRunTrackChange) { - for (CTR r : ((CTRunTrackChange) o).getRArray()) { - XWPFRun cr = new XWPFRun(r, this); + final CTRunTrackChange parentRecord = (CTRunTrackChange) o; + for (CTR r : parentRecord.getRArray()) { + XWPFRun cr = new XWPFRun(r, (IRunBody) this); runs.add(cr); iruns.add(cr); } + // add all the insertions as text + for (CTRunTrackChange change : parentRecord.getInsArray()) { + buildRunsInOrderFromXml(change); + } } if (o instanceof CTSmartTagRun) { // Smart Tags can be nested many times. // This implementation does not preserve the tagging information buildRunsInOrderFromXml(o); } - if (o instanceof CTRunTrackChange) { - // add all the insertions as text - for (CTRunTrackChange change : ((CTRunTrackChange) o).getInsArray()) { - buildRunsInOrderFromXml(change); - } - } } } } @@ -205,7 +203,7 @@ public class XWPFParagraph implements IBodyElement, IRunBody, ISDTContents, Para if (run instanceof XWPFRun) { XWPFRun xRun = (XWPFRun) run; // don't include the text if reviewing is enabled and this is a deleted run - if (xRun.getCTR().getDelTextArray().length == 0) { + if (xRun.getCTR().sizeOfDelTextArray() == 0) { out.append(xRun); } } else if (run instanceof XWPFSDT) { diff --git a/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFRun.java b/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFRun.java index 03249fa66d..3c0ac9eb89 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFRun.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFRun.java @@ -137,6 +137,7 @@ public class XWPFRun implements ISDTContents, IRunElement, CharacterRun { * @deprecated Use {@link XWPFRun#XWPFRun(CTR, IRunBody)} */ @Deprecated + @Removal(version = "7.0.0") public XWPFRun(CTR r, XWPFParagraph p) { this(r, (IRunBody) p); } @@ -1437,7 +1438,7 @@ public class XWPFRun implements ISDTContents, IRunElement, CharacterRun { @Override public String toString() { String phonetic = getPhonetic(); - if (phonetic.length() > 0) { + if (!phonetic.isEmpty()) { return text() + " (" + phonetic + ")"; } else { return text(); @@ -1485,7 +1486,7 @@ public class XWPFRun implements ISDTContents, IRunElement, CharacterRun { } } // Any picture text? - if (pictureText != null && pictureText.length() > 0) { + if (pictureText != null && !pictureText.isEmpty()) { text.append("\n").append(pictureText).append("\n"); } } diff --git a/poi-ooxml/src/test/java/org/apache/poi/xssf/streaming/TestOutOfOrderColumns.java b/poi-ooxml/src/test/java/org/apache/poi/xssf/streaming/TestOutOfOrderColumns.java new file mode 100644 index 0000000000..b7b6e67030 --- /dev/null +++ b/poi-ooxml/src/test/java/org/apache/poi/xssf/streaming/TestOutOfOrderColumns.java @@ -0,0 +1,122 @@ +/*
+ * ====================================================================
+ * 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.xssf.streaming;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test creates cells in reverse column order in XSSF and SXSSF and expects
+ * saved files to have fixed the order.
+ *
+ * This is necessary because if columns in the saved file are out of order
+ * Excel will show a repair dialog when opening the file and removing data.
+ */
+public final class TestOutOfOrderColumns {
+
+ @Test
+ void outOfOrderColumnsXSSF() throws IOException {
+ try (
+ XSSFWorkbook wb = new XSSFWorkbook();
+ UnsynchronizedByteArrayOutputStream bos = UnsynchronizedByteArrayOutputStream.builder().get()
+ ) {
+ XSSFSheet sheet = wb.createSheet();
+
+ Row row = sheet.createRow(0);
+ // create cells in reverse order
+ row.createCell(1).setCellValue("def");
+ row.createCell(0).setCellValue("abc");
+
+ wb.write(bos);
+
+ validateOrder(bos.toInputStream());
+ }
+ }
+
+ @Test
+ void outOfOrderColumnsSXSSF() throws IOException {
+ try (
+ SXSSFWorkbook wb = new SXSSFWorkbook();
+ UnsynchronizedByteArrayOutputStream bos = UnsynchronizedByteArrayOutputStream.builder().get()
+ ) {
+ Sheet sheet = wb.createSheet();
+
+ Row row = sheet.createRow(0);
+ // create cells in reverse order
+ row.createCell(1).setCellValue("xyz");
+ row.createCell(0).setCellValue("uvw");
+
+ wb.write(bos);
+
+ validateOrder(bos.toInputStream());
+ }
+ }
+
+ @Test
+ /** this is the problematic case, as XSSF column sorting is skipped when saving with SXSSF. */
+ void mixOfXSSFAndSXSSF() throws IOException {
+ try (
+ XSSFWorkbook wb = new XSSFWorkbook();
+ UnsynchronizedByteArrayOutputStream bos = UnsynchronizedByteArrayOutputStream.builder().get()
+ ) {
+ XSSFSheet sheet = wb.createSheet();
+
+ Row row1 = sheet.createRow(0);
+ // create cells in reverse order
+ row1.createCell(1).setCellValue("def");
+ row1.createCell(0).setCellValue("abc");
+
+ try (SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(wb)) {
+ Sheet sSheet = sxssfWorkbook.getSheetAt(0);
+ Row row2 = sSheet.createRow(1);
+ // create cells in reverse order
+ row2.createCell(1).setCellValue("xyz");
+ row2.createCell(0).setCellValue("uvw");
+
+ sxssfWorkbook.write(bos);
+
+ validateOrder(bos.toInputStream());
+ }
+ }
+ }
+
+ private void validateOrder(InputStream is) throws IOException {
+ // test if saved cells are in order
+ try (XSSFWorkbook xssfWorkbook = new XSSFWorkbook(is)) {
+ XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0);
+
+ Row resultRow = xssfSheet.getRow(0);
+ // POI doesn't show stored order because _cells TreeMap sorts it automatically.
+ // The only way to test is to compare the xml.
+ String s = resultRow.toString();
+ assertTrue(s.matches("(?s).*A1.*B1.*"), "unexpected order: " + s);
+ }
+ }
+
+}
diff --git a/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFCellStyle.java b/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFCellStyle.java index 9a48429f0b..3b605f515f 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFCellStyle.java +++ b/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFCellStyle.java @@ -1090,4 +1090,67 @@ class TestXSSFCellStyle { workbook.close(); } + + @Test + void cloneToHSSF() throws IOException { + try ( + XSSFWorkbook xssfWorkbook = new XSSFWorkbook(); + HSSFWorkbook hssfWorkbook = new HSSFWorkbook() + ) { + XSSFCellStyle cellStyle = xssfWorkbook.createCellStyle(); + DataFormat format = xssfWorkbook.createDataFormat(); + + cellStyle.setDataFormat(format.getFormat("###0")); + + cellStyle.setFillBackgroundColor(IndexedColors.DARK_BLUE.getIndex()); + cellStyle.setFillForegroundColor(IndexedColors.DARK_RED.getIndex()); + cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); + + cellStyle.setAlignment(HorizontalAlignment.RIGHT); + cellStyle.setVerticalAlignment(VerticalAlignment.TOP); + + HSSFCellStyle hssfCellStyle = hssfWorkbook.createCellStyle(); + hssfCellStyle.cloneStyleFrom(cellStyle); + + // not everything is cloned by some properties are set + assertEquals(cellStyle.getDataFormat(), hssfCellStyle.getDataFormat()); + assertEquals(IndexedColors.DARK_BLUE.getIndex(), hssfCellStyle.getFillBackgroundColor()); + assertEquals(IndexedColors.DARK_RED.getIndex(), hssfCellStyle.getFillForegroundColor()); + assertEquals(FillPatternType.SOLID_FOREGROUND, hssfCellStyle.getFillPattern()); + assertEquals(HorizontalAlignment.RIGHT, hssfCellStyle.getAlignment()); + assertEquals(VerticalAlignment.TOP, hssfCellStyle.getVerticalAlignment()); + } + } + + @Test + void cloneFromHSSF() throws IOException { + try ( + XSSFWorkbook xssfWorkbook = new XSSFWorkbook(); + HSSFWorkbook hssfWorkbook = new HSSFWorkbook() + ) { + HSSFCellStyle cellStyle = hssfWorkbook.createCellStyle(); + DataFormat format = hssfWorkbook.createDataFormat(); + + cellStyle.setDataFormat(format.getFormat("###0")); + + cellStyle.setFillBackgroundColor(IndexedColors.DARK_BLUE.getIndex()); + cellStyle.setFillForegroundColor(IndexedColors.DARK_RED.getIndex()); + cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); + + cellStyle.setAlignment(HorizontalAlignment.RIGHT); + cellStyle.setVerticalAlignment(VerticalAlignment.TOP); + + XSSFCellStyle xssfCellStyle = xssfWorkbook.createCellStyle(); + xssfCellStyle.cloneStyleFrom(cellStyle); + + // not everything is cloned by some properties are set + assertEquals(cellStyle.getDataFormat(), xssfCellStyle.getDataFormat()); + // assertEquals(IndexedColors.DARK_BLUE.getIndex(), xssfCellStyle.getFillBackgroundColor()); + assertEquals(IndexedColors.DARK_RED.getIndex(), xssfCellStyle.getFillForegroundColor()); + assertEquals(FillPatternType.SOLID_FOREGROUND, xssfCellStyle.getFillPattern()); + assertEquals(HorizontalAlignment.RIGHT, xssfCellStyle.getAlignment()); + assertEquals(VerticalAlignment.TOP, xssfCellStyle.getVerticalAlignment()); + } + } + } diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hmef/extractor/HMEFContentsExtractor.java b/poi-scratchpad/src/main/java/org/apache/poi/hmef/extractor/HMEFContentsExtractor.java index 59cbe79e91..8e01252c64 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hmef/extractor/HMEFContentsExtractor.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hmef/extractor/HMEFContentsExtractor.java @@ -141,10 +141,10 @@ public final class HMEFContentsExtractor { // Decide what to call it String filename = att.getLongFilename(); - if(filename == null || filename.length() == 0) { + if(filename == null || filename.isEmpty()) { filename = att.getFilename(); } - if(filename == null || filename.length() == 0) { + if(filename == null || filename.isEmpty()) { filename = "attachment" + count; if(att.getExtension() != null) { filename += att.getExtension(); diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hslf/usermodel/HSLFFill.java b/poi-scratchpad/src/main/java/org/apache/poi/hslf/usermodel/HSLFFill.java index 3f9f84b6d6..3a8c767e6d 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hslf/usermodel/HSLFFill.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hslf/usermodel/HSLFFill.java @@ -576,9 +576,7 @@ public final class HSLFFill { } else { EscherBSERecord bse = (EscherBSERecord) bstore.getChild(idx - 1); for (HSLFPictureData pd : pict) { - - // Reference equals is safe because these BSE belong to the same slideshow - if (pd.bse == bse) { + if (pd.getOffset() == bse.getOffset()) { return pd; } } diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hslf/usermodel/HSLFPictureShape.java b/poi-scratchpad/src/main/java/org/apache/poi/hslf/usermodel/HSLFPictureShape.java index 71e67084e1..e10a54cb55 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hslf/usermodel/HSLFPictureShape.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hslf/usermodel/HSLFPictureShape.java @@ -124,9 +124,7 @@ public class HSLFPictureShape extends HSLFSimpleShape implements PictureShape<HS LOG.atError().log("no reference to picture data found "); } else { for (HSLFPictureData pd : pict) { - - // Reference equals is safe because these BSE belong to the same slideshow - if (pd.bse == bse) { + if (pd.getOffset() == bse.getOffset()) { return pd; } } diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hslf/usermodel/HSLFShapePlaceholderDetails.java b/poi-scratchpad/src/main/java/org/apache/poi/hslf/usermodel/HSLFShapePlaceholderDetails.java index 8a467dfedc..348d901235 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hslf/usermodel/HSLFShapePlaceholderDetails.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hslf/usermodel/HSLFShapePlaceholderDetails.java @@ -47,6 +47,68 @@ import org.apache.poi.util.LocaleUtil; * @since POI 4.0.0 */ public class HSLFShapePlaceholderDetails extends HSLFPlaceholderDetails { + + static void updateSPRecord(final HSLFSimpleShape shape, final Placeholder placeholder) { + final EscherSpRecord spRecord = shape.getEscherChild(EscherSpRecord.RECORD_ID); + int flags = spRecord.getFlags(); + if (placeholder == null) { + flags ^= EscherSpRecord.FLAG_HAVEMASTER; + } else { + flags |= EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HAVEMASTER; + } + spRecord.setFlags(flags); + + // Placeholders can't be grouped + shape.setEscherProperty(EscherPropertyTypes.PROTECTION__LOCKAGAINSTGROUPING, (placeholder == null ? -1 : 262144)); + } + + static void removePlaceholder(final HSLFSimpleShape shape) { + final HSLFEscherClientDataRecord clientData = shape.getClientData(false); + if (clientData != null) { + clientData.removeChild(OEPlaceholderAtom.class); + clientData.removeChild(RoundTripHFPlaceholder12.class); + // remove client data if the placeholder was the only child to be carried + if (clientData.getChildRecords().isEmpty()) { + shape.getSpContainer().removeChildRecord(clientData); + } + } + } + + static OEPlaceholderAtom createOEPlaceholderAtom(final HSLFEscherClientDataRecord clientData) { + return createOEPlaceholderAtom(clientData, (byte) 0); + } + + static OEPlaceholderAtom createOEPlaceholderAtom(final HSLFEscherClientDataRecord clientData, + final byte placeholderId) { + OEPlaceholderAtom oePlaceholderAtom = new OEPlaceholderAtom(); + oePlaceholderAtom.setPlaceholderSize((byte)OEPlaceholderAtom.PLACEHOLDER_FULLSIZE); + // TODO: placement id only "SHOULD" be unique ... check other placeholders on sheet for unique id + oePlaceholderAtom.setPlacementId(-1); + oePlaceholderAtom.setPlaceholderId(placeholderId); + clientData.addChild(oePlaceholderAtom); + return oePlaceholderAtom; + } + + static byte getPlaceholderId(final HSLFSheet sheet, final Placeholder placeholder) { + if (placeholder == null) { + return 0; + } + byte phId; + // TODO: implement/switch NotesMaster + if (sheet instanceof HSLFSlideMaster) { + phId = (byte) placeholder.nativeSlideMasterId; + } else if (sheet instanceof HSLFNotes) { + phId = (byte) placeholder.nativeNotesId; + } else { + phId = (byte) placeholder.nativeSlideId; + } + + if (phId == -2) { + throw new HSLFException("Placeholder " + placeholder.name() + " not supported for this sheet type (" + sheet.getClass() + ")"); + } + return phId; + } + private enum PlaceholderContainer { slide, master, notes, notesMaster } @@ -78,7 +140,7 @@ public class HSLFShapePlaceholderDetails extends HSLFPlaceholderDetails { @Override public Placeholder getPlaceholder() { - updatePlaceholderAtom(false); + updatePlaceholderAtom(null, false); final int phId; if (oePlaceholderAtom != null) { phId = oePlaceholderAtom.getPlaceholderId(); @@ -105,17 +167,7 @@ public class HSLFShapePlaceholderDetails extends HSLFPlaceholderDetails { @Override public void setPlaceholder(final Placeholder placeholder) { - final EscherSpRecord spRecord = shape.getEscherChild(EscherSpRecord.RECORD_ID); - int flags = spRecord.getFlags(); - if (placeholder == null) { - flags ^= EscherSpRecord.FLAG_HAVEMASTER; - } else { - flags |= EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HAVEMASTER; - } - spRecord.setFlags(flags); - - // Placeholders can't be grouped - shape.setEscherProperty(EscherPropertyTypes.PROTECTION__LOCKAGAINSTGROUPING, (placeholder == null ? -1 : 262144)); + updateSPRecord(shape, placeholder); if (placeholder == null) { removePlaceholder(); @@ -123,7 +175,7 @@ public class HSLFShapePlaceholderDetails extends HSLFPlaceholderDetails { } // init client data - updatePlaceholderAtom(true); + updatePlaceholderAtom(placeholder, true); final byte phId = getPlaceholderId(placeholder); oePlaceholderAtom.setPlaceholderId(phId); @@ -158,7 +210,7 @@ public class HSLFShapePlaceholderDetails extends HSLFPlaceholderDetails { if (ph == null || size == null) { return; } - updatePlaceholderAtom(true); + updatePlaceholderAtom(ph, true); final byte ph_size; switch (size) { @@ -209,20 +261,12 @@ public class HSLFShapePlaceholderDetails extends HSLFPlaceholderDetails { } private void removePlaceholder() { - final HSLFEscherClientDataRecord clientData = shape.getClientData(false); - if (clientData != null) { - clientData.removeChild(OEPlaceholderAtom.class); - clientData.removeChild(RoundTripHFPlaceholder12.class); - // remove client data if the placeholder was the only child to be carried - if (clientData.getChildRecords().isEmpty()) { - shape.getSpContainer().removeChildRecord(clientData); - } - } + removePlaceholder(shape); oePlaceholderAtom = null; roundTripHFPlaceholder12 = null; } - private void updatePlaceholderAtom(final boolean create) { + private void updatePlaceholderAtom(final Placeholder placeholder, final boolean create) { localDateTime = null; if (shape instanceof HSLFTextBox) { EscherTextboxWrapper txtBox = ((HSLFTextBox)shape).getEscherTextboxWrapper(); @@ -255,11 +299,8 @@ public class HSLFShapePlaceholderDetails extends HSLFPlaceholderDetails { } if (oePlaceholderAtom == null) { - oePlaceholderAtom = new OEPlaceholderAtom(); - oePlaceholderAtom.setPlaceholderSize((byte)OEPlaceholderAtom.PLACEHOLDER_FULLSIZE); - // TODO: placement id only "SHOULD" be unique ... check other placeholders on sheet for unique id - oePlaceholderAtom.setPlacementId(-1); - clientData.addChild(oePlaceholderAtom); + final byte phId = getPlaceholderId(shape.getSheet(), placeholder); + oePlaceholderAtom = createOEPlaceholderAtom(clientData, phId); } if (roundTripHFPlaceholder12 == null) { roundTripHFPlaceholder12 = new RoundTripHFPlaceholder12(); diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java b/poi-scratchpad/src/main/java/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java index da930b6899..aa85a65a92 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hslf/usermodel/HSLFSimpleShape.java @@ -20,6 +20,9 @@ package org.apache.poi.hslf.usermodel; import java.awt.Color; import org.apache.logging.log4j.Logger; +import org.apache.poi.hslf.record.HSLFEscherClientDataRecord; +import org.apache.poi.hslf.record.OEPlaceholderAtom; +import org.apache.poi.hslf.record.RoundTripHFPlaceholder12; import org.apache.poi.logging.PoiLogManager; import org.apache.poi.ddf.AbstractEscherOptRecord; import org.apache.poi.ddf.EscherChildAnchorRecord; @@ -53,6 +56,11 @@ import org.apache.poi.sl.usermodel.StrokeStyle.LineDash; import org.apache.poi.util.LittleEndian; import org.apache.poi.util.Units; +import static org.apache.poi.hslf.usermodel.HSLFShapePlaceholderDetails.createOEPlaceholderAtom; +import static org.apache.poi.hslf.usermodel.HSLFShapePlaceholderDetails.getPlaceholderId; +import static org.apache.poi.hslf.usermodel.HSLFShapePlaceholderDetails.removePlaceholder; +import static org.apache.poi.hslf.usermodel.HSLFShapePlaceholderDetails.updateSPRecord; + /** * An abstract simple (non-group) shape. * This is the parent class for all primitive shapes like Line, Rectangle, etc. @@ -80,6 +88,8 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape<H */ protected HSLFHyperlink _hyperlink; + protected HSLFShapePlaceholderDetails _placeholderDetails; + /** * Create a SimpleShape object and initialize it from the supplied Record container. * @@ -564,10 +574,12 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape<H @Override public HSLFShapePlaceholderDetails getPlaceholderDetails() { - return new HSLFShapePlaceholderDetails(this); + if (_placeholderDetails == null) { + _placeholderDetails = new HSLFShapePlaceholderDetails(this); + } + return _placeholderDetails; } - @Override public Placeholder getPlaceholder() { return getPlaceholderDetails().getPlaceholder(); @@ -575,9 +587,62 @@ public abstract class HSLFSimpleShape extends HSLFShape implements SimpleShape<H @Override public void setPlaceholder(Placeholder placeholder) { - getPlaceholderDetails().setPlaceholder(placeholder); - } + // reset the placeholder details so that the next call to getPlaceholderDetails() will reinitialize it + _placeholderDetails = null; + updateSPRecord(this, placeholder); + if (placeholder == null) { + removePlaceholder(this); + return; + } + + HSLFEscherClientDataRecord clientData = getClientData(true); + + // OEPlaceholderAtom tells powerpoint that this shape is a placeholder + OEPlaceholderAtom oep = null; + RoundTripHFPlaceholder12 rtp = null; + for (org.apache.poi.hslf.record.Record r : clientData.getHSLFChildRecords()) { + if (r instanceof OEPlaceholderAtom) { + oep = (OEPlaceholderAtom) r; + break; + } + if (r instanceof RoundTripHFPlaceholder12) { + rtp = (RoundTripHFPlaceholder12) r; + break; + } + } + + /** + * Extract from MSDN: + * + * There is a special case when the placeholder does not have a position in the layout. + * This occurs when the user has moved the placeholder from its original position. + * In this case the placeholder ID is -1. + */ + final byte phId = getPlaceholderId(getSheet(), placeholder); + + switch (placeholder) { + case HEADER: + case FOOTER: + if (rtp == null) { + rtp = new RoundTripHFPlaceholder12(); + rtp.setPlaceholderId(phId); + clientData.addChild(rtp); + } + if (oep != null) { + clientData.removeChild(OEPlaceholderAtom.class); + } + break; + default: + if (rtp != null) { + clientData.removeChild(RoundTripHFPlaceholder12.class); + } + if (oep == null) { + oep = createOEPlaceholderAtom(clientData, phId); + } + break; + } + } @Override public void setStrokeStyle(Object... styles) { diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hsmf/extractor/OutlookTextExtractor.java b/poi-scratchpad/src/main/java/org/apache/poi/hsmf/extractor/OutlookTextExtractor.java index 62b09b374e..e87185ec93 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hsmf/extractor/OutlookTextExtractor.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hsmf/extractor/OutlookTextExtractor.java @@ -177,7 +177,7 @@ public class OutlookTextExtractor implements POIOLE2TextExtractor { * {@code "Nick <nick@example.com>; Jim <jim@example.com>"} */ protected void handleEmails(StringBuilder s, String type, String displayText, Iterator<String> emails) { - if (displayText == null || displayText.length() == 0) { + if (displayText == null || displayText.isEmpty()) { return; } diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hssf/converter/AbstractExcelUtils.java b/poi-scratchpad/src/main/java/org/apache/poi/hssf/converter/AbstractExcelUtils.java index 88498ea39e..3485f40911 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hssf/converter/AbstractExcelUtils.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hssf/converter/AbstractExcelUtils.java @@ -174,7 +174,7 @@ class AbstractExcelUtils { } static boolean isEmpty(String str) { - return str == null || str.length() == 0; + return str == null || str.isEmpty(); } static boolean isNotEmpty(String str) { diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hwpf/converter/AbstractWordConverter.java b/poi-scratchpad/src/main/java/org/apache/poi/hwpf/converter/AbstractWordConverter.java index aefe15d9da..0eca5fb49a 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hwpf/converter/AbstractWordConverter.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hwpf/converter/AbstractWordConverter.java @@ -310,8 +310,7 @@ public abstract class AbstractWordConverter { continue; } String text = characterRun.text(); - if (text == null || text.length() == 0 - || text.charAt(0) != FIELD_BEGIN_MARK) { + if (text == null || text.isEmpty() || text.charAt(0) != FIELD_BEGIN_MARK) { continue; } diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hwpf/converter/AbstractWordUtils.java b/poi-scratchpad/src/main/java/org/apache/poi/hwpf/converter/AbstractWordUtils.java index 9ef569fc6b..b2bd30f808 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hwpf/converter/AbstractWordUtils.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hwpf/converter/AbstractWordUtils.java @@ -417,7 +417,7 @@ public class AbstractWordUtils { static boolean isEmpty( String str ) { - return str == null || str.length() == 0; + return str == null || str.isEmpty(); } static boolean isNotEmpty( String str ) diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hwpf/converter/DefaultFontReplacer.java b/poi-scratchpad/src/main/java/org/apache/poi/hwpf/converter/DefaultFontReplacer.java index b9845fd348..2df337e1d3 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hwpf/converter/DefaultFontReplacer.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hwpf/converter/DefaultFontReplacer.java @@ -87,7 +87,7 @@ public class DefaultFontReplacer implements FontReplacer private static boolean isEmpty( String str ) { - return str == null || str.length() == 0; + return str == null || str.isEmpty(); } private static boolean isNotEmpty( String str ) diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hwpf/extractor/WordExtractor.java b/poi-scratchpad/src/main/java/org/apache/poi/hwpf/extractor/WordExtractor.java index 880c4e2d38..06c4a202ae 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hwpf/extractor/WordExtractor.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hwpf/extractor/WordExtractor.java @@ -139,7 +139,7 @@ public final class WordExtractor implements POIOLE2TextExtractor { * Add the header/footer text, if it's not empty */ private void appendHeaderFooter( String text, StringBuilder out ) { - if ( text == null || text.length() == 0 ) + if ( text == null || text.isEmpty()) return; text = text.replace( '\r', '\n' ); diff --git a/poi-scratchpad/src/test/java/org/apache/poi/hslf/model/TestLine.java b/poi-scratchpad/src/test/java/org/apache/poi/hslf/model/TestLine.java index 0384b19043..e26793bde5 100644 --- a/poi-scratchpad/src/test/java/org/apache/poi/hslf/model/TestLine.java +++ b/poi-scratchpad/src/test/java/org/apache/poi/hslf/model/TestLine.java @@ -18,6 +18,8 @@ package org.apache.poi.hslf.model; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.awt.Color; import java.awt.Rectangle; @@ -27,9 +29,11 @@ import java.util.Arrays; import org.apache.poi.hslf.HSLFTestDataSamples; import org.apache.poi.hslf.usermodel.HSLFLine; +import org.apache.poi.hslf.usermodel.HSLFPlaceholder; import org.apache.poi.hslf.usermodel.HSLFShape; import org.apache.poi.hslf.usermodel.HSLFSlide; import org.apache.poi.hslf.usermodel.HSLFSlideShow; +import org.apache.poi.hslf.usermodel.HSLFTextBox; import org.apache.poi.sl.usermodel.StrokeStyle.LineCompound; import org.apache.poi.sl.usermodel.StrokeStyle.LineDash; import org.junit.jupiter.api.Test; @@ -62,9 +66,14 @@ public final class TestLine { @Test void testCreateLines() throws IOException { + final String title = "Lines tester"; try (HSLFSlideShow ppt1 = new HSLFSlideShow()) { HSLFSlide slide1 = ppt1.createSlide(); - slide1.addTitle().setText("Lines tester"); + HSLFTextBox titleBox = slide1.addTitle(); + titleBox.setText(title); + assertInstanceOf(HSLFPlaceholder.class, titleBox); + HSLFPlaceholder pl = (HSLFPlaceholder) titleBox; + assertNotNull(pl.getPlaceholder()); for (Object[] line : lines) { HSLFLine hslfLine = new HSLFLine(); @@ -85,6 +94,7 @@ public final class TestLine { try (HSLFSlideShow ppt2 = HSLFTestDataSamples.writeOutAndReadBack(ppt1)) { HSLFSlide slide2 = ppt2.getSlides().get(0); + assertEquals(title, slide2.getTitle()); int idx = 0; for (HSLFShape shape : slide2.getShapes().subList(1,14)) { diff --git a/poi-scratchpad/src/test/java/org/apache/poi/hslf/usermodel/TestBugs.java b/poi-scratchpad/src/test/java/org/apache/poi/hslf/usermodel/TestBugs.java index 1cd5be3cf6..575d443b5b 100644 --- a/poi-scratchpad/src/test/java/org/apache/poi/hslf/usermodel/TestBugs.java +++ b/poi-scratchpad/src/test/java/org/apache/poi/hslf/usermodel/TestBugs.java @@ -68,19 +68,10 @@ import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.poifs.macros.VBAMacroReader; import org.apache.poi.sl.draw.DrawPaint; import org.apache.poi.sl.extractor.SlideShowExtractor; -import org.apache.poi.sl.usermodel.ColorStyle; -import org.apache.poi.sl.usermodel.PaintStyle; +import org.apache.poi.sl.usermodel.*; import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint; import org.apache.poi.sl.usermodel.PictureData.PictureType; -import org.apache.poi.sl.usermodel.Placeholder; -import org.apache.poi.sl.usermodel.ShapeType; -import org.apache.poi.sl.usermodel.Slide; -import org.apache.poi.sl.usermodel.SlideShow; -import org.apache.poi.sl.usermodel.SlideShowFactory; -import org.apache.poi.sl.usermodel.TextBox; -import org.apache.poi.sl.usermodel.TextParagraph; import org.apache.poi.sl.usermodel.TextParagraph.TextAlign; -import org.apache.poi.sl.usermodel.TextRun; import org.apache.poi.util.LittleEndian; import org.apache.poi.util.StringUtil; import org.apache.poi.util.Units; @@ -904,4 +895,22 @@ public final class TestBugs { assertEquals(ShapeType.NOT_PRIMITIVE, shList.get(2).getShapeType()); } } + + @Test + void test69697() throws Exception { + try (HSLFSlideShow ppt = open("bug69697.ppt")) { + HSLFSlide slide = ppt.getSlides().get(0); + for (HSLFShape sh : slide.getShapes()) { + if (sh instanceof HSLFPictureShape) { + HSLFPictureShape pict = (HSLFPictureShape) sh; + HSLFPictureData pictData = pict.getPictureData(); + assertNotNull(pictData, "PictureData should not be null for shape: " + pict.getShapeName()); + byte[] data = pictData.getData(); + assertNotNull(data, "Picture data should not be null for shape: " + pict.getShapeName()); + PictureData.PictureType type = pictData.getType(); + assertNotNull(type, "Picture type should not be null for shape: " + pict.getShapeName()); + } + } + } + } } diff --git a/poi/src/main/java/org/apache/poi/hssf/record/TextObjectRecord.java b/poi/src/main/java/org/apache/poi/hssf/record/TextObjectRecord.java index 17bcce71e7..d4a303f873 100644 --- a/poi/src/main/java/org/apache/poi/hssf/record/TextObjectRecord.java +++ b/poi/src/main/java/org/apache/poi/hssf/record/TextObjectRecord.java @@ -212,7 +212,7 @@ public final class TextObjectRecord extends ContinuableRecord { protected void serialize(ContinuableRecordOutput out) { serializeTXORecord(out); - if (_text.getString().length() > 0) { + if (!_text.getString().isEmpty()) { serializeTrailingRecords(out); } } diff --git a/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java index d1fe755fc3..20d3bcaf6e 100644 --- a/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java +++ b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java @@ -858,10 +858,11 @@ public final class HSSFCellStyle implements CellStyle, Duplicatable { public void cloneStyleFrom(CellStyle source) { if(source instanceof HSSFCellStyle) { this.cloneStyleFrom((HSSFCellStyle)source); - } else if (_hssfWorkbook != null) { + } else { CellUtil.cloneStyle(source, this, _hssfWorkbook); } } + public void cloneStyleFrom(HSSFCellStyle source) { // First we need to clone the extended format // record diff --git a/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFName.java b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFName.java index 75d8c0a303..60fdffa599 100644 --- a/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFName.java +++ b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFName.java @@ -180,7 +180,7 @@ public final class HSSFName implements Name { */ private static void validateName(String name) { - if (name.length() == 0) { + if (name.isEmpty()) { throw new IllegalArgumentException("Name cannot be blank"); } if (name.length() > 255) { diff --git a/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFSheet.java b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFSheet.java index bba313f57d..9e56f37714 100644 --- a/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFSheet.java +++ b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFSheet.java @@ -645,7 +645,7 @@ public final class HSSFSheet implements Sheet { } ExtendedFormatRecord xf = _book.getExFormatAt(styleIndex); - return new HSSFCellStyle(styleIndex, xf, _book); + return new HSSFCellStyle(styleIndex, xf, _workbook); } /** diff --git a/poi/src/main/java/org/apache/poi/hssf/usermodel/HeaderFooter.java b/poi/src/main/java/org/apache/poi/hssf/usermodel/HeaderFooter.java index aa883dd44e..3884462587 100644 --- a/poi/src/main/java/org/apache/poi/hssf/usermodel/HeaderFooter.java +++ b/poi/src/main/java/org/apache/poi/hssf/usermodel/HeaderFooter.java @@ -282,7 +282,7 @@ public abstract class HeaderFooter implements org.apache.poi.ss.usermodel.Header int pos; // Check we really got something to work on - if (pText == null || pText.length() == 0) { + if (pText == null || pText.isEmpty()) { return pText; } diff --git a/poi/src/main/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowColShifter.java b/poi/src/main/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowColShifter.java index 698f1d7279..dc68c77dcb 100644 --- a/poi/src/main/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowColShifter.java +++ b/poi/src/main/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowColShifter.java @@ -62,19 +62,19 @@ import static org.apache.logging.log4j.util.Unbox.box; } /** - * Update the formulas in specified row using the formula shifting policy specified by shifter + * Update the formulas in the specified row using the formula shifting policy specified by shifter * * @param row the row to update the formulas on * @param formulaShifter the formula shifting policy */ /*package*/ static void updateRowFormulas(HSSFRow row, FormulaShifter formulaShifter) { - HSSFSheet sheet = row.getSheet(); - for (Cell c : row) { - HSSFCell cell = (HSSFCell) c; - String formula = cell.getCellFormula(); - if (formula.length() > 0) { - String shiftedFormula = shiftFormula(row, formula, formulaShifter); - cell.setCellFormula(shiftedFormula); + for (Cell cell : row) { + if (cell.getCellType() == CellType.FORMULA) { + String formula = cell.getCellFormula(); + if (formula != null && !formula.isEmpty()) { + String shiftedFormula = shiftFormula(row, formula, formulaShifter); + cell.setCellFormula(shiftedFormula); + } } } } diff --git a/poi/src/main/java/org/apache/poi/poifs/eventfilesystem/POIFSReader.java b/poi/src/main/java/org/apache/poi/poifs/eventfilesystem/POIFSReader.java index b8d443aab4..4cb3ebb4e2 100644 --- a/poi/src/main/java/org/apache/poi/poifs/eventfilesystem/POIFSReader.java +++ b/poi/src/main/java/org/apache/poi/poifs/eventfilesystem/POIFSReader.java @@ -141,22 +141,23 @@ public class POIFSReader * assumed * @param name the document name * - * @throws NullPointerException if listener is null or name is - * null or empty + * @throws NullPointerException if listener is null or name is null * @throws IllegalStateException if read() has already been - * called + * called or name is empty */ - public void registerListener(final POIFSReaderListener listener, final POIFSDocumentPath path, final String name) { - if ((listener == null) || (name == null) || (name.length() == 0)) { - throw new NullPointerException(); + if (listener == null || name == null) { + throw new NullPointerException("invalid null parameter"); + } + if (name.isEmpty()) { + throw new IllegalStateException("name must not be empty"); } if (registryClosed) { - throw new IllegalStateException(); + throw new IllegalStateException("registry closed"); } - registry.registerListener(listener, (path == null) ? new POIFSDocumentPath() : path, name); + registry.registerListener(listener, path == null ? new POIFSDocumentPath() : path, name); } /** diff --git a/poi/src/main/java/org/apache/poi/poifs/filesystem/DocumentDescriptor.java b/poi/src/main/java/org/apache/poi/poifs/filesystem/DocumentDescriptor.java index b8d783638e..40ffdffaec 100644 --- a/poi/src/main/java/org/apache/poi/poifs/filesystem/DocumentDescriptor.java +++ b/poi/src/main/java/org/apache/poi/poifs/filesystem/DocumentDescriptor.java @@ -46,7 +46,7 @@ public class DocumentDescriptor { throw new NullPointerException("name must not be null"); } - if (name.length() == 0) + if (name.isEmpty()) { throw new IllegalArgumentException("name cannot be empty"); } diff --git a/poi/src/main/java/org/apache/poi/poifs/property/PropertyTable.java b/poi/src/main/java/org/apache/poi/poifs/property/PropertyTable.java index 203f23cd46..062627ae52 100644 --- a/poi/src/main/java/org/apache/poi/poifs/property/PropertyTable.java +++ b/poi/src/main/java/org/apache/poi/poifs/property/PropertyTable.java @@ -83,11 +83,14 @@ public final class PropertyTable implements BATManaged { for (ByteBuffer bb : dataSource) { // Turn it into an array - byte[] data; - if (bb.hasArray() && bb.arrayOffset() == 0 && - bb.array().length == _bigBigBlockSize.getBigBlockSize()) { - data = bb.array(); - } else { + byte[] data = null; + if (bb.hasArray() && bb.arrayOffset() == 0) { + final byte[] array = bb.array(); + if (array.length == _bigBigBlockSize.getBigBlockSize()) { + data = array; + } + } + if (data == null) { data = IOUtils.safelyAllocate(_bigBigBlockSize.getBigBlockSize(), POIFSFileSystem.getMaxRecordLength()); int toRead = data.length; diff --git a/poi/src/main/java/org/apache/poi/ss/format/CellFormatPart.java b/poi/src/main/java/org/apache/poi/ss/format/CellFormatPart.java index 990181baad..ea8eaa37b4 100644 --- a/poi/src/main/java/org/apache/poi/ss/format/CellFormatPart.java +++ b/poi/src/main/java/org/apache/poi/ss/format/CellFormatPart.java @@ -251,7 +251,7 @@ public class CellFormatPart { */ private static Color getColor(Matcher m) { String cdesc = m.group(COLOR_GROUP); - if (cdesc == null || cdesc.length() == 0) + if (cdesc == null || cdesc.isEmpty()) return null; Color c = NAMED_COLORS.get(cdesc); if (c == null) { @@ -270,7 +270,7 @@ public class CellFormatPart { */ private CellFormatCondition getCondition(Matcher m) { String mdesc = m.group(CONDITION_OPERATOR_GROUP); - if (mdesc == null || mdesc.length() == 0) + if (mdesc == null || mdesc.isEmpty()) return null; return CellFormatCondition.getInstance(m.group( CONDITION_OPERATOR_GROUP), m.group(CONDITION_VALUE_GROUP)); @@ -509,7 +509,7 @@ public class CellFormatPart { StringBuffer fmt = new StringBuffer(); while (m.find()) { String part = group(m, 0); - if (part.length() > 0) { + if (!part.isEmpty()) { String repl = partHandler.handlePart(m, part, type, fmt); if (repl == null) { switch (part.charAt(0)) { diff --git a/poi/src/main/java/org/apache/poi/ss/formula/EvaluationConditionalFormatRule.java b/poi/src/main/java/org/apache/poi/ss/formula/EvaluationConditionalFormatRule.java index 54199cbfca..3d0e441dbc 100644 --- a/poi/src/main/java/org/apache/poi/ss/formula/EvaluationConditionalFormatRule.java +++ b/poi/src/main/java/org/apache/poi/ss/formula/EvaluationConditionalFormatRule.java @@ -357,7 +357,7 @@ public class EvaluationConditionalFormatRule implements Comparable<EvaluationCon String f2 = rule.getFormula2(); ValueEval eval2 = BlankEval.instance; - if (f2 != null && f2.length() > 0) { + if (f2 != null && !f2.isEmpty()) { eval2 = unwrapEval(workbookEvaluator.evaluate(f2, ConditionalFormattingEvaluator.getRef(cell), region)); } diff --git a/poi/src/main/java/org/apache/poi/ss/formula/FormulaParser.java b/poi/src/main/java/org/apache/poi/ss/formula/FormulaParser.java index 020ae38d8c..c748231838 100644 --- a/poi/src/main/java/org/apache/poi/ss/formula/FormulaParser.java +++ b/poi/src/main/java/org/apache/poi/ss/formula/FormulaParser.java @@ -483,7 +483,7 @@ public final class FormulaParser { } else { // Is it a named range? String name = parseAsName(); - if (name.length() == 0) { + if (name.isEmpty()) { throw new FormulaParseException("Cell reference or Named Range " + "expected after sheet name at index " + _pointer + "."); } diff --git a/poi/src/main/java/org/apache/poi/ss/formula/atp/TextJoinFunction.java b/poi/src/main/java/org/apache/poi/ss/formula/atp/TextJoinFunction.java index 5e7ffe8b87..45b3ec31ac 100644 --- a/poi/src/main/java/org/apache/poi/ss/formula/atp/TextJoinFunction.java +++ b/poi/src/main/java/org/apache/poi/ss/formula/atp/TextJoinFunction.java @@ -86,7 +86,7 @@ final class TextJoinFunction implements FreeRefFunction { String textValue = OperandResolver.coerceValueToString(textArg); // If we're not ignoring empty values or if our value is not empty, add it to the list - if (!ignoreEmpty || (textValue != null && textValue.length() > 0)) { + if (!ignoreEmpty || (textValue != null && !textValue.isEmpty())) { textValues.add(textValue); } } diff --git a/poi/src/main/java/org/apache/poi/ss/formula/function/FunctionMetadataReader.java b/poi/src/main/java/org/apache/poi/ss/formula/function/FunctionMetadataReader.java index 349e62f044..4b6e6fa2e7 100644 --- a/poi/src/main/java/org/apache/poi/ss/formula/function/FunctionMetadataReader.java +++ b/poi/src/main/java/org/apache/poi/ss/formula/function/FunctionMetadataReader.java @@ -124,7 +124,7 @@ final class FunctionMetadataReader { byte returnClassCode = parseReturnTypeCode(parts[4]); byte[] parameterClassCodes = parseOperandTypeCodes(parts[5]); // 6 isVolatile - boolean hasNote = parts[7].length() > 0; + boolean hasNote = !parts[7].isEmpty(); validateFunctionName(functionName); // TODO - make POI use isVolatile @@ -134,7 +134,7 @@ final class FunctionMetadataReader { private static byte parseReturnTypeCode(String code) { - if(code.length() == 0) { + if(code.isEmpty()) { return Ptg.CLASS_REF; // happens for GETPIVOTDATA } return parseOperandTypeCode(code); diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/BaseNumberUtils.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/BaseNumberUtils.java index c39d082e10..db583f3229 100644 --- a/poi/src/main/java/org/apache/poi/ss/formula/functions/BaseNumberUtils.java +++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/BaseNumberUtils.java @@ -23,7 +23,7 @@ public class BaseNumberUtils { public static double convertToDecimal(String value, int base, int maxNumberOfPlaces) throws IllegalArgumentException { - if (value == null || value.length() == 0) { + if (value == null || value.isEmpty()) { return 0.0; } diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/Code.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/Code.java index 96f2a13204..30f2d3257f 100644 --- a/poi/src/main/java/org/apache/poi/ss/formula/functions/Code.java +++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/Code.java @@ -41,7 +41,7 @@ public class Code extends Fixed1ArgFunction { } String text = OperandResolver.coerceValueToString(veText1); - if (text.length() == 0) { + if (text.isEmpty()) { return ErrorEval.VALUE_INVALID; } diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/Complex.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/Complex.java index a10c54a758..a4fac2685e 100644 --- a/poi/src/main/java/org/apache/poi/ss/formula/functions/Complex.java +++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/Complex.java @@ -90,7 +90,7 @@ public class Complex extends Var2or3ArgFunction implements FreeRefFunction { } String suffixValue = OperandResolver.coerceValueToString(suffix); - if (suffixValue.length() == 0) { + if (suffixValue.isEmpty()) { suffixValue = DEFAULT_SUFFIX; } if (suffixValue.equals(DEFAULT_SUFFIX.toUpperCase(Locale.ROOT)) || diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/Countif.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/Countif.java index 93fdf033a4..ecb57e5e1a 100644 --- a/poi/src/main/java/org/apache/poi/ss/formula/functions/Countif.java +++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/Countif.java @@ -339,7 +339,7 @@ public final class Countif extends Fixed2ArgFunction { switch(getCode()) { case CmpOp.NONE: case CmpOp.EQ: - return _value.length() == 0; + return _value.isEmpty(); case CmpOp.NE: // pred '<>' matches empty string but not blank cell // pred '<>ABC' matches blank and 'not ABC' diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/Imaginary.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/Imaginary.java index 012aacdd01..f478c95d5b 100644 --- a/poi/src/main/java/org/apache/poi/ss/formula/functions/Imaginary.java +++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/Imaginary.java @@ -74,7 +74,7 @@ public class Imaginary extends Fixed1ArgFunction implements FreeRefFunction { String imaginaryGroup = m.group(5); boolean hasImaginaryPart = imaginaryGroup.equals("i") || imaginaryGroup.equals("j"); - if (imaginaryGroup.length() == 0) { + if (imaginaryGroup.isEmpty()) { return new StringEval(String.valueOf(0)); } diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/Rept.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/Rept.java index c3be177ce3..33b2c46366 100644 --- a/poi/src/main/java/org/apache/poi/ss/formula/functions/Rept.java +++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/Rept.java @@ -40,7 +40,6 @@ import org.apache.poi.ss.formula.eval.ValueEval; */ public class Rept extends Fixed2ArgFunction { - @Override public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval text, ValueEval number_times) { @@ -64,7 +63,7 @@ public class Rept extends Fixed2ArgFunction { strb.append(strText1); } - if (strb.toString().length() > 32767) { + if (strb.length() > 32767) { return ErrorEval.VALUE_INVALID; } diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/Value.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/Value.java index 451f0a2fab..87edae4b0e 100644 --- a/poi/src/main/java/org/apache/poi/ss/formula/functions/Value.java +++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/Value.java @@ -152,7 +152,7 @@ public final class Value extends Fixed1ArgFunction implements ArrayFunction { foundPercentage = true; break; } - if (remainingTextTrimmed.length() > 0) { + if (!remainingTextTrimmed.isEmpty()) { // intervening spaces not allowed once the digits start return null; } diff --git a/poi/src/main/java/org/apache/poi/ss/usermodel/DateUtil.java b/poi/src/main/java/org/apache/poi/ss/usermodel/DateUtil.java index 8018267a26..401b1c47a1 100644 --- a/poi/src/main/java/org/apache/poi/ss/usermodel/DateUtil.java +++ b/poi/src/main/java/org/apache/poi/ss/usermodel/DateUtil.java @@ -70,7 +70,7 @@ public class DateUtil { private static final Pattern date_ptrn2 = Pattern.compile("^\\[[a-zA-Z]+]"); private static final Pattern date_ptrn3a = Pattern.compile("[yYmMdDhHsS]"); // add "\u5e74 \u6708 \u65e5" for Chinese/Japanese date format:2017 \u5e74 2 \u6708 7 \u65e5 - private static final Pattern date_ptrn3b = Pattern.compile("^[\\[\\]yYmMdDhHsS\\-T/\u5e74\u6708\u65e5,. :\"\\\\]+0*[ampAMP/]*$"); + private static final Pattern date_ptrn3b = Pattern.compile("^[\\[\\]yYmMdDhHsS\\-T/\u5e74\u6708\u65e5,. :\"\\\\]+0* ?[ampAMP/]*$"); // elapsed time patterns: [h],[m] and [s] private static final Pattern date_ptrn4 = Pattern.compile("^\\[([hH]+|[mM]+|[sS]+)]"); @@ -548,6 +548,7 @@ public class DateUtil { // avoid re-checking DateUtil.isADateFormat(int, String) if a given format // string represents a date format if the same string is passed multiple times. // see https://issues.apache.org/bugzilla/show_bug.cgi?id=55611 + private static boolean maintainCache = true; private static final ThreadLocal<Integer> lastFormatIndex = ThreadLocal.withInitial(() -> -1); private static final ThreadLocal<String> lastFormatString = new ThreadLocal<>(); private static final ThreadLocal<Boolean> lastCachedResult = new ThreadLocal<>(); @@ -561,22 +562,24 @@ public class DateUtil { } private static boolean isCached(String formatString, int formatIndex) { - return formatIndex == lastFormatIndex.get() + return maintainCache && formatIndex == lastFormatIndex.get() && formatString.equals(lastFormatString.get()); } private static void cache(String formatString, int formatIndex, boolean cached) { - if (formatString == null || "".equals(formatString)) { - lastFormatString.remove(); - } else { - lastFormatString.set(formatString); - } - if (formatIndex == -1) { - lastFormatIndex.remove(); - } else { - lastFormatIndex.set(formatIndex); + if (maintainCache) { + if (formatString == null || "".equals(formatString)) { + lastFormatString.remove(); + } else { + lastFormatString.set(formatString); + } + if (formatIndex == -1) { + lastFormatIndex.remove(); + } else { + lastFormatIndex.set(formatIndex); + } + lastCachedResult.set(cached); } - lastCachedResult.set(cached); } /** @@ -624,7 +627,7 @@ public class DateUtil { } // If we didn't get a real string, don't even cache it as we can always find this out quickly - if(formatString == null || formatString.length() == 0) { + if(formatString == null || formatString.isEmpty()) { return false; } @@ -997,4 +1000,18 @@ public class DateUtil { return tm; } + + /** + * Enable or disable the thread-local cache for date format checking. + * If enabled, the date format checking will be cached per thread, + * which can improve performance when checking the same format multiple times. + * If disabled, the cache will not be used and each check will be performed independently. + * + * @param enable true to enable the cache, false to disable it (enabled, by default) + * @since POI 5.4.2 + */ + public static void enableThreadLocalCache(final boolean enable) { + // enable thread-local cache for date format checking + maintainCache = enable; + } } diff --git a/poi/src/main/java/org/apache/poi/ss/util/AreaReference.java b/poi/src/main/java/org/apache/poi/ss/util/AreaReference.java index fc45d81b4a..fd1cb4ff51 100644 --- a/poi/src/main/java/org/apache/poi/ss/util/AreaReference.java +++ b/poi/src/main/java/org/apache/poi/ss/util/AreaReference.java @@ -326,7 +326,7 @@ public class AreaReference { String currentSegment = ""; StringTokenizer st = new StringTokenizer(reference, ","); while(st.hasMoreTokens()) { - if (currentSegment.length() > 0) { + if (!currentSegment.isEmpty()) { currentSegment += ","; } currentSegment += st.nextToken(); @@ -336,7 +336,7 @@ public class AreaReference { currentSegment = ""; } } - if (currentSegment.length() > 0) { + if (!currentSegment.isEmpty()) { results.add(currentSegment); } return results.toArray(new String[0]); diff --git a/poi/src/main/java/org/apache/poi/ss/util/CellReference.java b/poi/src/main/java/org/apache/poi/ss/util/CellReference.java index 91a97b7edf..da1202c283 100644 --- a/poi/src/main/java/org/apache/poi/ss/util/CellReference.java +++ b/poi/src/main/java/org/apache/poi/ss/util/CellReference.java @@ -116,22 +116,22 @@ public class CellReference implements GenericRecord { _sheetName = parts.sheetName; String colRef = parts.colRef; - _isColAbs = (colRef.length() > 0) && colRef.charAt(0) == '$'; + _isColAbs = (!colRef.isEmpty()) && colRef.charAt(0) == '$'; if (_isColAbs) { colRef = colRef.substring(1); } - if (colRef.length() == 0) { + if (colRef.isEmpty()) { _colIndex = -1; } else { _colIndex = convertColStringToIndex(colRef); } String rowRef=parts.rowRef; - _isRowAbs = (rowRef.length() > 0) && rowRef.charAt(0) == '$'; + _isRowAbs = (!rowRef.isEmpty()) && rowRef.charAt(0) == '$'; if (_isRowAbs) { rowRef = rowRef.substring(1); } - if (rowRef.length() == 0) { + if (rowRef.isEmpty()) { _rowIndex = -1; } else { // throws NumberFormatException if rowRef is not convertible to an int diff --git a/poi/src/main/java/org/apache/poi/ss/util/CellUtil.java b/poi/src/main/java/org/apache/poi/ss/util/CellUtil.java index d0ee0db55b..b2879fb7fd 100644 --- a/poi/src/main/java/org/apache/poi/ss/util/CellUtil.java +++ b/poi/src/main/java/org/apache/poi/ss/util/CellUtil.java @@ -280,6 +280,7 @@ public final class CellUtil { map.put(LEFT_BORDER_COLOR, CellPropertyType.LEFT_BORDER_COLOR); map.put(RIGHT_BORDER_COLOR, CellPropertyType.RIGHT_BORDER_COLOR); map.put(TOP_BORDER_COLOR, CellPropertyType.TOP_BORDER_COLOR); + map.put(DATA_FORMAT, CellPropertyType.DATA_FORMAT); map.put(FILL_BACKGROUND_COLOR, CellPropertyType.FILL_BACKGROUND_COLOR); map.put(FILL_FOREGROUND_COLOR, CellPropertyType.FILL_FOREGROUND_COLOR); map.put(FILL_BACKGROUND_COLOR_COLOR, CellPropertyType.FILL_BACKGROUND_COLOR_COLOR); @@ -289,10 +290,11 @@ public final class CellUtil { map.put(HIDDEN, CellPropertyType.HIDDEN); map.put(INDENTION, CellPropertyType.INDENTION); map.put(LOCKED, CellPropertyType.LOCKED); + map.put(QUOTE_PREFIXED, CellPropertyType.QUOTE_PREFIXED); map.put(ROTATION, CellPropertyType.ROTATION); - map.put(VERTICAL_ALIGNMENT, CellPropertyType.VERTICAL_ALIGNMENT); map.put(SHRINK_TO_FIT, CellPropertyType.SHRINK_TO_FIT); - map.put(QUOTE_PREFIXED, CellPropertyType.QUOTE_PREFIXED); + map.put(VERTICAL_ALIGNMENT, CellPropertyType.VERTICAL_ALIGNMENT); + map.put(WRAP_TEXT, CellPropertyType.WRAP_TEXT); namePropertyMap = Collections.unmodifiableMap(map); } @@ -572,7 +574,7 @@ public final class CellUtil { @Deprecated @Removal(version = "7.0.0") public static void setCellStyleProperties(Cell cell, Map<String, Object> properties) { - EnumMap<CellPropertyType, Object> strPropMap = new EnumMap<>(CellPropertyType.class); + final EnumMap<CellPropertyType, Object> strPropMap = new EnumMap<>(CellPropertyType.class); properties.forEach((k, v) -> strPropMap.put(namePropertyMap.get(k), v)); setCellStyleProperties(cell, strPropMap, false); } @@ -685,10 +687,16 @@ public final class CellUtil { * @param cell The cell that is to be changed. * @param property The name of the property that is to be changed. * @param propertyValue The value of the property that is to be changed. - * + * @throws NullPointerException if {@code cell} or {@code property} is null * @since POI 5.4.0 */ public static void setCellStyleProperty(Cell cell, CellPropertyType property, Object propertyValue) { + if (cell == null) { + throw new NullPointerException("Cell must not be null"); + } + if (property == null) { + throw new NullPointerException("CellPropertyType must not be null"); + } boolean disableNullColorCheck = false; final Map<CellPropertyType, Object> propMap; if (CellPropertyType.FILL_FOREGROUND_COLOR_COLOR.equals(property) && propertyValue == null) { diff --git a/poi/src/main/java/org/apache/poi/util/DefaultTempFileCreationStrategy.java b/poi/src/main/java/org/apache/poi/util/DefaultTempFileCreationStrategy.java index 9b2e711ed4..584f7dec4c 100644 --- a/poi/src/main/java/org/apache/poi/util/DefaultTempFileCreationStrategy.java +++ b/poi/src/main/java/org/apache/poi/util/DefaultTempFileCreationStrategy.java @@ -52,6 +52,9 @@ public class DefaultTempFileCreationStrategy implements TempFileCreationStrategy /** The directory where the temporary files will be created (<code>null</code> to use the default directory). */ private volatile File dir; + /** The directory where that was passed to the constructor (<code>null</code> to use the default directory). */ + private final File initDir; + /** The lock to make dir initialized only once. */ private final Lock dirLock = new ReentrantLock(); @@ -65,14 +68,23 @@ public class DefaultTempFileCreationStrategy implements TempFileCreationStrategy } /** - * Creates the strategy allowing to set the + * Creates the strategy allowing to set a custom directory for the temporary files. + * <p> + * If you provide a non-null dir as input, it must be a directory (if it already exists). + * Since POI 5.4.2, this is checked at construction time. In previous versions, it was checked + * at the first call to {@link #createTempFile(String, String)} or {@link #createTempDirectory(String)}. + * </p> * * @param dir The directory where the temporary files will be created (<code>null</code> to use the default directory). - * + * @throws IllegalArgumentException if the provided directory does not exist or is not a directory * @see Files#createTempFile(Path, String, String, FileAttribute[]) */ public DefaultTempFileCreationStrategy(File dir) { + this.initDir = dir; this.dir = dir; + if (dir != null && dir.exists() && !dir.isDirectory()) { + throw new IllegalArgumentException("The provided file is not a directory: " + dir); + } } @Override @@ -117,7 +129,11 @@ public class DefaultTempFileCreationStrategy implements TempFileCreationStrategy } protected Path getPOIFilesDirectoryPath() throws IOException { - return Paths.get(getJavaIoTmpDir(), POIFILES); + if (initDir == null) { + return Paths.get(getJavaIoTmpDir(), POIFILES); + } else { + return initDir.toPath(); + } } // Create our temp dir only once by double-checked locking diff --git a/poi/src/main/java/org/apache/poi/util/TempFile.java b/poi/src/main/java/org/apache/poi/util/TempFile.java index 46bf8ccc8b..2b668dc885 100644 --- a/poi/src/main/java/org/apache/poi/util/TempFile.java +++ b/poi/src/main/java/org/apache/poi/util/TempFile.java @@ -19,6 +19,8 @@ package org.apache.poi.util; import java.io.File; import java.io.IOException; +import java.util.Objects; +import java.util.function.Supplier; /** * Interface for creating temporary files. Collects them all into one directory by default. @@ -27,7 +29,14 @@ public final class TempFile { /** The strategy used by {@link #createTempFile(String, String)} to create the temporary files. */ private static TempFileCreationStrategy strategy = new DefaultTempFileCreationStrategy(); - /** Define a constant for this property as it is sometimes mistypes as "tempdir" otherwise */ + /** If set for the thread, this is used instead of the strategy variable above. */ + private static final ThreadLocal<TempFileCreationStrategy> threadLocalStrategy = new ThreadLocal<>(); + static { + // allow to clear all thread-locals via ThreadLocalUtil + ThreadLocalUtil.registerCleaner(threadLocalStrategy::remove); + } + + /** Define a constant for this property as it is sometimes mistyped as "tempdir" otherwise */ public static final String JAVA_IO_TMPDIR = "java.io.tmpdir"; private TempFile() { @@ -47,6 +56,21 @@ public final class TempFile { } TempFile.strategy = strategy; } + + /** + * Configures the strategy used by {@link #createTempFile(String, String)} to create the temporary files. + * + * @param strategy The new strategy to be used to create the temporary files for this thread. + * <code>null</code> can be used to reset the strategy for this thread to the default one. + * @since POI 5.4.2 + */ + public static void setThreadLocalTempFileCreationStrategy(TempFileCreationStrategy strategy) { + if (strategy == null) { + threadLocalStrategy.remove(); + } else { + threadLocalStrategy.set(strategy); + } + } /** * Creates a new and empty temporary file. By default, files are collected into one directory and are not @@ -64,10 +88,44 @@ public final class TempFile { * @throws IOException If no temporary file could be created. */ public static File createTempFile(String prefix, String suffix) throws IOException { - return strategy.createTempFile(prefix, suffix); + return getStrategy().createTempFile(prefix, suffix); } public static File createTempDirectory(String name) throws IOException { - return strategy.createTempDirectory(name); + return getStrategy().createTempDirectory(name); + } + + /** + * Executes the given task ensuring that POI will use the given temp file creation strategy + * within the scope of the given task. The change of strategy is not visible to other threads, + * and the previous strategy is restored after the task completed (normally or exceptionally). + * + * @param newStrategy the temp file strategy to be used in the scope of the given task + * @param task the task to be executed with the given temp file strategy + * @return the result of the given task + * + * @since POI 5.4.2 + */ + public static <R> R withStrategy(TempFileCreationStrategy newStrategy, Supplier<? extends R> task) { + Objects.requireNonNull(newStrategy, "newStrategy"); + Objects.requireNonNull(task, "task"); + + TempFileCreationStrategy oldStrategy = threadLocalStrategy.get(); + try { + threadLocalStrategy.set(newStrategy); + return task.get(); + } finally { + setThreadLocalTempFileCreationStrategy(oldStrategy); + } + } + + private static TempFileCreationStrategy getStrategy() { + final TempFileCreationStrategy s = threadLocalStrategy.get(); + if (s == null) { + threadLocalStrategy.remove(); + return strategy; + } else { + return s; + } } } diff --git a/poi/src/test/java/org/apache/poi/POIDataSamples.java b/poi/src/test/java/org/apache/poi/POIDataSamples.java index e15843f377..f976cd0c91 100644 --- a/poi/src/test/java/org/apache/poi/POIDataSamples.java +++ b/poi/src/test/java/org/apache/poi/POIDataSamples.java @@ -169,7 +169,7 @@ public final class POIDataSamples { + "' not found in data dir '" + _resolvedDataDir.getAbsolutePath() + "'"); } try { - if(sampleFileName.length() > 0) { + if(!sampleFileName.isEmpty()) { String fn = sampleFileName; if(fn.indexOf('/') > 0) { fn = fn.substring(fn.indexOf('/')+1); diff --git a/poi/src/test/java/org/apache/poi/hssf/eventusermodel/TestFormatTrackingHSSFListener.java b/poi/src/test/java/org/apache/poi/hssf/eventusermodel/TestFormatTrackingHSSFListener.java index 2a99b3b395..3ef75d7f3d 100644 --- a/poi/src/test/java/org/apache/poi/hssf/eventusermodel/TestFormatTrackingHSSFListener.java +++ b/poi/src/test/java/org/apache/poi/hssf/eventusermodel/TestFormatTrackingHSSFListener.java @@ -16,7 +16,9 @@ ==================================================================== */ package org.apache.poi.hssf.eventusermodel; + import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -96,7 +98,7 @@ final class TestFormatTrackingHSSFListener { // Should always give us a string String s = listener.formatNumberDateCell(cvr); assertNotNull(s); - assertTrue(s.length() > 0); + assertNotEquals(0, s.length()); } } diff --git a/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java b/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java index 2949dbeeee..61cd1aba69 100644 --- a/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java +++ b/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java @@ -27,6 +27,7 @@ import java.util.TimeZone; import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.model.InternalWorkbook; +import org.apache.poi.ss.usermodel.DataFormatter; import org.apache.poi.ss.usermodel.DateUtil; import org.apache.poi.util.LocaleUtil; import org.junit.jupiter.api.AfterAll; @@ -60,7 +61,7 @@ class TestHSSFDateUtil { HSSFWorkbook workbook = HSSFTestDataSamples.openSampleWorkbook("DateFormats.xls"); HSSFSheet sheet = workbook.getSheetAt(0); - InternalWorkbook wb = workbook.getWorkbook(); + InternalWorkbook wb = workbook.getWorkbook(); assertNotNull(wb); HSSFRow row; @@ -115,4 +116,27 @@ class TestHSSFDateUtil { workbook.close(); } + + @Test + void testIsADateFormat() throws IOException { + try (HSSFWorkbook workbook = new HSSFWorkbook()) { + HSSFSheet sheet = workbook.createSheet(); + HSSFRow row = sheet.createRow(0); + HSSFCell cell = row.createCell(0); + cell.setCellValue(45825.5); // 2025-06-17 (midday) + HSSFCellStyle style = workbook.createCellStyle(); + style.setDataFormat(workbook.createDataFormat().getFormat("DD MMMM, YYYY hh:mm:ss.000 AM/PM")); + cell.setCellStyle(style); + DateUtil.enableThreadLocalCache(false); + try { + assertTrue(DateUtil.isCellDateFormatted(cell), "cell is date formatted?"); + DataFormatter formatter = new DataFormatter(); + String formattedValue = formatter.formatCellValue(cell); + assertEquals("17 June, 2025 12:00:00.000 PM", formattedValue); + } finally { + DateUtil.enableThreadLocalCache(true); + } + } + } + } diff --git a/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFRowCopyRowFrom.java b/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFRowCopyRowFrom.java new file mode 100644 index 0000000000..67dcc38f46 --- /dev/null +++ b/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFRowCopyRowFrom.java @@ -0,0 +1,130 @@ +/* ==================================================================== + 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.usermodel; + +import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellCopyContext; +import org.apache.poi.ss.usermodel.CellCopyPolicy; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.CreationHelper; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Iterator; + +public class TestHSSFRowCopyRowFrom { + @Test + void testCopyFrom() throws IOException { + CellCopyPolicy cellCopyPolicy = new CellCopyPolicy.Builder() + .cellFormula(false) // NOTE: setting to false allows for copying the evaluated formula value. + .cellStyle(CellCopyPolicy.DEFAULT_COPY_CELL_STYLE_POLICY) + .cellValue(CellCopyPolicy.DEFAULT_COPY_CELL_VALUE_POLICY) + .condenseRows(CellCopyPolicy.DEFAULT_CONDENSE_ROWS_POLICY) + .copyHyperlink(CellCopyPolicy.DEFAULT_COPY_HYPERLINK_POLICY) + .mergeHyperlink(CellCopyPolicy.DEFAULT_MERGE_HYPERLINK_POLICY) + .mergedRegions(CellCopyPolicy.DEFAULT_COPY_MERGED_REGIONS_POLICY) + .rowHeight(CellCopyPolicy.DEFAULT_COPY_ROW_HEIGHT_POLICY) + .build(); + + final LocalDateTime localDateTime = LocalDateTime.of(2023, 1, 1, 0, 0, 0); + final LocalDateTime nonValidExcelDate = LocalDateTime.of(1899, 12, 31, 0, 0, 0); + final Object[][] data = { + {"transaction_id", "transaction_date", "transaction_time"}, + {75, localDateTime, nonValidExcelDate.plusHours(9).plusMinutes(53).plusSeconds(44).toLocalTime()}, + {78, localDateTime, nonValidExcelDate.plusHours(9).plusMinutes(55).plusSeconds(16).toLocalTime()} + }; + + final UnsynchronizedByteArrayOutputStream workbookOutputStream = + UnsynchronizedByteArrayOutputStream.builder().get(); + try (Workbook workbook = new HSSFWorkbook()) { + final Sheet sheet = workbook.createSheet("SomeSheetName"); + populateSheet(sheet, data); + setCellStyles(sheet, workbook); + workbook.write(workbookOutputStream); + } + + try (HSSFWorkbook originalWorkbook = new HSSFWorkbook(workbookOutputStream.toInputStream())) { + final Iterator<Sheet> originalSheetsIterator = originalWorkbook.sheetIterator(); + final CellCopyContext cellCopyContext = new CellCopyContext(); + + while (originalSheetsIterator.hasNext()) { + final HSSFSheet originalSheet = (HSSFSheet) originalSheetsIterator.next(); + final String originalSheetName = originalSheet.getSheetName(); + final Iterator<Row> originalRowsIterator = originalSheet.rowIterator(); + + try (HSSFWorkbook newWorkbook = new HSSFWorkbook()) { + final HSSFSheet newSheet = newWorkbook.createSheet(originalSheetName); + while (originalRowsIterator.hasNext()) { + HSSFRow originalRow = (HSSFRow) originalRowsIterator.next(); + HSSFRow newRow = newSheet.createRow(originalRow.getRowNum()); + newRow.copyRowFrom(originalRow, cellCopyPolicy, cellCopyContext); + } + } + } + } + } + + private static void populateSheet(Sheet sheet, Object[][] data) { + int rowCount = 0; + for (Object[] dataRow : data) { + Row row = sheet.createRow(rowCount++); + int columnCount = 0; + + for (Object field : dataRow) { + Cell cell = row.createCell(columnCount++); + if (field instanceof String) { + cell.setCellValue((String) field); + } else if (field instanceof Integer) { + cell.setCellValue((Integer) field); + } else if (field instanceof Long) { + cell.setCellValue((Long) field); + } else if (field instanceof LocalDateTime) { + cell.setCellValue((LocalDateTime) field); + } else if (field instanceof LocalTime) { + cell.setCellValue(DateUtil.convertTime(DateTimeFormatter.ISO_LOCAL_TIME.format((LocalTime) field))); + } + } + } + } + + void setCellStyles(Sheet sheet, Workbook workbook) { + CreationHelper creationHelper = workbook.getCreationHelper(); + CellStyle dayMonthYearCellStyle = workbook.createCellStyle(); + dayMonthYearCellStyle.setDataFormat(creationHelper.createDataFormat().getFormat("dd/mm/yyyy")); + CellStyle hourMinuteSecond = workbook.createCellStyle(); + hourMinuteSecond.setDataFormat((short) 21); // 21 represents format h:mm:ss + for (int rowNum = sheet.getFirstRowNum() + 1; rowNum < sheet.getLastRowNum() + 1; rowNum++) { + Row row = sheet.getRow(rowNum); + row.getCell(1).setCellStyle(dayMonthYearCellStyle); + row.getCell(2).setCellStyle(hourMinuteSecond); + } + } +} diff --git a/poi/src/test/java/org/apache/poi/ss/formula/function/ExcelCetabFunctionExtractor.java b/poi/src/test/java/org/apache/poi/ss/formula/function/ExcelCetabFunctionExtractor.java index 87c0dcd1f3..3d6f525d2b 100644 --- a/poi/src/test/java/org/apache/poi/ss/formula/function/ExcelCetabFunctionExtractor.java +++ b/poi/src/test/java/org/apache/poi/ss/formula/function/ExcelCetabFunctionExtractor.java @@ -149,7 +149,7 @@ public final class ExcelCetabFunctionExtractor { public void addFunction(int funcIx, boolean hasFootnote, String funcName, int minParams, int maxParams, String returnClass, String paramClasses, String volatileFlagStr) { - boolean isVolatile = volatileFlagStr.length() > 0; + boolean isVolatile = !volatileFlagStr.isEmpty(); Integer funcIxKey = Integer.valueOf(funcIx); if(!_groupFunctionIndexes.add(funcIxKey)) { diff --git a/poi/src/test/java/org/apache/poi/ss/formula/function/ExcelFileFormatDocFunctionExtractor.java b/poi/src/test/java/org/apache/poi/ss/formula/function/ExcelFileFormatDocFunctionExtractor.java index a900169822..11dca94104 100644 --- a/poi/src/test/java/org/apache/poi/ss/formula/function/ExcelFileFormatDocFunctionExtractor.java +++ b/poi/src/test/java/org/apache/poi/ss/formula/function/ExcelFileFormatDocFunctionExtractor.java @@ -171,7 +171,7 @@ public final class ExcelFileFormatDocFunctionExtractor { public void addFunction(int funcIx, boolean hasFootnote, String funcName, int minParams, int maxParams, String returnClass, String paramClasses, String volatileFlagStr) { - boolean isVolatile = volatileFlagStr.length() > 0; + boolean isVolatile = !volatileFlagStr.isEmpty(); Integer funcIxKey = funcIx; if(!_groupFunctionIndexes.add(funcIxKey)) { diff --git a/poi/src/test/java/org/apache/poi/ss/util/TestCellUtil.java b/poi/src/test/java/org/apache/poi/ss/util/TestCellUtil.java new file mode 100644 index 0000000000..b7defbef99 --- /dev/null +++ b/poi/src/test/java/org/apache/poi/ss/util/TestCellUtil.java @@ -0,0 +1,36 @@ +/* ==================================================================== + 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.ss.util; + +import org.apache.poi.ss.usermodel.CellPropertyType; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test for CellUtil constants + */ +class TestCellUtil { + @Test + void testNamePropertyMap() { + Arrays.stream(CellPropertyType.values()).forEach(cellPropertyType -> + assertTrue(CellUtil.namePropertyMap.containsValue(cellPropertyType), + "missing " + cellPropertyType)); + } +} diff --git a/poi/src/test/java/org/apache/poi/util/DefaultTempFileCreationStrategyTest.java b/poi/src/test/java/org/apache/poi/util/DefaultTempFileCreationStrategyTest.java index 4e4f6b779e..d60e4da8ac 100644 --- a/poi/src/test/java/org/apache/poi/util/DefaultTempFileCreationStrategyTest.java +++ b/poi/src/test/java/org/apache/poi/util/DefaultTempFileCreationStrategyTest.java @@ -20,11 +20,14 @@ package org.apache.poi.util; import static org.apache.poi.util.DefaultTempFileCreationStrategy.DELETE_FILES_ON_EXIT; import static org.apache.poi.util.DefaultTempFileCreationStrategy.POIFILES; import static org.apache.poi.util.TempFile.JAVA_IO_TMPDIR; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.AfterEach; @@ -59,16 +62,62 @@ class DefaultTempFileCreationStrategyTest { checkGetFile(strategy); } - private static void checkGetFile(DefaultTempFileCreationStrategy strategy) throws IOException { - File file = strategy.createTempFile("POITest", ".tmp"); + @Test + void testProvidedDir() throws IOException { + DefaultTempFileCreationStrategy parentStrategy = new DefaultTempFileCreationStrategy(); + File dir = parentStrategy.createTempDirectory("testProvidedDir"); + assertNotNull(dir, "Failed to create temp directory"); try { - assertTrue(file.getParentFile().exists(), - "Failed for " + file.getParentFile()); + assertTrue(Files.isDirectory(dir.toPath()), "File is not a directory: " + dir); + DefaultTempFileCreationStrategy testStrategy = new DefaultTempFileCreationStrategy(dir); + checkGetFileAndPath(testStrategy, dir.toPath()); + } finally { + // Clean up the directory after the test + FileUtils.deleteDirectory(dir); + } + } - assertTrue(file.exists(), - "Failed for " + file); + @Test + void testProvidedDirThreadLocal() throws IOException { + DefaultTempFileCreationStrategy parentStrategy = new DefaultTempFileCreationStrategy(); + File dir = parentStrategy.createTempDirectory("testProvidedDir"); + assertNotNull(dir, "Failed to create temp directory"); + try { + assertTrue(Files.isDirectory(dir.toPath()), "File is not a directory: " + dir); + DefaultTempFileCreationStrategy testStrategy = new DefaultTempFileCreationStrategy(dir); + TempFile.setThreadLocalTempFileCreationStrategy(testStrategy); + checkGetFileAndPath(dir.toPath()); } finally { - assertTrue(file.delete()); + // Clean up the directory after the test + FileUtils.deleteDirectory(dir); + TempFile.setThreadLocalTempFileCreationStrategy(null); + } + } + + @Test + void testProvidedDirNotExists() throws IOException { + DefaultTempFileCreationStrategy parentStrategy = new DefaultTempFileCreationStrategy(); + File dir = parentStrategy.createTempDirectory("testProvidedDir"); + assertNotNull(dir, "Failed to create temp directory"); + assertTrue(dir.delete(), "directory not deleted: " + dir); + try { + DefaultTempFileCreationStrategy testStrategy = new DefaultTempFileCreationStrategy(dir); + checkGetFileAndPath(testStrategy, dir.toPath()); + } finally { + // Clean up the directory after the test + FileUtils.deleteDirectory(dir); + } + } + + @Test + void testProvidedDirIsActuallyAPlainFile() throws IOException { + DefaultTempFileCreationStrategy parentStrategy = new DefaultTempFileCreationStrategy(); + File dir = parentStrategy.createTempFile("test123", ".tmp"); + assertNotNull(dir, "Failed to create temp file"); + try { + assertThrows(IllegalArgumentException.class, () -> new DefaultTempFileCreationStrategy(dir)); + } finally { + dir.delete(); } } @@ -106,10 +155,11 @@ class DefaultTempFileCreationStrategyTest { } @Test - void testCustomDir() throws IOException { + void testCustomDirExists() throws IOException { File dirTest = File.createTempFile("POITest", ".dir"); try { assertTrue(dirTest.delete()); + assertTrue(dirTest.mkdir()); DefaultTempFileCreationStrategy strategy = new DefaultTempFileCreationStrategy(dirTest); checkGetFile(strategy); @@ -119,11 +169,12 @@ class DefaultTempFileCreationStrategyTest { } @Test - void testCustomDirExists() throws IOException { + void testCustomDirAndPoiFilesExists() throws IOException { File dirTest = File.createTempFile("POITest", ".dir"); try { assertTrue(dirTest.delete()); assertTrue(dirTest.mkdir()); + assertTrue(new File(dirTest, POIFILES).mkdir()); DefaultTempFileCreationStrategy strategy = new DefaultTempFileCreationStrategy(dirTest); checkGetFile(strategy); @@ -132,18 +183,35 @@ class DefaultTempFileCreationStrategyTest { } } - @Test - void testCustomDirAndPoiFilesExists() throws IOException { - File dirTest = File.createTempFile("POITest", ".dir"); + private static void checkGetFile(DefaultTempFileCreationStrategy strategy) throws IOException { + checkGetFileAndPath(strategy, null); + } + + private static void checkGetFileAndPath(DefaultTempFileCreationStrategy strategy, + Path path) throws IOException { + File file = strategy.createTempFile("POITest", ".tmp"); + testFileAndPath(file, path); + } + + private static void checkGetFileAndPath(Path path) throws IOException { + File file = TempFile.createTempFile("POITest", ".tmp"); + testFileAndPath(file, path); + } + + private static void testFileAndPath(File file, Path path) throws IOException { try { - assertTrue(dirTest.delete()); - assertTrue(dirTest.mkdir()); - assertTrue(new File(dirTest, POIFILES).mkdir()); + if (path != null) { + assertTrue(file.toPath().startsWith(path), + "File path does not start with expected path: " + path); + } - DefaultTempFileCreationStrategy strategy = new DefaultTempFileCreationStrategy(dirTest); - checkGetFile(strategy); + assertTrue(file.getParentFile().exists(), + "Failed for " + file.getParentFile()); + + assertTrue(file.exists(), + "Failed for " + file); } finally { - FileUtils.deleteDirectory(dirTest); + assertTrue(file.delete()); } } -}
\ No newline at end of file +} diff --git a/poi/src/test/java/org/apache/poi/util/TestThreadLocalTempFile.java b/poi/src/test/java/org/apache/poi/util/TestThreadLocalTempFile.java new file mode 100644 index 0000000000..86161e59ac --- /dev/null +++ b/poi/src/test/java/org/apache/poi/util/TestThreadLocalTempFile.java @@ -0,0 +1,68 @@ +/* ==================================================================== + 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.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.io.TempDir; + +class TestThreadLocalTempFile { + @AfterEach + void tearDown() { + TempFile.setTempFileCreationStrategy(new DefaultTempFileCreationStrategy()); + } + + @RepeatedTest(2) // Repeat it to ensure testing the case + void testThreadLocalStrategy(@TempDir Path tmpDir) { + Path rootDir = tmpDir.toAbsolutePath().normalize(); + Path globalTmpDir = rootDir.resolve("global-tmp-dir"); + Path localTmpDir1 = rootDir.resolve("local-tmp-dir1"); + Path localTmpDir2 = rootDir.resolve("local-tmp-dir2"); + + TempFile.setTempFileCreationStrategy(new DefaultTempFileCreationStrategy(globalTmpDir.toFile())); + assertTempFileIn(globalTmpDir); + + String result = TempFile.withStrategy(new DefaultTempFileCreationStrategy(localTmpDir1.toFile()), () -> { + assertTempFileIn(localTmpDir1); + String nestedResult = TempFile.withStrategy(new DefaultTempFileCreationStrategy(localTmpDir2.toFile()), () -> { + assertTempFileIn(localTmpDir2); + return "nested-test-result"; + }); + assertTempFileIn(localTmpDir1); + return "my-test-result-" + nestedResult; + }); + assertTempFileIn(globalTmpDir); + assertEquals("my-test-result-nested-test-result", result); + } + + private static void assertTempFileIn(Path expectedDir) { + Path tempFile; + try { + tempFile = TempFile.createTempFile("tmp-prefix", ".tmp").toPath(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + assertTrue(tempFile.startsWith(expectedDir), tempFile.toString()); + } +} diff --git a/test-data/slideshow/bug69697.ppt b/test-data/slideshow/bug69697.ppt Binary files differnew file mode 100644 index 0000000000..ff2cdcfbb8 --- /dev/null +++ b/test-data/slideshow/bug69697.ppt |