diff options
155 files changed, 2229 insertions, 653 deletions
diff --git a/build.gradle b/build.gradle index 482a717567..d3f3d5a490 100644 --- a/build.gradle +++ b/build.gradle @@ -31,13 +31,13 @@ buildscript { plugins { id 'base' - id 'com.dorongold.task-tree' version '2.1.1' + 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.0.27' - id 'de.thetaphi.forbiddenapis' version '3.8' + 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.2.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.0") + 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.13' + gradleVersion = '8.14.2' } group = 'org.apache.poi' @@ -89,7 +89,7 @@ allprojects { // apply plugin: 'eclipse' apply plugin: 'idea' - version = '5.4.1-SNAPSHOT' + version = '5.4.2-SNAPSHOT' } /** @@ -107,21 +107,21 @@ subprojects { apply plugin: 'com.adarshr.test-logger' ext { - bouncyCastleVersion = '1.80' + bouncyCastleVersion = '1.81' commonsCodecVersion = '1.18.0' commonsCompressVersion = '1.27.1' - commonsIoVersion = '2.18.0' + commonsIoVersion = '2.19.0' commonsMathVersion = '3.6.1' - junitVersion = '5.12.0' + junitVersion = '5.13.2' log4jVersion = '2.24.3' mockitoVersion = '4.11.0' hamcrestVersion = '3.0' xmlbeansVersion = '5.3.0' - batikVersion = '1.18' - graphics2dVersion = '3.0.2' - pdfboxVersion = '3.0.4' - saxonVersion = '12.5' - xmlSecVersion = '3.0.5' + batikVersion = '1.19' + graphics2dVersion = '3.0.3' + pdfboxVersion = '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.0' + 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.0' + // 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 { @@ -490,14 +491,13 @@ subprojects { } forbiddenApisTest { - // forbiddenapis bundled signatures max supported version is 17 - // also see https://github.com/policeman-tools/forbidden-apis/issues/191 - targetCompatibility = (JavaVersion.VERSION_17.isCompatibleWith(JavaVersion.current()) ? JavaVersion.current() : JavaVersion.VERSION_17) + // forbiddenapis:3.8 bundled signatures max supported version is 23 + targetCompatibility = (JavaVersion.VERSION_17.isCompatibleWith(JavaVersion.current()) ? JavaVersion.current() : JavaVersion.VERSION_23) } forbiddenApisMain { signaturesFiles += files('../src/resources/devtools/forbidden-signatures-prod.txt') - targetCompatibility = (JavaVersion.VERSION_17.isCompatibleWith(JavaVersion.current()) ? JavaVersion.current() : JavaVersion.VERSION_17) + targetCompatibility = (JavaVersion.VERSION_17.isCompatibleWith(JavaVersion.current()) ? JavaVersion.current() : JavaVersion.VERSION_23) } publishing { @@ -42,7 +42,7 @@ under the License. <description>The Apache POI project Ant build.</description> - <property name="version.id" value="5.4.1-SNAPSHOT"/> + <property name="version.id" value="5.4.2-SNAPSHOT"/> <property name="release.rc" value=""/> <property environment="env"/> @@ -264,29 +264,29 @@ under the License. <!-- jars in the /lib directory, see the fetch-jars target--> <dependency prefix="main.commons-codec" artifact="commons-codec:commons-codec:1.18.0" usage="main"/> - <dependency prefix="main.commons-collections4" artifact="org.apache.commons:commons-collections4:4.4" usage="main"/> + <dependency prefix="main.commons-collections4" artifact="org.apache.commons:commons-collections4:4.5.0" usage="main"/> <dependency prefix="main.commons-math3" artifact="org.apache.commons:commons-math3:3.6.1" usage="main"/> - <dependency prefix="main.commons-io" artifact="commons-io:commons-io:2.18.0" usage="main"/> + <dependency prefix="main.commons-io" artifact="commons-io:commons-io:2.19.0" usage="main"/> <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.0" usage="main-tests"/> - <dependency prefix="main.junit-jengine" artifact="org.junit.jupiter:junit-jupiter-engine:5.12.0" usage="main-tests"/> - <dependency prefix="main.junit-params" artifact="org.junit.jupiter:junit-jupiter-params:5.12.0" 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.0" usage="main-tests"/> - <dependency prefix="main.junit-pengine" artifact="org.junit.platform:junit-platform-engine:1.12.0" usage="main-tests"/> - <dependency prefix="main.junit-plauncher" artifact="org.junit.platform:junit-platform-launcher:1.12.0" 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.0" usage="main-tests"/> - <dependency prefix="main.byte-buddy-agent" artifact="net.bytebuddy:byte-buddy-agent:1.17.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"/> <dependency prefix="main.objenesis" artifact="org.objenesis:objenesis:3.1" usage="main-tests"/> <dependency prefix="main.log4j-core" artifact="org.apache.logging.log4j:log4j-core:2.24.3" usage="main-tests"/> <dependency prefix="main.commons-logging" artifact="commons-logging:commons-logging:1.2" usage="main-tests"/> @@ -295,40 +295,40 @@ 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.batik-anim" artifact="org.apache.xmlgraphics:batik-anim:1.18" usage="ooxml-batik"/> - <dependency prefix="svg.batik-awt-util" artifact="org.apache.xmlgraphics:batik-awt-util:1.18" usage="ooxml-batik"/> - <dependency prefix="svg.batik-bridge" artifact="org.apache.xmlgraphics:batik-bridge:1.18" usage="ooxml-batik"/> - <dependency prefix="svg.batik-codec" artifact="org.apache.xmlgraphics:batik-codec:1.18" usage="ooxml-batik"/> - <dependency prefix="svg.batik-constants" artifact="org.apache.xmlgraphics:batik-constants:1.18" usage="ooxml-batik"/> - <dependency prefix="svg.batik-css" artifact="org.apache.xmlgraphics:batik-css:1.18" usage="ooxml-batik"/> - <dependency prefix="svg.batik-dom" artifact="org.apache.xmlgraphics:batik-dom:1.18" usage="ooxml-batik"/> - <dependency prefix="svg.batik-ext" artifact="org.apache.xmlgraphics:batik-ext:1.18" usage="ooxml-batik"/> - <dependency prefix="svg.batik-gvt" artifact="org.apache.xmlgraphics:batik-gvt:1.18" usage="ooxml-batik"/> - <dependency prefix="svg.batik-i18n" artifact="org.apache.xmlgraphics:batik-i18n:1.18" usage="ooxml-batik"/> - <dependency prefix="svg.batik-parser" artifact="org.apache.xmlgraphics:batik-parser:1.18" usage="ooxml-batik"/> - <dependency prefix="svg.batik-script" artifact="org.apache.xmlgraphics:batik-script:1.18" usage="ooxml-batik"/> - <dependency prefix="svg.batik-shared-resources" artifact="org.apache.xmlgraphics:batik-shared-resources:1.18" usage="ooxml-batik"/> - <dependency prefix="svg.batik-svg-dom" artifact="org.apache.xmlgraphics:batik-svg-dom:1.18" usage="ooxml-batik"/> - <dependency prefix="svg.batik-svggen" artifact="org.apache.xmlgraphics:batik-svggen:1.18" usage="ooxml-batik"/> - <dependency prefix="svg.batik-svgrasterizer" artifact="org.apache.xmlgraphics:batik-svgrasterizer:1.18" usage="ooxml-batik"/> - <dependency prefix="svg.batik-transcoder" artifact="org.apache.xmlgraphics:batik-transcoder:1.18" usage="ooxml-batik"/> - <dependency prefix="svg.batik-util" artifact="org.apache.xmlgraphics:batik-util:1.18" usage="ooxml-batik"/> - <dependency prefix="svg.batik-xml" artifact="org.apache.xmlgraphics:batik-xml:1.18" usage="ooxml-batik"/> - <dependency prefix="pdf.pdfbox" artifact="org.apache.pdfbox:pdfbox:3.0.4" usage="ooxml-provided"/> - <dependency prefix="pdf.pdfbox.io" artifact="org.apache.pdfbox:pdfbox-io:3.0.4" usage="ooxml-provided"/> - <dependency prefix="pdf.fontbox" artifact="org.apache.pdfbox:fontbox:3.0.4" usage="ooxml-provided"/> - <dependency prefix="pdf.graphics2d" artifact="de.rototor.pdfbox:graphics2d:3.0.2" usage="ooxml-provided"/> + <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"/> + <dependency prefix="svg.batik-codec" artifact="org.apache.xmlgraphics:batik-codec:1.19" usage="ooxml-batik"/> + <dependency prefix="svg.batik-constants" artifact="org.apache.xmlgraphics:batik-constants:1.19" usage="ooxml-batik"/> + <dependency prefix="svg.batik-css" artifact="org.apache.xmlgraphics:batik-css:1.19" usage="ooxml-batik"/> + <dependency prefix="svg.batik-dom" artifact="org.apache.xmlgraphics:batik-dom:1.19" usage="ooxml-batik"/> + <dependency prefix="svg.batik-ext" artifact="org.apache.xmlgraphics:batik-ext:1.19" usage="ooxml-batik"/> + <dependency prefix="svg.batik-gvt" artifact="org.apache.xmlgraphics:batik-gvt:1.19" usage="ooxml-batik"/> + <dependency prefix="svg.batik-i18n" artifact="org.apache.xmlgraphics:batik-i18n:1.19" usage="ooxml-batik"/> + <dependency prefix="svg.batik-parser" artifact="org.apache.xmlgraphics:batik-parser:1.19" usage="ooxml-batik"/> + <dependency prefix="svg.batik-script" artifact="org.apache.xmlgraphics:batik-script:1.19" usage="ooxml-batik"/> + <dependency prefix="svg.batik-shared-resources" artifact="org.apache.xmlgraphics:batik-shared-resources:1.19" usage="ooxml-batik"/> + <dependency prefix="svg.batik-svg-dom" artifact="org.apache.xmlgraphics:batik-svg-dom:1.19" usage="ooxml-batik"/> + <dependency prefix="svg.batik-svggen" artifact="org.apache.xmlgraphics:batik-svggen:1.19" usage="ooxml-batik"/> + <dependency prefix="svg.batik-svgrasterizer" artifact="org.apache.xmlgraphics:batik-svgrasterizer:1.19" usage="ooxml-batik"/> + <dependency prefix="svg.batik-transcoder" artifact="org.apache.xmlgraphics:batik-transcoder:1.19" usage="ooxml-batik"/> + <dependency prefix="svg.batik-util" artifact="org.apache.xmlgraphics:batik-util:1.19" usage="ooxml-batik"/> + <dependency prefix="svg.batik-xml" artifact="org.apache.xmlgraphics:batik-xml:1.19" usage="ooxml-batik"/> + <dependency prefix="pdf.pdfbox" artifact="org.apache.pdfbox:pdfbox:3.0.5" usage="ooxml-provided"/> + <dependency prefix="pdf.pdfbox.io" artifact="org.apache.pdfbox:pdfbox-io:3.0.5" usage="ooxml-provided"/> + <dependency prefix="pdf.fontbox" artifact="org.apache.pdfbox:fontbox:3.0.5" usage="ooxml-provided"/> + <dependency prefix="pdf.graphics2d" artifact="de.rototor.pdfbox:graphics2d:3.0.3" usage="ooxml-provided"/> <!-- jars in the ooxml-lib directory, see the fetch-ooxml-jars target--> <dependency prefix="ooxml.curvesapi" artifact="com.github.virtuald:curvesapi:1.08" usage="ooxml"/> @@ -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> @@ -698,6 +701,7 @@ under the License. <include name="batik*.jar"/> <include name="slf4j*-1.*.jar"/> <include name="xmlsec-2*.jar"/> + <include name="xmlgraphics-commons-*.jar"/> </fileset> <fileset dir="${basedir}/lib/ooxml-tests"> <include name="guava-20.0.jar"/> @@ -833,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}"/> @@ -876,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/doap_POI.rdf b/doap_POI.rdf index 88606eb0bd..f1065b1ab6 100644 --- a/doap_POI.rdf +++ b/doap_POI.rdf @@ -37,6 +37,13 @@ <category rdf:resource="https://projects.apache.org/category/library" /> <release> <Version> + <name>Apache POI 5.4.1</name> + <created>2025-04-06</created> + <revision>5.4.1</revision> + </Version> + </release> + <release> + <Version> <name>Apache POI 5.4.0</name> <created>2025-01-08</created> <revision>5.4.0</revision> diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37f853b1c8..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.13-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 7f680ce0bc..d112999d2c 100644 --- a/jenkins/create_jobs.groovy +++ b/jenkins/create_jobs.groovy @@ -28,18 +28,20 @@ def poijobs = [ ], [ name: 'POI-DSL-1.17', jdk: '1.17', trigger: 'H */12 * * *', skipcigame: true ], - // Jenkins on ci-builds.apache.org does not support spotbugs with a new enough version of asm for Java18+ - [ name: 'POI-DSL-1.21', jdk: '1.21', trigger: 'H */12 * * *', skipcigame: true, skipSpotbugs: true + [ name: 'POI-DSL-1.21', jdk: '1.21', trigger: 'H */12 * * *', skipcigame: true ], - // Jenkins on ci-builds.apache.org does not support spotbugs with a new enough version of asm for Java18+ - [ name: 'POI-DSL-1.22', jdk: '1.22', trigger: triggerSundays, skipcigame: true, skipSpotbugs: true + [ name: 'POI-DSL-1.22', jdk: '1.22', trigger: triggerSundays, 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-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 Java18+ - [ name: 'POI-DSL-1.23', jdk: '1.23', trigger: triggerSundays, skipcigame: true, skipSpotbugs: true + [ name: 'POI-DSL-1.24', jdk: '1.24', trigger: triggerSundays, skipcigame: true, ], - // Jenkins on ci-builds.apache.org does not support spotbugs with a new enough version of asm for Java18+ - [ 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.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 @@ -84,11 +86,18 @@ def poijobs = [ ], [ name: 'POI-DSL-Windows-1.21', jdk: '1.21', trigger: 'H */12 * * *', windows: true, slaves: 'Windows', skipcigame: true ], - [ name: 'POI-DSL-Windows-1.22', jdk: '1.22', trigger: triggerSundays, windows: true, slaves: 'Windows', skipcigame: true + [ name: 'POI-DSL-Windows-1.22', jdk: '1.22', 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.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 @@ -105,24 +114,18 @@ def xmlbeansjobs = [ ], [ name: 'POI-XMLBeans-DSL-1.17', jdk: '1.17', trigger: 'H */12 * * *', skipcigame: true, ], - [ name: 'POI-XMLBeans-DSL-1.18', jdk: '1.18', trigger: triggerSundays, skipcigame: true, - // let's save some CPU cycles here, 18 is not an LTS and JDK 20 is out - disabled: true - ], - [ name: 'POI-XMLBeans-DSL-1.19', jdk: '1.19', trigger: triggerSundays, skipcigame: true, - // let's save some CPU cycles here, 19 is not an LTS - disabled: true - ], - [ name: 'POI-XMLBeans-DSL-1.20', jdk: '1.20', trigger: triggerSundays, skipcigame: true, - // let's save some CPU cycles here, 20 is not an LTS and JDK 21 is out - disabled: true - ], [ name: 'POI-XMLBeans-DSL-1.21', jdk: '1.21', trigger: 'H */12 * * *', skipcigame: true, ], [ name: 'POI-XMLBeans-DSL-1.22', jdk: '1.22', trigger: triggerSundays, skipcigame: true, + // let's save some CPU cycles here, 22 is not an LTS and JDK 23/24 is out + disabled: true ], [ name: 'POI-XMLBeans-DSL-1.23', jdk: '1.23', trigger: triggerSundays, skipcigame: true, ], + [ 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 ] @@ -151,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' ] ] @@ -619,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 bd66279760..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.4 + - 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/osgi/pom.xml b/osgi/pom.xml index c4854f4c12..351b2e276d 100644 --- a/osgi/pom.xml +++ b/osgi/pom.xml @@ -24,12 +24,12 @@ <groupId>org.apache.poi</groupId> <artifactId>poi-bundle</artifactId> <packaging>bundle</packaging> - <version>5.4.1-SNAPSHOT</version> + <version>5.4.2-SNAPSHOT</version> <name>Apache POI OSGi bundle</name> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> - <poi.version>5.4.1-SNAPSHOT</poi.version> + <poi.version>5.4.2-SNAPSHOT</poi.version> <pax.exam.version>4.14.0</pax.exam.version> </properties> 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 f9a1e84950..35e588e3cc 100644 --- a/poi-integration/build.gradle +++ b/poi-integration/build.gradle @@ -40,8 +40,8 @@ sourceSets { dependencies { testImplementation 'org.apache.ant:ant:1.10.15' - testImplementation 'org.apache.commons:commons-collections4:4.4' - testImplementation 'com.google.guava:guava:33.3.0-jre' + testImplementation 'org.apache.commons:commons-collections4:4.5.0' + testImplementation 'com.google.guava:guava:33.4.8-jre' misc(project(':poi-ooxml')) { capabilities { diff --git a/poi-integration/src/test/java/org/apache/poi/stress/TestAllFiles.java b/poi-integration/src/test/java/org/apache/poi/stress/TestAllFiles.java index 8b3baf2b01..b3aff917ac 100644 --- a/poi-integration/src/test/java/org/apache/poi/stress/TestAllFiles.java +++ b/poi-integration/src/test/java/org/apache/poi/stress/TestAllFiles.java @@ -136,7 +136,9 @@ public class TestAllFiles { "spreadsheet/clusterfuzz-testcase-minimized-POIHSSFFuzzer-4977868385681408.xls", "spreadsheet/clusterfuzz-testcase-minimized-POIHSSFFuzzer-4651309315719168.xls", "document/clusterfuzz-testcase-POIHWPFFuzzer-5696094627495936.doc", - "spreadsheet/clusterfuzz-testcase-minimized-POIHSSFFuzzer-4657005060816896.xls" + "spreadsheet/clusterfuzz-testcase-minimized-POIHSSFFuzzer-4657005060816896.xls", + "diagram/clusterfuzz-testcase-minimized-POIHDGFFuzzer-4913778037489664.vsd", + "diagram/clusterfuzz-testcase-minimized-POIHDGFFuzzer-6478389109981184.vsd" }); private static final Set<String> EXPECTED_FAILURES = StressTestUtils.unmodifiableHashSet( diff --git a/poi-ooxml-lite-agent/build.gradle b/poi-ooxml-lite-agent/build.gradle index f598b6358e..fef003b327 100644 --- a/poi-ooxml-lite-agent/build.gradle +++ b/poi-ooxml-lite-agent/build.gradle @@ -22,8 +22,8 @@ sourceSets { } dependencies { - api 'net.bytebuddy:byte-buddy:1.17.0' - api 'net.bytebuddy:byte-buddy-agent:1.17.0' + api 'net.bytebuddy:byte-buddy:1.17.5' + api 'net.bytebuddy:byte-buddy-agent:1.17.5' api "org.apache.xmlbeans:xmlbeans:${xmlbeansVersion}" } diff --git a/poi-ooxml/build.gradle b/poi-ooxml/build.gradle index aa7946cb7c..a09056cfdb 100644 --- a/poi-ooxml/build.gradle +++ b/poi-ooxml/build.gradle @@ -64,7 +64,7 @@ dependencies { api "commons-io:commons-io:${commonsIoVersion}" api 'com.github.virtuald:curvesapi:1.08' api "org.apache.logging.log4j:log4j-api:${log4jVersion}" - api 'org.apache.commons:commons-collections4:4.4' + api 'org.apache.commons:commons-collections4:4.5.0' signingImplementation "org.apache.santuario:xmlsec:${xmlSecVersion}" signingImplementation "org.bouncycastle:bcpkix-jdk18on:${bouncyCastleVersion}" @@ -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/ooxml/util/NumberHelper.java b/poi-ooxml/src/main/java/org/apache/poi/ooxml/util/NumberHelper.java new file mode 100644 index 0000000000..6d4cd26474 --- /dev/null +++ b/poi-ooxml/src/main/java/org/apache/poi/ooxml/util/NumberHelper.java @@ -0,0 +1,49 @@ +/* ==================================================================== + 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.ooxml.util; + +import org.apache.poi.util.Internal; + +/** + * Helper class for number related operations. + * <p>Note: This class is for internal POI usage only.</p> + * + * @since POI 5.4.2 + */ +@Internal +public class NumberHelper { + private NumberHelper() { + // no instances of this class + } + + /** + * @param number the number to convert + * @return the double representation of the number + * @throws IllegalArgumentException if the number cannot be converted + */ + public static double toDouble(Object number) { + if (number instanceof Number) { + return ((Number) number).doubleValue(); + } else if (number instanceof String) { + return Double.parseDouble((String) number); + } + throw new IllegalArgumentException("Cannot convert of class" + number.getClass().getName() + + " to double"); + } + +} diff --git a/poi-ooxml/src/main/java/org/apache/poi/ooxml/util/XPathHelper.java b/poi-ooxml/src/main/java/org/apache/poi/ooxml/util/XPathHelper.java index 26b2b2e737..f4fcf57fe7 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/ooxml/util/XPathHelper.java +++ b/poi-ooxml/src/main/java/org/apache/poi/ooxml/util/XPathHelper.java @@ -80,8 +80,8 @@ public final class XPathHelper { * It returns the first element found - the search order is: * <ul> * <li>searching for a direct child</li> - * <li>searching for a AlternateContent.Choice child</li> - * <li>searching for a AlternateContent.Fallback child</li> + * <li>searching for an AlternateContent.Choice child</li> + * <li>searching for an AlternateContent.Fallback child</li> * </ul> * The factory flag is * a workaround to process files based on a later edition. But it comes with the drawback: 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/openxml4j/util/ZipArchiveFakeEntry.java b/poi-ooxml/src/main/java/org/apache/poi/openxml4j/util/ZipArchiveFakeEntry.java index a10e43c277..9648476001 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/openxml4j/util/ZipArchiveFakeEntry.java +++ b/poi-ooxml/src/main/java/org/apache/poi/openxml4j/util/ZipArchiveFakeEntry.java @@ -35,7 +35,7 @@ import org.apache.poi.util.TempFile; * close this as soon as you can! * @see ZipInputStreamZipEntrySource#setThresholdBytesForTempFiles(int) */ -/* package */ class ZipArchiveFakeEntry extends ZipArchiveEntry implements Closeable { +public final class ZipArchiveFakeEntry extends ZipArchiveEntry implements Closeable { private static final Logger LOG = PoiLogManager.getLogger(ZipArchiveFakeEntry.class); // how large a single entry in a zip-file should become at max @@ -43,12 +43,22 @@ import org.apache.poi.util.TempFile; private static final int DEFAULT_MAX_ENTRY_SIZE = 100_000_000; private static int MAX_ENTRY_SIZE = DEFAULT_MAX_ENTRY_SIZE; + /** + * Set the maximum size of a single entry in a zip-file. + * @param maxEntrySize number of bytes at which a zip entry is regarded as too large for holding in memory + * - defaults to 100_000_000 (approx 100Mb). A value of -1 means the default value is used. + */ public static void setMaxEntrySize(int maxEntrySize) { - MAX_ENTRY_SIZE = maxEntrySize; + if(maxEntrySize < 0) { + MAX_ENTRY_SIZE = DEFAULT_MAX_ENTRY_SIZE; + } else { + MAX_ENTRY_SIZE = maxEntrySize; + } } public static int getMaxEntrySize() { - return MAX_ENTRY_SIZE; + final int ioMaxSize = IOUtils.getByteArrayMaxOverride(); + return ioMaxSize < 0 ? MAX_ENTRY_SIZE : Math.min(MAX_ENTRY_SIZE, ioMaxSize); } private byte[] data; @@ -61,7 +71,7 @@ import org.apache.poi.util.TempFile; final long entrySize = entry.getSize(); final int threshold = ZipInputStreamZipEntrySource.getThresholdBytesForTempFiles(); - if (threshold >= 0 && entrySize >= threshold) { + if (threshold >= 0 && (entrySize >= threshold || entrySize == -1)) { if (ZipInputStreamZipEntrySource.shouldEncryptTempFiles()) { encryptedTempData = new EncryptedTempData(); try (OutputStream os = encryptedTempData.getOutputStream()) { @@ -80,7 +90,7 @@ import org.apache.poi.util.TempFile; // Grab the de-compressed contents for later data = (entrySize == -1) ? IOUtils.toByteArrayWithMaxLength(inp, getMaxEntrySize()) : - IOUtils.toByteArray(inp, (int)entrySize, getMaxEntrySize()); + IOUtils.toByteArray(inp, entrySize, getMaxEntrySize()); } } diff --git a/poi-ooxml/src/main/java/org/apache/poi/openxml4j/util/ZipArchiveThresholdInputStream.java b/poi-ooxml/src/main/java/org/apache/poi/openxml4j/util/ZipArchiveThresholdInputStream.java index 68c813dbbb..fe6c77150b 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/openxml4j/util/ZipArchiveThresholdInputStream.java +++ b/poi-ooxml/src/main/java/org/apache/poi/openxml4j/util/ZipArchiveThresholdInputStream.java @@ -123,8 +123,8 @@ public class ZipArchiveThresholdInputStream extends FilterInputStream { final String entryName = entry == null ? "not set" : entry.getName(); // check the file size first, in case we are working on uncompressed streams - if (payloadSize > MAX_ENTRY_SIZE) { - throw new IOException(String.format(Locale.ROOT, MAX_ENTRY_SIZE_MSG, payloadSize, rawSize, MAX_ENTRY_SIZE, entryName)); + if (payloadSize > getMaxEntrySize()) { + throw new IOException(String.format(Locale.ROOT, MAX_ENTRY_SIZE_MSG, payloadSize, rawSize, getMaxEntrySize(), entryName)); } // don't alert for small expanded size 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/poifs/crypt/dsig/services/TimeStampSimpleHttpClient.java b/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/services/TimeStampSimpleHttpClient.java index f7e3b9aeb1..74a0138aa9 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/services/TimeStampSimpleHttpClient.java +++ b/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/dsig/services/TimeStampSimpleHttpClient.java @@ -24,6 +24,8 @@ import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Proxy; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; @@ -72,7 +74,8 @@ public class TimeStampSimpleHttpClient implements TimeStampHttpClient { * @return the max timestamp response size allowed */ public static int getMaxTimestampResponseSize() { - return MAX_TIMESTAMP_RESPONSE_SIZE; + final int ioMaxSize = IOUtils.getByteArrayMaxOverride(); + return ioMaxSize < 0 ? MAX_TIMESTAMP_RESPONSE_SIZE : Math.min(MAX_TIMESTAMP_RESPONSE_SIZE, ioMaxSize); } @@ -126,11 +129,11 @@ public class TimeStampSimpleHttpClient implements TimeStampHttpClient { proxy = Proxy.NO_PROXY; } else { try { - URL pUrl = new URL(proxyUrl); + URL pUrl = new URI(proxyUrl).toURL(); String host = pUrl.getHost(); int port = pUrl.getPort(); proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(InetAddress.getByName(host), (port == -1 ? 80 : port))); - } catch (IOException ignored) { + } catch (IOException | URISyntaxException ignored) { } } } @@ -204,7 +207,12 @@ public class TimeStampSimpleHttpClient implements TimeStampHttpClient { } protected TimeStampHttpClientResponse handleRedirect(String url, MethodHandler handler, boolean followRedirect) throws IOException { - HttpURLConnection huc = (HttpURLConnection)new URL(url).openConnection(proxy); + final HttpURLConnection huc; + try { + huc = (HttpURLConnection)new URI(url).toURL().openConnection(proxy); + } catch (URISyntaxException e) { + throw new IOException(e); + } if (ignoreHttpsCertificates) { recklessConnection(huc); } 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/xslf/usermodel/XSLFPictureData.java b/poi-ooxml/src/main/java/org/apache/poi/xslf/usermodel/XSLFPictureData.java index 60ef156abe..82daee8dbe 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xslf/usermodel/XSLFPictureData.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xslf/usermodel/XSLFPictureData.java @@ -58,7 +58,8 @@ public final class XSLFPictureData extends POIXMLDocumentPart implements Picture * @return the max image size allowed for XSLF pictures */ public static int getMaxImageSize() { - return MAX_IMAGE_SIZE; + final int ioMaxSize = IOUtils.getByteArrayMaxOverride(); + return ioMaxSize < 0 ? MAX_IMAGE_SIZE : Math.min(MAX_IMAGE_SIZE, ioMaxSize); } private Long checksum; 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/binary/XSSFBParser.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/binary/XSSFBParser.java index 8822053180..c56f8ac713 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/binary/XSSFBParser.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/binary/XSSFBParser.java @@ -107,7 +107,7 @@ public abstract class XSSFBParser { } if (records == null || records.get(recordId)) { - byte[] buff = IOUtils.safelyAllocate(recordLength, MAX_RECORD_LENGTH); + byte[] buff = IOUtils.safelyAllocate(recordLength, getMaxRecordLength()); is.readFully(buff); handleRecord(recordId, buff); } else { 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/SXSSFCell.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/SXSSFCell.java index 20689d5696..50f87b0d5b 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/SXSSFCell.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/SXSSFCell.java @@ -721,8 +721,6 @@ public class SXSSFCell extends CellBase { } //end of interface implementation - private static final DataFormatter DATA_FORMATTER = new DataFormatter(); - /** * Returns a string representation of the cell * <p> @@ -743,8 +741,14 @@ public class SXSSFCell extends CellBase { case FORMULA: return getCellFormula(); case NUMERIC: + if (DateUtil.isCellDateFormatted(this)) { + DataFormatter df = new DataFormatter(); + df.setUseCachedValuesForFormulaCells(true); + return df.formatCellValue(this); + } + return Double.toString(getNumericCellValue()); case STRING: - return DATA_FORMATTER.formatCellValue(this); + return getRichStringCellValue().toString(); default: return "Unknown Cell Type: " + getCellType(); } diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/SXSSFSheet.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/SXSSFSheet.java index fc5462620e..bdb87f1bad 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/SXSSFSheet.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/SXSSFSheet.java @@ -1814,7 +1814,7 @@ public class SXSSFSheet implements Sheet, OoxmlSheetExtensions { } /** - * Remove a Array Formula from this sheet. All cells contained in the Array Formula range are removed as well + * Remove an Array Formula from this sheet. All cells contained in the Array Formula range are removed as well * * @param cell any cell within Array Formula range * @return the {@link CellRange} of cells affected by this change 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 1017686091..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 @@ -33,8 +33,6 @@ import java.nio.charset.StandardCharsets; import java.util.Iterator; import java.util.PrimitiveIterator; -import org.apache.logging.log4j.Logger; -import org.apache.poi.logging.PoiLogManager; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.CellType; @@ -54,7 +52,6 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.STCellType; * so that it was renamed to "SheetDataWriter" */ public class SheetDataWriter implements Closeable { - private static final Logger LOG = PoiLogManager.getLogger(SheetDataWriter.class); private final File _fd; protected final Writer _out; @@ -385,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) ; @@ -394,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; } @@ -463,7 +460,7 @@ public class SheetDataWriter implements Closeable { try { _out.close(); } finally { - ret = _fd.delete(); + ret = _fd == null || _fd.delete(); } return ret; } diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/StreamingSheetWriter.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/StreamingSheetWriter.java index 382eb114dd..1398c57abc 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/StreamingSheetWriter.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/streaming/StreamingSheetWriter.java @@ -29,6 +29,7 @@ import java.nio.charset.StandardCharsets; import org.apache.logging.log4j.Logger; import org.apache.poi.logging.PoiLogManager; import org.apache.poi.util.Beta; +import org.apache.poi.util.Removal; /** * Unlike SheetDataWriter, this writer does not create a temporary file, it writes data directly @@ -40,6 +41,11 @@ public class StreamingSheetWriter extends SheetDataWriter { private static final Logger LOG = PoiLogManager.getLogger(StreamingSheetWriter.class); private boolean closed = false; + /** + * @throws IOException always thrown, use the constructor with an OutputStream + * @deprecated use {@link #StreamingSheetWriter(OutputStream)} + */ + @Removal(version = "6.0.0") public StreamingSheetWriter() throws IOException { throw new IllegalStateException("StreamingSheetWriter requires OutputStream"); } @@ -49,11 +55,17 @@ public class StreamingSheetWriter extends SheetDataWriter { LOG.atDebug().log("Preparing SXSSF sheet writer"); } + /** + * @throws IllegalStateException always thrown - not supported + */ @Override public File createTempFile() throws IOException { throw new IllegalStateException("Not supported with StreamingSheetWriter"); } + /** + * @throws IllegalStateException always thrown - not supported + */ @Override public Writer createWriter(File fd) throws IOException { throw new IllegalStateException("Not supported with StreamingSheetWriter"); @@ -75,6 +87,9 @@ public class StreamingSheetWriter extends SheetDataWriter { } } + /** + * @throws IllegalStateException always thrown - not supported + */ @Override public InputStream getWorksheetXMLInputStream() throws IOException { throw new IllegalStateException("Not supported with StreamingSheetWriter"); 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/XSSFCell.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFCell.java index 5ef93237fa..29088a5b48 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFCell.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFCell.java @@ -924,8 +924,6 @@ public final class XSSFCell extends CellBase { } } - private static final DataFormatter DATA_FORMATTER = new DataFormatter(); - /** * Returns a string representation of the cell * <p> @@ -938,8 +936,14 @@ public final class XSSFCell extends CellBase { public String toString() { switch (getCellType()) { case NUMERIC: + if (DateUtil.isCellDateFormatted(this)) { + DataFormatter df = new DataFormatter(); + df.setUseCachedValuesForFormulaCells(true); + return df.formatCellValue(this); + } + return Double.toString(getNumericCellValue()); case STRING: - return DATA_FORMATTER.formatCellValue(this); + return getRichStringCellValue().toString(); case FORMULA: return getCellFormula(); case BLANK: diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFCellStyle.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFCellStyle.java index a9d1c76ad3..3c4252b65e 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFCellStyle.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFCellStyle.java @@ -29,6 +29,7 @@ import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.IndexedColors; import org.apache.poi.ss.usermodel.ReadingOrder; import org.apache.poi.ss.usermodel.VerticalAlignment; +import org.apache.poi.ss.util.CellUtil; import org.apache.poi.util.Internal; import org.apache.poi.util.Removal; import org.apache.poi.xssf.model.StylesTable; @@ -202,7 +203,7 @@ public class XSSFCellStyle implements CellStyle, Duplicatable { _font = null; _cellAlignment = null; } else { - throw new IllegalArgumentException("Can only clone from one XSSFCellStyle to another, not between HSSFCellStyle and XSSFCellStyle"); + CellUtil.cloneStyle(source, this, null); } } diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFColor.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFColor.java index d7324431ba..849235a11d 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFColor.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFColor.java @@ -33,7 +33,7 @@ public class XSSFColor extends ExtendedColor { /** * @param color The ooxml color object to use - * @param map The IndexedColorMap to use instead of the default one (can be null) + * @param map The IndexedColorMap to use instead of the default one (can be null) * @return null if color is null, new instance otherwise */ public static XSSFColor from(CTColor color, IndexedColorMap map) { @@ -63,6 +63,7 @@ public class XSSFColor extends ExtendedColor { /** * new color with the given indexed color map + * * @param colorMap The IndexedColorMap to use instead of the default one (can be null) */ public XSSFColor(IndexedColorMap colorMap) { @@ -71,6 +72,7 @@ public class XSSFColor extends ExtendedColor { /** * TEST ONLY + * * @param clr awt Color * @param map The IndexedColorMap to use instead of the default one (can be null) */ @@ -80,7 +82,7 @@ public class XSSFColor extends ExtendedColor { } /** - * @param rgb The RGB-byte-values for the Color + * @param rgb The RGB-byte-values for the Color * @param colorMap The IndexedColorMap to use instead of the default one (can be null) */ public XSSFColor(byte[] rgb, IndexedColorMap colorMap) { @@ -98,7 +100,7 @@ public class XSSFColor extends ExtendedColor { /** * @param indexedColor color index (Enum named for default colors) - * @param colorMap The IndexedColorMap to use instead of the default one + * @param colorMap The IndexedColorMap to use instead of the default one */ public XSSFColor(IndexedColors indexedColor, IndexedColorMap colorMap) { this(CTColor.Factory.newInstance(), colorMap); @@ -163,8 +165,9 @@ public class XSSFColor extends ExtendedColor { */ @Override public short getIndex() { - return (short)ctColor.getIndexed(); + return (short) ctColor.getIndexed(); } + /** * @return Indexed ctColor value. Only used for backwards compatibility. References a ctColor in indexedColors. */ @@ -174,82 +177,84 @@ public class XSSFColor extends ExtendedColor { /** * Indexed ctColor value. Only used for backwards compatibility. References a ctColor in indexedColors. + * * @param indexed color index */ public void setIndexed(int indexed) { ctColor.setIndexed(indexed); } - /** - * Standard Red Green Blue ctColor value (RGB). - * If there was an A (Alpha) value, it will be stripped. - */ - @Override - public byte[] getRGB() { - byte[] rgb = getRGBOrARGB(); - if(rgb == null) { - return null; - } - - // Need to trim off the alpha - return rgb.length == 4 ? Arrays.copyOfRange(rgb, 1, 4) : rgb; - } - - /** - * Standard Alpha Red Green Blue ctColor value (ARGB). - */ - @Override - public byte[] getARGB() { - byte[] rgb = getRGBOrARGB(); - if(rgb == null) { - return null; - } - - if(rgb.length == 3) { - // Pad with the default Alpha - byte[] tmp = new byte[4]; - tmp[0] = -1; - System.arraycopy(rgb, 0, tmp, 1, 3); - return tmp; - } else { - return rgb; - } - } - - @Override - protected byte[] getStoredRGB() { - return ctColor.getRgb(); - } - - @Override - protected byte[] getIndexedRGB() { - if (isIndexed()) { - if (indexedColorMap != null) return indexedColorMap.getRGB(getIndex()); - return DefaultIndexedColorMap.getDefaultRGB(getIndex()); - } - return null; - } + /** + * Standard Red Green Blue ctColor value (RGB). + * If there was an A (Alpha) value, it will be stripped. + */ + @Override + public byte[] getRGB() { + byte[] rgb = getRGBOrARGB(); + if (rgb == null) { + return null; + } + + // Need to trim off the alpha + return rgb.length == 4 ? Arrays.copyOfRange(rgb, 1, 4) : rgb; + } /** * Standard Alpha Red Green Blue ctColor value (ARGB). */ - @Override + @Override + public byte[] getARGB() { + byte[] rgb = getRGBOrARGB(); + if (rgb == null) { + return null; + } + + if (rgb.length == 3) { + // Pad with the default Alpha + byte[] tmp = new byte[4]; + tmp[0] = -1; + System.arraycopy(rgb, 0, tmp, 1, 3); + return tmp; + } else { + return rgb; + } + } + + @Override + protected byte[] getStoredRGB() { + return ctColor.getRgb(); + } + + @Override + protected byte[] getIndexedRGB() { + if (isIndexed()) { + if (indexedColorMap != null) return indexedColorMap.getRGB(getIndex()); + return DefaultIndexedColorMap.getDefaultRGB(getIndex()); + } + return null; + } + + /** + * Standard Alpha Red Green Blue ctColor value (ARGB). + */ + @Override public void setRGB(byte[] rgb) { - ctColor.setRgb(rgb); + ctColor.setRgb(rgb); } /** - * Index into the {@code clrScheme} collection, referencing a particular {@code sysClr} or + * Index into the {@code cslrScheme} collection, referencing a particular {@code sysClr} or * {@code srgbClr} value expressed in the Theme part. */ - @Override - public int getTheme() { - return (int)ctColor.getTheme(); + @Override + public int getTheme() { + return (int) ctColor.getTheme(); } /** * Index into the {@code clrScheme} collection, referencing a particular {@code sysClr} or * {@code srgbClr} value expressed in the Theme part. + * * @param theme index */ public void setTheme(int theme) { @@ -354,7 +359,7 @@ public class XSSFColor extends ExtendedColor { * @return the underlying XML bean */ @Internal - public CTColor getCTColor(){ + public CTColor getCTColor() { return ctColor; } @@ -372,11 +377,11 @@ public class XSSFColor extends ExtendedColor { if (color != null && !(color instanceof XSSFColor)) { throw new IllegalArgumentException("Only XSSFColor objects are supported, but had " + color.getClass()); } - return (XSSFColor)color; + return (XSSFColor) color; } @Override - public int hashCode(){ + public int hashCode() { return ctColor.toString().hashCode(); } @@ -387,35 +392,39 @@ public class XSSFColor extends ExtendedColor { } return false; } + private boolean sameARGB(XSSFColor other) { if (isRGB() == other.isRGB()) { return !isRGB() || Arrays.equals(getARGB(), other.getARGB()); } return false; } + private boolean sameTheme(XSSFColor other) { if (isThemed() == other.isThemed()) { return !isThemed() || getTheme() == other.getTheme(); } return false; } + private boolean sameTint(XSSFColor other) { if (hasTint() == other.hasTint()) { return !hasTint() || getTint() == other.getTint(); } return false; } + private boolean sameAuto(XSSFColor other) { return isAuto() == other.isAuto(); } @Override - public boolean equals(Object o){ - if(!(o instanceof XSSFColor)) { + public boolean equals(Object o) { + if (!(o instanceof XSSFColor)) { return false; } - XSSFColor other = (XSSFColor)o; + XSSFColor other = (XSSFColor) o; // Compare each field in ctColor. // Cannot compare ctColor's XML string representation because equivalent 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/XSSFPictureData.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFPictureData.java index 9a2c1d02e1..b3f68f6988 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFPictureData.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFPictureData.java @@ -48,7 +48,8 @@ public class XSSFPictureData extends POIXMLDocumentPart implements PictureData { * @return the max image size allowed for XSSF pictures */ public static int getMaxImageSize() { - return MAX_IMAGE_SIZE; + final int ioMaxSize = IOUtils.getByteArrayMaxOverride(); + return ioMaxSize < 0 ? MAX_IMAGE_SIZE : Math.min(MAX_IMAGE_SIZE, ioMaxSize); } /** 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 fe3e27b10d..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 @@ -390,7 +390,7 @@ public class XSSFRichTextString implements RichTextString { * Return a copy of the font in use at a particular index. * * @param index The index. - * @return A copy of the font that's currently being applied at that + * @return A copy of the font that's currently being applied at that * index or null if no font is being applied or the * index is out of range. */ @@ -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 b30883966a..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()); @@ -4644,7 +4652,7 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet, OoxmlSheetEx StringBuilder rng = new StringBuilder(); rng.append(c); - if(rng.length() > 0 && r.length() > 0) { + if(rng.length() > 0 && !r.isEmpty()) { rng.append(','); } rng.append(r); 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 55e52db3d8..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 @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import org.apache.poi.ooxml.util.NumberHelper; import org.apache.poi.ooxml.util.POIXMLUnits; import org.apache.poi.util.Internal; import org.apache.poi.util.Units; @@ -64,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()); @@ -252,14 +253,30 @@ public class XSSFTextParagraph implements Iterable<XSSFTextRun>{ * @return the color of bullet characters within a given paragraph. * A <code>null</code> value means to use the text font color. */ - public Color getBulletFontColor(){ - ParagraphPropertyFetcher<Color> fetcher = new ParagraphPropertyFetcher<Color>(getLevel()){ + public Color getBulletFontColor() { + byte[] bytes = getBulletFontColorAsBytes(); + if (bytes == null) { + return null; + } else if (bytes.length == 3) { + return new Color(bytes[0] & 0xFF, bytes[1] & 0xFF, bytes[2] & 0xFF); + } else { + return new Color(0xFF & bytes[1], 0xFF & bytes[2], 0xFF & bytes[3], 0xFF & bytes[0]); + } + } + + /** + * + * @return the color of bullet characters within a given paragraph. + * A <code>null</code> value means to use the text font color. + * @since POI 5.4.2 + */ + public byte[] getBulletFontColorAsBytes() { + ParagraphPropertyFetcher<byte[]> fetcher = new ParagraphPropertyFetcher<byte[]>(getLevel()) { public boolean fetch(CTTextParagraphProperties props){ if(props.isSetBuClr()){ if(props.getBuClr().isSetSrgbClr()){ CTSRgbColor clr = props.getBuClr().getSrgbClr(); - byte[] rgb = clr.getVal(); - setValue(new Color(0xFF & rgb[0], 0xFF & rgb[1], 0xFF & rgb[2])); + setValue(clr.getVal()); return true; } } @@ -275,11 +292,21 @@ public class XSSFTextParagraph implements Iterable<XSSFTextRun>{ * * @param color the bullet color */ - public void setBulletFontColor(Color color){ + public void setBulletFontColor(Color color) { + setBulletFontColor(new byte[]{(byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue()}); + } + + /** + * Set the color to be used on bullet characters within a given paragraph. + * + * @param colorArray the bullet color (as byte array) + * @since POI 5.4.2 + */ + public void setBulletFontColor(byte[] colorArray) { CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr(); CTColor c = pr.isSetBuClr() ? pr.getBuClr() : pr.addNewBuClr(); CTSRgbColor clr = c.isSetSrgbClr() ? c.getSrgbClr() : c.addNewSrgbClr(); - clr.setVal(new byte[]{(byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue()}); + clr.setVal(colorArray); } /** @@ -552,8 +579,8 @@ public class XSSFTextParagraph implements Iterable<XSSFTextRun>{ if(lnSpc > 0) { // check if the percentage value is scaled CTTextNormalAutofit normAutofit = _shape.getTxBody().getBodyPr().getNormAutofit(); - if(normAutofit != null) { - double scale = 1 - (double)normAutofit.getLnSpcReduction() / 100000; + if(normAutofit != null && normAutofit.isSetLnSpcReduction()) { + double scale = 1 - NumberHelper.toDouble(normAutofit.getLnSpcReduction()) / 100000; lnSpc *= scale; } } diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFTextRun.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFTextRun.java index c525167e90..ad460a5cc7 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFTextRun.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFTextRun.java @@ -16,8 +16,7 @@ ==================================================================== */ package org.apache.poi.xssf.usermodel; -import java.awt.Color; - +import org.apache.poi.ooxml.util.NumberHelper; import org.apache.poi.ooxml.util.POIXMLUnits; import org.apache.poi.util.Units; import org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun; @@ -58,38 +57,53 @@ public class XSSFTextRun { return _r; } - public void setFontColor(Color color){ + public void setFontColor(java.awt.Color color) { + setFontColor(new byte[]{(byte)color.getRed(), (byte)color.getGreen(), (byte)color.getBlue()}); + } + + /** + * @param rgb + * @since POI 5.4.2 + */ + public void setFontColor(byte[] rgb) { CTTextCharacterProperties rPr = getRPr(); CTSolidColorFillProperties fill = rPr.isSetSolidFill() ? rPr.getSolidFill() : rPr.addNewSolidFill(); CTSRgbColor clr = fill.isSetSrgbClr() ? fill.getSrgbClr() : fill.addNewSrgbClr(); - clr.setVal(new byte[]{(byte)color.getRed(), (byte)color.getGreen(), (byte)color.getBlue()}); + clr.setVal(rgb); if(fill.isSetHslClr()) fill.unsetHslClr(); if(fill.isSetPrstClr()) fill.unsetPrstClr(); if(fill.isSetSchemeClr()) fill.unsetSchemeClr(); if(fill.isSetScrgbClr()) fill.unsetScrgbClr(); if(fill.isSetSysClr()) fill.unsetSysClr(); + } + public java.awt.Color getFontColor() { + final byte[] rgb = getFontColorAsBytes(); + if (rgb.length == 3) { + return new java.awt.Color(0xFF & rgb[0], 0xFF & rgb[1], 0xFF & rgb[2]); + } else { + return new java.awt.Color(0xFF & rgb[1], 0xFF & rgb[2], 0xFF & rgb[3], 0xFF & rgb[0]); + } } - public Color getFontColor(){ + /** + * @return the font color as a byte array. + * @since POI 5.4.2 + */ + public byte[] getFontColorAsBytes() { - CTTextCharacterProperties rPr = getRPr(); - if(rPr.isSetSolidFill()){ + CTTextCharacterProperties rPr = getRPrOrNull(); + if (rPr != null && rPr.isSetSolidFill()) { CTSolidColorFillProperties fill = rPr.getSolidFill(); if(fill.isSetSrgbClr()){ CTSRgbColor clr = fill.getSrgbClr(); - byte[] rgb = clr.getVal(); - if (rgb.length == 3) { - return new Color(0xFF & rgb[0], 0xFF & rgb[1], 0xFF & rgb[2]); - } else { - return new Color(0xFF & rgb[1], 0xFF & rgb[2], 0xFF & rgb[3], 0xFF & rgb[0]); - } + return clr.getVal(); } } - return new Color(0, 0, 0); + return new byte[]{0, 0, 0}; } /** @@ -97,7 +111,7 @@ public class XSSFTextRun { * @param fontSize font size in points. * The value of {@code -1} unsets the Sz attribute from the underlying xml bean */ - public void setFontSize(double fontSize){ + public void setFontSize(double fontSize) { CTTextCharacterProperties rPr = getRPr(); if(fontSize == -1.0) { if(rPr.isSetSz()) rPr.unsetSz(); @@ -113,14 +127,16 @@ public class XSSFTextRun { /** * @return font size in points or -1 if font size is not set. */ - public double getFontSize(){ + public double getFontSize() { double scale = 1; double size = XSSFFont.DEFAULT_FONT_SIZE; // default font size CTTextNormalAutofit afit = getParentParagraph().getParentShape().getTxBody().getBodyPr().getNormAutofit(); - if(afit != null) scale = (double)afit.getFontScale() / 100000; + if (afit != null && afit.isSetFontScale()) { + scale = NumberHelper.toDouble(afit.getFontScale()) / 100000; + } - CTTextCharacterProperties rPr = getRPr(); - if(rPr.isSetSz()){ + CTTextCharacterProperties rPr = getRPrOrNull(); + if(rPr != null && rPr.isSetSz()){ size = rPr.getSz()*0.01; } @@ -133,8 +149,8 @@ public class XSSFTextRun { * If this attribute is omitted then a value of 0 or no adjustment is assumed. */ public double getCharacterSpacing(){ - CTTextCharacterProperties rPr = getRPr(); - if(rPr.isSetSpc()){ + CTTextCharacterProperties rPr = getRPrOrNull(); + if(rPr != null && rPr.isSetSpc()){ return Units.toPoints(POIXMLUnits.parseLength(rPr.xgetSpc())); } return 0; @@ -192,19 +208,23 @@ public class XSSFTextRun { * @return font family or null if not set */ public String getFontFamily(){ - CTTextCharacterProperties rPr = getRPr(); - CTTextFont font = rPr.getLatin(); - if(font != null){ - return font.getTypeface(); + CTTextCharacterProperties rPr = getRPrOrNull(); + if (rPr != null) { + CTTextFont font = rPr.getLatin(); + if(font != null) { + return font.getTypeface(); + } } return XSSFFont.DEFAULT_FONT_NAME; } - public byte getPitchAndFamily(){ - CTTextCharacterProperties rPr = getRPr(); - CTTextFont font = rPr.getLatin(); - if(font != null){ - return font.getPitchFamily(); + public byte getPitchAndFamily() { + CTTextCharacterProperties rPr = getRPrOrNull(); + if (rPr != null) { + CTTextFont font = rPr.getLatin(); + if(font != null) { + return font.getPitchFamily(); + } } return 0; } @@ -222,8 +242,8 @@ public class XSSFTextRun { * @return whether a run of text will be formatted as strikethrough text. Default is false. */ public boolean isStrikethrough() { - CTTextCharacterProperties rPr = getRPr(); - if(rPr.isSetStrike()){ + CTTextCharacterProperties rPr = getRPrOrNull(); + if (rPr != null && rPr.isSetStrike()){ return rPr.getStrike() != STTextStrikeType.NO_STRIKE; } return false; @@ -233,8 +253,8 @@ public class XSSFTextRun { * @return whether a run of text will be formatted as a superscript text. Default is false. */ public boolean isSuperscript() { - CTTextCharacterProperties rPr = getRPr(); - if(rPr.isSetBaseline()){ + CTTextCharacterProperties rPr = getRPrOrNull(); + if (rPr != null && rPr.isSetBaseline()){ return POIXMLUnits.parsePercent(rPr.xgetBaseline()) > 0; } return false; @@ -274,8 +294,8 @@ public class XSSFTextRun { * @return whether a run of text will be formatted as a superscript text. Default is false. */ public boolean isSubscript() { - CTTextCharacterProperties rPr = getRPr(); - if(rPr.isSetBaseline()){ + CTTextCharacterProperties rPr = getRPrOrNull(); + if (rPr != null && rPr.isSetBaseline()){ return POIXMLUnits.parsePercent(rPr.xgetBaseline()) < 0; } return false; @@ -285,8 +305,8 @@ public class XSSFTextRun { * @return whether a run of text will be formatted as a superscript text. Default is false. */ public TextCap getTextCap() { - CTTextCharacterProperties rPr = getRPr(); - if(rPr.isSetCap()){ + CTTextCharacterProperties rPr = getRPrOrNull(); + if (rPr != null && rPr.isSetCap()) { return TextCap.values()[rPr.getCap().intValue() - 1]; } return TextCap.NONE; @@ -305,8 +325,8 @@ public class XSSFTextRun { * @return whether this run of text is formatted as bold text */ public boolean isBold(){ - CTTextCharacterProperties rPr = getRPr(); - if(rPr.isSetB()){ + CTTextCharacterProperties rPr = getRPrOrNull(); + if (rPr != null && rPr.isSetB()){ return rPr.getB(); } return false; @@ -323,8 +343,8 @@ public class XSSFTextRun { * @return whether this run of text is formatted as italic text */ public boolean isItalic(){ - CTTextCharacterProperties rPr = getRPr(); - if(rPr.isSetI()){ + CTTextCharacterProperties rPr = getRPrOrNull(); + if (rPr != null && rPr.isSetI()){ return rPr.getI(); } return false; @@ -341,17 +361,21 @@ public class XSSFTextRun { * @return whether this run of text is formatted as underlined text */ public boolean isUnderline(){ - CTTextCharacterProperties rPr = getRPr(); - if(rPr.isSetU()){ + CTTextCharacterProperties rPr = getRPrOrNull(); + if (rPr != null && rPr.isSetU()){ return rPr.getU() != STTextUnderlineType.NONE; } return false; } - protected CTTextCharacterProperties getRPr(){ + protected CTTextCharacterProperties getRPr() { return _r.isSetRPr() ? _r.getRPr() : _r.addNewRPr(); } + private CTTextCharacterProperties getRPrOrNull() { + return _r.isSetRPr() ? _r.getRPr() : null; + } + @Override public String toString(){ return "[" + getClass() + "]" + getText(); diff --git a/poi-ooxml/src/main/java/org/apache/poi/xwpf/extractor/XWPFWordExtractor.java b/poi-ooxml/src/main/java/org/apache/poi/xwpf/extractor/XWPFWordExtractor.java index 0673aee02d..3ef53d636a 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xwpf/extractor/XWPFWordExtractor.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xwpf/extractor/XWPFWordExtractor.java @@ -144,13 +144,13 @@ public class XWPFWordExtractor implements POIXMLTextExtractor { // Add comments XWPFCommentsDecorator decorator = new XWPFCommentsDecorator(paragraph, null); String commentText = decorator.getCommentText(); - if (commentText.length() > 0) { + if (!commentText.isEmpty()) { text.append(commentText).append('\n'); } // Do endnotes and footnotes String footnameText = paragraph.getFootnoteText(); - if (footnameText != null && footnameText.length() > 0) { + if (footnameText != null && !footnameText.isEmpty()) { text.append(footnameText).append('\n'); } 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/XWPFPictureData.java b/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFPictureData.java index d74440fcc9..7d541a627f 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFPictureData.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFPictureData.java @@ -49,7 +49,8 @@ public class XWPFPictureData extends POIXMLDocumentPart { * @return the max image size allowed for XSSF pictures */ public static int getMaxImageSize() { - return MAX_IMAGE_SIZE; + final int ioMaxSize = IOUtils.getByteArrayMaxOverride(); + return ioMaxSize < 0 ? MAX_IMAGE_SIZE : Math.min(MAX_IMAGE_SIZE, ioMaxSize); } /** 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 6a3e80b994..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); } @@ -148,7 +149,7 @@ public class XWPFRun implements ISDTContents, IRunElement, CharacterRun { */ static void preserveSpaces(XmlString xs) { String text = xs.getStringValue(); - if (text != null && text.length() >= 1 + if (text != null && !text.isEmpty() && (Character.isWhitespace(text.charAt(0)) || Character.isWhitespace(text.charAt(text.length()-1)))) { try (XmlCursor c = xs.newCursor()) { c.toNextToken(); @@ -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/main/java9/module-info.java b/poi-ooxml/src/main/java9/module-info.java index d740345f59..738f9a8c71 100644 --- a/poi-ooxml/src/main/java9/module-info.java +++ b/poi-ooxml/src/main/java9/module-info.java @@ -95,8 +95,7 @@ module org.apache.poi.ooxml { /* optional dependencies for slideshow rendering via PPTX2PNG */ requires static org.apache.xmlgraphics.batik.anim; requires static org.apache.xmlgraphics.batik.awt.util; - /* this typo appears in Batik 1.18 and will be fixed in a future release */ - requires static org.apache.xmlgraphics.batik.brdige; + requires static org.apache.xmlgraphics.batik.bridge; requires static org.apache.xmlgraphics.batik.codec; requires static org.apache.xmlgraphics.batik.constants; requires static org.apache.xmlgraphics.batik.css; @@ -112,7 +111,7 @@ module org.apache.poi.ooxml { requires static org.apache.xmlgraphics.batik.transcoder; requires static org.apache.xmlgraphics.batik.util; requires static org.apache.xmlgraphics.batik.xml; - requires static xmlgraphics.commons; + requires static org.apache.xmlgraphics.commons; requires static org.apache.pdfbox; requires static org.apache.fontbox; diff --git a/poi-ooxml/src/test/java/org/apache/poi/extractor/ooxml/TestExtractorFactory.java b/poi-ooxml/src/test/java/org/apache/poi/extractor/ooxml/TestExtractorFactory.java index 557752f2ee..acf4881f79 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/extractor/ooxml/TestExtractorFactory.java +++ b/poi-ooxml/src/test/java/org/apache/poi/extractor/ooxml/TestExtractorFactory.java @@ -17,7 +17,6 @@ package org.apache.poi.extractor.ooxml; import static org.apache.poi.POITestCase.assertContains; -import static org.apache.poi.extractor.ExtractorFactory.createExtractor; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; @@ -26,12 +25,14 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Locale; import java.util.stream.Stream; +import org.apache.poi.EmptyFileException; import org.apache.poi.POIDataSamples; import org.apache.poi.extractor.ExtractorFactory; import org.apache.poi.extractor.POIOLE2TextExtractor; @@ -142,7 +143,7 @@ class TestExtractorFactory { @ParameterizedTest @MethodSource("testFileData") void testFile(String testcase, File file, String extractor, int count) throws Exception { - try (POITextExtractor ext = createExtractor(file)) { + try (POITextExtractor ext = ExtractorFactory.createExtractor(file)) { assertNotNull(ext); testExtractor(ext, testcase, extractor, count); } @@ -154,7 +155,7 @@ class TestExtractorFactory { // test processing of InputStream try (FileInputStream fis = new FileInputStream(testFile); POIFSFileSystem poifs = new POIFSFileSystem(fis); - POITextExtractor ext = createExtractor(poifs)) { + POITextExtractor ext = ExtractorFactory.createExtractor(poifs)) { assertNotNull(ext); testExtractor(ext, testcase, extractor, count); } @@ -165,7 +166,7 @@ class TestExtractorFactory { void testOOXML(String testcase, File testFile, String extractor, int count) throws Exception { // test processing of InputStream try (FileInputStream fis = new FileInputStream(testFile); - POITextExtractor ext = createExtractor(fis)) { + POITextExtractor ext = ExtractorFactory.createExtractor(fis)) { assertNotNull(ext); testExtractor(ext, testcase, extractor, count); } @@ -187,15 +188,31 @@ class TestExtractorFactory { @Test void testFileInvalid() { //noinspection resource - IOException ex = assertThrows(IOException.class, () -> createExtractor(txt)); - assertEquals("Can't create extractor - unsupported file type: UNKNOWN", ex.getMessage()); + IOException ex = assertThrows(IOException.class, () -> ExtractorFactory.createExtractor(txt)); + assertEquals("Can't create extractor - unsupported file type: UNKNOWN", ex.getMessage(), + "Had: " + ex); } @Test void testInputStreamInvalid() throws IOException { try (FileInputStream fis = new FileInputStream(txt)) { - IOException ex = assertThrows(IOException.class, () -> createExtractor(fis)); - assertTrue(ex.getMessage().contains(FileMagic.UNKNOWN.name())); + IOException ex = assertThrows(IOException.class, () -> ExtractorFactory.createExtractor(fis)); + assertTrue(ex.getMessage().contains(FileMagic.UNKNOWN.name()), "Had: " + ex); + } + } + + @Test + void testInputStreamEmpty() throws IOException { + try (ByteArrayInputStream fis = new ByteArrayInputStream(new byte[0])) { + EmptyFileException ex = assertThrows(EmptyFileException.class, () -> ExtractorFactory.createExtractor(fis)); + } + } + + @Test + void testInputStreamTooShort() throws IOException { + try (ByteArrayInputStream fis = new ByteArrayInputStream(new byte[] { 0 })) { + IOException ex = assertThrows(IOException.class, () -> ExtractorFactory.createExtractor(fis)); + assertTrue(ex.getMessage().contains(FileMagic.UNKNOWN.name()), "Had: " + ex); } } @@ -204,7 +221,8 @@ class TestExtractorFactory { // Not really an Extractor test, but we'll leave it to test POIFS reaction anyway ... //noinspection resource IOException ex = assertThrows(IOException.class, () -> new POIFSFileSystem(txt)); - assertTrue(ex.getMessage().contains("Invalid header signature; read 0x3D20726F68747541, expected 0xE11AB1A1E011CFD0")); + assertTrue(ex.getMessage().contains("Invalid header signature; read 0x3D20726F68747541, expected 0xE11AB1A1E011CFD0"), + "Had: " + ex); } @Test @@ -240,7 +258,7 @@ class TestExtractorFactory { try { // Check we get the right extractors now - try (POITextExtractor extractor = createExtractor(new POIFSFileSystem(new FileInputStream(xls)))) { + try (POITextExtractor extractor = ExtractorFactory.createExtractor(new POIFSFileSystem(new FileInputStream(xls)))) { assertInstanceOf(EventBasedExcelExtractor.class, extractor); assertTrue(extractor.getText().length() > 200); } @@ -259,7 +277,7 @@ class TestExtractorFactory { assertNull(ExtractorFactory.getAllThreadsPreferEventExtractors()); // And back - try (POITextExtractor extractor = createExtractor(new POIFSFileSystem(new FileInputStream(xls)))) { + try (POITextExtractor extractor = ExtractorFactory.createExtractor(new POIFSFileSystem(new FileInputStream(xls)))) { assertInstanceOf(ExcelExtractor.class, extractor); assertTrue(extractor.getText().length() > 200); } @@ -298,7 +316,7 @@ class TestExtractorFactory { void testEmbedded(String format, File file, String expected) throws Exception { int numWord = 0, numXls = 0, numPpt = 0, numMsg = 0, numWordX = 0; - try (final POIOLE2TextExtractor ext = (POIOLE2TextExtractor) createExtractor(file)) { + try (final POIOLE2TextExtractor ext = (POIOLE2TextExtractor) ExtractorFactory.createExtractor(file)) { final POITextExtractor[] embeds = ExtractorFactory.getEmbeddedDocsTextExtractors(ext); for (POITextExtractor embed : embeds) { @@ -470,6 +488,6 @@ class TestExtractorFactory { } private static POITextExtractor ex(String filename) throws IOException { - return createExtractor(ssTests.getFile(filename)); + return ExtractorFactory.createExtractor(ssTests.getFile(filename)); } } diff --git a/poi-ooxml/src/test/java/org/apache/poi/ooxml/TestPOIXMLDocument.java b/poi-ooxml/src/test/java/org/apache/poi/ooxml/TestPOIXMLDocument.java index cc22c41ca9..86672fc7e5 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/ooxml/TestPOIXMLDocument.java +++ b/poi-ooxml/src/test/java/org/apache/poi/ooxml/TestPOIXMLDocument.java @@ -24,7 +24,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.ByteArrayInputStream; import java.io.File; @@ -136,8 +135,7 @@ public final class TestPOIXMLDocument { // see {@link org.apache.poi.openxml4j.opc.ZipPackage#saveImpl(java.io.OutputStream)} OpenXML4JRuntimeException e = assertThrows(OpenXML4JRuntimeException.class, () -> doc.write(out), "Should not be able to write to an output stream that has been closed."); - assertTrue(e.getMessage().matches("Fail to save: an error occurs while saving the package : " + - "The part .+ failed to be saved in the stream with marshaller .+")); + assertEquals("Failed to save: content types part", e.getMessage()); // Should not be able to write a document that has been closed doc.close(); diff --git a/poi-ooxml/src/test/java/org/apache/poi/poifs/crypt/dsig/TestSignatureInfo.java b/poi-ooxml/src/test/java/org/apache/poi/poifs/crypt/dsig/TestSignatureInfo.java index 241243a93e..d834fd1e87 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/poifs/crypt/dsig/TestSignatureInfo.java +++ b/poi-ooxml/src/test/java/org/apache/poi/poifs/crypt/dsig/TestSignatureInfo.java @@ -41,6 +41,8 @@ import java.net.ConnectException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.SocketTimeoutException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.security.GeneralSecurityException; import java.security.KeyStoreException; @@ -597,8 +599,8 @@ class TestSignatureInfo { public static String getAccessError(String destinationUrl, boolean fireRequest, int timeout) { URL url; try { - url = new URL(destinationUrl); - } catch (MalformedURLException e) { + url = new URI(destinationUrl).toURL(); + } catch (MalformedURLException | URISyntaxException e) { throw new IllegalArgumentException("Invalid destination URL", e); } diff --git a/poi-ooxml/src/test/java/org/apache/poi/util/tests/TestTempFileThreaded.java b/poi-ooxml/src/test/java/org/apache/poi/util/tests/TestTempFileThreaded.java index 625fa448e4..4110ef456b 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/util/tests/TestTempFileThreaded.java +++ b/poi-ooxml/src/test/java/org/apache/poi/util/tests/TestTempFileThreaded.java @@ -33,6 +33,7 @@ import java.util.Map; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.util.SuppressForbidden; import org.apache.poi.util.TempFile; import org.apache.poi.util.TempFileCreationStrategy; import org.apache.poi.xssf.streaming.SXSSFSheet; @@ -51,6 +52,7 @@ class TestTempFileThreaded { // the actual thread-safe temp-file strategy private static TempFileCreationStrategy createTempFileCreationStrategy(File poiTempFileDirectory) { return new TempFileCreationStrategy() { + @SuppressForbidden("Thread.getId() is deprecated and replaced with threadId() in JDK 19+") @Override public File createTempFile(String prefix, String suffix) throws IOException { long threadId = Thread.currentThread().getId(); diff --git a/poi-ooxml/src/test/java/org/apache/poi/xslf/usermodel/TestXSLFSlideShowFactory.java b/poi-ooxml/src/test/java/org/apache/poi/xslf/usermodel/TestXSLFSlideShowFactory.java index a51f1838e8..4d29222d9a 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/xslf/usermodel/TestXSLFSlideShowFactory.java +++ b/poi-ooxml/src/test/java/org/apache/poi/xslf/usermodel/TestXSLFSlideShowFactory.java @@ -42,7 +42,9 @@ import org.apache.poi.util.IOUtils; import org.apache.poi.util.TempFile; import org.apache.poi.xssf.XSSFTestDataSamples; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; +@Isolated // testFactoryFromFile() changes sample-files, thus should run in isolation until this is removed public final class TestXSLFSlideShowFactory extends BaseTestSlideShowFactory { private static final POIDataSamples _slTests = POIDataSamples.getSlideShowInstance(); private static final String filename = "SampleShow.pptx"; @@ -54,7 +56,7 @@ public final class TestXSLFSlideShowFactory extends BaseTestSlideShowFactory { @Test void testFactoryFromFile() { // Remove thrown.* when bug 58779 is resolved - // In the mean time, this function will modify SampleShow.pptx on disk. + // In the meantime, this function will modify SampleShow.pptx on disk. AssertionError ex = assertThrows(AssertionError.class, () -> testFactoryFromFile(filename), "Bug 58779: " + removeExpectedExceptionMsg); assertTrue(ex.getMessage().contains("SampleShow.pptx sample file was modified as a result of closing the slideshow")); @@ -97,7 +99,7 @@ public final class TestXSLFSlideShowFactory extends BaseTestSlideShowFactory { File file = XSSFTestDataSamples.getSampleFile("workbook.xml"); try (FileInputStream fis = new FileInputStream(file)) { try { - SlideShow slideShow = SlideShowFactory.create(fis); + SlideShow<?,?> slideShow = SlideShowFactory.create(fis); if (slideShow != null) slideShow.close(); fail("SlideShowFactory.create should have failed"); } catch (IOException ie) { @@ -105,7 +107,7 @@ public final class TestXSLFSlideShowFactory extends BaseTestSlideShowFactory { } } try { - SlideShow slideShow = SlideShowFactory.create(file); + SlideShow<?,?> slideShow = SlideShowFactory.create(file); if (slideShow != null) slideShow.close(); fail("SlideShowFactory.create should have failed"); } catch (IOException ie) { 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/streaming/TestSXSSFSheet.java b/poi-ooxml/src/test/java/org/apache/poi/xssf/streaming/TestSXSSFSheet.java index 5ab64a69aa..d0fbea91da 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/xssf/streaming/TestSXSSFSheet.java +++ b/poi-ooxml/src/test/java/org/apache/poi/xssf/streaming/TestSXSSFSheet.java @@ -63,6 +63,7 @@ public final class TestSXSSFSheet extends BaseTestXSheet { @Override @Test protected void cloneSheet() { + //noinspection Convert2MethodRef RuntimeException ex = assertThrows(RuntimeException.class, () -> super.cloneSheet()); assertEquals("Not Implemented", ex.getMessage()); } @@ -70,6 +71,7 @@ public final class TestSXSSFSheet extends BaseTestXSheet { @Override @Test protected void cloneSheetMultipleTimes() { + //noinspection Convert2MethodRef RuntimeException ex = assertThrows(RuntimeException.class, () -> super.cloneSheetMultipleTimes()); assertEquals("Not Implemented", ex.getMessage()); } @@ -80,6 +82,7 @@ public final class TestSXSSFSheet extends BaseTestXSheet { @Override @Test protected void shiftMerged() { + //noinspection Convert2MethodRef RuntimeException ex = assertThrows(RuntimeException.class, () -> super.shiftMerged()); assertEquals("Not Implemented", ex.getMessage()); } @@ -92,6 +95,7 @@ public final class TestSXSSFSheet extends BaseTestXSheet { @Override @Test protected void bug35084() { + //noinspection Convert2MethodRef RuntimeException ex = assertThrows(RuntimeException.class, () -> super.bug35084()); assertEquals("Not Implemented", ex.getMessage()); } diff --git a/poi-ooxml/src/test/java/org/apache/poi/xssf/streaming/TestSheetDataWriter.java b/poi-ooxml/src/test/java/org/apache/poi/xssf/streaming/TestSheetDataWriter.java index a452073f35..239477c352 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/xssf/streaming/TestSheetDataWriter.java +++ b/poi-ooxml/src/test/java/org/apache/poi/xssf/streaming/TestSheetDataWriter.java @@ -26,6 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.io.StringWriter; import java.nio.charset.StandardCharsets; import org.apache.poi.util.IOUtils; @@ -69,6 +70,7 @@ public final class TestSheetDataWriter { IOUtils.closeQuietly(writer); } } + @Test void testWriteNewLines() throws IOException { SheetDataWriter writer = new SheetDataWriter(); @@ -84,4 +86,17 @@ public final class TestSheetDataWriter { IOUtils.closeQuietly(writer); } } + + @Test + void testDispose() throws IOException { + SheetDataWriter writer = new SheetDataWriter(); + assertTrue(writer.dispose()); + } + + @Test + void testWriterDispose() throws IOException { + StringWriter sw = new StringWriter(); + SheetDataWriter writer = new SheetDataWriter(sw); + assertTrue(writer.dispose()); + } } 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-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFTextParagraph.java b/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFTextParagraph.java index ec13f604be..17daaf102c 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFTextParagraph.java +++ b/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFTextParagraph.java @@ -90,6 +90,10 @@ class TestXSSFTextParagraph { text.setBulletFontColor(color); assertEquals(color, text.getBulletFontColor()); + final byte[] colorBytes = new byte[]{(byte) 255, 127, 0}; + text.setBulletFontColor(colorBytes); + assertArrayEquals(colorBytes, text.getBulletFontColorAsBytes()); + assertEquals(100.0, text.getBulletFontSize(), 0.01); text.setBulletFontSize(1.0); assertEquals(1.0, text.getBulletFontSize(), 0.01); @@ -192,4 +196,32 @@ class TestXSSFTextParagraph { new XSSFTextParagraph(text.getXmlObject(), shape.getCTShape()); } } + + @Test + void testXSSFTextParagraph2() throws IOException { + try (XSSFWorkbook wb = new XSSFWorkbook()) { + XSSFSheet sheet = wb.createSheet(); + XSSFDrawing drawing = sheet.createDrawingPatriarch(); + + XSSFTextBox shape = drawing.createTextbox(new XSSFClientAnchor(0, 0, 0, 0, 2, 2, 3, 4)); + XSSFTextRun run = shape.getTextParagraphs().get(0).getTextRuns().get(0); + final byte[] colorBytes = new byte[]{0, (byte) 255, (byte) 255}; + run.setFont("Arial"); + run.setFontColor(colorBytes); + run.setText("Test String"); + + List<XSSFTextParagraph> paras = shape.getTextParagraphs(); + assertEquals(1, paras.size()); + + XSSFTextParagraph text = paras.get(0); + assertEquals("Test String", text.getText()); + + List<XSSFTextRun> runs = text.getTextRuns(); + assertEquals(1, runs.size()); + XSSFTextRun run2 = runs.get(0); + assertEquals(run.getText(), run2.getText()); + assertEquals(run.getFontFamily(), run2.getFontFamily()); + assertArrayEquals(run.getFontColorAsBytes(), run2.getFontColorAsBytes()); + } + } } diff --git a/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFTextRun.java b/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFTextRun.java index 74434b164b..fae3f42274 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFTextRun.java +++ b/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFTextRun.java @@ -45,11 +45,17 @@ class TestXSSFTextRun { assertNotNull(run.getXmlObject()); assertNotNull(run.getRPr()); - assertEquals(new Color(0, 0, 0), run.getFontColor()); + assertArrayEquals(new byte[]{0, 0, 0}, run.getFontColorAsBytes()); + final byte[] colorBytes = new byte[]{0, (byte) 255, (byte) 255}; Color color = new Color(0, 255, 255); run.setFontColor(color); assertEquals(color, run.getFontColor()); + assertArrayEquals(colorBytes, run.getFontColorAsBytes()); + + run.setFontColor(colorBytes); + assertEquals(color, run.getFontColor()); + assertArrayEquals(colorBytes, run.getFontColorAsBytes()); assertEquals(11.0, run.getFontSize(), 0.01); run.setFontSize(12.32); diff --git a/poi-ooxml/src/test/java/org/apache/poi/xwpf/usermodel/TestXWPFBugs.java b/poi-ooxml/src/test/java/org/apache/poi/xwpf/usermodel/TestXWPFBugs.java index 8afaaecb2d..4846e7a402 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/xwpf/usermodel/TestXWPFBugs.java +++ b/poi-ooxml/src/test/java/org/apache/poi/xwpf/usermodel/TestXWPFBugs.java @@ -28,6 +28,9 @@ import java.io.IOException; import java.math.BigInteger; import java.util.List; +import org.apache.poi.openxml4j.util.ZipArchiveFakeEntry; +import org.apache.poi.openxml4j.util.ZipInputStreamZipEntrySource; +import org.apache.poi.util.IOUtils; import org.apache.poi.util.Units; import org.apache.poi.xwpf.XWPFTestDataSamples; import org.apache.poi.xwpf.usermodel.XWPFRun.FontCharRange; @@ -149,22 +152,22 @@ class TestXWPFBugs { } } - /** - * Removing a run needs to take into account position of run if paragraph contains hyperlink runs - */ - @Test - void test58618() throws IOException { - try (XWPFDocument doc = XWPFTestDataSamples.openSampleDocument("58618.docx")) { - XWPFParagraph para = (XWPFParagraph) doc.getBodyElements().get(0); - assertNotNull(para); - assertEquals("Some text some hyper links link link and some text.....", para.getText()); - XWPFRun run = para.insertNewRun(para.getRuns().size()); - run.setText("New Text"); - assertEquals("Some text some hyper links link link and some text.....New Text", para.getText()); - para.removeRun(para.getRuns().size() - 2); - assertEquals("Some text some hyper links link linkNew Text", para.getText()); - } - } + /** + * Removing a run needs to take into account position of run if paragraph contains hyperlink runs + */ + @Test + void test58618() throws IOException { + try (XWPFDocument doc = XWPFTestDataSamples.openSampleDocument("58618.docx")) { + XWPFParagraph para = (XWPFParagraph) doc.getBodyElements().get(0); + assertNotNull(para); + assertEquals("Some text some hyper links link link and some text.....", para.getText()); + XWPFRun run = para.insertNewRun(para.getRuns().size()); + run.setText("New Text"); + assertEquals("Some text some hyper links link link and some text.....New Text", para.getText()); + para.removeRun(para.getRuns().size() - 2); + assertEquals("Some text some hyper links link linkNew Text", para.getText()); + } + } @Test void test59378() throws IOException { @@ -329,7 +332,7 @@ class TestXWPFBugs { } } - private static void addNumberingWithAbstractId(XWPFNumbering documentNumbering, int id){ + private static void addNumberingWithAbstractId(XWPFNumbering documentNumbering, int id) { // create a numbering scheme CTAbstractNum cTAbstractNum = CTAbstractNum.Factory.newInstance(); // give the scheme an ID @@ -340,4 +343,34 @@ class TestXWPFBugs { documentNumbering.addNum(abstractNumID); } + + @Test + void testBug69628() throws IOException { + final int expectedParagraphs = 24; + // bug69628.docx has -1 entry sizes in the zip data + try (XWPFDocument doc = XWPFTestDataSamples.openSampleDocument("bug69628.docx")) { + assertEquals(expectedParagraphs, doc.getParagraphs().size()); + } + // test again with smaller byte array max + ZipArchiveFakeEntry.setMaxEntrySize(30 * 1024 * 1024); + try (XWPFDocument doc = XWPFTestDataSamples.openSampleDocument("bug69628.docx")) { + assertEquals(expectedParagraphs, doc.getParagraphs().size()); + } finally { + ZipArchiveFakeEntry.setMaxEntrySize(-1); + } + // test again with smaller byte array max + IOUtils.setByteArrayMaxOverride(30 * 1024 * 1024); + try (XWPFDocument doc = XWPFTestDataSamples.openSampleDocument("bug69628.docx")) { + assertEquals(expectedParagraphs, doc.getParagraphs().size()); + } finally { + IOUtils.setByteArrayMaxOverride(-1); + } + // test again but temp files enabled + ZipInputStreamZipEntrySource.setThresholdBytesForTempFiles(1000); + try (XWPFDocument doc = XWPFTestDataSamples.openSampleDocument("bug69628.docx")) { + assertEquals(expectedParagraphs, doc.getParagraphs().size()); + } finally { + ZipInputStreamZipEntrySource.setThresholdBytesForTempFiles(-1); + } + } } diff --git a/poi-ooxml/src/test/java9/module-info.java b/poi-ooxml/src/test/java9/module-info.java index 3c6276dfd6..3fbcedfb34 100644 --- a/poi-ooxml/src/test/java9/module-info.java +++ b/poi-ooxml/src/test/java9/module-info.java @@ -96,8 +96,7 @@ module org.apache.poi.ooxml { /* optional dependencies for slideshow rendering via PPTX2PNG */ requires static org.apache.xmlgraphics.batik.anim; requires static org.apache.xmlgraphics.batik.awt.util; - /* this typo appears in Batik 1.18 and will be fixed in a future release */ - requires static org.apache.xmlgraphics.batik.brdige; + requires static org.apache.xmlgraphics.batik.bridge; requires static org.apache.xmlgraphics.batik.codec; requires static org.apache.xmlgraphics.batik.constants; requires static org.apache.xmlgraphics.batik.css; @@ -113,7 +112,7 @@ module org.apache.poi.ooxml { requires static org.apache.xmlgraphics.batik.transcoder; requires static org.apache.xmlgraphics.batik.util; requires static org.apache.xmlgraphics.batik.xml; - requires static xmlgraphics.commons; + requires static org.apache.xmlgraphics.commons; requires static org.apache.pdfbox; requires static org.apache.fontbox; diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hdgf/streams/PointerContainingStream.java b/poi-scratchpad/src/main/java/org/apache/poi/hdgf/streams/PointerContainingStream.java index 69964427fa..387cb8580d 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hdgf/streams/PointerContainingStream.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hdgf/streams/PointerContainingStream.java @@ -17,8 +17,6 @@ package org.apache.poi.hdgf.streams; -import org.apache.logging.log4j.Logger; -import org.apache.poi.logging.PoiLogManager; import org.apache.poi.hdgf.chunks.ChunkFactory; import org.apache.poi.hdgf.pointers.Pointer; import org.apache.poi.hdgf.pointers.PointerFactory; @@ -28,8 +26,6 @@ import org.apache.poi.hdgf.pointers.PointerFactory; * other data too. */ public class PointerContainingStream extends Stream { // TODO - instantiable superclass - private static final Logger LOG = PoiLogManager.getLogger(PointerContainingStream.class); - private static int MAX_CHILDREN_NESTING = 500; private final Pointer[] childPointers; @@ -68,9 +64,9 @@ public class PointerContainingStream extends Stream { // TODO - instantiable sup } private void findChildren(byte[] documentData, int nesting) { - if (nesting > MAX_CHILDREN_NESTING) { + if (nesting > getMaxChildrenNesting()) { throw new IllegalArgumentException("Encountered too deep nesting, cannot process stream " + - "with more than " + MAX_CHILDREN_NESTING + " nested children. " + + "with more than " + getMaxChildrenNesting() + " nested children. " + "Some data could not be parsed. You can call setMaxChildrenNesting() to adjust " + "this limit."); } diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hmef/attribute/MAPIDateAttribute.java b/poi-scratchpad/src/main/java/org/apache/poi/hmef/attribute/MAPIDateAttribute.java index c267bd82ed..00f92545f8 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hmef/attribute/MAPIDateAttribute.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hmef/attribute/MAPIDateAttribute.java @@ -70,7 +70,7 @@ public final class MAPIDateAttribute extends MAPIAttribute { } /** - * Returns the Date of a Attribute, converting as appropriate + * Returns the Date of an Attribute, converting as appropriate */ public static Date getAsDate(MAPIAttribute attr) { if(attr == null) { diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hmef/attribute/MAPIStringAttribute.java b/poi-scratchpad/src/main/java/org/apache/poi/hmef/attribute/MAPIStringAttribute.java index c30ec513cb..9e3d6d0cb9 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hmef/attribute/MAPIStringAttribute.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hmef/attribute/MAPIStringAttribute.java @@ -64,7 +64,7 @@ public final class MAPIStringAttribute extends MAPIAttribute { } /** - * Returns the string of a Attribute, converting as appropriate + * Returns the string of an Attribute, converting as appropriate */ public static String getAsString(MAPIAttribute attr) { if(attr == null) { diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hmef/attribute/TNEFDateAttribute.java b/poi-scratchpad/src/main/java/org/apache/poi/hmef/attribute/TNEFDateAttribute.java index ea6382b461..7c27f76fbe 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hmef/attribute/TNEFDateAttribute.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hmef/attribute/TNEFDateAttribute.java @@ -85,7 +85,7 @@ public final class TNEFDateAttribute extends TNEFAttribute { } /** - * Returns the Date of a Attribute, converting as appropriate + * Returns the Date of an Attribute, converting as appropriate */ public static Date getAsDate(TNEFAttribute attr) { if(attr == null) { diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hmef/attribute/TNEFStringAttribute.java b/poi-scratchpad/src/main/java/org/apache/poi/hmef/attribute/TNEFStringAttribute.java index c403754edb..99c4cf4412 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hmef/attribute/TNEFStringAttribute.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hmef/attribute/TNEFStringAttribute.java @@ -68,7 +68,7 @@ public final class TNEFStringAttribute extends TNEFAttribute { } /** - * Returns the string of a Attribute, converting as appropriate + * Returns the string of an Attribute, converting as appropriate */ public static String getAsString(TNEFAttribute attr) { if(attr == null) { 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/main/java9/module-info.java b/poi-scratchpad/src/main/java9/module-info.java index ea505f6c1c..10e92f26aa 100644 --- a/poi-scratchpad/src/main/java9/module-info.java +++ b/poi-scratchpad/src/main/java9/module-info.java @@ -21,6 +21,7 @@ module org.apache.poi.scratchpad { requires commons.math3; requires org.apache.commons.io; requires org.apache.commons.codec; + requires org.apache.commons.collections4; requires org.apache.logging.log4j; uses org.apache.poi.sl.usermodel.MetroShapeProvider; 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-scratchpad/src/test/java/org/apache/poi/hslf/usermodel/TestPictures.java b/poi-scratchpad/src/test/java/org/apache/poi/hslf/usermodel/TestPictures.java index 2b90916775..5474c7f8d0 100644 --- a/poi-scratchpad/src/test/java/org/apache/poi/hslf/usermodel/TestPictures.java +++ b/poi-scratchpad/src/test/java/org/apache/poi/hslf/usermodel/TestPictures.java @@ -29,6 +29,8 @@ import java.awt.geom.Dimension2D; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.util.Arrays; import java.util.Collections; @@ -271,9 +273,9 @@ public final class TestPictures { @Test @Disabled("requires an internet connection to a 3rd party site") // As of 2017-06-20, the file still exists at the specified URL and the test passes. - void testZeroPictureLength() throws IOException { + void testZeroPictureLength() throws IOException, URISyntaxException { // take the data from www instead of test directory - URL url = new URL("http://www.cs.sfu.ca/~anoop/courses/CMPT-882-Fall-2002/chris.ppt"); + URL url = new URI("http://www.cs.sfu.ca/~anoop/courses/CMPT-882-Fall-2002/chris.ppt").toURL(); HSLFSlideShowImpl hslf = new HSLFSlideShowImpl(url.openStream()); /* Assume that the file could retrieved... InputStream is; diff --git a/poi-scratchpad/src/test/java9/module-info.java b/poi-scratchpad/src/test/java9/module-info.java index 35dbc01a3e..748a2244a7 100644 --- a/poi-scratchpad/src/test/java9/module-info.java +++ b/poi-scratchpad/src/test/java9/module-info.java @@ -21,6 +21,7 @@ module org.apache.poi.scratchpad { requires commons.math3; requires org.apache.commons.io; requires org.apache.commons.codec; + requires org.apache.commons.collections4; requires org.apache.logging.log4j; uses org.apache.poi.sl.usermodel.MetroShapeProvider; diff --git a/poi/build.gradle b/poi/build.gradle index b755139a1d..2b1076840e 100644 --- a/poi/build.gradle +++ b/poi/build.gradle @@ -37,7 +37,7 @@ sourceSets { dependencies { api "commons-codec:commons-codec:${commonsCodecVersion}" - api 'org.apache.commons:commons-collections4:4.4' + api 'org.apache.commons:commons-collections4:4.5.0' api "org.apache.commons:commons-math3:${commonsMathVersion}" api "commons-io:commons-io:${commonsIoVersion}" api 'com.zaxxer:SparseBitSet:1.3' diff --git a/poi/src/main/java/org/apache/poi/hssf/record/EscherAggregate.java b/poi/src/main/java/org/apache/poi/hssf/record/EscherAggregate.java index 2c71372ee9..67beb21207 100644 --- a/poi/src/main/java/org/apache/poi/hssf/record/EscherAggregate.java +++ b/poi/src/main/java/org/apache/poi/hssf/record/EscherAggregate.java @@ -1037,11 +1037,7 @@ public final class EscherAggregate extends AbstractEscherHolderRecord { final UnsynchronizedByteArrayOutputStream buffer = UnsynchronizedByteArrayOutputStream.builder().get(); void addBytes(byte[] data) { - try { - buffer.write(data); - } catch (IOException e) { - throw new IllegalStateException("Couldn't get data from drawing/continue records", e); - } + buffer.write(data); } @Override 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/record/WriteAccessRecord.java b/poi/src/main/java/org/apache/poi/hssf/record/WriteAccessRecord.java index 88f8affd2d..712693554c 100644 --- a/poi/src/main/java/org/apache/poi/hssf/record/WriteAccessRecord.java +++ b/poi/src/main/java/org/apache/poi/hssf/record/WriteAccessRecord.java @@ -108,10 +108,19 @@ public final class WriteAccessRecord extends StandardRecord { data = IOUtils.safelyAllocate(in.remaining(), STRING_SIZE); in.readFully(data); if (UTF16FLAG.isSet(is16BitFlag)) { - byteCnt = Math.min(nChars * 2, data.length); + // the spec only allows up to 109 bytes for the string in this record, but it seems some broken + // software out there will generate invalid records + int min = Math.min(nChars * 2, data.length); + + // make sure byteCnt is divisible by 2 as we read UTF-16LE + byteCnt = min - (min % 2); + charset = StandardCharsets.UTF_16LE; } else { + // the spec only allows up to 109 bytes for the string in this record, but it seems some broken + // software out there will generate invalid records byteCnt = Math.min(nChars, data.length); + charset = StandardCharsets.ISO_8859_1; } } @@ -130,7 +139,8 @@ public final class WriteAccessRecord extends StandardRecord { boolean is16bit = StringUtil.hasMultibyte(username); int encodedByteCount = username.length() * (is16bit ? 2 : 1); if (encodedByteCount > STRING_SIZE) { - throw new IllegalArgumentException("Name is too long: " + username); + throw new IllegalArgumentException("Name is too long, expecting up to " + STRING_SIZE + + " bytes, but had: " + encodedByteCount + " bytes: " + username); } field_1_username = username; diff --git a/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFCell.java b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFCell.java index 1569553682..fd4f97365d 100644 --- a/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFCell.java +++ b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFCell.java @@ -1020,8 +1020,6 @@ public class HSSFCell extends CellBase { _sheet.getSheet().setActiveCellCol(col); } - private static final DataFormatter DATA_FORMATTER = new DataFormatter(); - /** * Returns a string representation of the cell * @@ -1045,8 +1043,14 @@ public class HSSFCell extends CellBase { case FORMULA: return getCellFormula(); case NUMERIC: + if (DateUtil.isCellDateFormatted(this)) { + DataFormatter df = new DataFormatter(); + df.setUseCachedValuesForFormulaCells(true); + return df.formatCellValue(this); + } + return Double.toString(getNumericCellValue()); case STRING: - return DATA_FORMATTER.formatCellValue(this); + return getRichStringCellValue().toString(); default: return "Unknown Cell Type: " + getCellType(); } 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 0983c45497..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 @@ -35,6 +35,7 @@ import org.apache.poi.ss.usermodel.FillPatternType; import org.apache.poi.ss.usermodel.Font; import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.VerticalAlignment; +import org.apache.poi.ss.util.CellUtil; import org.apache.poi.util.Removal; import org.apache.poi.util.ThreadLocalUtil; @@ -49,22 +50,29 @@ public final class HSSFCellStyle implements CellStyle, Duplicatable { private final ExtendedFormatRecord _format; private final short _index; private final InternalWorkbook _workbook; + private final HSSFWorkbook _hssfWorkbook; - - /** Creates new HSSFCellStyle why would you want to do this?? */ protected HSSFCellStyle(short index, ExtendedFormatRecord rec, HSSFWorkbook workbook) { - this(index, rec, workbook.getWorkbook()); + _workbook = workbook.getInternalWorkbook(); + _hssfWorkbook = workbook; + _index = index; + _format = rec; } + + @Deprecated + @Removal(version = "7.0.0") protected HSSFCellStyle(short index, ExtendedFormatRecord rec, InternalWorkbook workbook) { _workbook = workbook; + _hssfWorkbook = null; _index = index; - _format = rec; + _format = rec; } protected HSSFCellStyle(HSSFCellStyle other) { _workbook = other._workbook; + _hssfWorkbook = other._hssfWorkbook; _index = other._index; _format = other._format; } @@ -851,9 +859,10 @@ public final class HSSFCellStyle implements CellStyle, Duplicatable { if(source instanceof HSSFCellStyle) { this.cloneStyleFrom((HSSFCellStyle)source); } else { - throw new IllegalArgumentException("Can only clone from one HSSFCellStyle to another, not between HSSFCellStyle and XSSFCellStyle"); + 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/HSSFWorkbook.java b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java index 0fea271340..6c7bb93a2b 100644 --- a/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java +++ b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java @@ -234,7 +234,8 @@ public final class HSSFWorkbook extends POIDocument implements Workbook { * @return the max image length allowed for HSSFWorkbook */ public static int getMaxImageLength() { - return MAX_IMAGE_LENGTH; + final int ioMaxSize = IOUtils.getByteArrayMaxOverride(); + return ioMaxSize < 0 ? MAX_IMAGE_LENGTH : Math.min(MAX_IMAGE_LENGTH, ioMaxSize); } /** @@ -1978,7 +1979,7 @@ public final class HSSFWorkbook extends POIDocument implements Workbook { case PICTURE_TYPE_WMF: // remove first 22 bytes if file starts with the WMF placeable header if (FileMagic.valueOf(pictureData) == FileMagic.WMF) { - pictureData = IOUtils.safelyClone(pictureData, 22, pictureData.length - 22, MAX_IMAGE_LENGTH); + pictureData = IOUtils.safelyClone(pictureData, 22, pictureData.length - 22, getMaxImageLength()); } // fall through case PICTURE_TYPE_EMF: 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/hssf/util/HSSFColor.java b/poi/src/main/java/org/apache/poi/hssf/util/HSSFColor.java index 263a7c0da9..107b116798 100644 --- a/poi/src/main/java/org/apache/poi/hssf/util/HSSFColor.java +++ b/poi/src/main/java/org/apache/poi/hssf/util/HSSFColor.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Objects; import org.apache.poi.ss.usermodel.Color; +import org.apache.poi.util.Removal; /** @@ -34,7 +35,7 @@ import org.apache.poi.ss.usermodel.Color; * Each color has an index (for the standard palette in Excel (tm) ), * native (RGB) triplet and string triplet. The string triplet is as the * color would be represented by Gnumeric. Having (string) this here is a bit of a - * collision of function between HSSF and the HSSFSerializer but I think its + * collision of function between HSSF and the HSSFSerializer but I think it's * a reasonable one in this case. */ public class HSSFColor implements Color { @@ -42,7 +43,7 @@ public class HSSFColor implements Color { private static Map<Integer,HSSFColor> indexHash; private static Map<HSSFColorPredefined,HSSFColor> enumList; - private final java.awt.Color color; + private final int rgb; private final int index; private final int index2; @@ -110,7 +111,7 @@ public class HSSFColor implements Color { private final HSSFColor color; HSSFColorPredefined(int index, int index2, int rgb) { - this.color = new HSSFColor(index, index2, new java.awt.Color(rgb)); + this.color = new HSSFColor(index, index2, rgb); } /** @@ -145,7 +146,7 @@ public class HSSFColor implements Color { * @return (a copy of) the HSSFColor assigned to the enum */ public HSSFColor getColor() { - return new HSSFColor(getIndex(), getIndex2(), color.color); + return new HSSFColor(getIndex(), getIndex2(), color.rgb); } } @@ -153,13 +154,40 @@ public class HSSFColor implements Color { /** Creates a new instance of HSSFColor */ public HSSFColor() { // automatic index - this(0x40, -1, java.awt.Color.BLACK); + this(0x40, -1, 0x000000); } + /** Constructs new instance of {@code HSSFColor} by + * extracting RGB from {@link java.awt.Color}. The code is equivalent + * to calling: + * <pre> + * new HSSFColor(index, index2, color.getRGB()); + * </pre> + * or specifying {@link #HSSFColor(int, int, int) RGB directly}. + * + * @param index + * @param index2 + * @param color color to extract RGB from + * @deprecated use {@link #HSSFColor(int, int, int)} instead + */ + @Removal(version = "7.0.0") public HSSFColor(int index, int index2, java.awt.Color color) { + this(index, index2, color.getRGB()); + } + + /** Constructs new instance of {@code HSSFColor} by + * specifying RGB as an {@code int} value. Given {@code blue}, {@code green} and + * {@code blue} being byte values between {@code 0x00 to 0xFF}, then + * {@code rgb = blue + (green >> 8) + (red >> 16)}. + * @param index + * @param index2 + * @param rgb combined value of RGB + * @since POI 5.4.2 + */ + public HSSFColor(int index, int index2, int rgb) { this.index = index; this.index2 = index2; - this.color = color; + this.rgb = 0xFF000000 | rgb; } /** @@ -203,7 +231,7 @@ public class HSSFColor implements Color { } /** - * this function returns all colors in a hastable. It's not implemented as a + * This function returns all colors in a map. It's not implemented as a * static member/statically initialized because that would be dirty in a * server environment as it is intended. This means you'll eat the time * it takes to create it once per request but you will not hold onto it @@ -235,7 +263,7 @@ public class HSSFColor implements Color { private static synchronized Map<HSSFColorPredefined,HSSFColor> mapEnumToColorClass() { if (enumList == null) { enumList = new EnumMap<>(HSSFColorPredefined.class); - // AUTOMATIC is not add to list + // AUTOMATIC is not added to list addHSSFColorPredefined(HSSFColorPredefined.BLACK); addHSSFColorPredefined(HSSFColorPredefined.BROWN); addHSSFColorPredefined(HSSFColorPredefined.OLIVE_GREEN); @@ -315,7 +343,7 @@ public class HSSFColor implements Color { */ public short [] getTriplet() { - return new short[] { (short)color.getRed(), (short)color.getGreen(), (short)color.getBlue() }; + return new short[] { getRed(), getGreen(), getBlue() }; } /** @@ -324,9 +352,19 @@ public class HSSFColor implements Color { */ public String getHexString() { - return (Integer.toHexString(color.getRed()*0x101) + ":" + - Integer.toHexString(color.getGreen()*0x101) + ":" + - Integer.toHexString(color.getBlue()*0x101)).toUpperCase(Locale.ROOT); + return (Integer.toHexString(getRed()*0x101) + ":" + + Integer.toHexString(getGreen()*0x101) + ":" + + Integer.toHexString(getBlue()*0x101)).toUpperCase(Locale.ROOT); + } + + private final short getBlue() { + return (short) (rgb & 0xFF); + } + private final short getGreen() { + return (short) ((rgb >> 8) & 0xFF); + } + private final short getRed() { + return (short) ((rgb >> 16) & 0xFF); } @Override @@ -338,12 +376,12 @@ public class HSSFColor implements Color { if (index != hssfColor.index) return false; if (index2 != hssfColor.index2) return false; - return Objects.equals(color, hssfColor.color); + return Objects.equals(rgb, hssfColor.rgb); } @Override public int hashCode() { - return Objects.hash(color,index,index2); + return Objects.hash(rgb, index, index2); } /** 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/WorkbookEvaluator.java b/poi/src/main/java/org/apache/poi/ss/formula/WorkbookEvaluator.java index 11ae822a21..4b703fc143 100644 --- a/poi/src/main/java/org/apache/poi/ss/formula/WorkbookEvaluator.java +++ b/poi/src/main/java/org/apache/poi/ss/formula/WorkbookEvaluator.java @@ -971,7 +971,7 @@ public final class WorkbookEvaluator { } /** - * Register a ATP function in runtime. + * Register an ATP function in runtime. * * @param name the function name * @param func the function to register diff --git a/poi/src/main/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java b/poi/src/main/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java index dd165383ee..d7e2db2236 100644 --- a/poi/src/main/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java +++ b/poi/src/main/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java @@ -1,12 +1,19 @@ -/* - * ==================================================================== 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. - * ==================================================================== - */ +/* ==================================================================== + 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.formula.atp; @@ -17,6 +24,8 @@ import java.util.Locale; import java.util.Map; import java.util.TreeSet; +import org.apache.logging.log4j.Logger; +import org.apache.poi.logging.PoiLogManager; import org.apache.poi.ss.formula.OperationEvaluationContext; import org.apache.poi.ss.formula.eval.NotImplementedFunctionException; import org.apache.poi.ss.formula.eval.ValueEval; @@ -32,6 +41,8 @@ public final class AnalysisToolPak implements UDFFinder { public static final UDFFinder instance = new AnalysisToolPak(); + private static final Logger LOG = PoiLogManager.getLogger(AnalysisToolPak.class); + private static final class NotImplemented implements FreeRefFunction { private final String _functionName; @@ -180,6 +191,7 @@ public final class AnalysisToolPak implements UDFFinder { r(m, "RECEIVED", null); r(m, "RTD", null); r(m, "SERIESSUM", null); + r(m, "SHEET", Sheet.instance); r(m, "SINGLE", Single.instance); r(m, "SQRTPI", Sqrtpi.instance); r(m, "STDEV.S", Stdevs.instance); @@ -265,9 +277,26 @@ public final class AnalysisToolPak implements UDFFinder { * @throws IllegalArgumentException if the function is unknown or already registered. * @since 3.8 beta6 */ - public static void registerFunction(String name, FreeRefFunction func){ + public static void registerFunction(String name, FreeRefFunction func) { + registerFunction(name, func, false); + } + + /** + * Register an ATP function in runtime. + * + * @param name the function name + * @param func the function to register + * @param force force registration even if the function is already registered or unknown to POI + * @throws IllegalArgumentException if the function is unknown or already registered (and `force` is not true). + * @since POI 5.4.2 + */ + public static void registerFunction(String name, FreeRefFunction func, boolean force) { AnalysisToolPak inst = (AnalysisToolPak)instance; - if(!isATPFunction(name)) { + if (force) { + // Excel regularly adds new functions, so the ones registered in POI + // can be well out of date - allow users who know what they are doing + // to force their update + } else if(!isATPFunction(name)) { FunctionMetadata metaData = FunctionMetadataRegistry.getFunctionByName(name); if(metaData != null) { throw new IllegalArgumentException(name + " is a built-in Excel function. " + @@ -276,13 +305,20 @@ public final class AnalysisToolPak implements UDFFinder { throw new IllegalArgumentException(name + " is not a function from the Excel Analysis Toolpack."); } + FreeRefFunction f = inst.findFunction(name); if(f != null && !(f instanceof NotImplemented)) { - throw new IllegalArgumentException("POI already implements " + name + - ". You cannot override POI's implementations of Excel functions"); + if (force) { + LOG.info("POI already implements " + name + + ". You are overriding the implementation."); + } else { + throw new IllegalArgumentException("POI already implements " + name + + ". You cannot override POI's implementations of Excel functions"); + } } // FIXME: inconsistent case-sensitivity inst._functionsByName.put(name, func); } + } 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 b03c732e89..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); @@ -155,7 +155,7 @@ final class FunctionMetadataReader { // (all unspecified params are assumed to be the same as the last) nItems --; } - byte[] result = IOUtils.safelyAllocate(nItems, MAX_RECORD_LENGTH); + byte[] result = IOUtils.safelyAllocate(nItems, getMaxRecordLength()); for (int i = 0; i < nItems; i++) { result[i] = parseOperandTypeCode(array[i]); } 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/Sheet.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/Sheet.java new file mode 100644 index 0000000000..37b78dbb89 --- /dev/null +++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/Sheet.java @@ -0,0 +1,89 @@ +/* ==================================================================== + 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.formula.functions; + +import org.apache.poi.ss.formula.EvaluationWorkbook; +import org.apache.poi.ss.formula.OperationEvaluationContext; +import org.apache.poi.ss.formula.eval.*; + +/** + * Implementation for Excel SHEET() function. + * <p> + * <b>Syntax</b>:<br> <b>SHEET</b>([value])<br> + * </p> + * <p> + * Returns the sheet number of the referenced sheet or the current sheet if no argument is provided. + * </p> + * <p> + * Examples: + * </p> + * <ul> + * <li><code>=SHEET()</code> → returns the current sheet number (1-based)</li> + * <li><code>=SHEET(A1)</code> → returns the sheet number of the reference A1</li> + * <li><code>=SHEET(A1:B5)</code> → returns the sheet number of the range A1:B5</li> + * <li><code>=SHEET("Sheet3")</code> → returns the sheet number of the sheet named "Sheet3"</li> + * </ul> + * <p> + * See <a href="https://support.microsoft.com/en-us/office/sheet-function-44718b6f-8b87-47a1-a9d6-b701c06cff24">Microsoft Documentation</a> + * </p> + */ +public class Sheet implements FreeRefFunction { + + public static final Sheet instance = new Sheet(); + + @Override + public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) { + try { + if (args.length == 0) { + // No argument provided → return the current sheet index +1 (Excel uses 1-based index) + return new NumberEval((double) ec.getSheetIndex() + 1); + } else { + ValueEval arg = args[0]; + + if (arg instanceof RefEval) { + // Argument is a single cell reference → return the sheet index of that reference +1 + RefEval ref = (RefEval) arg; + int sheetIndex = ref.getFirstSheetIndex(); + return new NumberEval((double) sheetIndex + 1); + } else if (arg instanceof AreaEval) { + // Argument is a cell range → return the sheet index of that area +1 + AreaEval area = (AreaEval) arg; + int sheetIndex = area.getFirstSheetIndex(); + return new NumberEval((double) sheetIndex + 1); + } else if (arg instanceof StringEval) { + // Argument is a string (sheet name, e.g., "Sheet3") → look up the sheet index by name + String sheetName = ((StringEval) arg).getStringValue(); + EvaluationWorkbook wb = ec.getWorkbook(); + int sheetIndex = wb.getSheetIndex(sheetName); + if (sheetIndex >= 0) { + return new NumberEval((double) sheetIndex + 1); + } else { + // Sheet name not found → return #N/A error + return ErrorEval.NA; + } + } else { + // Unsupported argument type → return #N/A error + return ErrorEval.NA; + } + } + } catch (Exception e) { + // Any unexpected exception (e.g., null pointers) → return #VALUE! error + 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 1749878163..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 @@ -451,7 +451,7 @@ public class CellReference implements GenericRecord { } /** - * Takes in a 0-based base-10 column and returns a ALPHA-26 + * Takes in a 0-based base-10 column and returns an ALPHA-26 * representation. * eg {@code convertNumToColString(3)} returns {@code "D"} */ 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 64ad761e2e..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 @@ -18,6 +18,7 @@ package org.apache.poi.ss.util; import java.util.Collections; +import java.util.EnumMap; import java.util.EnumSet; import java.util.HashMap; import java.util.Locale; @@ -46,6 +47,7 @@ import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.VerticalAlignment; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.util.Beta; +import org.apache.poi.util.Internal; import org.apache.poi.util.Removal; /** @@ -278,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); @@ -287,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); } @@ -570,7 +574,7 @@ public final class CellUtil { @Deprecated @Removal(version = "7.0.0") public static void setCellStyleProperties(Cell cell, Map<String, Object> properties) { - Map<CellPropertyType, Object> strPropMap = new HashMap<>(properties.size()); + final EnumMap<CellPropertyType, Object> strPropMap = new EnumMap<>(CellPropertyType.class); properties.forEach((k, v) -> strPropMap.put(namePropertyMap.get(k), v)); setCellStyleProperties(cell, strPropMap, false); } @@ -610,7 +614,7 @@ public final class CellUtil { CellStyle originalStyle = cell.getCellStyle(); CellStyle newStyle = null; - Map<CellPropertyType, Object> values = getFormatProperties(originalStyle); + EnumMap<CellPropertyType, Object> values = getFormatProperties(originalStyle); if (properties.containsKey(CellPropertyType.FILL_FOREGROUND_COLOR_COLOR) && properties.get(CellPropertyType.FILL_FOREGROUND_COLOR_COLOR) == null) { values.remove(CellPropertyType.FILL_FOREGROUND_COLOR); } @@ -627,11 +631,11 @@ public final class CellUtil { // index seems like what index the cellstyle is in the list of styles for a workbook. // not good to compare on! - int numberCellStyles = workbook.getNumCellStyles(); + final int numberCellStyles = workbook.getNumCellStyles(); for (int i = 0; i < numberCellStyles; i++) { CellStyle wbStyle = workbook.getCellStyleAt(i); - Map<CellPropertyType, Object> wbStyleMap = getFormatProperties(wbStyle); + EnumMap<CellPropertyType, Object> wbStyleMap = getFormatProperties(wbStyle); // the desired style already exists in the workbook. Use the existing style. if (styleMapsMatch(wbStyleMap, values, disableNullColorCheck)) { @@ -651,8 +655,8 @@ public final class CellUtil { private static boolean styleMapsMatch(final Map<CellPropertyType, Object> newProps, final Map<CellPropertyType, Object> storedProps, final boolean disableNullColorCheck) { - final Map<CellPropertyType, Object> map1Copy = new HashMap<>(newProps); - final Map<CellPropertyType, Object> map2Copy = new HashMap<>(storedProps); + final EnumMap<CellPropertyType, Object> map1Copy = new EnumMap<>(newProps); + final EnumMap<CellPropertyType, Object> map2Copy = new EnumMap<>(storedProps); final Object backColor1 = map1Copy.remove(CellPropertyType.FILL_BACKGROUND_COLOR_COLOR); final Object backColor2 = map2Copy.remove(CellPropertyType.FILL_BACKGROUND_COLOR_COLOR); final Object foreColor1 = map1Copy.remove(CellPropertyType.FILL_FOREGROUND_COLOR_COLOR); @@ -683,20 +687,26 @@ 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) { disableNullColorCheck = true; - propMap = new HashMap<>(); + propMap = new EnumMap<>(CellPropertyType.class); propMap.put(CellPropertyType.FILL_FOREGROUND_COLOR_COLOR, null); propMap.put(CellPropertyType.FILL_FOREGROUND_COLOR, null); } else if (CellPropertyType.FILL_BACKGROUND_COLOR_COLOR.equals(property) && propertyValue == null) { disableNullColorCheck = true; - propMap = new HashMap<>(); + propMap = new EnumMap<>(CellPropertyType.class); propMap.put(CellPropertyType.FILL_BACKGROUND_COLOR_COLOR, null); propMap.put(CellPropertyType.FILL_BACKGROUND_COLOR, null); } else { @@ -739,8 +749,8 @@ public final class CellUtil { * @return map of format properties (CellPropertyType -> Object) * @see #setFormatProperties(CellStyle, Workbook, Map) */ - private static Map<CellPropertyType, Object> getFormatProperties(CellStyle style) { - Map<CellPropertyType, Object> properties = new HashMap<>(); + private static EnumMap<CellPropertyType, Object> getFormatProperties(CellStyle style) { + EnumMap<CellPropertyType, Object> properties = new EnumMap<>(CellPropertyType.class); put(properties, CellPropertyType.ALIGNMENT, style.getAlignment()); put(properties, CellPropertyType.VERTICAL_ALIGNMENT, style.getVerticalAlignment()); put(properties, CellPropertyType.BORDER_BOTTOM, style.getBorderBottom()); @@ -776,7 +786,6 @@ public final class CellUtil { * * @param src the property map to copy from (read-only) * @param dest the property map to copy into - * @since POI 3.15 beta 3 */ private static void putAll(final Map<CellPropertyType, Object> src, Map<CellPropertyType, Object> dest) { for (final CellPropertyType key : src.keySet()) { @@ -806,7 +815,7 @@ public final class CellUtil { * Sets the format properties of the given style based on the given map. * * @param style cell style - * @param workbook parent workbook + * @param workbook parent workbook (can be null but some fomt info will not be copied if null is passed) * @param properties map of format properties (CellPropertyType -> Object) * @see #getFormatProperties(CellStyle) */ @@ -848,7 +857,9 @@ public final class CellUtil { } } - style.setFont(workbook.getFontAt(getInt(properties, CellPropertyType.FONT))); + if (workbook != null) { + style.setFont(workbook.getFontAt(getInt(properties, CellPropertyType.FONT))); + } style.setHidden(getBoolean(properties, CellPropertyType.HIDDEN)); style.setIndention(getShort(properties, CellPropertyType.INDENTION)); style.setLeftBorderColor(getShort(properties, CellPropertyType.LEFT_BORDER_COLOR)); @@ -862,6 +873,25 @@ public final class CellUtil { } /** + * Clones the style from one cell to another. For internal use only. + * Users should use the cloneStyleFrom method on CellStyle instead. + * + * @param src source cell style + * @param dest destination cell style + * @param destWorkbook destination workbook (can be null but some font info will not be copied if null is passed) + * @throws IllegalArgumentException if source or destination styles are null + * @since POI 5.4.2 + */ + @Internal + public static void cloneStyle(CellStyle src, CellStyle dest, Workbook destWorkbook) { + if (src == null || dest == null) { + throw new IllegalArgumentException("Source and destination styles must not be null"); + } + EnumMap<CellPropertyType, Object> properties = getFormatProperties(src); + setFormatProperties(dest, destWorkbook, properties); + } + + /** * Utility method that returns the named short value from the given map. * * @param properties map of named properties (CellPropertyType -> Object) diff --git a/poi/src/main/java/org/apache/poi/ss/util/PropertyTemplate.java b/poi/src/main/java/org/apache/poi/ss/util/PropertyTemplate.java index 2f74af55d7..e00b785070 100644 --- a/poi/src/main/java/org/apache/poi/ss/util/PropertyTemplate.java +++ b/poi/src/main/java/org/apache/poi/ss/util/PropertyTemplate.java @@ -28,9 +28,11 @@ import org.apache.poi.ss.usermodel.IndexedColors; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.util.Removal; +import java.util.EnumMap; +import java.util.EnumSet; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -42,9 +44,9 @@ import java.util.Set; * sheet in any workbook. * * This class requires the full spreadsheet to be in memory, so - * {@link org.apache.poi.xssf.streaming.SXSSFWorkbook} Spreadsheets are not + * <code>SXSSFWorkbook</code> Spreadsheets are not * supported. The same PropertyTemplate can, however, be applied to both - * {@link HSSFWorkbook} and {@link org.apache.poi.xssf.usermodel.XSSFWorkbook} + * {@link HSSFWorkbook} and <code>XSSFWorkbook</code> * objects if necessary. Portions of the border that fall outside the max range * of the {@link Workbook} sheet are ignored. * </p> @@ -59,7 +61,7 @@ public final class PropertyTemplate { * This is a list of cell properties for one shot application to a range of * cells at a later time. */ - private final Map<CellAddress, Map<CellPropertyType, Object>> _propertyTemplate; + private final Map<CellAddress, EnumMap<CellPropertyType, Object>> _propertyTemplate; /** * Create a PropertyTemplate object @@ -75,17 +77,17 @@ public final class PropertyTemplate { */ public PropertyTemplate(PropertyTemplate template) { this(); - for (Map.Entry<CellAddress, Map<CellPropertyType, Object>> entry : template.getTemplate().entrySet()) { + for (Map.Entry<CellAddress, EnumMap<CellPropertyType, Object>> entry : template.getTemplate().entrySet()) { _propertyTemplate.put(new CellAddress(entry.getKey()), cloneCellProperties(entry.getValue())); } } - private Map<CellAddress, Map<CellPropertyType, Object>> getTemplate() { + private Map<CellAddress, EnumMap<CellPropertyType, Object>> getTemplate() { return _propertyTemplate; } - private static Map<CellPropertyType, Object> cloneCellProperties(Map<CellPropertyType, Object> properties) { - return new HashMap<>(properties); + private static EnumMap<CellPropertyType, Object> cloneCellProperties(EnumMap<CellPropertyType, Object> properties) { + return new EnumMap<>(properties); } /** @@ -409,11 +411,11 @@ public final class PropertyTemplate { * @param range - {@link CellRangeAddress} range of cells to remove borders. */ private void removeBorders(CellRangeAddress range) { - Set<CellPropertyType> properties = new HashSet<>(); - properties.add(CellPropertyType.BORDER_TOP); - properties.add(CellPropertyType.BORDER_BOTTOM); - properties.add(CellPropertyType.BORDER_LEFT); - properties.add(CellPropertyType.BORDER_RIGHT); + EnumSet<CellPropertyType> properties = EnumSet.of( + CellPropertyType.BORDER_TOP, + CellPropertyType.BORDER_BOTTOM, + CellPropertyType.BORDER_LEFT, + CellPropertyType.BORDER_RIGHT); for (int row = range.getFirstRow(); row <= range.getLastRow(); row++) { for (int col = range.getFirstColumn(); col <= range .getLastColumn(); col++) { @@ -433,7 +435,7 @@ public final class PropertyTemplate { */ public void applyBorders(Sheet sheet) { Workbook wb = sheet.getWorkbook(); - for (Map.Entry<CellAddress, Map<CellPropertyType, Object>> entry : _propertyTemplate + for (Map.Entry<CellAddress, EnumMap<CellPropertyType, Object>> entry : _propertyTemplate .entrySet()) { CellAddress cellAddress = entry.getKey(); if (cellAddress.getRow() < wb.getSpreadsheetVersion().getMaxRows() @@ -756,11 +758,11 @@ public final class PropertyTemplate { * @param range - {@link CellRangeAddress} range of cells to remove borders. */ private void removeBorderColors(CellRangeAddress range) { - Set<CellPropertyType> properties = new HashSet<>(); - properties.add(CellPropertyType.TOP_BORDER_COLOR); - properties.add(CellPropertyType.BOTTOM_BORDER_COLOR); - properties.add(CellPropertyType.LEFT_BORDER_COLOR); - properties.add(CellPropertyType.RIGHT_BORDER_COLOR); + Set<CellPropertyType> properties = EnumSet.of( + CellPropertyType.TOP_BORDER_COLOR, + CellPropertyType.BOTTOM_BORDER_COLOR, + CellPropertyType.LEFT_BORDER_COLOR, + CellPropertyType.RIGHT_BORDER_COLOR); for (int row = range.getFirstRow(); row <= range.getLastRow(); row++) { for (int col = range.getFirstColumn(); col <= range .getLastColumn(); col++) { @@ -781,9 +783,9 @@ public final class PropertyTemplate { */ private void addProperty(int row, int col, CellPropertyType property, Object value) { CellAddress cell = new CellAddress(row, col); - Map<CellPropertyType, Object> cellProperties = _propertyTemplate.get(cell); + EnumMap<CellPropertyType, Object> cellProperties = _propertyTemplate.get(cell); if (cellProperties == null) { - cellProperties = new HashMap<>(); + cellProperties = new EnumMap<>(CellPropertyType.class); } cellProperties.put(property, value); _propertyTemplate.put(cell, cellProperties); @@ -795,7 +797,7 @@ public final class PropertyTemplate { */ private void removeProperties(int row, int col, Set<CellPropertyType> properties) { CellAddress cell = new CellAddress(row, col); - Map<CellPropertyType, Object> cellProperties = _propertyTemplate.get(cell); + EnumMap<CellPropertyType, Object> cellProperties = _propertyTemplate.get(cell); if (cellProperties != null) { cellProperties.keySet().removeAll(properties); if (cellProperties.isEmpty()) { @@ -946,6 +948,7 @@ public final class PropertyTemplate { * @deprecated See {@link #getTemplateProperty(int, int, CellPropertyType)} */ @Deprecated + @Removal(version = "7.0.0") public short getTemplateProperty(int row, int col, String propertyName) { return getTemplateProperty(new CellAddress(row, col), CellUtil.namePropertyMap.get(propertyName)); } 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/IOUtils.java b/poi/src/main/java/org/apache/poi/util/IOUtils.java index 430e895557..ff86043a54 100644 --- a/poi/src/main/java/org/apache/poi/util/IOUtils.java +++ b/poi/src/main/java/org/apache/poi/util/IOUtils.java @@ -108,6 +108,14 @@ public final class IOUtils { } /** + * @return The maximum number of bytes that should be possible to be allocated in one step. + * @since 5.4.1 + */ + public static int getByteArrayMaxOverride() { + return BYTE_ARRAY_MAX_OVERRIDE; + } + + /** * Peeks at the first 8 bytes of the stream. Returns those bytes, but * with the stream unaffected. Requires a stream that supports mark/reset, * or a PushbackInputStream. If the stream has >0 but <8 bytes, @@ -209,6 +217,27 @@ public final class IOUtils { } /** + * Reads up to {@code length} bytes from the input stream, and returns the bytes read. + * + * @param stream The byte stream of data to read. + * @param length The maximum length to read, use {@link Integer#MAX_VALUE} to read the stream + * until EOF + * @param maxLength if the input is equal to/longer than {@code maxLength} bytes, + * then throw an {@link IOException} complaining about the length. + * use {@link Integer#MAX_VALUE} to disable the check - if {@link #setByteArrayMaxOverride(int)} is + * set then that max of that value and this maxLength is used + * @return A byte array with the read bytes. + * @throws IOException If reading data fails or EOF is encountered too early for the given length. + * @throws RecordFormatException If the requested length is invalid. + * @since POI 5.4.1 + */ + public static byte[] toByteArray(InputStream stream, final long length, final int maxLength) throws IOException { + return toByteArray(stream, + length > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) length, + maxLength, true, length != Integer.MAX_VALUE); + } + + /** * Reads the input stream, and returns the bytes read. * * @param stream The byte stream of data to read. @@ -227,15 +256,12 @@ public final class IOUtils { private static byte[] toByteArray(InputStream stream, final int length, final int maxLength, final boolean checkEOFException, final boolean isLengthKnown) throws IOException { - if (length < 0 || maxLength < 0) { - throw new RecordFormatException("Can't allocate an array of length < 0"); - } final int derivedMaxLength = Math.max(maxLength, BYTE_ARRAY_MAX_OVERRIDE); if ((length != Integer.MAX_VALUE) || (derivedMaxLength != Integer.MAX_VALUE)) { checkLength(length, derivedMaxLength); } - final int derivedLen = isLengthKnown ? Math.min(length, derivedMaxLength) : derivedMaxLength; + final int derivedLen = isLengthKnown && length >= 0 ? Math.min(length, derivedMaxLength) : derivedMaxLength; final int byteArrayInitLen = calculateByteArrayInitLength(isLengthKnown, length, derivedMaxLength); final int internalBufferLen = DEFAULT_BUFFER_SIZE; try (UnsynchronizedByteArrayOutputStream baos = UnsynchronizedByteArrayOutputStream.builder().setBufferSize(byteArrayInitLen).get()) { @@ -254,7 +280,7 @@ public final class IOUtils { throwRecordTruncationException(derivedMaxLength); } - if (checkEOFException && derivedLen != Integer.MAX_VALUE && totalBytes < derivedLen) { + if (checkEOFException && length >= 0 && derivedLen != Integer.MAX_VALUE && totalBytes < derivedLen) { throw new EOFException("unexpected EOF - expected len: " + derivedLen + " - actual len: " + totalBytes); } 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/model/TestDrawingAggregate.java b/poi/src/test/java/org/apache/poi/hssf/model/TestDrawingAggregate.java index 12854be920..8d07fc4d0e 100644 --- a/poi/src/test/java/org/apache/poi/hssf/model/TestDrawingAggregate.java +++ b/poi/src/test/java/org/apache/poi/hssf/model/TestDrawingAggregate.java @@ -118,11 +118,7 @@ class TestDrawingAggregate { UnsynchronizedByteArrayOutputStream out = UnsynchronizedByteArrayOutputStream.builder().get(); for (RecordBase rb : aggRecords) { Record r = (org.apache.poi.hssf.record.Record) rb; - try { - out.write(r.serialize()); - } catch (IOException e) { - throw new RuntimeException(e); - } + out.write(r.serialize()); } return out.toByteArray(); } @@ -263,11 +259,7 @@ class TestDrawingAggregate { UnsynchronizedByteArrayOutputStream out = UnsynchronizedByteArrayOutputStream.builder().get(); for (RecordBase rb : records) { Record r = (org.apache.poi.hssf.record.Record) rb; - try { - out.write(r.serialize()); - } catch (IOException e) { - throw new RuntimeException(e); - } + out.write(r.serialize()); } return out.toByteArray(); } diff --git a/poi/src/test/java/org/apache/poi/hssf/model/TestEscherRecordFactory.java b/poi/src/test/java/org/apache/poi/hssf/model/TestEscherRecordFactory.java index 091802098c..f641b6eeb3 100644 --- a/poi/src/test/java/org/apache/poi/hssf/model/TestEscherRecordFactory.java +++ b/poi/src/test/java/org/apache/poi/hssf/model/TestEscherRecordFactory.java @@ -42,11 +42,7 @@ class TestEscherRecordFactory { UnsynchronizedByteArrayOutputStream out = UnsynchronizedByteArrayOutputStream.builder().get(); for (RecordBase rb : records) { Record r = (org.apache.poi.hssf.record.Record) rb; - try { - out.write(r.serialize()); - } catch (IOException e) { - throw new RuntimeException(e); - } + out.write(r.serialize()); } return out.toByteArray(); } diff --git a/poi/src/test/java/org/apache/poi/hssf/model/TestWorkbook.java b/poi/src/test/java/org/apache/poi/hssf/model/TestWorkbook.java index 76b306b1d9..5b277e52cc 100644 --- a/poi/src/test/java/org/apache/poi/hssf/model/TestWorkbook.java +++ b/poi/src/test/java/org/apache/poi/hssf/model/TestWorkbook.java @@ -22,10 +22,12 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; +import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream; import org.apache.poi.hssf.record.CountryRecord; import org.apache.poi.hssf.record.FontRecord; import org.apache.poi.hssf.record.RecalcIdRecord; @@ -170,4 +172,56 @@ final class TestWorkbook { int newRecordsCount = iwb.getNumRecords(); assertEquals(oldRecordsCount, newRecordsCount, "records count after getWriteAccess"); } + + @Test + void testSetUserName() throws IOException { + // normal username + setAndReadUserName("username", false); + + // 109 characters max if no "multibyte" character + setAndReadUserName("1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", false); + + // 54 max if there is at least one "multibyte" character + setAndReadUserName("€23456789012345678901234567890123456789012345678901234", false); + + // also works for very strange characters + setAndReadUserName("\uD801\uDC37\uD852\uDF62€$ⵃⵥꭓꭃꭦ﹄4Uカ\uD800\uDFB1\uD800\uDFC9\uD834\uDF45\uD834\uDF4B\uD83E\uDF22\uD83E\uDF53\uD83E\uDFB5\uD83E\uDFF6\uD801\uDC37\uD852\uDF62€$ⵃⵥꭓꭃꭦ﹄4Uカ\uD800\uDFB1\uD800\uDFC9\uD834\uDF45\uD834\uDF4B", false); + + // fails with longer strings + setAndReadUserName("12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", + true); + setAndReadUserName("€234567890123456789012345678901234567890123456789012345", true); + setAndReadUserName("\uD801\uDC37\uD852\uDF62€$ⵃⵥꭓꭃꭦ﹄4Uカ\uD800\uDFB1\uD800\uDFC9\uD834\uDF45\uD834\uDF4B\uD83E\uDF22\uD83E\uDF53\uD83E\uDFB5\uD83E\uDFF6\uD801\uDC37\uD852\uDF62€$ⵃⵥꭓꭃꭦ﹄4Uカ\uD800\uDFB1\uD800\uDFC9\uD834\uDF45\uD834\uDF4B\uDF4B", + true); + } + + private static void setAndReadUserName(String username, boolean fails) throws IOException { + try (HSSFWorkbook wb = new HSSFWorkbook()) { + InternalWorkbook iwb = wb.getInternalWorkbook(); + + String prev = iwb.getWriteAccess().getUsername(); + if (fails) { + assertThrows(IllegalArgumentException.class, + () -> iwb.getWriteAccess().setUsername(username), + "Expected to fail with username " + username); + assertEquals(prev, iwb.getWriteAccess().getUsername(), + "Username should not have been changed, but had: " + prev + " and " + + iwb.getWriteAccess().getUsername()); + + // cannot test more if username is too long + return; + } else { + iwb.getWriteAccess().setUsername(username); + } + + assertEquals(username, iwb.getWriteAccess().getUsername()); + try (UnsynchronizedByteArrayOutputStream os = UnsynchronizedByteArrayOutputStream.builder().get()) { + wb.write(os); + try (HSSFWorkbook wb2 = new HSSFWorkbook(os.toInputStream())) { + InternalWorkbook iwb2 = wb2.getInternalWorkbook(); + assertEquals(username, iwb2.getWriteAccess().getUsername()); + } + } + } + } } diff --git a/poi/src/test/java/org/apache/poi/hssf/record/TestRecordFactory.java b/poi/src/test/java/org/apache/poi/hssf/record/TestRecordFactory.java index a44cce0e42..fb3f44eb6c 100644 --- a/poi/src/test/java/org/apache/poi/hssf/record/TestRecordFactory.java +++ b/poi/src/test/java/org/apache/poi/hssf/record/TestRecordFactory.java @@ -211,11 +211,7 @@ final class TestRecordFactory { }; UnsynchronizedByteArrayOutputStream baos = UnsynchronizedByteArrayOutputStream.builder().get(); for (org.apache.poi.hssf.record.Record rec : recs) { - try { - baos.write(rec.serialize()); - } catch (IOException e) { - throw new RuntimeException(e); - } + baos.write(rec.serialize()); } //simulate the bad padding at the end of the workbook stream in attachment 23483 of bug 46987 baos.write(0x00); diff --git a/poi/src/test/java/org/apache/poi/hssf/record/TestWriteAccessRecord.java b/poi/src/test/java/org/apache/poi/hssf/record/TestWriteAccessRecord.java index ee3cfec38b..8286f442c0 100644 --- a/poi/src/test/java/org/apache/poi/hssf/record/TestWriteAccessRecord.java +++ b/poi/src/test/java/org/apache/poi/hssf/record/TestWriteAccessRecord.java @@ -93,4 +93,47 @@ final class TestWriteAccessRecord { confirmRecordEncoding(WriteAccessRecord.sid, expectedEncoding, rec.serialize()); } + + @Test + void testUTF16LE() { + byte[] data = HexRead.readFromString("" + + "5c 00 70 00 1C 00 01 " + + "44 00 61 00 74 00 61 00 20 00 44 00 79 00 6e 00 " + + "61 00 6d 00 69 00 63 00 73 00 27 00 20 00 53 00 " + + "70 00 72 00 65 00 61 00 64 00 42 00 75 00 69 00 " + + "6c 00 64 00 65 00 72 00 20 00 20 00 20 00 20 00 " + + "20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 " + + "20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 " + + "20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 " + + "20 00 20 00 20 00" + ); + RecordInputStream in = TestcaseRecordInputStream.create(data); + + WriteAccessRecord rec = new WriteAccessRecord(in); + + assertEquals("Data Dynamics' SpreadBuilder", rec.getUsername()); + } + + @Test + void testUTF16LE_wrong_size() { + // "0x51" on position 5 is an incorrect size, as it would require 162 bytes to encode as UTF-16LE + // the spec only allows up to 109 bytes for the string in this record, but it seems some broken + // software out there will generate such a file + byte[] data = HexRead.readFromString("" + + "5c 00 70 00 51 00 01 " + + "44 00 61 00 74 00 61 00 20 00 44 00 79 00 6e 00 " + + "61 00 6d 00 69 00 63 00 73 00 27 00 20 00 53 00 " + + "70 00 72 00 65 00 61 00 64 00 42 00 75 00 69 00 " + + "6c 00 64 00 65 00 72 00 20 00 20 00 20 00 20 00 " + + "20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 " + + "20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 " + + "20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 " + + "20 00 20 00 20 00" + ); + RecordInputStream in = TestcaseRecordInputStream.create(data); + + WriteAccessRecord rec = new WriteAccessRecord(in); + + assertEquals("Data Dynamics' SpreadBuilder", rec.getUsername()); + } } diff --git a/poi/src/test/java/org/apache/poi/hssf/usermodel/TestBugs.java b/poi/src/test/java/org/apache/poi/hssf/usermodel/TestBugs.java index 3b026b2ce1..df7da70e19 100644 --- a/poi/src/test/java/org/apache/poi/hssf/usermodel/TestBugs.java +++ b/poi/src/test/java/org/apache/poi/hssf/usermodel/TestBugs.java @@ -26,6 +26,8 @@ import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; @@ -2316,12 +2318,12 @@ final class TestBugs extends BaseTestBugzillaIssues { } @Test - void test46515() throws IOException { + void test46515() throws IOException, URISyntaxException { try (Workbook wb = openSampleWorkbook("46515.xls")) { // Get structure from webservice String urlString = "https://poi.apache.org/components/spreadsheet/images/calendar.jpg"; - URL structURL = new URL(urlString); + URL structURL = new URI(urlString).toURL(); BufferedImage bimage; try { bimage = ImageIO.read(structURL); 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/hssf/util/TestHSSFColor.java b/poi/src/test/java/org/apache/poi/hssf/util/TestHSSFColor.java index 059dbb28b3..1bde058114 100644 --- a/poi/src/test/java/org/apache/poi/hssf/util/TestHSSFColor.java +++ b/poi/src/test/java/org/apache/poi/hssf/util/TestHSSFColor.java @@ -17,6 +17,7 @@ package org.apache.poi.hssf.util; +import java.awt.Color; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -56,4 +57,15 @@ final class TestHSSFColor { triplets.get(HSSFColorPredefined.YELLOW.getHexString()) ); } + + @Test + void testRgbAndColorWorkTheSame() { + Color color = java.awt.Color.YELLOW; + @SuppressWarnings("deprecation") + HSSFColor fromColor = new HSSFColor(10, 20, color); + HSSFColor fromRgb = new HSSFColor(10, 20, color.getRGB()); + + assertEquals(fromColor, fromRgb, "Both colors are equal"); + assertEquals(fromColor.hashCode(), fromRgb.hashCode(), "Both have the same hash code"); + } } diff --git a/poi/src/test/java/org/apache/poi/ss/formula/atp/TestAnalysisToolPak.java b/poi/src/test/java/org/apache/poi/ss/formula/atp/TestAnalysisToolPak.java new file mode 100644 index 0000000000..2861cab7f0 --- /dev/null +++ b/poi/src/test/java/org/apache/poi/ss/formula/atp/TestAnalysisToolPak.java @@ -0,0 +1,75 @@ +/* ==================================================================== + 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.formula.atp; + +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.formula.OperationEvaluationContext; +import org.apache.poi.ss.formula.eval.ErrorEval; +import org.apache.poi.ss.formula.eval.ValueEval; +import org.apache.poi.ss.formula.functions.FreeRefFunction; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class TestAnalysisToolPak { + private static class NullFunction implements FreeRefFunction { + @Override + public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) { + return ErrorEval.DIV_ZERO; + } + } + + @Test + void testOverrideKnownButUnimplemented() { + // JIS is a known function in Excel, but it is not implemented in POI + AnalysisToolPak.registerFunction("JIS", new NullFunction()); + try (HSSFWorkbook workbook = new HSSFWorkbook()) { + HSSFSheet sheet = workbook.createSheet("Sheet1"); + HSSFCell cell = sheet.createRow(0).createCell(0); + cell.setCellFormula("JIS(\"test\")"); + workbook.getCreationHelper().createFormulaEvaluator().evaluateAll(); + assertEquals(ErrorEval.DIV_ZERO.getErrorCode(), cell.getErrorCellValue()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + void testOverrideUnknown() { + assertThrows(IllegalArgumentException.class, () -> { + AnalysisToolPak.registerFunction("UNKNOWN", new NullFunction()); + }); + } + + @Test + void testOverrideUnknownButForceAllowed() { + AnalysisToolPak.registerFunction("FAKE", new NullFunction(), true); + try (HSSFWorkbook workbook = new HSSFWorkbook()) { + HSSFSheet sheet = workbook.createSheet("Sheet1"); + HSSFCell cell = sheet.createRow(0).createCell(0); + cell.setCellFormula("FAKE(\"test\")"); + workbook.getCreationHelper().createFormulaEvaluator().evaluateAll(); + assertEquals(ErrorEval.DIV_ZERO.getErrorCode(), cell.getErrorCellValue()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} 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 b131071abf..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 @@ -26,6 +26,8 @@ import java.io.UncheckedIOException; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.nio.charset.StandardCharsets; @@ -169,9 +171,9 @@ 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 = Integer.valueOf(funcIx); + Integer funcIxKey = funcIx; if(!_groupFunctionIndexes.add(funcIxKey)) { throw new RuntimeException("Duplicate function index (" + funcIx + ")"); } @@ -209,7 +211,7 @@ public final class ExcelFileFormatDocFunctionExtractor { throw new RuntimeException("changing function '" + funcName + "' definition without foot-note"); } - _allFunctionsByIndex.remove(Integer.valueOf(fdPrev.getIndex())); + _allFunctionsByIndex.remove(fdPrev.getIndex()); } } @@ -326,7 +328,7 @@ public final class ExcelFileFormatDocFunctionExtractor { processTableRow(cellData, noteFlags); } else if(matchesRelPath(TABLE_CELL_RELPATH_NAMES)) { _rowData.add(_textNodeBuffer.toString().trim()); - _rowNoteFlags.add(Boolean.valueOf(_cellHasNote)); + _rowNoteFlags.add(_cellHasNote); _textNodeBuffer.setLength(0); } } @@ -350,7 +352,7 @@ public final class ExcelFileFormatDocFunctionExtractor { } int funcIx = parseInt(funcIxStr); - boolean hasFootnote = noteFlags[i + 1].booleanValue(); + boolean hasFootnote = noteFlags[i + 1]; String funcName = cellData[i + 1]; int minParams = parseInt(cellData[i + 2]); int maxParams = parseInt(cellData[i + 3]); @@ -577,8 +579,8 @@ public final class ExcelFileFormatDocFunctionExtractor { private static File downloadSourceFile() { URL url; try { - url = new URL("http://sc.openoffice.org/" + SOURCE_DOC_FILE_NAME); - } catch (MalformedURLException e) { + url = new URI("http://sc.openoffice.org/" + SOURCE_DOC_FILE_NAME).toURL(); + } catch (MalformedURLException | URISyntaxException e) { throw new RuntimeException(e); } diff --git a/poi/src/test/java/org/apache/poi/ss/formula/functions/TestNumericFunction.java b/poi/src/test/java/org/apache/poi/ss/formula/functions/TestNumericFunction.java index 632c53ad39..6343cb1a06 100644 --- a/poi/src/test/java/org/apache/poi/ss/formula/functions/TestNumericFunction.java +++ b/poi/src/test/java/org/apache/poi/ss/formula/functions/TestNumericFunction.java @@ -69,7 +69,7 @@ final class TestNumericFunction { void testDOLLAR() { Locale defaultLocale = LocaleUtil.getUserLocale(); try { - LocaleUtil.setUserLocale(new Locale("en", "US")); + LocaleUtil.setUserLocale(new Locale.Builder().setLanguage("en").setRegion("US").build()); HSSFWorkbook wb = new HSSFWorkbook(); HSSFCell cell = wb.createSheet().createRow(0).createCell(0); HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); @@ -89,7 +89,7 @@ final class TestNumericFunction { void testDOLLARIreland() { Locale defaultLocale = LocaleUtil.getUserLocale(); try { - LocaleUtil.setUserLocale(new Locale("en", "IE")); + LocaleUtil.setUserLocale(new Locale.Builder().setLanguage("en").setRegion("IE").build()); HSSFWorkbook wb = new HSSFWorkbook(); HSSFCell cell = wb.createSheet().createRow(0).createCell(0); HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); @@ -104,7 +104,7 @@ final class TestNumericFunction { void testDOLLARSpain() { Locale defaultLocale = LocaleUtil.getUserLocale(); try { - LocaleUtil.setUserLocale(new Locale("es", "ES")); + LocaleUtil.setUserLocale(new Locale.Builder().setLanguage("es").setRegion("ES").build()); HSSFWorkbook wb = new HSSFWorkbook(); HSSFCell cell = wb.createSheet().createRow(0).createCell(0); HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); @@ -119,7 +119,7 @@ final class TestNumericFunction { void testDOLLARJapan() { Locale defaultLocale = LocaleUtil.getUserLocale(); try { - LocaleUtil.setUserLocale(new Locale("ja", "JP")); + LocaleUtil.setUserLocale(new Locale.Builder().setLanguage("ja").setRegion("JP").build()); HSSFWorkbook wb = new HSSFWorkbook(); HSSFCell cell = wb.createSheet().createRow(0).createCell(0); HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); @@ -135,7 +135,7 @@ final class TestNumericFunction { void testDOLLARDenmark() { Locale defaultLocale = LocaleUtil.getUserLocale(); try { - LocaleUtil.setUserLocale(new Locale("da", "DK")); + LocaleUtil.setUserLocale(new Locale.Builder().setLanguage("da").setRegion("DK").build()); HSSFWorkbook wb = new HSSFWorkbook(); HSSFCell cell = wb.createSheet().createRow(0).createCell(0); HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); diff --git a/poi/src/test/java/org/apache/poi/ss/formula/functions/TestSheet.java b/poi/src/test/java/org/apache/poi/ss/formula/functions/TestSheet.java new file mode 100644 index 0000000000..894db09814 --- /dev/null +++ b/poi/src/test/java/org/apache/poi/ss/formula/functions/TestSheet.java @@ -0,0 +1,89 @@ +/* ==================================================================== + 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.formula.functions; + +import org.apache.poi.hssf.usermodel.*; +import org.apache.poi.ss.formula.OperationEvaluationContext; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.FormulaError; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +final class TestSheet { + + private static final OperationEvaluationContext ec = new OperationEvaluationContext(null, null, 2, 0, 2, null); + + @Test + void testSheetFunctionWithRealWorkbook() throws IOException { + try (HSSFWorkbook wb = new HSSFWorkbook()) { + // Add three sheets: Sheet1, Sheet2, Sheet3 + HSSFSheet sheet1 = wb.createSheet("Sheet1"); + HSSFSheet sheet2 = wb.createSheet("Sheet2"); + HSSFSheet sheet3 = wb.createSheet("Sheet3"); + + // Add data + sheet1.createRow(0).createCell(0).setCellValue(123); // A1 in Sheet1 + sheet2.createRow(1).createCell(0).setCellValue(456); // A2 in Sheet2 + + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); + + // Define formulas and expected results + String[] formulas = { + "SHEET()", + "SHEET(A1)", + "SHEET(A1:B5)", + "SHEET(Sheet2!A2)", + "SHEET(\"Sheet3\")", + "SHEET(\"invalid\")" + }; + + Object[] expected = { + 1.0, // current sheet + 1.0, // A1 in same sheet + 1.0, // A1:B5 in same sheet + 2.0, // Sheet2!A2 + 3.0, // Sheet3 + FormulaError.NA.getCode() // unknown sheet → #N/A + }; + + // Write formulas to separate cells and evaluate + HSSFRow formulaRow = sheet1.createRow(1); + for (int i = 0; i < formulas.length; i++) { + String formula = formulas[i]; + HSSFCell cell = formulaRow.createCell(i); + cell.setCellFormula(formula); + CellType resultType = fe.evaluateFormulaCell(cell); + + if (expected[i] instanceof Double) { + assertEquals(CellType.NUMERIC, resultType, + "Unexpected cell type for formula: " + formula); + assertEquals((Double) expected[i], cell.getNumericCellValue(), + "Unexpected numeric result for formula: " + formula); + } else if (expected[i] instanceof Byte) { + assertEquals(CellType.ERROR, resultType, + "Unexpected cell type for formula: " + formula); + assertEquals((byte) expected[i], cell.getErrorCellValue(), + "Unexpected error code for formula: " + formula); + } + } + } + } +} diff --git a/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestCell.java b/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestCell.java index de2c4ad7c6..e42a662813 100644 --- a/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestCell.java +++ b/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestCell.java @@ -365,6 +365,9 @@ public abstract class BaseTestCell { dateStyle.setDataFormat(formatId); r.getCell(7).setCellStyle(dateStyle); + // null rich text + r.createCell(8).setCellValue(factory.createRichTextString(null)); // blank + assertEquals("FALSE", r.getCell(0).toString(), "Boolean"); assertEquals("TRUE", r.getCell(1).toString(), "Boolean"); assertEquals("1.5", r.getCell(2).toString(), "Numeric"); @@ -375,6 +378,7 @@ public abstract class BaseTestCell { // toString on a date-formatted cell displays dates as dd-MMM-yyyy, which has locale problems with the month String dateCell1 = r.getCell(7).toString(); assertEquals("2/2/10 0:00", dateCell1); + assertEquals("", r.getCell(8).toString(), "Blank"); //Write out the file, read it in, and then check cell values try (Workbook wb2 = _testDataProvider.writeOutAndReadBack(wb1)) { @@ -388,6 +392,7 @@ public abstract class BaseTestCell { assertEquals("", r.getCell(6).toString(), "Blank"); String dateCell2 = r.getCell(7).toString(); assertEquals(dateCell1, dateCell2, "Date"); + assertEquals("", r.getCell(8).toString(), "Blank"); } } } @@ -1468,6 +1473,36 @@ public abstract class BaseTestCell { verify(cell).setBlank(); } + @Test + protected void setCellNullString() throws IOException { + try (Workbook wb = _testDataProvider.createWorkbook()) { + Cell cell = getInstance(wb); + + cell.setCellValue((String)null); + + // setting string "null" leads to a BLANK cell + assertEquals(CellType.BLANK, cell.getCellType()); + assertEquals("", cell.getStringCellValue()); + assertEquals("", cell.toString()); + + cell.setCellType(CellType.STRING); + + // forcing to string type leads to STRING cell, but still empty strings + assertEquals(CellType.STRING, cell.getCellType()); + assertEquals("", cell.getStringCellValue()); + assertEquals("", cell.toString()); + + try (Workbook wb2 = _testDataProvider.writeOutAndReadBack(wb)) { + // read first sheet, first row, first cell + Cell cellBack = wb2.iterator().next().iterator().next().iterator().next(); + + assertEquals(CellType.STRING, cell.getCellType()); + assertEquals("", cell.getStringCellValue()); + assertEquals("", cell.toString()); + } + } + } + private Cell getInstance(Workbook wb) { return wb.createSheet().createRow(0).createCell(0); } diff --git a/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestConditionalFormatting.java b/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestConditionalFormatting.java index 29f62da235..64f585608a 100644 --- a/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestConditionalFormatting.java +++ b/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestConditionalFormatting.java @@ -531,7 +531,7 @@ public abstract class BaseTestConditionalFormatting { // Sanity check data assertEquals("Values", s.getRow(0).getCell(0).toString()); - assertEquals("10", s.getRow(2).getCell(0).toString()); + assertEquals(10.0, s.getRow(2).getCell(0).getNumericCellValue()); // Check we found all the conditional formatting rules we should have SheetConditionalFormatting sheetCF = s.getSheetConditionalFormatting(); diff --git a/poi/src/test/java/org/apache/poi/ss/usermodel/TestDataFormatter.java b/poi/src/test/java/org/apache/poi/ss/usermodel/TestDataFormatter.java index 0d49bee3ab..b1fced30a5 100644 --- a/poi/src/test/java/org/apache/poi/ss/usermodel/TestDataFormatter.java +++ b/poi/src/test/java/org/apache/poi/ss/usermodel/TestDataFormatter.java @@ -937,7 +937,7 @@ class TestDataFormatter { // 2017-12-01 09:54:33 which is 42747.412892397523 as double DataFormatter dfDE = new DataFormatter(Locale.GERMANY); DataFormatter dfZH = new DataFormatter(Locale.PRC); - DataFormatter dfIE = new DataFormatter(new Locale("GA", "IE")); + DataFormatter dfIE = new DataFormatter(new Locale.Builder().setLanguage("GA").setRegion("IE").build()); double date = 42747.412892397523; String format = "dd MMMM yyyy HH:mm:ss"; assertEquals("12 Januar 2017 09:54:33", dfDE.formatRawCellContents(date, -1, format)); diff --git a/poi/src/test/java/org/apache/poi/ss/usermodel/TestExcelStyleDateFormatter.java b/poi/src/test/java/org/apache/poi/ss/usermodel/TestExcelStyleDateFormatter.java index b3ccd06a4c..f905470f88 100644 --- a/poi/src/test/java/org/apache/poi/ss/usermodel/TestExcelStyleDateFormatter.java +++ b/poi/src/test/java/org/apache/poi/ss/usermodel/TestExcelStyleDateFormatter.java @@ -58,11 +58,12 @@ class TestExcelStyleDateFormatter { formatter.setDateFormatSymbols(DateFormatSymbols.getInstance(locale)); String result = formatter.format(d, sb, fp).toString(); - String msg = "Failed testDates for locale " + locale + ", provider: " + provider + - " and date " + d + ", having: " + result; int actIdx = localeIndex(locale); + String msg = "Failed testDates for locale " + locale + ", provider: " + provider + + " and date " + d + ", having actIdx: " + actIdx + " and result '" + result + "' (" + result.length() + ")"; + assertNotNull(result, msg); assertTrue(result.length() > actIdx, msg); assertEquals(expected.charAt(month), result.charAt(actIdx), msg); @@ -102,15 +103,15 @@ class TestExcelStyleDateFormatter { public static Stream<Arguments> initializeLocales() throws ParseException { Object[][] locExps = { { Locale.GERMAN, "JFMAMJJASOND" }, - { new Locale("de", "AT"), "JFMAMJJASOND" }, + { new Locale.Builder().setLanguage("de").setRegion("AT").build(), "JFMAMJJASOND" }, { Locale.UK, "JFMAMJJASOND" }, - { new Locale("en", "IN"), "JFMAMJJASOND" }, - { new Locale("in", "ID"), "JFMAMJJASOND" }, + { new Locale.Builder().setLanguage("en").setRegion("IN").build(), "JFMAMJJASOND" }, + { new Locale.Builder().setLanguage("in").setRegion("ID").build(), "JFMAMJJASOND" }, { Locale.FRENCH, "jfmamjjasond" }, - { new Locale("ru", "RU"), "\u044f\u0444\u043c\u0430\u043c\u0438\u0438\u0430\u0441\u043e\u043d\u0434" }, + { new Locale.Builder().setLanguage("ru").setRegion("RU").build(), "\u044f\u0444\u043c\u0430\u043c\u0438\u0438\u0430\u0441\u043e\u043d\u0434" }, { Locale.CHINESE, localeIndex(Locale.CHINESE) == 0 ? "\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u5341\u5341" : "123456789111" }, - { new Locale("tr", "TR"), "\u004f\u015e\u004d\u004e\u004d\u0048\u0054\u0041\u0045\u0045\u004b\u0041" }, - { new Locale("hu", "HU"), "\u006a\u0066\u006d\u00e1\u006d\u006a\u006a\u0061\u0073\u006f\u006e\u0064" } + { new Locale.Builder().setLanguage("tr").setRegion("TR").build(), "\u004f\u015e\u004d\u004e\u004d\u0048\u0054\u0041\u0045\u0045\u004b\u0041" }, + { new Locale.Builder().setLanguage("hu").setRegion("HU").build(), "\u006a\u0066\u006d\u00e1\u006d\u006a\u006a\u0061\u0073\u006f\u006e\u0064" } }; String[] dates = { diff --git a/poi/src/test/java/org/apache/poi/ss/util/BaseTestCellUtil.java b/poi/src/test/java/org/apache/poi/ss/util/BaseTestCellUtil.java index 9784e080b5..fb2200ba57 100644 --- a/poi/src/test/java/org/apache/poi/ss/util/BaseTestCellUtil.java +++ b/poi/src/test/java/org/apache/poi/ss/util/BaseTestCellUtil.java @@ -22,6 +22,7 @@ import org.apache.poi.ss.usermodel.*; import org.junit.jupiter.api.Test; import java.io.IOException; +import java.util.EnumMap; import java.util.HashMap; import java.util.Map; @@ -339,7 +340,7 @@ public abstract class BaseTestCellUtil { // Add multiple border properties to cell should create a single new style int styCnt1 = wb.getNumCellStyles(); - Map<CellPropertyType, Object> props = new HashMap<>(); + EnumMap<CellPropertyType, Object> props = new EnumMap<>(CellPropertyType.class); props.put(CellPropertyType.BORDER_TOP, BorderStyle.THIN); props.put(CellPropertyType.BORDER_BOTTOM, BorderStyle.THIN); props.put(CellPropertyType.BORDER_LEFT, BorderStyle.THIN); @@ -574,7 +575,7 @@ public abstract class BaseTestCellUtil { protected void setFillForegroundColorBeforeFillBackgroundColorEnumByEnum() throws IOException { try (Workbook wb1 = _testDataProvider.createWorkbook()) { Cell A1 = wb1.createSheet().createRow(0).createCell(0); - Map<CellPropertyType, Object> properties = new HashMap<>(); + EnumMap<CellPropertyType, Object> properties = new EnumMap<>(CellPropertyType.class); properties.put(CellPropertyType.FILL_PATTERN, FillPatternType.BRICKS); properties.put(CellPropertyType.FILL_FOREGROUND_COLOR, IndexedColors.BLUE.index); properties.put(CellPropertyType.FILL_BACKGROUND_COLOR, IndexedColors.RED.index); 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/ss/util/TestDateFormatConverter.java b/poi/src/test/java/org/apache/poi/ss/util/TestDateFormatConverter.java index 059426289e..59c8acf695 100644 --- a/poi/src/test/java/org/apache/poi/ss/util/TestDateFormatConverter.java +++ b/poi/src/test/java/org/apache/poi/ss/util/TestDateFormatConverter.java @@ -130,7 +130,7 @@ final class TestDateFormatConverter { void testJDK8EmptyLocale() { // JDK 8 seems to add an empty locale-string to the list returned via DateFormat.getAvailableLocales() // therefore we now cater for this special locale as well - String prefix = getPrefixForLocale(new Locale("")); + String prefix = getPrefixForLocale(new Locale.Builder().build()); assertEquals("", prefix); } 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/ExceptionUtilTest.java b/poi/src/test/java/org/apache/poi/util/ExceptionUtilTest.java index d5d4fdd77a..d78e679789 100644 --- a/poi/src/test/java/org/apache/poi/util/ExceptionUtilTest.java +++ b/poi/src/test/java/org/apache/poi/util/ExceptionUtilTest.java @@ -42,7 +42,7 @@ class ExceptionUtilTest { assertTrue(ExceptionUtil.isFatal(new VirtualMachineError(){})); } - + @SuppressForbidden("Test with ThreadDeath on purpose here") @Test void testThreadDeath() { assertTrue(ExceptionUtil.isFatal(new ThreadDeath())); diff --git a/poi/src/test/java/org/apache/poi/util/TestIOUtils.java b/poi/src/test/java/org/apache/poi/util/TestIOUtils.java index bb12f9932e..7f026adc74 100644 --- a/poi/src/test/java/org/apache/poi/util/TestIOUtils.java +++ b/poi/src/test/java/org/apache/poi/util/TestIOUtils.java @@ -110,8 +110,25 @@ final class TestIOUtils { } @Test - void testToByteArrayNegativeLength() { - assertThrows(RecordFormatException.class, () -> IOUtils.toByteArray(data123(), -1)); + void testToByteArrayNegativeLength() throws IOException { + final byte[] array = new byte[]{1, 2, 3, 4, 5, 6, 7}; + IOUtils.setByteArrayMaxOverride(30 * 1024 * 1024); + try (ByteArrayInputStream is = new ByteArrayInputStream(array)) { + assertArrayEquals(array, IOUtils.toByteArray(is, -1, 100)); + } finally { + IOUtils.setByteArrayMaxOverride(-1); + } + } + + @Test + void testToByteArrayNegativeLength2() throws IOException { + final byte[] array = new byte[]{1, 2, 3, 4, 5, 6, 7}; + IOUtils.setByteArrayMaxOverride(30 * 1024 * 1024); + try (ByteArrayInputStream is = new ByteArrayInputStream(array)) { + assertArrayEquals(array, IOUtils.toByteArray(is, -1)); + } finally { + IOUtils.setByteArrayMaxOverride(-1); + } } @Test 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/document/bug69628.docx b/test-data/document/bug69628.docx Binary files differnew file mode 100644 index 0000000000..359a3b3608 --- /dev/null +++ b/test-data/document/bug69628.docx 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 |