Compare commits

..

56 Commits

Author SHA1 Message Date
Harald Kuhr c61e47b3e7 [maven-release-plugin] prepare for next development iteration 2021-12-11 22:54:21 +01:00
Harald Kuhr 1d47d2ef90 [maven-release-plugin] prepare release twelvemonkeys-3.7.1 2021-12-11 22:54:18 +01:00
Harald Kuhr 94554b0660 #631 New way of forcing profile activation + guarding all invocations of ICC_Profile.getInstance()
(cherry picked from commit b2c5915db8)
2021-12-11 18:59:19 +01:00
Harald Kuhr a39bca4d2f #645 AAIOBE in CCITTFaxDecoderStream now wrapped in IOException
(cherry picked from commit 3911191b04)
2021-12-11 18:59:18 +01:00
Harald Kuhr fdbbcc54a8 Preparing for next release. 2021-12-10 17:02:41 +01:00
Harald Kuhr 00cd9471dd #483: Rollback 2021-12-10 17:00:58 +01:00
Snyk bot eb6a3bde39 fix: upgrade commons-fileupload:commons-fileupload from 1.3.3 to 1.4 (#642)
Snyk has created this PR to upgrade commons-fileupload:commons-fileupload from 1.3.3 to 1.4.

See this package in Maven Repository:
https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload/

See this project in Snyk:
https://app.snyk.io/org/haraldk/project/3eb50c00-e586-4f4c-a39c-90c80f89cc60?utm_source=github&utm_medium=referral&page=upgrade-pr

(cherry picked from commit bc328419ac)
2021-12-10 16:57:00 +01:00
Harald Kuhr d3dd4c29d5 Avoid fetching external resources in XMPReader.
(cherry picked from commit da4efe98bf)
2021-12-10 16:57:00 +01:00
Harald Kuhr 3355acfa03 Minor clean-up.
(cherry picked from commit 6653f4a85d)
2021-12-10 16:56:58 +01:00
Harald Kuhr b70e52561a Minor AffineTransformOp clean-up + removed test output.
(cherry picked from commit 511a29beb9)
2021-12-10 16:56:56 +01:00
Gauthier 49ddbfa85e Make tests pass on JDK 16 and 17 (#635)
* make tests pass on JDK 16 and 17
replace deprecated mockito-all by mockito-core, and updated to latest 3.x
replace deprecated org.mockito.Matchers

* code cleanup from IDE suggestions

* add oracle jdk 16 and 17 to Travis

(cherry picked from commit 5617b4323c)
2021-12-10 16:56:56 +01:00
Harald Kuhr 215db21569 #629: Fixed build
(cherry picked from commit 16d0af357d)
2021-12-10 16:56:54 +01:00
Harald Kuhr 0ac8011053 #629: Preliminary WebP animation (ANIM/ANMF) support
(cherry picked from commit 74927d5396)
2021-12-10 16:56:52 +01:00
Harald Kuhr abbbca9be3 Readme improvements
(cherry picked from commit 7e809dd834)
2021-12-10 16:56:52 +01:00
Harald Kuhr bc5473aec6 Fixed Travis link + bonus project summary updates
(cherry picked from commit 7aa95a08bc)
2021-12-10 16:56:52 +01:00
Harald Kuhr aca8b1256a Minor servlet clean-up.
(cherry picked from commit c28963ae49)
2021-12-10 16:56:51 +01:00
Harald Kuhr 18c1edf0e5 Servlet deprecation
(cherry picked from commit 0327f5fc1a)
2021-12-10 16:56:51 +01:00
Harald Kuhr 6a01466ebb #628: Stabilized build + better dependency scopes and module names in interop modules
(cherry picked from commit 1c59057c30)
2021-12-10 16:56:31 +01:00
Harald Kuhr b19df1640b #626 Clean up + fill order support for all compression types
(cherry picked from commit 3e1f85c4dc)
2021-12-10 16:53:27 +01:00
Harald Kuhr e8e3e0f1d6 #628 TIFF metadata fix, now always outputs denominator for rationals.
+ Bonus: Added JAI TIFF interop module with test and other minor fixes.

(cherry picked from commit 11227a68a0)
2021-12-10 16:53:23 +01:00
Oliver Schmidtmer c249a21c8c #626: Handle fillOrder in TIFFImageReader, not in CCITTFaxDecoderStream (#627)
(cherry picked from commit 62ba73a30e)
2021-12-10 16:53:23 +01:00
Harald Kuhr 182b2fdfa9 Fixed NullPointerException due to missing PhotometricInterpretation, now uses fallback as we do when reading.
(cherry picked from commit 1f33afb5a1)
2021-12-10 16:53:22 +01:00
Harald Kuhr df068e350d #626 TIFF CCITT detection only once per IFD
(cherry picked from commit 9d3f271867)
2021-12-10 16:53:20 +01:00
Harald Kuhr b55c623b87 #623: TGAImageReader, PCXImageReader and SGIImageReader now return more standard image types as default, for better AffineTransformOp compatibility. Added tests.
Bonus: Subsampling fix for TGAImageReader and BMPImageReader.

(cherry picked from commit 812e12acb0)
2021-12-10 16:53:16 +01:00
Harald Kuhr 09573b52ac #624: Added metadata support for 16 bit USHORT gray.
(cherry picked from commit 060b6cf852)
2021-12-10 16:53:15 +01:00
Koen De Groote ff7edbd223 Certain pixeldepth-16 TGA files fail to process, classcast exception (#624)
* Added fixed for monochrome tga16 bit. Uncertain if that description is complete.
Test files added. Without the changed code, the tests fail.

* Fix suggested by HaraldK

Co-authored-by: Koen De Groote <koen.degroote@limecraft.com>
(cherry picked from commit e68ce7ffd1)
2021-12-10 16:53:15 +01:00
Harald Kuhr d8f0cd97a2 Fix typo in TIFFImageMetadataFormat mk II.
(cherry picked from commit 778cdef69c)
2021-12-10 16:52:21 +01:00
Harald Kuhr f6971a9bce Fix typo in TIFFImageMetadataFormat.
(cherry picked from commit d46a76fca8)
2021-12-10 16:52:20 +01:00
Harald Kuhr 821965df0d #621 Don't add ICC profile for default gray images
(cherry picked from commit 105a1ee466)
2021-12-10 16:52:12 +01:00
Harald Kuhr ed46305d31 #619: Fix WebP Y'CbCr->RGB conversion (now uses rec 601)
(cherry picked from commit 976e5d6210)
2021-12-10 16:51:49 +01:00
Harald Kuhr ca3adb7c45 Minor clean-up.
(cherry picked from commit 6daca00fcd)
2021-12-10 16:51:45 +01:00
Harald Kuhr 59f76209bc Some more minor clean-up.
(cherry picked from commit ce997a6951)
2021-12-10 16:51:31 +01:00
Harald Kuhr 2076235313 Minor clean-up.
(cherry picked from commit 23bf5cb7b2)
2021-12-10 16:51:31 +01:00
Harald Kuhr 88bebf31f8 #616: Remove dependency on old xmlgraphics-commons (no longer needed)
(cherry picked from commit 564778f415)
2021-12-10 16:51:21 +01:00
Harald Kuhr 37beb21c29 Fix WebP ICC handling for images with alpha.
(cherry picked from commit e28bf8fb44)
2021-12-10 16:51:20 +01:00
Harald Kuhr 88c0d27516 Add WebP to BOM.
(cherry picked from commit cf8d630d01)
2021-12-10 16:51:20 +01:00
Harald Kuhr dcd4ffccf4 Switch build from travis.ci.org to com
(cherry picked from commit 0ff7224912)
2021-12-10 16:51:12 +01:00
Koen De Groote f35f5c6b24 Documentation cleanup (#612)
* Added the `@Deprecated` tag to instances where is was mentioned in documentation, but not for the actual code itself.

Changed one documentation link pointing at a non-existing item.

* As per PR suggestion.

(cherry picked from commit 196081a317)
2021-12-10 16:51:12 +01:00
Harald Kuhr 9dcf53d985 #609 Fixed ICC Profile handling in WebP.
(cherry picked from commit ff50180d86)
2021-12-10 16:51:11 +01:00
Harald Kuhr 44a2066b79 Minor code clean-up.
(cherry picked from commit 8f2c482167)
2021-12-10 16:51:06 +01:00
Oliver Schmidtmer 382c51db7c Invert EOF check
(cherry picked from commit cd42d81817)
2021-12-10 16:50:59 +01:00
Oliver Schmidtmer 660f3f7e86 #579 Deeper EOL search in the CCITT stream
(cherry picked from commit ba5c667b6c)
2021-12-10 16:50:59 +01:00
Harald Kuhr b4c30872c4 XXX: Remove another old servlet class.
(cherry picked from commit 94eac2d6e5)
2021-12-10 16:50:49 +01:00
Harald Kuhr 402d4d466d HTTPS links in README.md
(cherry picked from commit f63a33d541)
2021-12-10 16:50:49 +01:00
Harald Kuhr 7860bf7e17 XXX: Remove old servlet class.
(cherry picked from commit 2f9768a1d4)
2021-12-10 16:50:49 +01:00
Harald Kuhr d52522fb80 #483 Minor optimization
(cherry picked from commit 06bcf22242)
2021-12-10 16:41:24 +01:00
Harald Kuhr 8a8c6b1931 #483 Add license headers.
(cherry picked from commit 20c7f8e60e)
2021-12-10 16:41:24 +01:00
Harald Kuhr 2930708a54 #483 Initial PSD Write support
(cherry picked from commit 15a9ad0a9b)
2021-12-10 16:41:23 +01:00
Harald Kuhr 5661e7459c #606: Fix bug introduced by more aggressive readDirect.
(cherry picked from commit 4e2bf131d2)
2021-12-10 16:39:20 +01:00
Harald Kuhr 4d45ea4966 #606: Workaround for broken JDK WBMPImageReader
(cherry picked from commit d0c4a07556)
2021-12-10 16:39:19 +01:00
Harald Kuhr 34852f7be5 Cleaner tests for Java 6 or later... A little late. :-)
(cherry picked from commit 21059c8d5a)
2021-12-10 16:39:17 +01:00
Harald Kuhr abc929a531 Adding GitHub sponsors link.
(cherry picked from commit fa7b530809)
2021-12-10 16:39:16 +01:00
Harald Kuhr 247a09ca61 Test clean-up.
(cherry picked from commit 790cf3b32e)
2021-12-10 16:39:15 +01:00
Harald Kuhr c2880fe793 Rename file, add missing file extension.
(cherry picked from commit b1baaad23b)
2021-12-10 16:39:15 +01:00
Harald Kuhr c9e522475b Bump plugins and stop deploying useless (internal) artifacts.
(cherry picked from commit 7fa704ace5)
2021-12-10 16:39:15 +01:00
Harald Kuhr 1da16e4d7a Updated versions to 3.7.0.
(cherry picked from commit 8d07f4fe90)
2021-12-10 16:33:02 +01:00
154 changed files with 18264 additions and 2479 deletions
-53
View File
@@ -1,53 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: Reported bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**Version information**
1. The version of the TwelveMonkeys ImageIO library in use.
For example: 4.0.0
2. The *exact* output of `java --version` (or `java -version` for older Java releases).
For example:
java version "1.8.0_271"
Java(TM) SE Runtime Environment (build 1.8.0_271-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)
3. Extra information about OS version, server version, standalone program or web application packaging, executable wrapper, etc. Please state exact version numbers where applicable.
**To Reproduce**
Steps to reproduce the behavior:
1. Compile the below sample code
2. Download the sample image file
3. Run the code with the sample file
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Example code**
Preferably as a failing JUnit test, or a standalone program with a `main` method that showcases the problem.
Less is more. Don't add your entire project, only the code required to reproduce the problem. 😀
**Sample file(s)**
Attach any sample files needed to reproduce the problem. Use a ZIP-file if the format is not directly supported by GitHub.
**Stak trace**
Always include the stack trace you experience.
**Screenshots**
If applicable, add screenshots to help explain your problem.
Do not add screenshots of code or stack traces. 😀
**Additional context**
Add any other context about the problem here.
-20
View File
@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: New feature
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem or use case is.
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here, like links to specifications or sample files.
@@ -1,13 +0,0 @@
---
name: Trouble shooting and programming help
about: "General programming issues will reach a wider audience at StackOverflow. Tag
questions with javax-imageio and/or twelvemonkeys \U0001F600 "
title: ''
labels: Trouble-shooting
assignees: ''
---
General programming issues and problems will reach a much wider audience at StackOverflow, we suggest you ask them there. This will offload our work with maintaining the library, and make sure you get better help sooner.
Tag the question with `javax-imageio` and/or `twelvemonkeys` and we'll find them there.
@@ -1,11 +0,0 @@
**What is fixed** Add link to the issue this PR fixes.
Example: Fixes #42.
**Why is this change proposed** If this change does *not* fix an open issue, briefly describe the rationale for this PR.
**What is changed** Briefly describe the changes proposed in this pull request:
* Fixed rare exception happening in `x >= 42` case
* Small optimization of `decompress()` method
* Corrected API doc for `compress()` method to reflect current implementation
+36 -46
View File
@@ -31,19 +31,19 @@ As there is lots of legacy data out there, we see the need for open implementati
| [PCX](https://github.com/haraldk/TwelveMonkeys/wiki/PCX-Plugin) | PCX | ZSoft Paintbrush Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
| | DCX | Multi-page PCX fax document | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
| [PICT](https://github.com/haraldk/TwelveMonkeys/wiki/PICT-Plugin) | PICT | Apple QuickTime Picture Format | ✔ | ✔ | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
| | PNTG | Apple MacPaint Picture Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
| | PNTG | Apple MacPaint Picture Format | ✔ | | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
| [PNM](https://github.com/haraldk/TwelveMonkeys/wiki/PNM-Plugin) | PAM | NetPBM Portable Any Map | ✔ | ✔ | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
| | PBM | NetPBM Portable Bit Map | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
| | PGM | NetPBM Portable Grey Map | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
| | PPM | NetPBM Portable Pix Map | ✔ | ✔ | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
| | PFM | Portable Float Map | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
| [PSD](https://github.com/haraldk/TwelveMonkeys/wiki/PSD-Plugin) | **PSD** | Adobe Photoshop Document | ✔ | (✔) | Native & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
| [PSD](https://github.com/haraldk/TwelveMonkeys/wiki/PSD-Plugin) | **PSD** | Adobe Photoshop Document | ✔ | - | Native & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
| | PSB | Adobe Photoshop Large Document | ✔ | - | Native & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
| [SGI](https://github.com/haraldk/TwelveMonkeys/wiki/SGI-Plugin) | SGI | Silicon Graphics Image Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
| [TGA](https://github.com/haraldk/TwelveMonkeys/wiki/TGA-Plugin) | TGA | Truevision TGA Image Format | ✔ | ✔ | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|ThumbsDB| Thumbs.db| MS Windows Thumbs DB | ✔ | - | - | OLE2 Compound Document based format only
| [TIFF](https://github.com/haraldk/TwelveMonkeys/wiki/TIFF-Plugin) | **TIFF** | Aldus/Adobe Tagged Image File Format | ✔ | ✔ | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/tiff_metadata.html#ImageMetadata) & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
| | BigTIFF | | ✔ | | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/tiff_metadata.html#ImageMetadata) & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
| | BigTIFF | | ✔ | - | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/tiff_metadata.html#ImageMetadata) & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
| [WebP](https://github.com/haraldk/TwelveMonkeys/wiki/WebP-Plugin) | **WebP** | Google WebP Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
| XWD | XWD | X11 Window Dump Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
@@ -271,32 +271,22 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId>
<version>3.8.0</version>
<version>3.7.0</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId>
<version>3.8.0</version>
<version>3.7.0</version>
</dependency>
<!--
Optional dependency. Needed only if you deploy ImageIO plugins as part of a web app.
Make sure you add the IIOProviderContextListener to your web.xml, see above.
-->
<dependency>
<groupId>com.twelvemonkeys.servlet</groupId>
<artifactId>servlet</artifactId>
<version>3.8.0</version>
</dependency>
<!--
Or Jakarta version, for Servlet API 5.0
-->
<dependency>
<groupId>com.twelvemonkeys.servlet</groupId>
<artifactId>servlet</artifactId>
<version>3.7.0</version>
<classifier>jakarta</classifier>
</dependency>
</dependencies>
```
@@ -305,13 +295,13 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
To depend on the JPEG and TIFF plugin in your IDE or program, add all of the following JARs to your class path:
twelvemonkeys-common-lang-3.8.0.jar
twelvemonkeys-common-io-3.8.0.jar
twelvemonkeys-common-image-3.8.0.jar
twelvemonkeys-imageio-core-3.8.0.jar
twelvemonkeys-imageio-metadata-3.8.0.jar
twelvemonkeys-imageio-jpeg-3.8.0.jar
twelvemonkeys-imageio-tiff-3.8.0.jar
twelvemonkeys-common-lang-3.7.0.jar
twelvemonkeys-common-io-3.7.0.jar
twelvemonkeys-common-image-3.7.0.jar
twelvemonkeys-imageio-core-3.7.0.jar
twelvemonkeys-imageio-metadata-3.7.0.jar
twelvemonkeys-imageio-jpeg-3.7.0.jar
twelvemonkeys-imageio-tiff-3.7.0.jar
#### Deploying the plugins in a web app
@@ -377,44 +367,44 @@ Other "fat" JAR bundlers will probably have similar mechanisms to merge entries
### Links to prebuilt binaries
##### Latest version (3.8.0)
##### Latest version (3.7.0)
Requires Java 7 or later.
Common dependencies
* [common-lang-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.8.0/common-lang-3.8.0.jar)
* [common-io-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.8.0/common-io-3.8.0.jar)
* [common-image-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.8.0/common-image-3.8.0.jar)
* [common-lang-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.7.0/common-lang-3.7.0.jar)
* [common-io-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.7.0/common-io-3.7.0.jar)
* [common-image-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.7.0/common-image-3.7.0.jar)
ImageIO dependencies
* [imageio-core-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.8.0/imageio-core-3.8.0.jar)
* [imageio-metadata-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.8.0/imageio-metadata-3.8.0.jar)
* [imageio-core-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.7.0/imageio-core-3.7.0.jar)
* [imageio-metadata-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.7.0/imageio-metadata-3.7.0.jar)
ImageIO plugins
* [imageio-bmp-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.8.0/imageio-bmp-3.8.0.jar)
* [imageio-hdr-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.8.0/imageio-hdr-3.8.0.jar)
* [imageio-icns-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.8.0/imageio-icns-3.8.0.jar)
* [imageio-iff-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.8.0/imageio-iff-3.8.0.jar)
* [imageio-jpeg-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.8.0/imageio-jpeg-3.8.0.jar)
* [imageio-pcx-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.8.0/imageio-pcx-3.8.0.jar)
* [imageio-pict-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.8.0/imageio-pict-3.8.0.jar)
* [imageio-pnm-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.8.0/imageio-pnm-3.8.0.jar)
* [imageio-psd-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.8.0/imageio-psd-3.8.0.jar)
* [imageio-sgi-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.8.0/imageio-sgi-3.8.0.jar)
* [imageio-tga-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.8.0/imageio-tga-3.8.0.jar)
* [imageio-thumbsdb-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.8.0/imageio-thumbsdb-3.8.0.jar)
* [imageio-tiff-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.8.0/imageio-tiff-3.8.0.jar)
* [imageio-webp-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-webp/3.8.0/imageio-webp-3.8.0.jar)
* [imageio-xwd-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-xwd/3.8.0/imageio-xwd-3.8.0.jar)
* [imageio-bmp-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.7.0/imageio-bmp-3.7.0.jar)
* [imageio-hdr-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.7.0/imageio-hdr-3.7.0.jar)
* [imageio-icns-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.7.0/imageio-icns-3.7.0.jar)
* [imageio-iff-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.7.0/imageio-iff-3.7.0.jar)
* [imageio-jpeg-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.7.0/imageio-jpeg-3.7.0.jar)
* [imageio-pcx-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.7.0/imageio-pcx-3.7.0.jar)
* [imageio-pict-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.7.0/imageio-pict-3.7.0.jar)
* [imageio-pnm-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.7.0/imageio-pnm-3.7.0.jar)
* [imageio-psd-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.7.0/imageio-psd-3.7.0.jar)
* [imageio-sgi-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.7.0/imageio-sgi-3.7.0.jar)
* [imageio-tga-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.7.0/imageio-tga-3.7.0.jar)
* [imageio-thumbsdb-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.7.0/imageio-thumbsdb-3.7.0.jar)
* [imageio-tiff-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.7.0/imageio-tiff-3.7.0.jar)
* [imageio-webp-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-webp/3.7.0/imageio-webp-3.7.0.jar)
* [imageio-xwd-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-xwd/3.7.0/imageio-xwd-3.7.0.jar)
ImageIO plugins requiring 3rd party libs
* [imageio-batik-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.8.0/imageio-batik-3.8.0.jar)
* [imageio-batik-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.7.0/imageio-batik-3.7.0.jar)
Photoshop Path support for ImageIO
* [imageio-clippath-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.8.0/imageio-clippath-3.8.0.jar)
* [imageio-clippath-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.7.0/imageio-clippath-3.7.0.jar)
Servlet support
* [servlet-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.8.0/servlet-3.8.0.jar)
* [servlet-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.7.0/servlet-3.7.0.jar)
##### Old version (3.0.x)
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.bom</groupId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>common-image</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>common-io</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>common-lang</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.contrib</groupId>
<artifactId>contrib</artifactId>
+1 -8
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-batik</artifactId>
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
@@ -48,13 +48,6 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-rasterizer-ext</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-bmp</artifactId>
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-clippath</artifactId>
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-core</artifactId>
<name>TwelveMonkeys :: ImageIO :: Core</name>
@@ -1,563 +0,0 @@
/*
* Copyright (c) 2021, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.color;
import com.twelvemonkeys.io.FileUtil;
import com.twelvemonkeys.lang.Platform;
import com.twelvemonkeys.lang.SystemUtil;
import com.twelvemonkeys.lang.Validate;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Properties;
import static com.twelvemonkeys.imageio.color.ColorSpaces.DEBUG;
/**
* A helper class for working with ICC color profiles.
* <p>
* Standard ICC color profiles are read from system-specific locations
* for known operating systems.
* </p>
* <p>
* Color profiles may be configured by placing a property-file
* {@code com/twelvemonkeys/imageio/color/icc_profiles.properties}
* on the classpath, specifying the full path to the profiles.
* ICC color profiles are probably already present on your system, or
* can be downloaded from
* <a href="http://www.color.org/profiles2.xalter">ICC</a>,
* <a href="http://www.adobe.com/downloads/">Adobe</a> or other places.
* * </p>
* <p>
* Example property file:
* </p>
* <pre>
* # icc_profiles.properties
* ADOBE_RGB_1998=/path/to/Adobe RGB 1998.icc
* GENERIC_CMYK=/path/to/Generic CMYK.icc
* </pre>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: ColorSpaces.java,v 1.0 24.01.11 17.51 haraldk Exp$
*/
public final class ColorProfiles {
/**
* We need special ICC profile handling for KCMS vs LCMS. Delegate to specific strategy.
*/
private final static ICCProfileSanitizer profileCleaner = ICCProfileSanitizer.Factory.get();
static final int ICC_PROFILE_MAGIC = 'a' << 24 | 'c' << 16 | 's' << 8 | 'p';
static final int ICC_PROFILE_HEADER_SIZE = 128;
static {
// In case we didn't activate through SPI already
ProfileDeferralActivator.activateProfiles();
}
private ColorProfiles() {
}
static byte[] getProfileHeaderWithProfileId(final ICC_Profile profile) {
// Get *entire profile data*... :-/
return getProfileHeaderWithProfileId(profile.getData());
}
static byte[] getProfileHeaderWithProfileId(byte[] data) {
// ICC profile header is the first 128 bytes
byte[] header = Arrays.copyOf(data, ICC_PROFILE_HEADER_SIZE);
// Clear out preferred CMM, platform & creator, as these don't affect the profile in any way
// - LCMS updates CMM + creator to "lcms" and platform to current platform
// - KCMS keeps the values in the file...
Arrays.fill(header, ICC_Profile.icHdrCmmId, ICC_Profile.icHdrCmmId + 4, (byte) 0);
Arrays.fill(header, ICC_Profile.icHdrPlatform, ICC_Profile.icHdrPlatform + 4, (byte) 0);
// + Clear out rendering intent, as this may be updated by application
Arrays.fill(header, ICC_Profile.icHdrRenderingIntent, ICC_Profile.icHdrRenderingIntent + 4, (byte) 0);
Arrays.fill(header, ICC_Profile.icHdrCreator, ICC_Profile.icHdrCreator + 4, (byte) 0);
// Clear out any existing MD5, as it is no longer correct
Arrays.fill(header, ICC_Profile.icHdrProfileID, ICC_Profile.icHdrProfileID + 16, (byte) 0);
// Generate new MD5 and store in header
byte[] md5 = computeMD5(header, data);
System.arraycopy(md5, 0, header, ICC_Profile.icHdrProfileID, md5.length);
return header;
}
private static byte[] computeMD5(byte[] header, byte[] data) {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(header, 0, ICC_PROFILE_HEADER_SIZE);
digest.update(data, ICC_PROFILE_HEADER_SIZE, data.length - ICC_PROFILE_HEADER_SIZE);
return digest.digest();
}
catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Missing MD5 MessageDigest");
}
}
/**
* Tests whether an ICC color profile is equal to the default sRGB profile.
*
* @param profile the ICC profile to test. May not be {@code null}.
* @return {@code true} if {@code profile} is equal to the default sRGB profile.
* @throws IllegalArgumentException if {@code profile} is {@code null}
* @see java.awt.color.ColorSpace#CS_sRGB
* @see java.awt.color.ColorSpace#isCS_sRGB()
*/
public static boolean isCS_sRGB(final ICC_Profile profile) {
Validate.notNull(profile, "profile");
return profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(getProfileHeaderWithProfileId(profile), sRGB.header);
}
/**
* Tests whether an ICC color profile is equal to the default GRAY profile.
*
* @param profile the ICC profile to test. May not be {@code null}.
* @return {@code true} if {@code profile} is equal to the default GRAY profile.
* @throws IllegalArgumentException if {@code profile} is {@code null}
* @see java.awt.color.ColorSpace#CS_GRAY
*/
public static boolean isCS_GRAY(final ICC_Profile profile) {
Validate.notNull(profile, "profile");
return profile.getColorSpaceType() == ColorSpace.TYPE_GRAY && Arrays.equals(getProfileHeaderWithProfileId(profile), GRAY.header);
}
/**
* Tests whether an ICC color profile is known to cause problems for {@link java.awt.image.ColorConvertOp}.
* <p>
* <em>
* Note that this method only tests if a color conversion using this profile is known to fail.
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
* </em>
* </p>
*
* @param profile the ICC color profile. May not be {@code null}.
* @return {@code true} if known to be offending, {@code false} otherwise
* @throws IllegalArgumentException if {@code profile} is {@code null}
*/
static boolean isOffendingColorProfile(final ICC_Profile profile) {
Validate.notNull(profile, "profile");
// NOTE:
// Several embedded ICC color profiles are non-compliant with Java pre JDK7 and throws CMMException
// The problem with these embedded ICC profiles seems to be the rendering intent
// being 1 (01000000) - "Media Relative Colormetric" in the offending profiles,
// and 0 (00000000) - "Perceptual" in the good profiles
// (that is 1 single bit of difference right there.. ;-)
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7064516
// This is particularly annoying, as the byte copying isn't really necessary,
// except the getRenderingIntent method is package protected in java.awt.color
byte[] header = profile.getData(ICC_Profile.icSigHead);
return header[ICC_Profile.icHdrRenderingIntent] != 0 || header[ICC_Profile.icHdrRenderingIntent + 1] != 0
|| header[ICC_Profile.icHdrRenderingIntent + 2] != 0 || header[ICC_Profile.icHdrRenderingIntent + 3] > 3;
}
/**
* Tests whether an ICC color profile is valid.
* Invalid profiles are known to cause problems for {@link java.awt.image.ColorConvertOp}.
* <p>
* <em>
* Note that this method only tests if a color conversion using this profile is known to fail.
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
* </em>
* </p>
*
* @param profile the ICC color profile. May not be {@code null}.
* @return {@code profile} if valid.
* @throws IllegalArgumentException if {@code profile} is {@code null}
* @throws java.awt.color.CMMException if {@code profile} is invalid.
*/
public static ICC_Profile validateProfile(final ICC_Profile profile) {
// Fix profile before validation
profileCleaner.fixProfile(profile);
ColorSpaces.validateColorSpace(new ICC_ColorSpace(profile)); // TODO: Should use createColorSpace and cache if good?
return profile;
}
/**
* Reads an ICC Profile from the given input stream, as-is, with no validation.
*
* This method behaves exactly like {@code ICC_Profile.getInstance(input)}.
*
* @param input the input stream to read from, may not be {@code null}
* @return an {@code ICC_Profile} object as read from the input stream.
* @throws IOException If an I/O error occurs while reading the stream.
* @throws IllegalArgumentException If {@code input} is {@code null}
* or the stream does not contain valid ICC Profile data.
* @see ICC_Profile#getInstance(InputStream)
* @see #readProfile(InputStream)
*/
public static ICC_Profile readProfileRaw(final InputStream input) throws IOException {
Validate.notNull(input, "input");
return ICC_Profile.getInstance(input);
}
/**
* Reads an ICC Profile from the given input stream, with extra validation.
*
* If a matching profile already exists in cache, the cached instance is returned.
*
* @param input the input stream to read from, may not be {@code null}
* @return an {@code ICC_Profile} object as read from the input stream.
* @throws IOException If an I/O error occurs while reading the stream.
* @throws IllegalArgumentException If {@code input} is {@code null}
* or the stream does not contain valid ICC Profile data.
* @see ICC_Profile#getInstance(InputStream)
*/
public static ICC_Profile readProfile(final InputStream input) throws IOException {
Validate.notNull(input, "input");
DataInputStream dataInput = new DataInputStream(input);
byte[] header = new byte[ICC_PROFILE_HEADER_SIZE];
try {
dataInput.readFully(header);
int size = validateHeaderAndGetSize(header);
byte[] data = Arrays.copyOf(header, size);
dataInput.readFully(data, header.length, size - header.length);
return createProfile(data);
}
catch (EOFException e) {
throw new IllegalArgumentException("Truncated ICC Profile data", e);
}
}
/**
* Creates an ICC Profile from the given byte array, as-is, with no validation.
*
* This method behaves exactly like {@code ICC_Profile.getInstance(input)},
* except that extraneous bytes at the end of the array is ignored.
*
* @param input the byte array to create a profile from, may not be {@code null}
* @return an {@code ICC_Profile} object created from the byte array
* @throws IllegalArgumentException If {@code input} is {@code null}
* or the byte array does not contain valid ICC Profile data.
* @see ICC_Profile#getInstance(byte[])
* @see #createProfile(byte[])
*/
public static ICC_Profile createProfileRaw(final byte[] input) {
int size = validateHeaderAndGetSize(input);
// Unlike the InputStream version, the byte version of ICC_Profile.getInstance()
// does not discard extra bytes at the end. We'll chop them off here for convenience
return ICC_Profile.getInstance(limit(input, size));
}
/**
* Reads an ICC Profile from the given byte array, with extra validation.
* Extraneous bytes at the end of the array are ignored.
*
* If a matching profile already exists in cache, the cached instance is returned.
*
* @param input the byte array to create a profile from, may not be {@code null}
* @return an {@code ICC_Profile} object created from the byte array
* @throws IllegalArgumentException If {@code input} is {@code null}
* or the byte array does not contain valid ICC Profile data.
* @see ICC_Profile#getInstance(byte[])
*/
public static ICC_Profile createProfile(final byte[] input) {
int size = validateAndGetSize(input);
// Look up in cache before returning, these are already validated
byte[] profileHeader = getProfileHeaderWithProfileId(input);
ICC_Profile internal = getInternalProfile(profileHeader);
if (internal != null) {
return internal;
}
ICC_ColorSpace cached = ColorSpaces.getCachedCS(profileHeader);
if (cached != null) {
return cached.getProfile();
}
ICC_Profile profile = ICC_Profile.getInstance(limit(input, size));
// We'll validate & cache by creating a color space and returning its profile...
// TODO: Rewrite with separate cache for profiles...
return ColorSpaces.createColorSpace(profile).getProfile();
}
private static byte[] limit(byte[] input, int size) {
return input.length == size ? input : Arrays.copyOf(input, size);
}
private static int validateAndGetSize(byte[] input) {
int size = validateHeaderAndGetSize(input);
if (size < 0 || size > input.length) {
throw new IllegalArgumentException("Truncated ICC profile data, length < " + size + ": " + input.length);
}
return size;
}
private static int validateHeaderAndGetSize(byte[] input) {
Validate.notNull(input, "input");
if (input.length < ICC_PROFILE_HEADER_SIZE) { // Can't be less than size of ICC header
throw new IllegalArgumentException("Truncated ICC profile data, length < 128: " + input.length);
}
int size = intBigEndian(input, ICC_Profile.icHdrSize);
if (intBigEndian(input, ICC_Profile.icHdrMagic) != ICC_PROFILE_MAGIC) {
throw new IllegalArgumentException("Not an ICC profile, missing file signature");
}
return size;
}
private static ICC_Profile getInternalProfile(final byte[] profileHeader) {
int profileCSType = getCsType(profileHeader);
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.sRGB.header)) {
return ICC_Profile.getInstance(ColorSpace.CS_sRGB);
}
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, ColorProfiles.GRAY.header)) {
return ICC_Profile.getInstance(ColorSpace.CS_GRAY);
}
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, ColorProfiles.PYCC.header)) {
return ICC_Profile.getInstance(ColorSpace.CS_PYCC);
}
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.LINEAR_RGB.header)) {
return ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB);
}
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, ColorProfiles.CIEXYZ.header)) {
return ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ);
}
return null;
}
private static int intBigEndian(byte[] data, int index) {
return (data[index] & 0xff) << 24 | (data[index + 1] & 0xff) << 16 | (data[index + 2] & 0xff) << 8 | (data[index + 3] & 0xff);
}
private static int getCsType(byte[] profileHeader) {
int csSig = intBigEndian(profileHeader, ICC_Profile.icHdrColorSpace);
switch (csSig) {
case ICC_Profile.icSigXYZData:
return ColorSpace.TYPE_XYZ;
case ICC_Profile.icSigLabData:
return ColorSpace.TYPE_Lab;
case ICC_Profile.icSigLuvData:
return ColorSpace.TYPE_Luv;
case ICC_Profile.icSigYCbCrData:
return ColorSpace.TYPE_YCbCr;
case ICC_Profile.icSigYxyData:
return ColorSpace.TYPE_Yxy;
case ICC_Profile.icSigRgbData:
return ColorSpace.TYPE_RGB;
case ICC_Profile.icSigGrayData:
return ColorSpace.TYPE_GRAY;
case ICC_Profile.icSigHsvData:
return ColorSpace.TYPE_HSV;
case ICC_Profile.icSigHlsData:
return ColorSpace.TYPE_HLS;
case ICC_Profile.icSigCmykData:
return ColorSpace.TYPE_CMYK;
// Note: There is no TYPE_* 10...
case ICC_Profile.icSigCmyData:
return ColorSpace.TYPE_CMY;
case ICC_Profile.icSigSpace2CLR:
return ColorSpace.TYPE_2CLR;
case ICC_Profile.icSigSpace3CLR:
return ColorSpace.TYPE_3CLR;
case ICC_Profile.icSigSpace4CLR:
return ColorSpace.TYPE_4CLR;
case ICC_Profile.icSigSpace5CLR:
return ColorSpace.TYPE_5CLR;
case ICC_Profile.icSigSpace6CLR:
return ColorSpace.TYPE_6CLR;
case ICC_Profile.icSigSpace7CLR:
return ColorSpace.TYPE_7CLR;
case ICC_Profile.icSigSpace8CLR:
return ColorSpace.TYPE_8CLR;
case ICC_Profile.icSigSpace9CLR:
return ColorSpace.TYPE_9CLR;
case ICC_Profile.icSigSpaceACLR:
return ColorSpace.TYPE_ACLR;
case ICC_Profile.icSigSpaceBCLR:
return ColorSpace.TYPE_BCLR;
case ICC_Profile.icSigSpaceCCLR:
return ColorSpace.TYPE_CCLR;
case ICC_Profile.icSigSpaceDCLR:
return ColorSpace.TYPE_DCLR;
case ICC_Profile.icSigSpaceECLR:
return ColorSpace.TYPE_ECLR;
case ICC_Profile.icSigSpaceFCLR:
return ColorSpace.TYPE_FCLR;
default:
throw new IllegalArgumentException("Invalid ICC color space signature: " + csSig); // TODO: fourCC?
}
}
static ICC_Profile readProfileFromClasspathResource(@SuppressWarnings("SameParameterValue") final String profilePath) {
InputStream stream = ColorSpaces.class.getResourceAsStream(profilePath);
if (stream != null) {
if (DEBUG) {
System.out.println("Loading profile from classpath resource: " + profilePath);
}
try {
return ICC_Profile.getInstance(stream);
}
catch (@SuppressWarnings("CatchMayIgnoreException") IOException ignore) {
if (DEBUG) {
ignore.printStackTrace();
}
}
finally {
FileUtil.close(stream);
}
}
return null;
}
static ICC_Profile readProfileFromPath(final String profilePath) {
if (profilePath != null) {
if (DEBUG) {
System.out.println("Loading profile from: " + profilePath);
}
try {
return ICC_Profile.getInstance(profilePath);
}
catch (@SuppressWarnings("CatchMayIgnoreException") SecurityException | IOException ignore) {
if (DEBUG) {
ignore.printStackTrace();
}
}
}
return null;
}
static void fixProfile(ICC_Profile profile) {
profileCleaner.fixProfile(profile);
}
static boolean validationAltersProfileHeader() {
return profileCleaner.validationAltersProfileHeader();
}
// Cache header profile data to avoid excessive array creation/copying. Use static inner class for on-demand lazy init
static class sRGB {
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_sRGB));
}
static class CIEXYZ {
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ));
}
static class PYCC {
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_PYCC));
}
static class GRAY {
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
}
static class LINEAR_RGB {
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB));
}
static class Profiles {
// TODO: Honour java.iccprofile.path property?
private static final Properties PROFILES = loadProfiles();
private static Properties loadProfiles() {
Properties systemDefaults;
try {
systemDefaults = SystemUtil.loadProperties(ColorSpaces.class, "com/twelvemonkeys/imageio/color/icc_profiles_" + Platform.os().id());
}
catch (@SuppressWarnings("CatchMayIgnoreException") SecurityException | IOException ignore) {
System.err.printf(
"Warning: Could not load system default ICC profile locations from %s, will use bundled fallback profiles.\n",
ignore.getMessage()
);
if (DEBUG) {
ignore.printStackTrace();
}
systemDefaults = null;
}
// Create map with defaults and add user overrides if any
Properties profiles = new Properties(systemDefaults);
try {
Properties userOverrides = SystemUtil.loadProperties(
ColorSpaces.class,
"com/twelvemonkeys/imageio/color/icc_profiles"
);
profiles.putAll(userOverrides);
}
catch (SecurityException | IOException ignore) {
// Most likely, this file won't be there
}
if (DEBUG) {
System.out.println("User ICC profiles: " + profiles);
System.out.println("System ICC profiles : " + systemDefaults);
}
return profiles;
}
static String getPath(final String profileName) {
return PROFILES.getProperty(profileName);
}
}
}
@@ -30,17 +30,24 @@
package com.twelvemonkeys.imageio.color;
import com.twelvemonkeys.io.FileUtil;
import com.twelvemonkeys.lang.Platform;
import com.twelvemonkeys.lang.SystemUtil;
import com.twelvemonkeys.lang.Validate;
import com.twelvemonkeys.util.LRUHashMap;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Map;
import static com.twelvemonkeys.imageio.color.ColorProfiles.*;
import java.util.Properties;
/**
* A helper class for working with ICC color profiles and color spaces.
@@ -77,6 +84,9 @@ public final class ColorSpaces {
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.color.debug"));
/** We need special ICC profile handling for KCMS vs LCMS. Delegate to specific strategy. */
private final static ICCProfileSanitizer profileCleaner = ICCProfileSanitizer.Factory.get();
// NOTE: java.awt.color.ColorSpace.CS_* uses 1000-1004, we'll use 5000+ to not interfere with future additions
/** The Adobe RGB 1998 (or compatible) color space. Either read from disk or built-in. */
@@ -87,13 +97,14 @@ public final class ColorSpaces {
@SuppressWarnings("WeakerAccess")
public static final int CS_GENERIC_CMYK = 5001;
// TODO: Move to ColorProfiles OR cache ICC_ColorSpace instead?
static final int ICC_PROFILE_HEADER_SIZE = 128;
// Weak references to hold the color spaces while cached
private static WeakReference<ICC_Profile> adobeRGB1998 = new WeakReference<>(null);
private static WeakReference<ICC_Profile> genericCMYK = new WeakReference<>(null);
// Cache for the latest used color spaces
private static final Map<Key, ICC_ColorSpace> cache = new LRUHashMap<>(16);
private static final Map<Key, ICC_ColorSpace> cache = new LRUHashMap<>(10);
static {
// In case we didn't activate through SPI already
@@ -118,7 +129,7 @@ public final class ColorSpaces {
Validate.notNull(profile, "profile");
// Fix profile before lookup/create
fixProfile(profile);
profileCleaner.fixProfile(profile);
byte[] profileHeader = getProfileHeaderWithProfileId(profile);
@@ -130,20 +141,55 @@ public final class ColorSpaces {
return getCachedOrCreateCS(profile, profileHeader);
}
static ICC_ColorSpace getInternalCS(final int profileCSType, final byte[] profileHeader) {
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.sRGB.header)) {
private static byte[] getProfileHeaderWithProfileId(final ICC_Profile profile) {
// Get *entire profile data*... :-/
return getProfileHeaderWithProfileId(profile.getData());
}
private static byte[] getProfileHeaderWithProfileId(byte[] data) {
// Clear out preferred CMM, platform & creator, as these don't affect the profile in any way
// - LCMS updates CMM + creator to "lcms" and platform to current platform
// - KCMS keeps the values in the file...
Arrays.fill(data, ICC_Profile.icHdrCmmId, ICC_Profile.icHdrCmmId + 4, (byte) 0);
Arrays.fill(data, ICC_Profile.icHdrPlatform, ICC_Profile.icHdrPlatform + 4, (byte) 0);
// + Clear out rendering intent, as this may be updated by application
Arrays.fill(data, ICC_Profile.icHdrRenderingIntent, ICC_Profile.icHdrRenderingIntent + 4, (byte) 0);
Arrays.fill(data, ICC_Profile.icHdrCreator, ICC_Profile.icHdrCreator + 4, (byte) 0);
// Clear out any existing MD5, as it is no longer correct
Arrays.fill(data, ICC_Profile.icHdrProfileID, ICC_Profile.icHdrProfileID + 16, (byte) 0);
// Generate new MD5 and store in header
byte[] md5 = computeMD5(data);
System.arraycopy(md5, 0, data, ICC_Profile.icHdrProfileID, md5.length);
// ICC profile header is the first 128 bytes
return Arrays.copyOf(data, ICC_PROFILE_HEADER_SIZE);
}
private static byte[] computeMD5(byte[] data) {
try {
return MessageDigest.getInstance("MD5").digest(data);
}
catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Missing MD5 MessageDigest");
}
}
private static ICC_ColorSpace getInternalCS(final int profileCSType, final byte[] profileHeader) {
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, sRGB.header)) {
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_sRGB);
}
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, ColorProfiles.GRAY.header)) {
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, GRAY.header)) {
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_GRAY);
}
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, ColorProfiles.PYCC.header)) {
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, PYCC.header)) {
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_PYCC);
}
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.LINEAR_RGB.header)) {
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, LINEAR_RGB.header)) {
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB);
}
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, ColorProfiles.CIEXYZ.header)) {
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, CIEXYZ.header)) {
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
}
@@ -164,7 +210,7 @@ public final class ColorSpaces {
cache.put(key, cs);
// On LCMS, validation *alters* the profile header, need to re-generate key
if (ColorProfiles.validationAltersProfileHeader()) {
if (profileCleaner.validationAltersProfileHeader()) {
cache.put(new Key(getProfileHeaderWithProfileId(cs.getProfile())), cs);
}
}
@@ -179,11 +225,11 @@ public final class ColorSpaces {
}
}
static ICC_ColorSpace getCachedCS(final byte[] profileHeader) {
private static ICC_ColorSpace getCachedCS(final byte[] profileHeader) {
return getCachedCS(new Key(profileHeader));
}
static void validateColorSpace(final ICC_ColorSpace cs) {
private static void validateColorSpace(final ICC_ColorSpace cs) {
// Validate the color space, to avoid caching bad profiles/color spaces
// Will throw IllegalArgumentException or CMMException if the profile is bad
cs.fromRGB(new float[] {0.999f, 0.5f, 0.001f});
@@ -194,27 +240,220 @@ public final class ColorSpaces {
}
/**
* @deprecated Use {@link ColorProfiles#isCS_sRGB(ICC_Profile)} instead.
* Tests whether an ICC color profile is equal to the default sRGB profile.
*
* @param profile the ICC profile to test. May not be {@code null}.
* @return {@code true} if {@code profile} is equal to the default sRGB profile.
* @throws IllegalArgumentException if {@code profile} is {@code null}
*
* @see java.awt.color.ColorSpace#CS_sRGB
* @see java.awt.color.ColorSpace#isCS_sRGB()
*/
@Deprecated
public static boolean isCS_sRGB(final ICC_Profile profile) {
return ColorProfiles.isCS_sRGB(profile);
Validate.notNull(profile, "profile");
return profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(getProfileHeaderWithProfileId(profile), sRGB.header);
}
/**
* @deprecated Use {@link ColorProfiles#isCS_GRAY(ICC_Profile)} instead.
* Tests whether an ICC color profile is equal to the default GRAY profile.
*
* @param profile the ICC profile to test. May not be {@code null}.
* @return {@code true} if {@code profile} is equal to the default GRAY profile.
* @throws IllegalArgumentException if {@code profile} is {@code null}
*
* @see java.awt.color.ColorSpace#CS_GRAY
*/
@Deprecated
public static boolean isCS_GRAY(final ICC_Profile profile) {
return ColorProfiles.isCS_GRAY(profile);
Validate.notNull(profile, "profile");
return profile.getColorSpaceType() == ColorSpace.TYPE_GRAY && Arrays.equals(getProfileHeaderWithProfileId(profile), GRAY.header);
}
/**
* @deprecated Use {@link ColorProfiles#validateProfile(ICC_Profile)} instead.
* Tests whether an ICC color profile is known to cause problems for {@link java.awt.image.ColorConvertOp}.
* <p>
* <em>
* Note that this method only tests if a color conversion using this profile is known to fail.
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
* </em>
* </p>
*
* @param profile the ICC color profile. May not be {@code null}.
* @return {@code true} if known to be offending, {@code false} otherwise
* @throws IllegalArgumentException if {@code profile} is {@code null}
*/
static boolean isOffendingColorProfile(final ICC_Profile profile) {
Validate.notNull(profile, "profile");
// NOTE:
// Several embedded ICC color profiles are non-compliant with Java pre JDK7 and throws CMMException
// The problem with these embedded ICC profiles seems to be the rendering intent
// being 1 (01000000) - "Media Relative Colormetric" in the offending profiles,
// and 0 (00000000) - "Perceptual" in the good profiles
// (that is 1 single bit of difference right there.. ;-)
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7064516
// This is particularly annoying, as the byte copying isn't really necessary,
// except the getRenderingIntent method is package protected in java.awt.color
byte[] header = profile.getData(ICC_Profile.icSigHead);
return header[ICC_Profile.icHdrRenderingIntent] != 0 || header[ICC_Profile.icHdrRenderingIntent + 1] != 0
|| header[ICC_Profile.icHdrRenderingIntent + 2] != 0 || header[ICC_Profile.icHdrRenderingIntent + 3] > 3;
}
/**
* Tests whether an ICC color profile is valid.
* Invalid profiles are known to cause problems for {@link java.awt.image.ColorConvertOp}.
* <p>
* <em>
* Note that this method only tests if a color conversion using this profile is known to fail.
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
* </em>
* </p>
*
* @param profile the ICC color profile. May not be {@code null}.
* @return {@code profile} if valid.
* @throws IllegalArgumentException if {@code profile} is {@code null}
* @throws java.awt.color.CMMException if {@code profile} is invalid.
*/
@Deprecated
public static ICC_Profile validateProfile(final ICC_Profile profile) {
return ColorProfiles.validateProfile(profile);
// Fix profile before validation
profileCleaner.fixProfile(profile);
validateColorSpace(new ICC_ColorSpace(profile));
return profile;
}
public static ICC_Profile readProfileRaw(final InputStream input) throws IOException {
return ICC_Profile.getInstance(input);
}
public static ICC_Profile readProfile(final InputStream input) throws IOException {
// TODO: Implement this smarter?
// Could read the header 128 bytes, get size + magic, then read read rest into array and feed the byte[] method...
ICC_Profile profile = ICC_Profile.getInstance(input);
if (profile == null) {
throw new IllegalArgumentException("Invalid ICC Profile Data");
}
return createProfile(profile.getData());
}
public static ICC_Profile createProfileRaw(final byte[] input) {
try {
return readProfileRaw(new ByteArrayInputStream(input));
}
catch (IOException e) {
throw new IllegalArgumentException("Invalid ICC Profile Data", e);
}
}
public static ICC_Profile createProfile(final byte[] input) {
Validate.notNull(input, "input");
if (input.length < ICC_PROFILE_HEADER_SIZE) { // Can't be less than size of ICC header
throw new IllegalArgumentException("Truncated ICC profile, length < 128: " + input.length);
}
int size = intBigEndian(input, 0);
if (size < 0 || size > input.length) {
throw new IllegalArgumentException("Truncated ICC profile, length < " + size + ": " + input.length);
}
if (input[36] != 'a' || input[37] != 'c' || input[38] != 's' || input[39] != 'p') {
throw new IllegalArgumentException("Not an ICC profile, missing file signature");
}
// Look up in cache before returning, these are already validated
byte[] profileHeader = getProfileHeaderWithProfileId(input);
int csType = getCsType(profileHeader);
ICC_ColorSpace internal = getInternalCS(csType, profileHeader);
if (internal != null) {
return internal.getProfile();
}
ICC_ColorSpace cached = getCachedCS(profileHeader);
if (cached != null) {
return cached.getProfile();
}
// WEIRDNESS: Unlike the InputStream version, the byte version
// of ICC_Profile.getInstance() does not discard extra bytes at the end.
// We'll chop them off here for convenience
byte[] profileBytes = input.length == size ? input : Arrays.copyOf(input, size);
ICC_Profile profile = ICC_Profile.getInstance(profileBytes);
// We'll validate & cache by creating a color space and returning its profile...
// TODO: Rewrite with separate cache for profiles...
return createColorSpace(profile).getProfile();
}
private static int intBigEndian(byte[] data, int index) {
return (data[index] & 0xff) << 24 | (data[index + 1] & 0xff) << 16 | (data[index + 2] & 0xff) << 8 | (data[index + 3] & 0xff);
}
private static int getCsType(byte[] profileHeader) {
int csSig = intBigEndian(profileHeader, ICC_Profile.icHdrColorSpace);
// TODO: Wonder why they didn't just use the sig as type, when there is obviously a 1:1 mapping...
switch (csSig) {
case ICC_Profile.icSigXYZData:
return ColorSpace.TYPE_XYZ;
case ICC_Profile.icSigLabData:
return ColorSpace.TYPE_Lab;
case ICC_Profile.icSigLuvData:
return ColorSpace.TYPE_Luv;
case ICC_Profile.icSigYCbCrData:
return ColorSpace.TYPE_YCbCr;
case ICC_Profile.icSigYxyData:
return ColorSpace.TYPE_Yxy;
case ICC_Profile.icSigRgbData:
return ColorSpace.TYPE_RGB;
case ICC_Profile.icSigGrayData:
return ColorSpace.TYPE_GRAY;
case ICC_Profile.icSigHsvData:
return ColorSpace.TYPE_HSV;
case ICC_Profile.icSigHlsData:
return ColorSpace.TYPE_HLS;
case ICC_Profile.icSigCmykData:
return ColorSpace.TYPE_CMYK;
// Note: There is no TYPE_* 10...
case ICC_Profile.icSigCmyData:
return ColorSpace.TYPE_CMY;
case ICC_Profile.icSigSpace2CLR:
return ColorSpace.TYPE_2CLR;
case ICC_Profile.icSigSpace3CLR:
return ColorSpace.TYPE_3CLR;
case ICC_Profile.icSigSpace4CLR:
return ColorSpace.TYPE_4CLR;
case ICC_Profile.icSigSpace5CLR:
return ColorSpace.TYPE_5CLR;
case ICC_Profile.icSigSpace6CLR:
return ColorSpace.TYPE_6CLR;
case ICC_Profile.icSigSpace7CLR:
return ColorSpace.TYPE_7CLR;
case ICC_Profile.icSigSpace8CLR:
return ColorSpace.TYPE_8CLR;
case ICC_Profile.icSigSpace9CLR:
return ColorSpace.TYPE_9CLR;
case ICC_Profile.icSigSpaceACLR:
return ColorSpace.TYPE_ACLR;
case ICC_Profile.icSigSpaceBCLR:
return ColorSpace.TYPE_BCLR;
case ICC_Profile.icSigSpaceCCLR:
return ColorSpace.TYPE_CCLR;
case ICC_Profile.icSigSpaceDCLR:
return ColorSpace.TYPE_DCLR;
case ICC_Profile.icSigSpaceECLR:
return ColorSpace.TYPE_ECLR;
case ICC_Profile.icSigSpaceFCLR:
return ColorSpace.TYPE_FCLR;
default:
throw new IllegalArgumentException("Invalid ICC color space signature: " + csSig); // TODO: fourCC?
}
}
/**
@@ -297,6 +536,50 @@ public final class ColorSpaces {
}
}
@SuppressWarnings("SameParameterValue")
private static ICC_Profile readProfileFromClasspathResource(final String profilePath) {
InputStream stream = ColorSpaces.class.getResourceAsStream(profilePath);
if (stream != null) {
if (DEBUG) {
System.out.println("Loading profile from classpath resource: " + profilePath);
}
try {
return ICC_Profile.getInstance(stream);
}
catch (IOException ignore) {
if (DEBUG) {
ignore.printStackTrace();
}
}
finally {
FileUtil.close(stream);
}
}
return null;
}
private static ICC_Profile readProfileFromPath(final String profilePath) {
if (profilePath != null) {
if (DEBUG) {
System.out.println("Loading profile from: " + profilePath);
}
try {
return ICC_Profile.getInstance(profilePath);
}
catch (SecurityException | IOException ignore) {
if (DEBUG) {
ignore.printStackTrace();
}
}
}
return null;
}
private static final class Key {
private final byte[] data;
@@ -319,4 +602,78 @@ public final class ColorSpaces {
return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
}
}
// Cache header profile data to avoid excessive array creation/copying. Use static inner class for on-demand lazy init
private static class sRGB {
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_sRGB));
}
private static class CIEXYZ {
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ));
}
private static class PYCC {
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_PYCC));
}
private static class GRAY {
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
}
private static class LINEAR_RGB {
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB));
}
private static class Profiles {
// TODO: Honour java.iccprofile.path property?
private static final Properties PROFILES = loadProfiles();
private static Properties loadProfiles() {
Properties systemDefaults;
try {
systemDefaults = SystemUtil.loadProperties(
ColorSpaces.class,
"com/twelvemonkeys/imageio/color/icc_profiles_" + Platform.os().id()
);
}
catch (SecurityException | IOException ignore) {
System.err.printf(
"Warning: Could not load system default ICC profile locations from %s, will use bundled fallback profiles.\n",
ignore.getMessage()
);
if (DEBUG) {
ignore.printStackTrace();
}
systemDefaults = null;
}
// Create map with defaults and add user overrides if any
Properties profiles = new Properties(systemDefaults);
try {
Properties userOverrides = SystemUtil.loadProperties(
ColorSpaces.class,
"com/twelvemonkeys/imageio/color/icc_profiles"
);
profiles.putAll(userOverrides);
}
catch (SecurityException | IOException ignore) {
// Most likely, this file won't be there
}
if (DEBUG) {
System.out.println("User ICC profiles: " + profiles);
System.out.println("System ICC profiles : " + systemDefaults);
}
return profiles;
}
static String getPath(final String profileName) {
return PROFILES.getProperty(profileName);
}
}
}
@@ -1,33 +1,3 @@
/*
* Copyright (c) 2021, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.color;
import javax.imageio.spi.ImageInputStreamSpi;
@@ -45,7 +45,6 @@ import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -176,22 +175,15 @@ public final class IIOUtil {
* @param providerClassName name of the provider class.
* @param category provider category
*
* @return the provider instance, or {@code null} if not found
* @return the provider instance, or {@code null}.
*/
public static <T> T lookupProviderByName(final ServiceRegistry registry, final String providerClassName, Class<T> category) {
// NOTE: While more verbose, this is more OSGi-friendly than using
// registry.getServiceProviderByClass(Class.forName(providerClassName))
Iterator<T> providers = registry.getServiceProviders(category, true);
while (providers.hasNext()) {
T provider = providers.next();
if (provider.getClass().getName().equals(providerClassName)) {
return provider;
}
try {
return category.cast(registry.getServiceProviderByClass(Class.forName(providerClassName)));
}
catch (ClassNotFoundException ignore) {
return null;
}
return null;
}
/**
@@ -1,4 +1,2 @@
com.twelvemonkeys.imageio.stream.BufferedFileImageInputStreamSpi
com.twelvemonkeys.imageio.stream.BufferedRAFImageInputStreamSpi
# Use SPI loading as a hook for early profile activation
com.twelvemonkeys.imageio.color.ProfileDeferralActivator$Spi
@@ -1,247 +0,0 @@
package com.twelvemonkeys.imageio.color;
import org.junit.Test;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;
import static org.junit.Assert.*;
public class ColorProfilesTest {
@Test
public void testCreateColorSpaceFromBrokenProfileIsFixedCS_sRGB() {
ICC_Profile internal = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
ICC_Profile profile = createBrokenProfile(internal);
assertNotSame(internal, profile); // Sanity check
assertTrue(ColorProfiles.isOffendingColorProfile(profile));
ICC_ColorSpace created = ColorSpaces.createColorSpace(profile);
assertSame(ColorSpace.getInstance(ColorSpace.CS_sRGB), created);
assertTrue(created.isCS_sRGB());
}
private ICC_Profile createBrokenProfile(ICC_Profile internal) {
byte[] data = internal.getData();
data[ICC_Profile.icHdrRenderingIntent] = 1; // Intent: 1 == Relative Colormetric Little Endian
data[ICC_Profile.icHdrRenderingIntent + 1] = 0;
data[ICC_Profile.icHdrRenderingIntent + 2] = 0;
data[ICC_Profile.icHdrRenderingIntent + 3] = 0;
return ICC_Profile.getInstance(data);
}
@Test
public void testIsOffendingColorProfile() {
ICC_Profile broken = createBrokenProfile(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
assertTrue(ColorProfiles.isOffendingColorProfile(broken));
}
@Test
public void testIsCS_sRGBTrue() {
assertTrue(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
}
@Test
public void testIsCS_sRGBFalse() {
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ)));
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_GRAY)));
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
}
@Test(expected = IllegalArgumentException.class)
public void testIsCS_sRGBNull() {
ColorProfiles.isCS_sRGB(null);
}
@Test
public void testIsCS_GRAYTrue() {
assertTrue(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_GRAY)));
}
@Test
public void testIsCS_GRAYFalse() {
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ)));
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
}
@Test(expected = IllegalArgumentException.class)
public void testIsCS_GRAYNull() {
ColorProfiles.isCS_GRAY(null);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileNull() {
ColorProfiles.createProfile(null);
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileNull() throws IOException {
ColorProfiles.readProfile(null);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileRawNull() {
ColorProfiles.createProfileRaw(null);
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileRawNull() throws IOException {
ColorProfiles.readProfileRaw(null);
}
@Test
public void testCreateProfileRaw() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ICC_Profile profileRaw = ColorProfiles.createProfileRaw(data);
assertArrayEquals(data, profileRaw.getData());
}
@Test
public void testReadProfileRaw() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ICC_Profile profileRaw = ColorProfiles.readProfileRaw(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
assertArrayEquals(data, profileRaw.getData());
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileRawBadData() {
ColorProfiles.createProfileRaw(new byte[5]);
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileRawBadData() throws IOException {
// NOTE: The array here is larger, as there's a bug in OpenJDK 15 & 16, that throws
// ArrayIndexOutOfBoundsException if the stream is shorter than the profile signature...
ColorProfiles.readProfileRaw(new ByteArrayInputStream(new byte[40]));
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileBadData() {
ColorProfiles.createProfile(new byte[5]);
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileBadData() throws IOException {
ColorProfiles.readProfile(new ByteArrayInputStream(new byte[5]));
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileRawTruncated() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.createProfileRaw(Arrays.copyOf(data, 200));
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileRawTruncated() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.readProfileRaw(new ByteArrayInputStream(data, 0, 200));
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileTruncated() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.createProfile(Arrays.copyOf(data, 200));
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileTruncated() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.readProfile(new ByteArrayInputStream(data, 0, 200));
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileRawTruncatedHeader() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.createProfileRaw(Arrays.copyOf(data, 125));
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileRawTruncatedHeader() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.readProfileRaw(new ByteArrayInputStream(data, 0, 125));
}
@Test(expected = IllegalArgumentException.class)
public void testCreateProfileTruncatedHeader() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.createProfile(Arrays.copyOf(data, 125));
}
@Test(expected = IllegalArgumentException.class)
public void testReadProfileTruncatedHeader() throws IOException {
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
ColorProfiles.readProfile(new ByteArrayInputStream(data, 0, 125));
}
@Test
public void testCreateProfileBytesSame() throws IOException {
ICC_Profile profile = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
ICC_Profile profile1 = ColorProfiles.createProfile(profile.getData());
ICC_Profile profile2 = ColorProfiles.createProfile(profile.getData());
assertEquals(profile1, profile2);
assertSame(profile1, profile2);
}
@Test
public void testReadProfileInputStreamSame() throws IOException {
ICC_Profile profile1 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
ICC_Profile profile2 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
assertEquals(profile1, profile2);
assertSame(profile1, profile2);
}
@Test
public void testReadProfileDifferent() throws IOException {
// These profiles are extracted from various JPEGs, and have the exact same profile header (but are different profiles)...
ICC_Profile profile1 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
ICC_Profile profile2 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/color_match_rgb.icc"));
assertNotSame(profile1, profile2);
}
@Test
public void testCreateProfileBytesSameAsCached() throws IOException {
ICC_Profile profile = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
ICC_ColorSpace cs1 = ColorSpaces.createColorSpace(profile);
ICC_Profile profile2 = ColorProfiles.createProfile(profile.getData());
assertEquals(cs1.getProfile(), profile2);
assertSame(cs1.getProfile(), profile2);
}
@Test
public void testReadProfileInputStreamSameAsCached() throws IOException {
ICC_ColorSpace cs1 = ColorSpaces.createColorSpace(ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")));
ICC_Profile profile2 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
assertEquals(cs1.getProfile(), profile2);
assertSame(cs1.getProfile(), profile2);
}
@Test
public void testCreateProfileBytesSameAsInternal() {
ICC_Profile profile1 = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
ICC_Profile profile2 = ColorProfiles.createProfile(ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData());
assertEquals(profile1, profile2);
assertSame(profile1, profile2);
}
@Test
public void testReadProfileInputStreamSameAsInternal() throws IOException {
ICC_Profile profile1 = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
ICC_Profile profile2 = ColorProfiles.readProfile(new ByteArrayInputStream(ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData()));
assertEquals(profile1, profile2);
assertSame(profile1, profile2);
}
}
@@ -35,6 +35,7 @@ import org.junit.Test;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import static org.junit.Assert.*;
@@ -90,6 +91,34 @@ public class ColorSpacesTest {
assertTrue(created.isCS_sRGB());
}
@Test
public void testCreateColorSpaceFromBrokenProfileIsFixedCS_sRGB() {
ICC_Profile internal = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
ICC_Profile profile = createBrokenProfile(internal);
assertNotSame(internal, profile); // Sanity check
assertTrue(ColorSpaces.isOffendingColorProfile(profile));
ICC_ColorSpace created = ColorSpaces.createColorSpace(profile);
assertSame(ColorSpace.getInstance(ColorSpace.CS_sRGB), created);
assertTrue(created.isCS_sRGB());
}
private ICC_Profile createBrokenProfile(ICC_Profile internal) {
byte[] data = internal.getData();
data[ICC_Profile.icHdrRenderingIntent] = 1; // Intent: 1 == Relative Colormetric Little Endian
data[ICC_Profile.icHdrRenderingIntent + 1] = 0;
data[ICC_Profile.icHdrRenderingIntent + 2] = 0;
data[ICC_Profile.icHdrRenderingIntent + 3] = 0;
return ICC_Profile.getInstance(data);
}
@Test
public void testIsOffendingColorProfile() {
ICC_Profile broken = createBrokenProfile(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
assertTrue(ColorSpaces.isOffendingColorProfile(broken));
}
@Test
public void testCreateColorSpaceFromKnownProfileReturnsInternalCS_GRAY() {
ICC_Profile profile = ICC_Profile.getInstance(ColorSpace.CS_GRAY);
@@ -138,13 +167,11 @@ public class ColorSpacesTest {
assertEquals(ColorSpace.TYPE_CMYK, ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK).getType());
}
@SuppressWarnings("deprecation")
@Test
public void testIsCS_sRGBTrue() {
assertTrue(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
}
@SuppressWarnings("deprecation")
@Test
public void testIsCS_sRGBFalse() {
assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
@@ -153,19 +180,16 @@ public class ColorSpacesTest {
assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
}
@SuppressWarnings("deprecation")
@Test(expected = IllegalArgumentException.class)
public void testIsCS_sRGBNull() {
ColorSpaces.isCS_sRGB(null);
}
@SuppressWarnings("deprecation")
@Test
public void testIsCS_GRAYTrue() {
assertTrue(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_GRAY)));
}
@SuppressWarnings("deprecation")
@Test
public void testIsCS_GRAYFalse() {
assertFalse(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
@@ -174,7 +198,6 @@ public class ColorSpacesTest {
assertFalse(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
}
@SuppressWarnings("deprecation")
@Test(expected = IllegalArgumentException.class)
public void testIsCS_GRAYNull() {
ColorSpaces.isCS_GRAY(null);
@@ -193,4 +216,69 @@ public class ColorSpacesTest {
assertNotSame(cs1, cs2);
}
@Test
public void testReadProfileBytesSame() throws IOException {
ICC_Profile profile = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
ICC_Profile profile1 = ColorSpaces.createProfile(profile.getData());
ICC_Profile profile2 = ColorSpaces.createProfile(profile.getData());
assertEquals(profile1, profile2);
assertSame(profile1, profile2);
}
@Test
public void testReadProfileInputStreamSame() throws IOException {
ICC_Profile profile1 = ColorSpaces.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
ICC_Profile profile2 = ColorSpaces.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
assertEquals(profile1, profile2);
assertSame(profile1, profile2);
}
@Test
public void testReadProfileDifferent() throws IOException {
// These profiles are extracted from various JPEGs, and have the exact same profile header (but are different profiles)...
ICC_Profile profile1 = ColorSpaces.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
ICC_Profile profile2 = ColorSpaces.readProfile(getClass().getResourceAsStream("/profiles/color_match_rgb.icc"));
assertNotSame(profile1, profile2);
}
@Test
public void testReadProfileBytesSameAsCached() throws IOException {
ICC_Profile profile = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
ICC_ColorSpace cs1 = ColorSpaces.createColorSpace(profile);
ICC_Profile profile2 = ColorSpaces.createProfile(profile.getData());
assertEquals(cs1.getProfile(), profile2);
assertSame(cs1.getProfile(), profile2);
}
@Test
public void testReadProfileInputStreamSameAsCached() throws IOException {
ICC_ColorSpace cs1 = ColorSpaces.createColorSpace(ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")));
ICC_Profile profile2 = ColorSpaces.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
assertEquals(cs1.getProfile(), profile2);
assertSame(cs1.getProfile(), profile2);
}
@Test
public void testReadProfileBytesSameAsInternal() {
ICC_Profile profile1 = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
ICC_Profile profile2 = ColorSpaces.createProfile(ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData());
assertEquals(profile1, profile2);
assertSame(profile1, profile2);
}
@Test
public void testReadProfileInputStreamSameAsInternal() throws IOException {
ICC_Profile profile1 = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
ICC_Profile profile2 = ColorSpaces.readProfile(new ByteArrayInputStream(ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData()));
assertEquals(profile1, profile2);
assertSame(profile1, profile2);
}
}
@@ -1,28 +0,0 @@
package com.twelvemonkeys.imageio.color;
import org.junit.Test;
import javax.imageio.spi.ImageInputStreamSpi;
import javax.imageio.spi.ServiceRegistry;
import static org.mockito.Mockito.*;
public class ProfileDeferralActivatorTest {
@Test
public void testActivateProfiles() {
// Should just run with no exceptions...
ProfileDeferralActivator.activateProfiles();
}
@Test
public void testSpiRegistration() {
ProfileDeferralActivator.Spi spi = new ProfileDeferralActivator.Spi();
ServiceRegistry registry = mock(ServiceRegistry.class);
Class<ImageInputStreamSpi> category = ImageInputStreamSpi.class;
spi.onRegistration(registry, category);
verify(registry, only()).deregisterServiceProvider(spi, category);
}
}
@@ -78,7 +78,7 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
protected abstract ImageWriterSpi createProvider();
protected final T createWriter() throws IOException {
return writerClass.cast(provider.createWriterInstance());
return writerClass.cast(provider.createWriterInstance(null));
}
protected abstract List<? extends RenderedImage> getTestData();
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-hdr</artifactId>
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-icns</artifactId>
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-iff</artifactId>
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-jpeg-jai-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG/JAI TIFF Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-jpeg-jep262-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG/JEP-262 Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-jpeg</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
@@ -31,7 +31,6 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.color.ColorProfiles;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.color.YCbCrConverter;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
@@ -336,7 +335,7 @@ public final class JPEGImageReader extends ImageReaderBase {
// We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is)
// - or only filter out the bad ICC profiles in the JPEGSegmentImageInputStream.
else if (bogusAdobeDCT
|| profile != null && !ColorProfiles.isCS_sRGB(profile)
|| profile != null && !ColorSpaces.isCS_sRGB(profile)
|| (long) sof.lines * sof.samplesPerLine > Integer.MAX_VALUE
|| delegateCSTypeMismatch(jfif, adobeDCT, sof, sourceCSType)) {
if (DEBUG) {
@@ -631,7 +630,7 @@ public final class JPEGImageReader extends ImageReaderBase {
intToBigEndian(ICC_Profile.icSigDisplayClass, profileData, ICC_Profile.icHdrDeviceClass); // Header is first
return ColorProfiles.createProfile(profileData);
return ColorSpaces.createProfile(profileData);
}
}
@@ -951,6 +950,10 @@ public final class JPEGImageReader extends ImageReaderBase {
return null;
}
int segmentDataStart = segment.identifier.length() + 3; // ICC_PROFILE + null + chunk number + count
int iccChunkDataSize = segment.data.length - segmentDataStart;
int iccSize = segment.data.length < segmentDataStart + 4 ? 0 : intFromBigEndian(segment.data, segmentDataStart);
return readICCProfileSafe(stream, allowBadIndexes);
}
else if (!segments.isEmpty()) {
@@ -986,6 +989,9 @@ public final class JPEGImageReader extends ImageReaderBase {
InputStream[] streams = new InputStream[count];
streams[badICC ? 0 : chunkNumber - 1] = stream;
int iccChunkDataSize = 0;
int iccSize = 0;
for (int i = 1; i < count; i++) {
Application segment = segments.get(i);
stream = new DataInputStream(segment.data());
@@ -998,6 +1004,12 @@ public final class JPEGImageReader extends ImageReaderBase {
int index = badICC ? i : chunkNumber - 1;
streams[index] = stream;
int segmentDataStart = segment.identifier.length() + 3; // ICC_PROFILE + null + chunk number + count
iccChunkDataSize += segment.data.length - segmentDataStart;
if (index == 0) {
iccSize = intFromBigEndian(segment.data, segmentDataStart);
}
}
return readICCProfileSafe(new SequenceInputStream(Collections.enumeration(Arrays.asList(streams))), allowBadIndexes);
@@ -1008,8 +1020,10 @@ public final class JPEGImageReader extends ImageReaderBase {
private ICC_Profile readICCProfileSafe(final InputStream stream, final boolean allowBadProfile) {
try {
ICC_Profile profile = ColorSpaces.readProfileRaw(stream);
// NOTE: Need to ensure we have a display profile *before* validating, for the caching to work
return allowBadProfile ? ColorProfiles.readProfileRaw(stream) : ensureDisplayProfile(ColorProfiles.readProfile(stream));
return allowBadProfile ? profile : ColorSpaces.validateProfile(ensureDisplayProfile(profile));
}
catch (IOException | RuntimeException e) {
// NOTE: Throws either IllegalArgumentException or CMMException, depending on platform.
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>imageio-metadata</artifactId>
@@ -30,7 +30,7 @@
package com.twelvemonkeys.imageio.metadata.jpeg;
import com.twelvemonkeys.imageio.color.ColorProfiles;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.psd.PSD;
@@ -353,7 +353,7 @@ public final class JPEGSegmentUtil {
Directory psd = new PSDReader().read(stream);
Entry iccEntry = psd.getEntryById(PSD.RES_ICC_PROFILE);
if (iccEntry != null) {
ICC_Profile profile = ColorProfiles.createProfile((byte[]) iccEntry.getValue());
ICC_Profile profile = ColorSpaces.createProfileRaw((byte[]) iccEntry.getValue());
System.err.println("ICC Profile: " + profile);
}
System.err.println("PSD: " + psd);
@@ -30,22 +30,27 @@
package com.twelvemonkeys.imageio.metadata.tiff;
import static com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry.getType;
import static com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry.getValueLength;
import java.io.IOException;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageOutputStream;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.MetadataWriter;
import com.twelvemonkeys.lang.Validate;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageOutputStream;
import java.io.IOException;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import static com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry.getType;
import static com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry.getValueLength;
/**
* TIFFWriter
*
@@ -57,24 +62,7 @@ public final class TIFFWriter extends MetadataWriter {
private static final int WORD_LENGTH = 2;
private static final int LONGWORD_LENGTH = 4;
// TODO: We probably want to gloss over client code writing IFDs in BigTIFF (or vice versa) somehow... Silently convert IFD -> IFD8
private final boolean longOffsets;
private final int offsetSize;
private final long entryLength;
private final int directoryCountLength;
public TIFFWriter() {
this(LONGWORD_LENGTH);
}
public TIFFWriter(int offsetSize) {
this.offsetSize = Validate.isTrue(offsetSize == 4 || offsetSize == 8, offsetSize, "offsetSize must be 4 for TIFF or 8 for BigTIFF");
longOffsets = offsetSize == 8;
directoryCountLength = longOffsets ? 8 : WORD_LENGTH;
entryLength = 2 * WORD_LENGTH + 2 * offsetSize;
}
private static final long ENTRY_LENGTH = 12;
public boolean write(final Collection<? extends Entry> entries, final ImageOutputStream stream) throws IOException {
return write(new IFD(entries), stream);
@@ -103,7 +91,7 @@ public final class TIFFWriter extends MetadataWriter {
}
// Offset to next IFD (EOF)
writeOffset(stream, 0);
stream.writeInt(0);
return true;
}
@@ -112,12 +100,7 @@ public final class TIFFWriter extends MetadataWriter {
// Header
ByteOrder byteOrder = stream.getByteOrder();
stream.writeShort(byteOrder == ByteOrder.BIG_ENDIAN ? TIFF.BYTE_ORDER_MARK_BIG_ENDIAN : TIFF.BYTE_ORDER_MARK_LITTLE_ENDIAN);
stream.writeShort(longOffsets ? TIFF.BIGTIFF_MAGIC : TIFF.TIFF_MAGIC);
if (longOffsets) {
stream.writeShort(offsetSize); // Always 8 in this case
stream.writeShort(0);
}
stream.writeShort(42);
}
public long writeIFD(final Collection<Entry> entries, final ImageOutputStream stream) throws IOException {
@@ -139,42 +122,37 @@ public final class TIFFWriter extends MetadataWriter {
long dataSize = computeDataSize(ordered);
// Offset to this IFD
final long ifdOffset = stream.getStreamPosition() + dataSize + offsetSize;
final long ifdOffset = stream.getStreamPosition() + dataSize + LONGWORD_LENGTH;
if (!isSubIFD) {
writeOffset(stream, ifdOffset);
dataOffset += offsetSize;
stream.writeInt(assertIntegerOffset(ifdOffset));
dataOffset += LONGWORD_LENGTH;
// Seek to offset
stream.seek(ifdOffset);
}
else {
dataOffset += directoryCountLength + ordered.size() * entryLength;
dataOffset += WORD_LENGTH + ordered.size() * ENTRY_LENGTH;
}
// Write directory
writeDirectoryCount(stream, ordered.size());
stream.writeShort(ordered.size());
for (Entry entry : ordered) {
// Write tag id, type & value count
// Write tag id
stream.writeShort((Integer) entry.getIdentifier());
// Write tag type
stream.writeShort(getType(entry));
writeValueCount(stream, getCount(entry));
// Write value count
stream.writeInt(getCount(entry));
// Write value
Object value = entry.getValue();
if (value instanceof Directory) {
if (value instanceof CompoundDirectory) {
// Can't have both nested and linked IFDs
throw new AssertionError("SubIFD cannot contain linked IFDs");
}
// We can't write offset here, we need to write value, as both LONG/IFD and LONG8/IFD8 is allowed
// TODO: Or possibly gloss over, by always writing IFD8 for BigTIFF?
long streamPosition = stream.getStreamPosition() + offsetSize;
writeValueInline(dataOffset, getType(entry), stream);
if (entry.getValue() instanceof Directory) {
// TODO: This could possibly be a compound directory, in which case the count should be > 1
stream.writeInt(assertIntegerOffset(dataOffset));
long streamPosition = stream.getStreamPosition();
stream.seek(dataOffset);
Directory subIFD = (Directory) value;
Directory subIFD = (Directory) entry.getValue();
writeIFD(subIFD, stream, true);
dataOffset += computeDataSize(subIFD);
stream.seek(streamPosition);
@@ -187,26 +165,8 @@ public final class TIFFWriter extends MetadataWriter {
return ifdOffset;
}
private void writeDirectoryCount(ImageOutputStream stream, int count) throws IOException {
if (longOffsets) {
stream.writeLong(count);
}
else {
stream.writeShort(count);
}
}
private void writeValueCount(ImageOutputStream stream, int count) throws IOException {
if (longOffsets) {
stream.writeLong(count);
}
else {
stream.writeInt(count);
}
}
public long computeIFDSize(final Collection<? extends Entry> directory) {
return directoryCountLength + computeDataSize(new IFD(directory)) + directory.size() * entryLength;
public long computeIFDSize(final Collection<Entry> directory) {
return WORD_LENGTH + computeDataSize(new IFD(directory)) + directory.size() * ENTRY_LENGTH;
}
private long computeDataSize(final Directory directory) {
@@ -219,13 +179,13 @@ public final class TIFFWriter extends MetadataWriter {
throw new IllegalArgumentException(String.format("Unknown size for entry %s", entry));
}
if (length > offsetSize) {
if (length > LONGWORD_LENGTH) {
dataSize += length;
}
if (entry.getValue() instanceof Directory) {
Directory subIFD = (Directory) entry.getValue();
long subIFDSize = directoryCountLength + computeDataSize(subIFD) + subIFD.size() * entryLength;
long subIFDSize = WORD_LENGTH + subIFD.size() * ENTRY_LENGTH + computeDataSize(subIFD);
dataSize += subIFDSize;
}
}
@@ -273,11 +233,11 @@ public final class TIFFWriter extends MetadataWriter {
short type = getType(entry);
long valueLength = getValueLength(type, getCount(entry));
if (valueLength <= offsetSize) {
if (valueLength <= LONGWORD_LENGTH) {
writeValueInline(entry.getValue(), type, stream);
// Pad
for (long i = valueLength; i < offsetSize; i++) {
for (long i = valueLength; i < LONGWORD_LENGTH; i++) {
stream.write(0);
}
@@ -388,28 +348,12 @@ public final class TIFFWriter extends MetadataWriter {
doubles = (double[]) value;
}
else {
throw new IllegalArgumentException("Unsupported type for TIFF DOUBLE: " + value.getClass());
throw new IllegalArgumentException("Unsupported type for TIFF FLOAT: " + value.getClass());
}
stream.writeDoubles(doubles, 0, doubles.length);
break;
case TIFF.TYPE_LONG8:
case TIFF.TYPE_SLONG8:
if (longOffsets) {
long[] longs;
if (value instanceof long[]) {
longs = (long[]) value;
}
else {
throw new IllegalArgumentException("Unsupported type for TIFF LONG8: " + value.getClass());
}
stream.writeLongs(longs, 0, longs.length);
break;
}
default:
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
@@ -433,7 +377,6 @@ public final class TIFFWriter extends MetadataWriter {
break;
case TIFF.TYPE_LONG:
case TIFF.TYPE_SLONG:
case TIFF.TYPE_IFD:
stream.writeInt(((Number) value).intValue());
break;
case TIFF.TYPE_RATIONAL:
@@ -448,13 +391,6 @@ public final class TIFFWriter extends MetadataWriter {
case TIFF.TYPE_DOUBLE:
stream.writeDouble(((Number) value).doubleValue());
break;
case TIFF.TYPE_LONG8:
case TIFF.TYPE_SLONG8:
case TIFF.TYPE_IFD8:
if (longOffsets) {
stream.writeLong(((Number) value).longValue());
break;
}
default:
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
@@ -463,39 +399,18 @@ public final class TIFFWriter extends MetadataWriter {
}
private void writeValueAt(final long dataOffset, final Object value, final short type, final ImageOutputStream stream) throws IOException {
writeOffset(stream, dataOffset);
stream.writeInt(assertIntegerOffset(dataOffset));
long position = stream.getStreamPosition();
stream.seek(dataOffset);
writeValueInline(value, type, stream);
stream.seek(position);
}
public void writeOffset(final ImageOutputStream output, long offset) throws IOException {
if (longOffsets) {
output.writeLong(assertLongOffset(offset));
}
else {
output.writeInt(assertIntegerOffset(offset)); // Treated as unsigned
}
}
public int offsetSize() {
return offsetSize;
}
private int assertIntegerOffset(final long offset) throws IIOException {
if (offset < 0 || offset > Integer.MAX_VALUE - (long) Integer.MIN_VALUE) {
private int assertIntegerOffset(long offset) throws IIOException {
if (offset > Integer.MAX_VALUE - (long) Integer.MIN_VALUE) {
throw new IIOException("Integer overflow for TIFF stream");
}
return (int) offset;
}
private long assertLongOffset(final long offset) throws IIOException {
if (offset < 0) {
throw new IIOException("Long overflow for BigTIFF stream");
}
return offset;
}
}
@@ -1,345 +0,0 @@
/*
* Copyright (c) 2013, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.metadata.tiff;
import com.twelvemonkeys.imageio.metadata.*;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.io.FastByteArrayOutputStream;
import org.junit.Test;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageOutputStream;
import javax.imageio.stream.ImageOutputStreamImpl;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* TIFFWriterTest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: TIFFWriterTest.java,v 1.0 18.07.13 09:53 haraldk Exp$
*/
public class BigTIFFWriterTest extends MetadataWriterAbstractTest {
@Override
protected InputStream getData() throws IOException {
// TODO: Replace with BigTIFF resource
return getResource("/exif/exif-jpeg-segment.bin").openStream();
}
protected TIFFReader createReader() {
return new TIFFReader();
}
@Override
protected TIFFWriter createWriter() {
return new TIFFWriter(8);
}
@Test
public void testWriteReadSimple() throws IOException {
ArrayList<Entry> entries = new ArrayList<>();
entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, TIFF.TYPE_SHORT, 1));
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_SHORT, 1600));
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
entries.add(new TIFFEntry(TIFF.TAG_ARTIST, TIFF.TYPE_ASCII, "Harald K."));
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
Directory directory = new AbstractDirectory(entries) {};
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
createWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
byte[] data = output.toByteArray();
assertEquals(164, data.length);
assertEquals('M', data[0]);
assertEquals('M', data[1]);
assertEquals(0, data[2]);
assertEquals(43, data[3]);
Directory read = createReader().read(new ByteArrayImageInputStream(data));
assertNotNull(read);
assertEquals(5, read.size());
// TODO: Assert that the tags are written in ascending order (don't test the read directory, but the file structure)!
assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE));
assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_WIDTH));
assertEquals(1600, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue());
assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_HEIGHT));
assertEquals(900, read.getEntryById(TIFF.TAG_IMAGE_HEIGHT).getValue());
assertNotNull(read.getEntryById(TIFF.TAG_ORIENTATION));
assertEquals(1, read.getEntryById(TIFF.TAG_ORIENTATION).getValue());
assertNotNull(read.getEntryById(TIFF.TAG_ARTIST));
assertEquals("Harald K.", read.getEntryById(TIFF.TAG_ARTIST).getValue());
}
@Test
public void testWriteMotorola() throws IOException {
ArrayList<Entry> entries = new ArrayList<>();
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_LONG, Integer.MAX_VALUE));
Directory directory = new AbstractDirectory(entries) {};
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
imageStream.setByteOrder(ByteOrder.BIG_ENDIAN); // BE = Motorola
createWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
byte[] data = output.toByteArray();
assertEquals(94, data.length);
assertEquals('M', data[0]);
assertEquals('M', data[1]);
assertEquals(0, data[2]);
assertEquals(43, data[3]);
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(data));
assertNotNull(read);
assertEquals(2, read.size());
assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE));
assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_WIDTH));
assertEquals((long) Integer.MAX_VALUE, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue());
}
@Test
public void testWriteIntel() throws IOException {
ArrayList<Entry> entries = new ArrayList<>();
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_LONG, Integer.MAX_VALUE));
Directory directory = new AbstractDirectory(entries) {};
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
imageStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // LE = Intel
createWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
byte[] data = output.toByteArray();
assertEquals(94, data.length);
assertEquals('I', data[0]);
assertEquals('I', data[1]);
assertEquals(43, data[2]);
assertEquals(0, data[3]);
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(data));
assertNotNull(read);
assertEquals(2, read.size());
assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE));
assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_WIDTH));
assertEquals((long) Integer.MAX_VALUE, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue());
}
@Test
public void testNestingIFD8Long8() throws IOException {
TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO");
TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD8, new IFD(Collections.singletonList(artist)));
TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG8, new IFD(Collections.singletonList(subSubSubSubIFD)));
TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG8, new IFD(Collections.singletonList(subSubSubIFD)));
TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD8, new IFD(Collections.singletonList(subSubIFD)));
Directory directory = new IFD(Collections.<Entry>singletonList(subIFD));
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
createWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(output.toByteArray()));
assertNotNull(read);
assertEquals(1, read.size());
assertEquals(subIFD, read.getEntryById(TIFF.TAG_SUB_IFD)); // Recursively tests content!
}
@Test
public void testNestingIFDLong() throws IOException {
TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO");
TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD, new IFD(Collections.singletonList(artist)));
TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubSubIFD)));
TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubIFD)));
TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD, new IFD(Collections.singletonList(subSubIFD)));
Directory directory = new IFD(Collections.<Entry>singletonList(subIFD));
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
createWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(output.toByteArray()));
assertNotNull(read);
assertEquals(1, read.size());
assertEquals(subIFD, read.getEntryById(TIFF.TAG_SUB_IFD)); // Recursively tests content!
}
@Test
public void testReadWriteRead() throws IOException {
Directory original = createReader().read(getDataAsIIS());
ByteArrayOutputStream output = new FastByteArrayOutputStream(256);
ImageOutputStream imageOutput = ImageIO.createImageOutputStream(output);
try {
createWriter().write(original, imageOutput);
}
finally {
imageOutput.close();
}
Directory read = createReader().read(new ByteArrayImageInputStream(output.toByteArray()));
assertEquals(original, read);
}
@Test
public void testComputeIFDSize() throws IOException {
ArrayList<Entry> entries = new ArrayList<>();
entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, TIFF.TYPE_SHORT, 1));
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_SHORT, 1600));
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
entries.add(new TIFFEntry(TIFF.TAG_ARTIST, TIFF.TYPE_ASCII, "Harald K."));
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
TIFFWriter writer = createWriter();
ImageOutputStream stream = new NullImageOutputStream();
writer.writeIFD(entries, stream);
assertEquals(140, writer.computeIFDSize(entries));
assertEquals(148, stream.getStreamPosition());
}
@Test
public void testComputeIFDSizeNestedIFD8Long8() throws IOException {
TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO");
TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD8, new IFD(Collections.singletonList(artist)));
TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG8, new IFD(Collections.singletonList(subSubSubSubIFD)));
TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG8, new IFD(Collections.singletonList(subSubSubIFD)));
TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD8, new IFD(Collections.singletonList(subSubIFD)));
List<Entry> entries = Collections.<Entry>singletonList(subIFD);
TIFFWriter writer = createWriter();
ImageOutputStream stream = new NullImageOutputStream();
writer.writeIFD(entries, stream);
assertEquals(162, writer.computeIFDSize(entries));
assertEquals(170, stream.getStreamPosition());
}
@Test
public void testComputeIFDSizeNestedIFDLong() throws IOException {
TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO");
TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD, new IFD(Collections.singletonList(artist)));
TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubSubIFD)));
TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubIFD)));
TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD, new IFD(Collections.singletonList(subSubIFD)));
List<Entry> entries = Collections.<Entry>singletonList(subIFD);
TIFFWriter writer = createWriter();
ImageOutputStream stream = new NullImageOutputStream();
writer.writeIFD(entries, stream);
assertEquals(162, writer.computeIFDSize(entries)); // 162 = 5 * (8 + 20) + 22
assertEquals(170, stream.getStreamPosition()); // 170 = 8 + 5 * (8 + 20) + 22
}
private static class NullImageOutputStream extends ImageOutputStreamImpl {
@Override
public void write(int b) throws IOException {
streamPos++;
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
streamPos += len;
}
@Override
public int read() throws IOException {
throw new UnsupportedOperationException("Method read not implemented");
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
throw new UnsupportedOperationException("Method read not implemented");
}
}
}
@@ -33,7 +33,6 @@ package com.twelvemonkeys.imageio.metadata.tiff;
import com.twelvemonkeys.imageio.metadata.*;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.io.FastByteArrayOutputStream;
import org.junit.Test;
import javax.imageio.ImageIO;
@@ -85,7 +84,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
createWriter().write(directory, imageStream);
new TIFFWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
@@ -133,7 +132,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
imageStream.setByteOrder(ByteOrder.BIG_ENDIAN); // BE = Motorola
createWriter().write(directory, imageStream);
new TIFFWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
@@ -168,7 +167,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
imageStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // LE = Intel
createWriter().write(directory, imageStream);
new TIFFWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
@@ -205,7 +204,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
createWriter().write(directory, imageStream);
new TIFFWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
@@ -248,10 +247,9 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
TIFFWriter writer = createWriter();
ImageOutputStream stream = new NullImageOutputStream();
writer.writeIFD(entries, stream);
writer.write(new IFD(entries), stream);
assertEquals(94, writer.computeIFDSize(entries));
assertEquals(98, stream.getStreamPosition());
assertEquals(stream.getStreamPosition(), writer.computeIFDSize(entries) + 12);
}
@Test
@@ -268,10 +266,9 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
TIFFWriter writer = createWriter();
ImageOutputStream stream = new NullImageOutputStream();
writer.writeIFD(entries, stream);
writer.write(new IFD(entries), stream);
assertEquals(92, writer.computeIFDSize(entries)); // 92 = 5 * (2 + 12) + 22
assertEquals(96, stream.getStreamPosition()); // 96 = 4 + 5 * (2 + 12) + 22
assertEquals(stream.getStreamPosition(), writer.computeIFDSize(entries) + 12);
}
private static class NullImageOutputStream extends ImageOutputStreamImpl {
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-pcx</artifactId>
<name>TwelveMonkeys :: ImageIO :: PCX plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-pdf</artifactId>
<name>TwelveMonkeys :: ImageIO :: PDF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-pict</artifactId>
<name>TwelveMonkeys :: ImageIO :: PICT plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-pnm</artifactId>
<name>TwelveMonkeys :: ImageIO :: PNM plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-psd</artifactId>
<name>TwelveMonkeys :: ImageIO :: PSD plugin</name>
@@ -30,7 +30,7 @@
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.color.ColorProfiles;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.util.IIOUtil;
import javax.imageio.stream.ImageInputStream;
@@ -56,7 +56,7 @@ final class ICCProfile extends PSDImageResource {
@Override
protected void readData(final ImageInputStream pInput) throws IOException {
try (InputStream stream = IIOUtil.createStreamAdapter(pInput, size)) {
profile = ColorProfiles.readProfileRaw(stream);
profile = ColorSpaces.readProfile(stream);
}
}
@@ -1,400 +0,0 @@
/*
* Copyright (c) 2021, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.ImageWriterBase;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.RasterUtils;
import com.twelvemonkeys.io.enc.EncoderStream;
import com.twelvemonkeys.io.enc.PackBitsEncoder;
import javax.imageio.*;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageWriterSpi;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.*;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteOrder;
import java.util.Collections;
/**
* Minimal ImageWriter for Adobe Photoshop Document (PSD) format.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDImageWriter.java,v 1.0 Apr 29, 2008 4:45:52 PM haraldk Exp$
* @see <a href="http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/">Adobe Photoshop File Formats Specification</a>
* @see <a href="http://www.fileformat.info/format/psd/egff.htm">Adobe Photoshop File Format Summary</a>
*/
public final class PSDImageWriter extends ImageWriterBase {
PSDImageWriter(ImageWriterSpi provider) {
super(provider);
}
@Override
public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
// TODO: Implement
return null;
}
@Override
public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) {
// TODO: Implement
return null;
}
@Override
public ImageWriteParam getDefaultWriteParam() {
return new PSDImageWriteParam(getLocale());
}
@Override
public void write(IIOMetadata streamMetadata, IIOImage iioImage, ImageWriteParam param) throws IOException {
assertOutput();
imageOutput.setByteOrder(ByteOrder.BIG_ENDIAN);
RenderedImage image = iioImage.getRenderedImage();
SampleModel sampleModel = image.getSampleModel();
int colorComponents = image.getColorModel().getColorSpace().getNumComponents();
int channels = sampleModel.getNumBands();
int width = image.getWidth();
int height = image.getHeight();
int bits = getBitsPerSample(sampleModel);
int mode = getColorMode(image.getColorModel());
// TODO: Allow stream metadata or param to force PSD/PSB (version 1/2)?
boolean largeFormat = width > PSDHeader.PSD_MAX_SIZE || height > PSDHeader.PSD_MAX_SIZE;
new PSDHeader(channels, width, height, bits, mode, largeFormat).write(imageOutput);
writeColorModeData(image, mode);
writeImageResources(image, mode);
// Length of the layer and mask information section. (**PSB** length is 8 bytes.)
// TODO: Write an empty dummy layer here, if there's alpha? See below... Or see if Photoshop handles alpha if no layers at all...
if (largeFormat) {
imageOutput.writeLong(0);
}
else {
imageOutput.writeInt(0);
}
processImageStarted(0);
// Image Data Section (composite layer only).
// The last section of a Photoshop file contains the image pixel data.
// Image data is stored in planar order: first all the red data, then all the green data, etc.
// Each plane is stored in scan-line order, with no pad bytes,
final int compression = PSDImageWriteParam.getCompressionType(param);
imageOutput.writeShort(compression);
long byteCountPos = imageOutput.getStreamPosition();
// PSB (large format) byte counts are actually 32 bit offsets, not 16 bit as described in spec
int[] byteCounts = new int[compression == PSD.COMPRESSION_RLE ? height * channels : 0];
imageOutput.skipBytes(byteCounts.length * (largeFormat ? 4 : 2));
// TODO: Loop over tiles?
Raster tile = sampleModel.getTransferType() == DataBuffer.TYPE_INT && sampleModel instanceof SinglePixelPackedSampleModel
? RasterUtils.asByteRaster(image.getTile(0, 0))
: image.getTile(0, 0);
for (int channel = 0; channel < channels; channel++) {
// TODO: Alpha issues:
// 1. Alpha channel is written (but not read, because there are no layers, and alpha is considered present only if layer count is negative)
// - Can we write a small hidden layer, just to have -1 layers?
// 2. Alpha needs to be premultiplied against white background (to avoid inverse halo)
Raster channelRaster = tile.createChild(0, 0, width, height, 0, 0, new int[] {channel});
switch (bits) {
case 1:
// TODO: Figure out why we can't write multi-pixel packed 1 bit samples as bytes...
case 8:
write8BitChannel(channel, colorComponents, mode, compression, channelRaster, byteCounts);
break;
case 16:
write16BitChannel(channel, colorComponents, mode, compression, channelRaster, byteCounts);
break;
case 32:
write32BitChannel(channel, colorComponents, mode, compression, channelRaster, byteCounts);
break;
default:
throw new AssertionError(); // Should be guarded against already
}
processImageProgress(channel * 100f / channels);
}
updateByteCounts(byteCountPos, byteCounts, largeFormat);
processImageComplete();
}
private void updateByteCounts(long byteCountPos, int[] byteCounts, boolean largeFormat) throws IOException {
if (byteCounts.length == 0) {
return;
}
// Update byte counts for RLE
long pos = imageOutput.getStreamPosition();
imageOutput.seek(byteCountPos);
if (largeFormat) {
imageOutput.writeInts(byteCounts, 0, byteCounts.length);
}
else {
for (int byteCount : byteCounts) {
imageOutput.writeShort(byteCount);
}
}
imageOutput.seek(pos);
}
private void writeColorModeData(RenderedImage image, int mode) throws IOException {
if (mode == PSD.COLOR_MODE_INDEXED) {
IndexColorModel icm = (IndexColorModel) image.getColorModel();
// Indexed color images: length is 768; color data contains the color table for the image, in non-interleaved order.
imageOutput.writeInt(768);
byte[] colors = new byte[256];
icm.getReds(colors);
imageOutput.write(colors);
icm.getGreens(colors);
imageOutput.write(colors);
icm.getBlues(colors);
imageOutput.write(colors);
}
else {
imageOutput.writeInt(0);
}
}
private void writeImageResources(RenderedImage image, int mode) throws IOException {
// Length of image resource section. The length may be zero
imageOutput.writeInt(0);
long startImageResources = imageOutput.getStreamPosition();
// Write ICC color profile if not "native" sRGB or gray (or bitmap/indexed)
if (mode != PSD.COLOR_MODE_BITMAP && mode != PSD.COLOR_MODE_INDEXED) {
ColorSpace colorSpace = image.getColorModel().getColorSpace();
if (!colorSpace.isCS_sRGB() && colorSpace instanceof ICC_ColorSpace) {
ICC_Profile profile = ((ICC_ColorSpace) colorSpace).getProfile();
ICCProfile.writeData(imageOutput, profile);
}
}
// Write creator software (Exif)
Entry software = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO PSD writer " + originatingProvider.getVersion());
PSDEXIF1Data.writeData(imageOutput, Collections.singleton(software));
long endImageResources = imageOutput.getStreamPosition();
// Update image resources length
imageOutput.seek(startImageResources - 4);
imageOutput.writeInt((int) (endImageResources - startImageResources));
imageOutput.seek(endImageResources);
}
private void write8BitChannel(int channel, int colorComponents, int colorMode, int compression, Raster raster, int[] byteCounts) throws IOException {
int width = raster.getWidth();
int height = raster.getHeight();
byte[] rowBytes = null;
for (int y = 0; y < height; y++) {
rowBytes = (byte[]) raster.getDataElements(0, y, width, 1, rowBytes);
// Photoshop likes to store CMYK values inverted (but not the alpha value)
if (colorMode == PSD.COLOR_MODE_CMYK && channel < colorComponents) {
for (int i = 0; i < rowBytes.length; i++) {
rowBytes[i] = (byte) (0xff - rowBytes[i] & 0xff);
}
}
if (compression == PSD.COMPRESSION_NONE) {
imageOutput.write(rowBytes);
}
else if (compression == PSD.COMPRESSION_RLE) {
long startPos = imageOutput.getStreamPosition();
// The RLE compressed data follows, with each scan line compressed separately
try (OutputStream stream = new EncoderStream(IIOUtil.createStreamAdapter(imageOutput), new PackBitsEncoder())) {
stream.write(rowBytes);
}
long endPos = imageOutput.getStreamPosition();
byteCounts[y + channel * height] = (int) (endPos - startPos);
}
else {
throw new IIOException("PSD with ZIP compression not supported");
}
}
}
private void write16BitChannel(int channel, int colorComponents, int colorMode, int compression, Raster raster, int[] byteCounts) throws IOException {
int width = raster.getWidth();
int height = raster.getHeight();
short[] row = null;
for (int y = 0; y < height; y++) {
row = (short[]) raster.getDataElements(0, y, width, 1, row);
// Photoshop likes to store CMYK values inverted (but not the alpha value)
if (colorMode == PSD.COLOR_MODE_CMYK && channel < colorComponents) {
for (int i = 0; i < row.length; i++) {
row[i] = (short) (0xffff - row[i] & 0xffff);
}
}
if (compression == PSD.COMPRESSION_NONE) {
imageOutput.writeShorts(row, 0, row.length);
}
else if (compression == PSD.COMPRESSION_RLE) {
long startPos = imageOutput.getStreamPosition();
// The RLE compressed data follows, with each scan line compressed separately
try (DataOutputStream stream = new DataOutputStream(new EncoderStream(IIOUtil.createStreamAdapter(imageOutput), new PackBitsEncoder()))) {
for (short sample : row) {
stream.writeShort(sample);
}
}
long endPos = imageOutput.getStreamPosition();
byteCounts[y + channel * height] = (int) (endPos - startPos);
}
else {
throw new IIOException("PSD with ZIP compression not supported");
}
}
}
private void write32BitChannel(int channel, int colorComponents, int colorMode, int compression, Raster raster, int[] byteCounts) throws IOException {
int width = raster.getWidth();
int height = raster.getHeight();
int[] row = null;
for (int y = 0; y < height; y++) {
row = (int[]) raster.getDataElements(0, y, width, 1, row);
// Photoshop likes to store CMYK values inverted (but not the alpha value)
if (colorMode == PSD.COLOR_MODE_CMYK && channel < colorComponents) {
for (int i = 0; i < row.length; i++) {
row[i] = 0xffffffff - row[i];
}
}
if (compression == PSD.COMPRESSION_NONE) {
imageOutput.writeInts(row, 0, row.length);
}
else if (compression == PSD.COMPRESSION_RLE) {
long startPos = imageOutput.getStreamPosition();
// The RLE compressed data follows, with each scan line compressed separately
try (DataOutputStream stream = new DataOutputStream(new EncoderStream(IIOUtil.createStreamAdapter(imageOutput), new PackBitsEncoder()))) {
for (int sample : row) {
stream.writeInt(sample);
}
}
long endPos = imageOutput.getStreamPosition();
byteCounts[y + channel * height] = (int) (endPos - startPos);
}
else {
throw new IIOException("PSD with ZIP compression not supported");
}
}
}
static int getColorMode(ColorModel colorModel) {
if (colorModel instanceof IndexColorModel) {
if (colorModel.getPixelSize() == 1) {
return PSD.COLOR_MODE_BITMAP;
}
else {
return PSD.COLOR_MODE_INDEXED;
}
}
int csType = colorModel.getColorSpace().getType();
switch (csType) {
case ColorSpace.TYPE_GRAY:
if (colorModel.getPixelSize() == 1) {
return PSD.COLOR_MODE_BITMAP;
}
else {
return PSD.COLOR_MODE_GRAYSCALE;
}
case ColorSpace.TYPE_RGB:
return PSD.COLOR_MODE_RGB;
case ColorSpace.TYPE_CMYK:
return PSD.COLOR_MODE_CMYK;
default:
throw new IllegalArgumentException("Unsupported color space type for PSD: " + csType);
}
}
static int getBitsPerSample(SampleModel sampleModel) {
int bits = sampleModel.getSampleSize(0);
for (int i = 1; i < sampleModel.getNumBands(); i++) {
if (bits != sampleModel.getSampleSize(i)) {
throw new IllegalArgumentException("All samples must be of equal size for PSD: " + bits);
}
}
switch (bits) {
case 1:
case 8:
case 16:
case 32:
return (short) bits;
default:
throw new IllegalArgumentException("Unsupported sample size for PSD (expected 1, 8, 16 or 32): " + bits);
}
}
public static void main(String[] args) throws IOException {
BufferedImage image = ImageIO.read(new File(args[0]));
ImageIO.write(image, "PSD", new File("test.psd"));
}
}
@@ -54,8 +54,8 @@ final class PSDProviderInfo extends ReaderWriterProviderInfo {
},
"com.twelvemonkeys.imageio.plugins.psd.PSDImageReader",
new String[] {"com.twelvemonkeys.imageio.plugins.psd.PSDImageReaderSpi"},
"com.twelvemonkeys.imageio.plugins.psd.PSDImageWriter",
new String[] {"com.twelvemonkeys.imageio.plugins.psd.PSDImageWriterSpi"},
null,
null,
false, null, null, null, null,
true, PSDMetadata.NATIVE_METADATA_FORMAT_NAME, PSDMetadata.NATIVE_METADATA_FORMAT_CLASS_NAME, null, null
);
@@ -1 +0,0 @@
com.twelvemonkeys.imageio.plugins.psd.PSDImageWriterSpi
@@ -1,37 +0,0 @@
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
import javax.imageio.spi.ImageWriterSpi;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.util.Arrays;
import java.util.List;
/**
* PSDImageWriterTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDImageWriterTest.java,v 1.0 05/05/2021 haraldk Exp$
*/
public class PSDImageWriterTest extends ImageWriterAbstractTest<PSDImageWriter> {
@Override
protected ImageWriterSpi createProvider() {
return new PSDImageWriterSpi();
}
@Override
protected List<? extends RenderedImage> getTestData() {
return Arrays.asList(
new BufferedImage(300, 200, BufferedImage.TYPE_INT_RGB),
new BufferedImage(301, 199, BufferedImage.TYPE_INT_ARGB),
new BufferedImage(299, 201, BufferedImage.TYPE_3BYTE_BGR),
new BufferedImage(160, 90, BufferedImage.TYPE_4BYTE_ABGR),
new BufferedImage(90, 160, BufferedImage.TYPE_BYTE_GRAY),
new BufferedImage(30, 20, BufferedImage.TYPE_USHORT_GRAY),
new BufferedImage(30, 20, BufferedImage.TYPE_BYTE_BINARY),
new BufferedImage(30, 20, BufferedImage.TYPE_BYTE_INDEXED)
);
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-reference</artifactId>
<name>TwelveMonkeys :: ImageIO :: JDK Reference Tests</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-sgi</artifactId>
<name>TwelveMonkeys :: ImageIO :: SGI plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-tga</artifactId>
<name>TwelveMonkeys :: ImageIO :: TGA plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-thumbsdb</artifactId>
<name>TwelveMonkeys :: ImageIO :: Thumbs.db plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-tiff-jai-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF/JAI Metadata Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-tiff-jdk-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF/JDK JPEG Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-tiff</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF plugin</name>
@@ -50,8 +50,8 @@ final class BigTIFFProviderInfo extends ReaderWriterProviderInfo {
},
"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReader",
new String[] {"com.twelvemonkeys.imageio.plugins.tiff.BigTIFFImageReaderSpi"},
"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriter",
new String[] {"com.twelvemonkeys.imageio.plugins.tiff.BigTIFFImageWriterSpi"},
null,
null,
false, TIFFStreamMetadata.SUN_NATIVE_STREAM_METADATA_FORMAT_NAME, "com.twelvemonkeys.imageio.plugins.tiff.TIFFStreamMetadataFormat", null, null,
true, TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, "com.twelvemonkeys.imageio.plugins.tiff.TIFFMedataFormat", null, null
);
@@ -34,8 +34,10 @@ import com.twelvemonkeys.lang.Validate;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.image.*;
import java.util.Objects;
import java.awt.image.ComponentColorModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import static java.awt.image.DataBuffer.getDataTypeSize;
@@ -97,67 +99,4 @@ final class ExtraSamplesColorModel extends ComponentColorModel {
private int getAlphaComponent() {
return super.getNumComponents() - 1;
}
@Override
public Object getDataElements(final int rgb, final Object pixel) {
return super.getDataElements(rgb, pixel == null ? createDataArray() : pixel);
}
private Object createDataArray() {
switch (transferType) {
case DataBuffer.TYPE_BYTE:
return new byte[numComponents];
case DataBuffer.TYPE_SHORT:
case DataBuffer.TYPE_USHORT:
return new short[numComponents];
case DataBuffer.TYPE_INT:
return new int[numComponents];
case DataBuffer.TYPE_FLOAT:
return new float[numComponents];
case DataBuffer.TYPE_DOUBLE:
return new double[numComponents];
}
throw new IllegalArgumentException("This method has not been implemented for transferType " + transferType);
}
@Override
public boolean equals(final Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
ExtraSamplesColorModel that = (ExtraSamplesColorModel) other;
if (hasAlpha() != that.hasAlpha() ||
isAlphaPremultiplied() != that.isAlphaPremultiplied() ||
getPixelSize() != that.getPixelSize() ||
getTransparency() != that.getTransparency() ||
numComponents != that.numComponents) {
return false;
}
int[] nBits = getComponentSize();
int[] nb = that.getComponentSize();
if ((nBits == null) || (nb == null)) {
return ((nBits == null) && (nb == null));
}
for (int i = 0; i < nBits.length; i++) {
if (nBits[i] != nb[i]) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), numComponents, componentSize);
}
}
@@ -33,7 +33,6 @@ package com.twelvemonkeys.imageio.plugins.tiff;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.color.CIELabColorConverter;
import com.twelvemonkeys.imageio.color.CIELabColorConverter.Illuminant;
import com.twelvemonkeys.imageio.color.ColorProfiles;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.color.YCbCrConverter;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
@@ -2494,12 +2493,12 @@ public final class TIFFImageReader extends ImageReaderBase {
return value;
}
private ICC_Profile getICCProfile() {
private ICC_Profile getICCProfile() throws IOException {
Entry entry = currentIFD.getEntryById(TIFF.TAG_ICC_PROFILE);
if (entry != null) {
try {
return ColorProfiles.createProfile((byte[]) entry.getValue());
return ColorSpaces.createProfile((byte[]) entry.getValue());
}
catch (CMMException | IllegalArgumentException e) {
processWarningOccurred("Ignoring broken/incompatible ICC profile: " + e.getMessage());
@@ -32,7 +32,7 @@ package com.twelvemonkeys.imageio.plugins.tiff;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageWriterBase;
import com.twelvemonkeys.imageio.color.ColorProfiles;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.tiff.Rational;
@@ -204,26 +204,26 @@ public final class TIFFImageWriter extends ImageWriterBase {
long streamPosition = imageOutput.getStreamPosition();
long ifdSize = tiffWriter.computeIFDSize(entries.values());
long stripOffset = streamPosition + tiffWriter.offsetSize() + ifdSize + tiffWriter.offsetSize();
long stripOffset = streamPosition + 4 + ifdSize + 4;
long stripByteCount = ((long) renderedImage.getWidth() * renderedImage.getHeight() * pixelSize + 7L) / 8L;
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, TIFF.TYPE_LONG, stripOffset));
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, TIFF.TYPE_LONG, stripByteCount));
long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes care of ordering tags
long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes case of ordering tags
nextIFDPointerOffset = imageOutput.getStreamPosition();
// If we have a previous IFD, update pointer
if (streamPosition > lastIFDPointerOffset) {
imageOutput.seek(lastIFDPointerOffset);
tiffWriter.writeOffset(imageOutput, ifdPointer);
imageOutput.writeInt((int) ifdPointer);
imageOutput.seek(nextIFDPointerOffset);
}
tiffWriter.writeOffset(imageOutput, 0); // Update next IFD pointer later
imageOutput.writeInt(0); // Update next IFD pointer later
}
else {
tiffWriter.writeOffset(imageOutput, 0); // Update current IFD pointer later
imageOutput.writeInt(0); // Update current IFD pointer later
}
long stripOffset = imageOutput.getStreamPosition();
@@ -262,7 +262,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, TIFF.TYPE_LONG, stripOffset));
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, TIFF.TYPE_LONG, stripByteCount));
long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes care of ordering tags
long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes case of ordering tags
nextIFDPointerOffset = imageOutput.getStreamPosition();
@@ -270,10 +270,10 @@ public final class TIFFImageWriter extends ImageWriterBase {
// However, need to update here, because to the writeIFD method writes the pointer, but at the incorrect offset
// TODO: Refactor writeIFD to take an offset
imageOutput.seek(lastIFDPointerOffset);
tiffWriter.writeOffset(imageOutput, ifdPointer);
imageOutput.writeInt((int) ifdPointer);
imageOutput.seek(nextIFDPointerOffset);
tiffWriter.writeOffset(imageOutput, 0); // Next IFD pointer updated later
imageOutput.writeInt(0); // Next IFD pointer updated later
}
return nextIFDPointerOffset;
@@ -855,7 +855,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
// * is not sRGB (assuming sRGB to be the default RGB interpretation), and
// * is not gray scale (assuming photometric either BlackIsZero or WhiteIsZero)
ColorSpace colorSpace = colorModel.getColorSpace();
if (colorSpace instanceof ICC_ColorSpace && !colorSpace.isCS_sRGB() && !ColorProfiles.isCS_GRAY(((ICC_ColorSpace) colorSpace).getProfile())) {
if (colorSpace instanceof ICC_ColorSpace && !colorSpace.isCS_sRGB() && !ColorSpaces.isCS_GRAY(((ICC_ColorSpace) colorSpace).getProfile())) {
entries.put(TIFF.TAG_ICC_PROFILE, new TIFFEntry(TIFF.TAG_ICC_PROFILE, ((ICC_ColorSpace) colorSpace).getProfile().getData()));
}
}
@@ -959,15 +959,11 @@ public final class TIFFImageWriter extends ImageWriterBase {
configureStreamByteOrder(streamMetadata, imageOutput);
writingSequence = true;
sequenceTIFFWriter = new TIFFWriter(isBigTIFF() ? 8 : 4);
sequenceTIFFWriter = new TIFFWriter();
sequenceTIFFWriter.writeTIFFHeader(imageOutput);
sequenceLastIFDPos = imageOutput.getStreamPosition();
}
private boolean isBigTIFF() throws IOException {
return "bigtiff".equalsIgnoreCase(getFormatName());
}
@Override
public void writeToSequence(final IIOImage image, final ImageWriteParam param) throws IOException {
if (!writingSequence) {
@@ -1023,7 +1019,8 @@ public final class TIFFImageWriter extends ImageWriterBase {
BufferedImage original;
// BufferedImage original = ImageIO.read(file);
try (ImageInputStream inputStream = ImageIO.createImageInputStream(file)) {
ImageInputStream inputStream = ImageIO.createImageInputStream(file);
try {
Iterator<ImageReader> readers = ImageIO.getImageReaders(inputStream);
if (!readers.hasNext()) {
@@ -1053,6 +1050,9 @@ public final class TIFFImageWriter extends ImageWriterBase {
original = reader.read(0, param);
}
finally {
inputStream.close();
}
System.err.println("original: " + original);
@@ -1088,11 +1088,12 @@ public final class TIFFImageWriter extends ImageWriterBase {
// output.deleteOnExit();
System.err.println("output: " + output);
TIFFImageWriter writer = new TIFFImageWriter(new TIFFImageWriterSpi());
TIFFImageWriter writer = new TIFFImageWriter(null);
// ImageWriter writer = ImageIO.getImageWritersByFormatName("PNG").next();
// ImageWriter writer = ImageIO.getImageWritersByFormatName("BMP").next();
ImageOutputStream stream = ImageIO.createImageOutputStream(output);
try (ImageOutputStream stream = ImageIO.createImageOutputStream(output)) {
try {
writer.setOutput(stream);
ImageWriteParam param = writer.getDefaultWriteParam();
@@ -1110,6 +1111,9 @@ public final class TIFFImageWriter extends ImageWriterBase {
writer.write(null, new IIOImage(image, null, null), param);
System.err.println("Write time: " + (System.currentTimeMillis() - start) + " ms");
}
finally {
stream.close();
}
System.err.println("output.length: " + output.length());
@@ -1,2 +1 @@
com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriterSpi
com.twelvemonkeys.imageio.plugins.tiff.BigTIFFImageWriterSpi
@@ -1,117 +0,0 @@
/*
* Copyright (c) 2021, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.tiff;
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
import org.junit.Test;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import static com.twelvemonkeys.imageio.util.ImageReaderAbstractTest.assertRGBEquals;
import static org.junit.Assert.assertArrayEquals;
/**
* BigTIFFImageWriterTest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BigTIFFImageWriterTest.java,v 1.0 19.09.13 13:22 haraldk Exp$
*/
public class BigTIFFImageWriterTest extends ImageWriterAbstractTest<TIFFImageWriter> {
@Override
protected ImageWriterSpi createProvider() {
return new BigTIFFImageWriterSpi();
}
@Override
protected List<? extends RenderedImage> getTestData() {
return Arrays.asList(
new BufferedImage(300, 200, BufferedImage.TYPE_INT_RGB),
new BufferedImage(301, 199, BufferedImage.TYPE_INT_ARGB),
new BufferedImage(299, 201, BufferedImage.TYPE_3BYTE_BGR),
new BufferedImage(160, 90, BufferedImage.TYPE_4BYTE_ABGR),
new BufferedImage(90, 160, BufferedImage.TYPE_BYTE_GRAY),
new BufferedImage(30, 20, BufferedImage.TYPE_USHORT_GRAY)
);
}
@Test
public void roundrtip() throws IOException {
TIFFImageWriter writer = createWriter();
ImageReader reader = ImageIO.getImageReader(writer);
try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource("/bigtiff/BigTIFF.tif"))) {
reader.setInput(input);
BufferedImage image = reader.read(0);
ByteArrayOutputStream temp = new ByteArrayOutputStream();
try (ImageOutputStream output = ImageIO.createImageOutputStream(temp)) {
writer.setOutput(output);
writer.write(image);
}
finally {
writer.dispose();
}
// Validate we actually write BigTIFF
byte[] data = temp.toByteArray();
assertArrayEquals(new byte[] { 'M', 'M', 0, TIFF.BIGTIFF_MAGIC}, Arrays.copyOf(data, 4));
// Read image back and see that it is the same
try (ImageInputStream stream = new ByteArrayImageInputStream(data)) {
reader.setInput(stream);
BufferedImage after = reader.read(0);
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
assertRGBEquals("Pixel values differ: ", image.getRGB(x, y), after.getRGB(x, y), 0);
}
}
}
}
finally {
reader.dispose();
}
}
}
@@ -32,7 +32,6 @@ package com.twelvemonkeys.imageio.plugins.tiff;
import com.twelvemonkeys.image.ResampleOp;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import org.junit.Test;
import java.awt.*;
@@ -40,23 +39,20 @@ import java.awt.color.ColorSpace;
import java.awt.image.*;
import java.util.Hashtable;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class ExtraSamplesColorModelTest {
private BufferedImage createExtraSamplesImage(int w, int h, ColorSpace cs, boolean hasAlpha, int extraComponents) {
int samplesPerPixel = cs.getNumComponents() + (hasAlpha ? 1 : 0) + extraComponents;
ExtraSamplesColorModel colorModel = createExtraSamplesColorModel(cs, hasAlpha, extraComponents);
ExtraSamplesColorModel colorModel = new ExtraSamplesColorModel(cs, hasAlpha, true, DataBuffer.TYPE_BYTE, extraComponents);
SampleModel sampleModel = new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, w, h, samplesPerPixel, samplesPerPixel * w, createOffsets(samplesPerPixel));
WritableRaster raster = Raster.createWritableRaster(sampleModel, new Point(0, 0));
return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), new Hashtable<>());
}
private ExtraSamplesColorModel createExtraSamplesColorModel(ColorSpace cs, boolean hasAlpha, int extraComponents) {
return new ExtraSamplesColorModel(cs, hasAlpha, true, DataBuffer.TYPE_BYTE, extraComponents);
return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), new Hashtable());
}
private static int[] createOffsets(int samplesPerPixel) {
@@ -70,8 +66,8 @@ public class ExtraSamplesColorModelTest {
@Test
public void testImageWithExtraSamplesCanBeResampledGray() {
for (int i = 1; i < 8; i++) {
BufferedImage image = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpace.CS_GRAY), false, i);
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(image, null);
BufferedImage bufferedImage = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpace.CS_GRAY), false, i);
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(bufferedImage, null);
assertNotNull(resampled);
assertEquals(5, resampled.getWidth());
@@ -82,8 +78,8 @@ public class ExtraSamplesColorModelTest {
@Test
public void testImageWithExtraSamplesCanBeResampledGrayAlpha() {
for (int i = 1; i < 8; i++) {
BufferedImage image = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpace.CS_GRAY), true, i);
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(image, null);
BufferedImage bufferedImage = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpace.CS_GRAY), true, i);
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(bufferedImage, null);
assertNotNull(resampled);
assertEquals(5, resampled.getWidth());
@@ -94,8 +90,8 @@ public class ExtraSamplesColorModelTest {
@Test
public void testImageWithExtraSamplesCanBeResampledRGB() {
for (int i = 1; i < 8; i++) {
BufferedImage image = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), false, i);
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(image, null);
BufferedImage bufferedImage = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), false, i);
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(bufferedImage, null);
assertNotNull(resampled);
assertEquals(5, resampled.getWidth());
@@ -106,8 +102,8 @@ public class ExtraSamplesColorModelTest {
@Test
public void testImageWithExtraSamplesCanBeResampledRGBAlpha() {
for (int i = 1; i < 8; i++) {
BufferedImage image = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), true, i);
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(image, null);
BufferedImage bufferedImage = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), true, i);
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(bufferedImage, null);
assertNotNull(resampled);
assertEquals(5, resampled.getWidth());
@@ -118,8 +114,8 @@ public class ExtraSamplesColorModelTest {
@Test
public void testImageWithExtraSamplesCanBeResampledCMYK() {
for (int i = 1; i < 8; i++) {
BufferedImage image = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), false, i);
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(image, null);
BufferedImage bufferedImage = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), false, i);
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(bufferedImage, null);
assertNotNull(resampled);
assertEquals(5, resampled.getWidth());
@@ -130,43 +126,12 @@ public class ExtraSamplesColorModelTest {
@Test
public void testImageWithExtraSamplesCanBeResampledCMYKAlpha() {
for (int i = 1; i < 8; i++) {
BufferedImage image = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), true, i);
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(image, null);
BufferedImage bufferedImage = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), true, i);
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(bufferedImage, null);
assertNotNull(resampled);
assertEquals(5, resampled.getWidth());
assertEquals(5, resampled.getHeight());
}
}
@Test
public void testSetRGB() {
BufferedImage image = createExtraSamplesImage(1, 1, ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), false, 1);
image.setRGB(0, 0, Color.BLACK.getRGB());
assertEquals(Color.BLACK.getRGB(), image.getRGB(0, 0));
}
@Test
public void testSetRGBs() {
BufferedImage image = createExtraSamplesImage(2, 2, ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), false, 1);
image.setRGB(0, 0, 2, 1, new int[]{Color.BLACK.getRGB(), Color.WHITE.getRGB()}, 0, 2);
assertEquals(Color.BLACK.getRGB(), image.getRGB(0, 0));
assertEquals(Color.WHITE.getRGB(), image.getRGB(1, 0));
}
@Test
public void testEquals() {
ExtraSamplesColorModel original = createExtraSamplesColorModel(ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), true, 1);
ExtraSamplesColorModel equal = createExtraSamplesColorModel(ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), true, 1);
assertEquals(original, equal);
assertEquals(equal, original);
ExtraSamplesColorModel different = createExtraSamplesColorModel(ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), true, 2);
ExtraSamplesColorModel differentToo = createExtraSamplesColorModel(ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), false, 1);
assertNotEquals(original, different);
assertNotEquals(original, differentToo);
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-webp</artifactId>
<name>TwelveMonkeys :: ImageIO :: WebP plugin</name>
@@ -32,7 +32,6 @@
package com.twelvemonkeys.imageio.plugins.webp;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.color.ColorProfiles;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
@@ -288,7 +287,7 @@ final class WebPImageReader extends ImageReaderBase {
long chunkStart = imageInput.getStreamPosition();
if (nextChunk == WebP.CHUNK_ICCP) {
iccProfile = ColorProfiles.readProfile(IIOUtil.createStreamAdapter(imageInput, chunkLength));
iccProfile = ColorSpaces.readProfile(IIOUtil.createStreamAdapter(imageInput, chunkLength));
}
else {
processWarningOccurred(String.format("Expected 'ICCP' chunk, '%s' chunk encountered", fourCC(nextChunk)));
@@ -366,7 +365,7 @@ final class WebPImageReader extends ImageReaderBase {
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
readHeader(imageIndex);
if (iccProfile != null && !ColorProfiles.isCS_sRGB(iccProfile)) {
if (iccProfile != null && !ColorSpaces.isCS_sRGB(iccProfile)) {
ICC_ColorSpace colorSpace = ColorSpaces.createColorSpace(iccProfile);
int[] bandOffsets = header.containsALPH ? new int[] {0, 1, 2, 3} : new int[] {0, 1, 2};
return ImageTypeSpecifiers.createInterleaved(colorSpace, bandOffsets, DataBuffer.TYPE_BYTE, header.containsALPH, false);
@@ -526,7 +525,7 @@ final class WebPImageReader extends ImageReaderBase {
if (!iccProfile.equals(destinationProfile)) {
if (DEBUG) {
System.err.println("Converting from " + iccProfile + " to " + (ColorProfiles.isCS_sRGB(destinationProfile) ? "sRGB" : destinationProfile));
System.err.println("Converting from " + iccProfile + " to " + (ColorSpaces.isCS_sRGB(destinationProfile) ? "sRGB" : destinationProfile));
}
WritableRaster raster = colorModel.hasAlpha()
@@ -35,7 +35,6 @@ import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.Locale;
@@ -77,8 +76,7 @@ public final class WebPImageReaderSpi extends ImageReaderSpiBase {
switch (chunk) {
// TODO. Support lossless
// case WebP.CHUNK_VP8L:
case WebP.CHUNK_VP8X:
return containsSupportedChunk(stream, chunk);
// case WebP.CHUNK_VP8X:
case WebP.CHUNK_VP8_:
return true;
default:
@@ -91,30 +89,6 @@ public final class WebPImageReaderSpi extends ImageReaderSpiBase {
}
}
private static boolean containsSupportedChunk(ImageInputStream stream, int chunk) throws IOException {
// Temporary: Seek for VP8_, either first or second (after ICCP), or inside ANMF...
try {
while (chunk != WebP.CHUNK_VP8L && chunk != WebP.CHUNK_ALPH) {
long length = stream.readUnsignedInt();
stream.seek(stream.getStreamPosition() + length);
chunk = stream.readInt();
// Look inside ANMF chunks...
if (chunk == WebP.CHUNK_ANMF) {
stream.seek(stream.getStreamPosition() + 4 + 16);
chunk = stream.readInt();
}
if (chunk == WebP.CHUNK_VP8_) {
return true;
}
}
}
catch (EOFException ignore) {}
return false;
}
@Override
public ImageReader createReaderInstance(final Object extension) {
return new WebPImageReader(this);
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-xwd</artifactId>
<name>TwelveMonkeys :: ImageIO :: XWD plugin</name>
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.twelvemonkeys.imageio</groupId>
+2 -2
View File
@@ -9,7 +9,7 @@
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Twelvemonkeys</name>
@@ -86,7 +86,7 @@
<connection>scm:git:https://github.com/haraldk/TwelveMonkeys</connection>
<developerConnection>scm:git:ssh://git@github.com/haraldk/TwelveMonkeys</developerConnection>
<url>https://github.com/haraldk/TwelveMonkeys</url>
<tag>twelvemonkeys-3.8.1</tag>
<tag>twelvemonkeys-3.7.0</tag>
</scm>
<properties>
+48 -33
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.1</version>
<version>3.7.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -12,6 +12,37 @@
<name>TwelveMonkeys :: Servlet</name>
<dependencies>
<dependency>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common-lang</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common-io</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common-image</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common-lang</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common-io</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
@@ -19,17 +50,31 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.1.0</version>
<version>3.12.4</version>
<scope>test</scope>
</dependency>
</dependencies>
@@ -62,36 +107,6 @@
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<id>jakarta</id>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>twelvemonkeys-${project.artifactId}-${project.version}-jakarta</finalName>
<shadedArtifactAttached>true</shadedArtifactAttached>
<createDependencyReducedPom>false</createDependencyReducedPom>
<artifactSet>
<includes>
<include>${project.groupId}:${project.artifactId}</include>
</includes>
</artifactSet>
<relocations>
<relocation>
<pattern>javax.servlet</pattern>
<shadedPattern>jakarta.servlet</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
@@ -0,0 +1,149 @@
/*
* Copyright (c) 2009, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.Set;
/**
* AbstractServletMapAdapter
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: AbstractServletMapAdapter.java#1 $
*/
abstract class AbstractServletMapAdapter<T> extends AbstractMap<String, T> {
// TODO: This map is now a little too lazy.. Should cache entries!
private transient Set<Entry<String, T>> entries;
protected abstract Iterator<String> keysImpl();
protected abstract T valueImpl(String pName);
@Override
public T get(final Object pKey) {
if (pKey instanceof String) {
return valueImpl((String) pKey);
}
return null;
}
@Override
public int size() {
// Avoid creating expensive entry set for computing size
int size = 0;
for (Iterator<String> names = keysImpl(); names.hasNext(); names.next()) {
size++;
}
return size;
}
public Set<Entry<String, T>> entrySet() {
if (entries == null) {
entries = new AbstractSet<Entry<String, T>>() {
public Iterator<Entry<String, T>> iterator() {
return new Iterator<Entry<String, T>>() {
Iterator<String> keys = keysImpl();
public boolean hasNext() {
return keys.hasNext();
}
public Entry<String, T> next() {
// TODO: Replace with cached lookup
return new HeaderEntry(keys.next());
}
public void remove() {
keys.remove();
}
};
}
public int size() {
return AbstractServletMapAdapter.this.size();
}
};
}
return entries;
}
private class HeaderEntry implements Entry<String, T> {
final String key;
public HeaderEntry(final String pKey) {
key = pKey;
}
public String getKey() {
return key;
}
public T getValue() {
return get(key);
}
public T setValue(final T pValue) {
// Write-through if supported
return put(key, pValue);
}
@Override
public int hashCode() {
T value = getValue();
return (key == null ? 0 : key.hashCode()) ^
(value == null ? 0 : value.hashCode());
}
@Override
public boolean equals(final Object pOther) {
if (pOther == this) {
return true;
}
if (pOther instanceof Entry) {
Entry other = (Entry) pOther;
return ((other.getKey() == null && getKey() == null) ||
(getKey() != null && getKey().equals(other.getKey()))) &&
((other.getValue() == null && getValue() == null) ||
(getValue() != null && getValue().equals(other.getValue())));
}
return false;
}
}
}
@@ -0,0 +1,168 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import com.twelvemonkeys.lang.StringUtil;
/**
* BrowserHelperFilter
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: BrowserHelperFilter.java#1 $
*/
@Deprecated
public class BrowserHelperFilter extends GenericFilter {
private static final String HTTP_HEADER_ACCEPT = "Accept";
protected static final String HTTP_HEADER_USER_AGENT = "User-Agent";
// TODO: Consider using unmodifiable LinkedHashMap<Pattern, String> instead
private Pattern[] knownAgentPatterns;
private String[] knownAgentAccepts;
/**
* Sets the accept-mappings for this filter
* @param pPropertiesFile name of accept-mappings properties files
* @throws ServletConfigException if the accept-mappings properties
* file cannot be read.
*/
@InitParam(name = "accept-mappings-file")
public void setAcceptMappingsFile(String pPropertiesFile) throws ServletConfigException {
// NOTE: Format is:
// <agent-name>=<reg-exp>
// <agent-name>.accept=<http-accept-header>
Properties mappings = new Properties();
try {
log("Reading Accept-mappings properties file: " + pPropertiesFile);
mappings.load(getServletContext().getResourceAsStream(pPropertiesFile));
//System.out.println("--> Loaded file: " + pPropertiesFile);
}
catch (IOException e) {
throw new ServletConfigException("Could not read Accept-mappings properties file: " + pPropertiesFile, e);
}
parseMappings(mappings);
}
private void parseMappings(Properties mappings) {
List<Pattern> patterns = new ArrayList<Pattern>();
List<String> accepts = new ArrayList<String>();
for (Object key : mappings.keySet()) {
String agent = (String) key;
if (agent.endsWith(".accept")) {
continue;
}
//System.out.println("--> Adding accept-mapping for User-Agent: " + agent);
try {
String accept = (String) mappings.get(agent + ".accept");
if (!StringUtil.isEmpty(accept)) {
patterns.add(Pattern.compile((String) mappings.get(agent)));
accepts.add(accept);
//System.out.println("--> " + agent + " accepts: " + accept);
}
else {
log("Missing Accept mapping for User-Agent: " + agent);
}
}
catch (PatternSyntaxException e) {
log("Could not parse User-Agent identification for " + agent, e);
}
knownAgentPatterns = patterns.toArray(new Pattern[patterns.size()]);
knownAgentAccepts = accepts.toArray(new String[accepts.size()]);
}
}
public void init() throws ServletException {
if (knownAgentAccepts == null || knownAgentAccepts.length == 0) {
throw new ServletConfigException("No User-Agent/Accept mappings for filter: " + getFilterName());
}
}
protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
if (pRequest instanceof HttpServletRequest) {
//System.out.println("--> Trying to find User-Agent/Accept headers...");
HttpServletRequest request = (HttpServletRequest) pRequest;
// Check if User-Agent is in list of known agents
if (knownAgentPatterns != null && knownAgentPatterns.length > 0) {
String agent = request.getHeader(HTTP_HEADER_USER_AGENT);
//System.out.println("--> User-Agent: " + agent);
for (int i = 0; i < knownAgentPatterns.length; i++) {
Pattern pattern = knownAgentPatterns[i];
//System.out.println("--> Pattern: " + pattern);
if (pattern.matcher(agent).matches()) {
// TODO: Consider merge known with real accept, in case plugins add extra capabilities?
final String fakeAccept = knownAgentAccepts[i];
//System.out.println("--> User-Agent: " + agent + " accepts: " + fakeAccept);
pRequest = new HttpServletRequestWrapper(request) {
public String getHeader(String pName) {
if (HTTP_HEADER_ACCEPT.equals(pName)) {
return fakeAccept;
}
return super.getHeader(pName);
}
};
break;
}
}
}
}
pChain.doFilter(pRequest, pResponse);
}
}
@@ -0,0 +1,122 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* DebugServlet class description.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: DebugServlet.java#1 $
*/
@Deprecated
public class DebugServlet extends GenericServlet {
private long dateModified;
public final void service(ServletRequest pRequest, ServletResponse pResponse) throws ServletException, IOException {
service((HttpServletRequest) pRequest, (HttpServletResponse) pResponse);
}
public void init() throws ServletException {
super.init();
dateModified = System.currentTimeMillis();
}
public void service(HttpServletRequest pRequest, HttpServletResponse pResponse) throws ServletException, IOException {
pResponse.setContentType("text/plain");
// Include these to allow browser caching
pResponse.setDateHeader("Last-Modified", dateModified);
pResponse.setHeader("ETag", getServletName());
ServletOutputStream out = pResponse.getOutputStream();
out.println("Remote address: " + pRequest.getRemoteAddr());
out.println("Remote host name: " + pRequest.getRemoteHost());
out.println("Remote user: " + pRequest.getRemoteUser());
out.println();
out.println("Request Method: " + pRequest.getMethod());
out.println("Request Scheme: " + pRequest.getScheme());
out.println("Request URI: " + pRequest.getRequestURI());
out.println("Request URL: " + pRequest.getRequestURL().toString());
out.println("Request PathInfo: " + pRequest.getPathInfo());
out.println("Request ContentLength: " + pRequest.getContentLength());
out.println();
out.println("Request Headers:");
Enumeration headerNames = pRequest.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
Enumeration headerValues = pRequest.getHeaders(headerName);
if (headerName != null) {
while (headerValues.hasMoreElements()) {
String value = (String) headerValues.nextElement();
out.println(" " + headerName + ": " + value);
}
}
}
out.println();
out.println("Request parameters:");
Enumeration paramNames = pRequest.getParameterNames();
while (paramNames.hasMoreElements()) {
String name = (String) paramNames.nextElement();
String[] values = pRequest.getParameterValues(name);
for (String value : values) {
out.println(" " + name + ": " + value);
}
}
out.println();
out.println("Request attributes:");
Enumeration attribNames = pRequest.getAttributeNames();
while (attribNames.hasMoreElements()) {
String name = (String) attribNames.nextElement();
Object value = pRequest.getAttribute(name);
out.println(" " + name + ": " + value);
}
out.flush();
}
}
@@ -0,0 +1,403 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import com.twelvemonkeys.lang.BeanUtil;
/**
* Defines a generic, protocol-independent filter.
* <p>
* {@code GenericFilter} is inspired by {@link GenericServlet}, and
* implements the {@code Filter} and {@code FilterConfig} interfaces.
* </p>
* <p>
* {@code GenericFilter} makes writing filters easier. It provides simple
* versions of the lifecycle methods {@code init} and {@code destroy}
* and of the methods in the {@code FilterConfig} interface.
* {@code GenericFilter} also implements the {@code log} methods,
* declared in the {@code ServletContext} interface.
* </p>
* <p>
* {@code GenericFilter} has an auto-init system, that automatically invokes
* the method matching the signature {@code void setX(&lt;Type&gt;)},
* for every init-parameter {@code x}. Both camelCase and lisp-style parameter
* naming is supported, lisp-style names will be converted to camelCase.
* Parameter values are automatically converted from string representation to
* most basic types, if necessary.
* </p>
* <p>
* To write a generic filter, you need only override the abstract
* {@link #doFilterImpl(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)} doFilterImpl} method.
* </p>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
*
* @version $Id: GenericFilter.java#1 $
*
* @see Filter
* @see FilterConfig
*/
@Deprecated
public abstract class GenericFilter implements Filter, FilterConfig, Serializable {
// TODO: Rewrite to use ServletConfigurator instead of BeanUtil
/**
* The filter config.
*/
private transient FilterConfig filterConfig = null;
/**
* Makes sure the filter runs once per request
*
* @see #isRunOnce
* @see #ATTRIB_RUN_ONCE_VALUE
* @see #oncePerRequest
*/
private final static String ATTRIB_RUN_ONCE_EXT = ".REQUEST_HANDLED";
/**
* Makes sure the filter runs once per request.
* Must be configured through init method, as the filter name is not
* available before we have a {@code FilterConfig} object.
*
* @see #isRunOnce
* @see #ATTRIB_RUN_ONCE_VALUE
* @see #oncePerRequest
*/
private String attribRunOnce = null;
/**
* Makes sure the filter runs once per request
*
* @see #isRunOnce
* @see #ATTRIB_RUN_ONCE_EXT
* @see #oncePerRequest
*/
private static final Object ATTRIB_RUN_ONCE_VALUE = new Object();
/**
* Indicates if this filter should run once per request ({@code true}),
* or for each forward/include resource ({@code false}).
* <p>
* Set this variable to true, to make sure the filter runs once per request.
* </p>
* <p>
* <em>NOTE: As of Servlet 2.4, this field
* should always be left to it's default value ({@code false}).
* <br>
* To run the filter once per request, the {@code filter-mapping} element
* of the web-descriptor should include a {@code dispatcher} element:
* </em>
* </p>
* <pre>&lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;</pre>
*/
protected boolean oncePerRequest = false;
/**
* Does nothing.
*/
public GenericFilter() {}
/**
* Called by the web container to indicate to a filter that it is being
* placed into service.
* <p>
* This implementation stores the {@code FilterConfig} object it
* receives from the servlet container for later use.
* Generally, there's no reason to override this method, override the
* no-argument {@code init} instead. However, <em>if</em> you are
* overriding this form of the method,
* always call {@code super.init(config)}.
* </p>
* <p>
* This implementation will also set all configured key/value pairs, that
* have a matching setter method annotated with {@link InitParam}.
* </p>
*
* @param pConfig the filter config
* @throws ServletException if an error occurs during init
*
* @see Filter#init(javax.servlet.FilterConfig)
* @see #init() init
* @see BeanUtil#configure(Object, java.util.Map, boolean)
*/
public void init(final FilterConfig pConfig) throws ServletException {
if (pConfig == null) {
throw new ServletConfigException("filter config == null");
}
// Store filter config
filterConfig = pConfig;
// Configure this
try {
BeanUtil.configure(this, ServletUtil.asMap(pConfig), true);
}
catch (InvocationTargetException e) {
throw new ServletConfigException("Could not configure " + getFilterName(), e.getCause());
}
// Create run-once attribute name
attribRunOnce = pConfig.getFilterName() + ATTRIB_RUN_ONCE_EXT;
log("init (oncePerRequest=" + oncePerRequest + ", attribRunOnce=" + attribRunOnce + ")");
init();
}
/**
* A convenience method which can be overridden so that there's no need to
* call {@code super.init(config)}.
*
* @see #init(FilterConfig)
*
* @throws ServletException if an error occurs during init
*/
public void init() throws ServletException {}
/**
* The {@code doFilter} method of the Filter is called by the container
* each time a request/response pair is passed through the chain due to a
* client request for a resource at the end of the chain.
* <p>
* Subclasses <em>should not override this method</em>, but rather the
* abstract {@link #doFilterImpl(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)} doFilterImpl} method.
* </p>
*
* @param pRequest the servlet request
* @param pResponse the servlet response
* @param pFilterChain the filter chain
*
* @throws IOException
* @throws ServletException
*
* @see Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) Filter.doFilter
* @see #doFilterImpl(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) doFilterImpl
*/
public final void doFilter(final ServletRequest pRequest, final ServletResponse pResponse, final FilterChain pFilterChain) throws IOException, ServletException {
// If request filter and already run, continue chain and return fast
if (oncePerRequest && isRunOnce(pRequest)) {
pFilterChain.doFilter(pRequest, pResponse);
return;
}
// Do real filter
doFilterImpl(pRequest, pResponse, pFilterChain);
}
/**
* If request is filtered, returns true, otherwise marks request as filtered
* and returns false.
* A return value of false, indicates that the filter has not yet run.
* A return value of true, indicates that the filter has run for this
* request, and processing should not continue.
* <p>
* Note that the method will mark the request as filtered on first
* invocation.
* </p>
*
* @see #ATTRIB_RUN_ONCE_EXT
* @see #ATTRIB_RUN_ONCE_VALUE
*
* @param pRequest the servlet request
* @return {@code true} if the request is already filtered, otherwise
* {@code false}.
*/
private boolean isRunOnce(final ServletRequest pRequest) {
// If request already filtered, return true (skip)
if (pRequest.getAttribute(attribRunOnce) == ATTRIB_RUN_ONCE_VALUE) {
return true;
}
// Set attribute and return false (continue)
pRequest.setAttribute(attribRunOnce, ATTRIB_RUN_ONCE_VALUE);
return false;
}
/**
* Invoked once, or each time a request/response pair is passed through the
* chain, depending on the {@link #oncePerRequest} member variable.
*
* @param pRequest the servlet request
* @param pResponse the servlet response
* @param pChain the filter chain
*
* @throws IOException if an I/O error occurs
* @throws ServletException if an exception occurs during the filter process
*
* @see #oncePerRequest
* @see #doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) doFilter
* @see Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) Filter.doFilter
*/
protected abstract void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain)
throws IOException, ServletException;
/**
* Called by the web container to indicate to a filter that it is being
* taken out of service.
*
* @see Filter#destroy
*/
public void destroy() {
log("destroy");
filterConfig = null;
}
/**
* Returns the filter-name of this filter as defined in the deployment
* descriptor.
*
* @return the filter-name
* @see FilterConfig#getFilterName
*/
public String getFilterName() {
return filterConfig.getFilterName();
}
/**
* Returns a reference to the {@link ServletContext} in which the caller is
* executing.
*
* @return the {@code ServletContext} object, used by the caller to
* interact with its servlet container
* @see FilterConfig#getServletContext
* @see ServletContext
*/
public ServletContext getServletContext() {
return filterConfig.getServletContext();
}
/**
* Returns a {@code String} containing the value of the named
* initialization parameter, or null if the parameter does not exist.
*
* @param pKey a {@code String} specifying the name of the
* initialization parameter
* @return a {@code String} containing the value of the initialization
* parameter
*/
public String getInitParameter(final String pKey) {
return filterConfig.getInitParameter(pKey);
}
/**
* Returns the names of the servlet's initialization parameters as an
* {@code Enumeration} of {@code String} objects, or an empty
* {@code Enumeration} if the servlet has no initialization parameters.
*
* @return an {@code Enumeration} of {@code String} objects
* containing the mNames of the servlet's initialization parameters
*/
public Enumeration getInitParameterNames() {
return filterConfig.getInitParameterNames();
}
/**
* Writes the specified message to a servlet log file, prepended by the
* filter's name.
*
* @param pMessage the log message
* @see ServletContext#log(String)
*/
protected void log(final String pMessage) {
getServletContext().log(getFilterName() + ": " + pMessage);
}
/**
* Writes an explanatory message and a stack trace for a given
* {@code Throwable} to the servlet log file, prepended by the
* filter's name.
*
* @param pMessage the log message
* @param pThrowable the exception
* @see ServletContext#log(String,Throwable)
*/
protected void log(final String pMessage, final Throwable pThrowable) {
getServletContext().log(getFilterName() + ": " + pMessage, pThrowable);
}
/**
* Initializes the filter.
*
* @param pFilterConfig the filter config
* @see #init init
*
* @deprecated For compatibility only, use {@link #init init} instead.
*/
@Deprecated
@SuppressWarnings("UnusedDeclaration")
public void setFilterConfig(final FilterConfig pFilterConfig) {
try {
init(pFilterConfig);
}
catch (ServletException e) {
log("Error in init(), see stack trace for details.", e);
}
}
/**
* Gets the {@code FilterConfig} for this filter.
*
* @return the {@code FilterConfig} for this filter
* @see FilterConfig
*/
public FilterConfig getFilterConfig() {
return filterConfig;
}
/**
* Specifies if this filter should run once per request ({@code true}),
* or for each forward/include resource ({@code false}).
* Called automatically from the {@code init}-method, with settings
* from web.xml.
*
* @param pOncePerRequest {@code true} if the filter should run only
* once per request
* @see #oncePerRequest
*/
@InitParam(name = "once-per-request")
public void setOncePerRequest(final boolean pOncePerRequest) {
oncePerRequest = pOncePerRequest;
}
}
@@ -0,0 +1,95 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet;
import java.lang.reflect.InvocationTargetException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import com.twelvemonkeys.lang.BeanUtil;
/**
* Defines a generic, protocol-independent servlet.
* <p>
* {@code GenericServlet} has an auto-init system, that automatically invokes
* the method matching the signature {@code void setX(&lt;Type&gt;)},
* for every init-parameter {@code x}. Both camelCase and lisp-style parameter
* naming is supported, lisp-style names will be converted to camelCase.
* Parameter values are automatically converted from string representation to
* most basic types, if necessary.
* </p>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
*
* @version $Id: GenericServlet.java#1 $
*/
@Deprecated
public abstract class GenericServlet extends javax.servlet.GenericServlet {
// TODO: Rewrite to use ServletConfigurator instead of BeanUtil
/**
* Called by the web container to indicate to a servlet that it is being
* placed into service.
* <p>
* This implementation stores the {@code ServletConfig} object it
* receives from the servlet container for later use. When overriding this
* form of the method, call {@code super.init(config)}.
* </p>
* <p>
* This implementation will also set all configured key/value pairs, that
* have a matching setter method annotated with {@link InitParam}.
* </p>
*
* @param pConfig the servlet config
* @throws ServletException if the servlet could not be initialized.
*
* @see javax.servlet.GenericServlet#init
* @see #init() init
* @see BeanUtil#configure(Object, java.util.Map, boolean)
*/
@Override
public void init(final ServletConfig pConfig) throws ServletException {
if (pConfig == null) {
throw new ServletConfigException("servlet config == null");
}
try {
BeanUtil.configure(this, ServletUtil.asMap(pConfig), true);
}
catch (InvocationTargetException e) {
throw new ServletConfigException("Could not configure " + getServletName(), e.getCause());
}
super.init(pConfig);
}
}
@@ -0,0 +1,95 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet;
import java.lang.reflect.InvocationTargetException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import com.twelvemonkeys.lang.BeanUtil;
/**
* Defines a generic, HTTP specific servlet.
* <p>
* {@code HttpServlet} has an auto-init system, that automatically invokes
* the method matching the signature {@code void setX(&lt;Type&gt;)},
* for every init-parameter {@code x}. Both camelCase and lisp-style parameter
* naming is supported, lisp-style names will be converted to camelCase.
* Parameter values are automatically converted from string representation to
* most basic types, if necessary.
* </p>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
*
* @version $Id: HttpServlet.java#1 $
*/
@Deprecated
public abstract class HttpServlet extends javax.servlet.http.HttpServlet {
// TODO: Rewrite to use ServletConfigurator instead of BeanUtil
/**
* Called by the web container to indicate to a servlet that it is being
* placed into service.
* <p>
* This implementation stores the {@code ServletConfig} object it
* receives from the servlet container for later use. When overriding this
* form of the method, call {@code super.init(config)}.
* </p>
* <p>
* This implementation will also set all configured key/value pairs, that
* have a matching setter method annotated with {@link InitParam}.
* </p>
*
* @param pConfig the servlet config
* @throws ServletException if an error occurred during init
*
* @see javax.servlet.GenericServlet#init
* @see #init() init
* @see BeanUtil#configure(Object, java.util.Map, boolean)
*/
@Override
public void init(ServletConfig pConfig) throws ServletException {
if (pConfig == null) {
throw new ServletConfigException("servlet config == null");
}
try {
BeanUtil.configure(this, ServletUtil.asMap(pConfig), true);
}
catch (InvocationTargetException e) {
throw new ServletConfigException("Could not configure " + getServletName(), e.getCause());
}
super.init(pConfig);
}
}
@@ -0,0 +1,63 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to be used by servlets/filters, to have their {@code init}-method
* automatically convert and set values from their respective configuration.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: InitParam.java#1 $
* @see com.twelvemonkeys.servlet.ServletConfigurator
* @see com.twelvemonkeys.servlet.GenericFilter#init(javax.servlet.FilterConfig)
* @see com.twelvemonkeys.servlet.GenericServlet#init(javax.servlet.ServletConfig)
* @see com.twelvemonkeys.servlet.HttpServlet#init(javax.servlet.ServletConfig)
*/
// TODO: Actually implement for version 3.0!
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD/*, TODO: ElementType.FIELD*/})
@Deprecated
public @interface InitParam {
static final String UNDEFINED = "";
String name() default UNDEFINED;
String defaultValue() default UNDEFINED; // TODO: Consider separate annotation?
boolean required() default false; // TODO: Consider separate annotation?
}
@@ -0,0 +1,127 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet;
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.ServletOutputStream;
import com.twelvemonkeys.lang.Validate;
/**
* A {@code ServletOutputStream} implementation backed by a
* {@link java.io.OutputStream}. For filters that need to buffer the
* response and do post filtering, it may be used like this:<pre>
* ByteArrayOutputStream buffer = new ByteArraOutputStream();
* ServletOutputStream adapter = new OutputStreamAdapter(buffer);
* </pre>
* <p>
* As a {@code ServletOutputStream} is itself an {@code OutputStream}, this
* class may also be used as a superclass for wrappers of other
* {@code ServletOutputStream}s, like this:
* </p>
* <pre>
* class FilterServletOutputStream extends OutputStreamAdapter {
* public FilterServletOutputStream(ServletOutputStream out) {
* super(out);
* }
*
* public void write(int abyte) {
* // do filtering...
* super.write(...);
* }
* }
*
* ...
*
* ServletOutputStream original = response.getOutputStream();
* ServletOutputStream wrapper = new FilterServletOutputStream(original);
* </pre>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author $Author: haku $
* @version $Id: OutputStreamAdapter.java#1 $
*
*/
@Deprecated
public class OutputStreamAdapter extends ServletOutputStream {
/** The wrapped {@code OutputStream}. */
protected final OutputStream out;
/**
* Creates an {@code OutputStreamAdapter}.
*
* @param pOut the wrapped {@code OutputStream}
*
* @throws IllegalArgumentException if {@code pOut} is {@code null}.
*/
public OutputStreamAdapter(final OutputStream pOut) {
Validate.notNull(pOut, "out");
out = pOut;
}
/**
* Returns the wrapped {@code OutputStream}.
*
* @return the wrapped {@code OutputStream}.
*/
public OutputStream getOutputStream() {
return out;
}
@Override
public String toString() {
return "ServletOutputStream adapted from " + out.toString();
}
/**
* Writes a byte to the underlying stream.
*
* @param pByte the byte to write.
*
* @throws IOException if an error occurs during writing
*/
public void write(final int pByte) throws IOException {
out.write(pByte);
}
// Overide for efficiency
public void write(final byte pBytes[]) throws IOException {
out.write(pBytes);
}
// Overide for efficiency
public void write(final byte pBytes[], final int pOff, final int pLen) throws IOException {
out.write(pBytes, pOff, pLen);
}
}
@@ -0,0 +1,136 @@
/*
* Copyright (c) 2013, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import java.util.Enumeration;
import java.util.Iterator;
import static com.twelvemonkeys.lang.Validate.notNull;
/**
* ServletAttributesMap
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: ServletAttributesMap.java,v 1.0 01.03.13 10:34 haraldk Exp$
*/
class ServletAttributesMapAdapter extends AbstractServletMapAdapter<Object> {
private final ServletContext context;
private final ServletRequest request;
ServletAttributesMapAdapter(final ServletContext context) {
this(notNull(context), null);
}
ServletAttributesMapAdapter(final ServletRequest request) {
this(null, notNull(request));
}
private ServletAttributesMapAdapter(final ServletContext context, final ServletRequest request) {
this.context = context;
this.request = request;
}
@SuppressWarnings("unchecked")
private Enumeration<String> getAttributeNames() {
return context != null ? context.getAttributeNames() : request.getAttributeNames();
}
private Object getAttribute(final String name) {
return context != null ? context.getAttribute(name) : request.getAttribute(name);
}
private Object setAttribute(String name, Object value) {
Object oldValue = getAttribute(name);
if (context != null) {
context.setAttribute(name, value);
}
else {
request.setAttribute(name, value);
}
return oldValue;
}
private Object removeAttribute(String name) {
Object oldValue = getAttribute(name);
if (context != null) {
context.removeAttribute(name);
}
else {
request.removeAttribute(name);
}
return oldValue;
}
@Override
protected Iterator<String> keysImpl() {
final Enumeration<String> keys = getAttributeNames();
return new Iterator<String>() {
private String key;
public boolean hasNext() {
return keys.hasMoreElements();
}
public String next() {
key = keys.nextElement();
return key;
}
public void remove() {
// Support removal of attribute through key iterator
removeAttribute(key);
}
};
}
@Override
protected Object valueImpl(String pName) {
return getAttribute(pName);
}
@Override
public Object put(String key, Object value) {
return setAttribute(key, value);
}
@Override
public Object remove(Object key) {
return key instanceof String ? removeAttribute((String) key) : null;
}
}
@@ -0,0 +1,84 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet;
import javax.servlet.ServletException;
/**
* ServletConfigException.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: ServletConfigException.java#2 $
*/
@Deprecated
public class ServletConfigException extends ServletException {
// TODO: Parameters for init-param at fault, and possibly servlet name?
/**
* Creates a {@code ServletConfigException} with the given message.
*
* @param pMessage the exception message
*/
public ServletConfigException(String pMessage) {
super(pMessage);
}
/**
* Creates a {@code ServletConfigException} with the given message and cause.
*
* @param pMessage the exception message
* @param pCause the exception cause
*/
public ServletConfigException(final String pMessage, final Throwable pCause) {
super(pMessage, pCause);
maybeInitCause(pCause);
}
/**
* Creates a {@code ServletConfigException} with the cause.
*
* @param pCause the exception cause
*/
public ServletConfigException(final Throwable pCause) {
super(String.format("Error in Servlet configuration: %s", pCause.getMessage()), pCause);
maybeInitCause(pCause);
}
private void maybeInitCause(Throwable pCause) {
// Workaround for ServletExceptions that does not do proper exception chaining
if (getCause() == null) {
initCause(pCause);
}
}
}
@@ -0,0 +1,284 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.lang.Validate;
import javax.servlet.FilterConfig;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import java.io.Serializable;
import java.util.*;
/**
* {@code ServletConfig} or {@code FilterConfig} adapter, that implements
* the {@code Map} interface for interoperability with collection-based API's.
* <p>
* This {@code Map} is not synchronized.
* </p>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: ServletConfigMapAdapter.java#2 $
*/
class ServletConfigMapAdapter extends AbstractMap<String, String> implements Map<String, String>, Serializable, Cloneable {
enum ConfigType {
ServletConfig, FilterConfig, ServletContext
}
private final ConfigType type;
private final ServletConfig servletConfig;
private final FilterConfig filterConfig;
private final ServletContext servletContext;
// Cache the entry set
private transient Set<Entry<String, String>> entrySet;
public ServletConfigMapAdapter(final ServletConfig pConfig) {
this(pConfig, ConfigType.ServletConfig);
}
public ServletConfigMapAdapter(final FilterConfig pConfig) {
this(pConfig, ConfigType.FilterConfig);
}
public ServletConfigMapAdapter(final ServletContext pContext) {
this(pContext, ConfigType.ServletContext);
}
private ServletConfigMapAdapter(final Object pConfig, final ConfigType pType) {
// Could happen if client code invokes with null reference
Validate.notNull(pConfig, "config");
type = pType;
switch (type) {
case ServletConfig:
servletConfig = (ServletConfig) pConfig;
filterConfig = null;
servletContext = null;
break;
case FilterConfig:
servletConfig = null;
filterConfig = (FilterConfig) pConfig;
servletContext = null;
break;
case ServletContext:
servletConfig = null;
filterConfig = null;
servletContext = (ServletContext) pConfig;
break;
default:
throw new IllegalArgumentException("Wrong type: " + pType);
}
}
/**
* Gets the servlet or filter name from the config.
*
* @return the servlet or filter name
*/
public final String getName() {
switch (type) {
case ServletConfig:
return servletConfig.getServletName();
case FilterConfig:
return filterConfig.getFilterName();
case ServletContext:
return servletContext.getServletContextName();
default:
throw new IllegalStateException();
}
}
/**
* Gets the servlet context from the config.
*
* @return the servlet context
*/
public final ServletContext getServletContext() {
switch (type) {
case ServletConfig:
return servletConfig.getServletContext();
case FilterConfig:
return filterConfig.getServletContext();
case ServletContext:
return servletContext;
default:
throw new IllegalStateException();
}
}
public final Enumeration getInitParameterNames() {
switch (type) {
case ServletConfig:
return servletConfig.getInitParameterNames();
case FilterConfig:
return filterConfig.getInitParameterNames();
case ServletContext:
return servletContext.getInitParameterNames();
default:
throw new IllegalStateException();
}
}
public final String getInitParameter(final String pName) {
switch (type) {
case ServletConfig:
return servletConfig.getInitParameter(pName);
case FilterConfig:
return filterConfig.getInitParameter(pName);
case ServletContext:
return servletContext.getInitParameter(pName);
default:
throw new IllegalStateException();
}
}
public Set<Entry<String, String>> entrySet() {
if (entrySet == null) {
entrySet = createEntrySet();
}
return entrySet;
}
private Set<Entry<String, String>> createEntrySet() {
return new AbstractSet<Entry<String, String>>() {
// Cache size, if requested, -1 means not calculated
private int size = -1;
public Iterator<Entry<String, String>> iterator() {
return new Iterator<Entry<String, String>>() {
// Iterator is backed by initParameterNames enumeration
final Enumeration names = getInitParameterNames();
public boolean hasNext() {
return names.hasMoreElements();
}
public Entry<String, String> next() {
final String key = (String) names.nextElement();
return new Entry<String, String>() {
public String getKey() {
return key;
}
public String getValue() {
return get(key);
}
public String setValue(String pValue) {
throw new UnsupportedOperationException();
}
// NOTE: Override equals
public boolean equals(Object pOther) {
if (!(pOther instanceof Map.Entry)) {
return false;
}
Map.Entry e = (Map.Entry) pOther;
Object value = get(key);
Object rKey = e.getKey();
Object rValue = e.getValue();
return (key == null ? rKey == null : key.equals(rKey))
&& (value == null ? rValue == null : value.equals(rValue));
}
// NOTE: Override hashCode to keep the map's
// hashCode constant and compatible
public int hashCode() {
Object value = get(key);
return ((key == null) ? 0 : key.hashCode()) ^
((value == null) ? 0 : value.hashCode());
}
public String toString() {
return key + "=" + get(key);
}
};
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public int size() {
if (size < 0) {
size = calculateSize();
}
return size;
}
private int calculateSize() {
final Enumeration names = getInitParameterNames();
int size = 0;
while (names.hasMoreElements()) {
size++;
names.nextElement();
}
return size;
}
};
}
public String get(Object pKey) {
return getInitParameter(StringUtil.valueOf(pKey));
}
/// Unsupported Map methods
@Override
public String put(String pKey, String pValue) {
throw new UnsupportedOperationException();
}
@Override
public String remove(Object pKey) {
throw new UnsupportedOperationException();
}
@Override
public void putAll(Map pMap) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
}
@@ -0,0 +1,271 @@
/*
* Copyright (c) 2011, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.util.FilterIterator;
import com.twelvemonkeys.util.convert.ConversionException;
import com.twelvemonkeys.util.convert.Converter;
import javax.servlet.Filter;
import javax.servlet.FilterConfig;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
/**
* ServletConfigurator
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: ServletConfigurator.java,v 1.0 Apr 30, 2010 2:51:38 PM haraldk Exp$
* @see com.twelvemonkeys.servlet.InitParam
*/
final class ServletConfigurator {
// TODO: Rethink @InitParam? Allow annotation of method parameters instead? Allows setLocation(@InitParam int x, @InitParam int y)
// TODO: At least allow field injection
// TODO: defaultValue, required
private ServletConfigurator() {
}
public static void configure(final Servlet pServlet, final ServletConfig pConfig) throws ServletConfigException {
new Configurator(pServlet, pConfig.getServletName()).configure(ServletUtil.asMap(pConfig));
}
public static void configure(final Filter pFilter, final FilterConfig pConfig) throws ServletConfigException {
new Configurator(pFilter, pConfig.getFilterName()).configure(ServletUtil.asMap(pConfig));
}
private static class Configurator {
private final Object servletOrFilter;
private final String name;
private Configurator(final Object servletOrFilter, final String name) {
this.servletOrFilter = servletOrFilter;
this.name = name;
}
private void configure(final Map<String, String> pMapping) throws ServletConfigException {
// Loop over methods with InitParam annotations
for (Method method : annotatedMethods(servletOrFilter.getClass(), InitParam.class)) {
assertAcceptableMethod(method);
// Get value or default, throw exception if missing required value
Object value = getConfiguredValue(method, pMapping);
if (value != null) {
// Inject value to this method
try {
method.invoke(servletOrFilter, value);
}
catch (IllegalAccessException e) {
// We know the method is accessible, so this should never happen
throw new Error(e);
}
catch (InvocationTargetException e) {
throw new ServletConfigException(String.format("Could not configure %s: %s", name, e.getCause().getMessage()), e.getCause());
}
}
}
// TODO: Loop over fields with InitParam annotations
// TODO: Log warning for mappings not present among InitParam annotated methods?
}
private Object getConfiguredValue(final Method method, final Map<String, String> mapping) throws ServletConfigException {
InitParam initParam = method.getAnnotation(InitParam.class);
String paramName = getParameterName(method, initParam);
// Get parameter value
String stringValue = mapping.get(paramName);
if (stringValue == null && initParam.name().equals(InitParam.UNDEFINED)) {
stringValue = mapping.get(StringUtil.camelToLisp(paramName));
}
if (stringValue == null) {
// InitParam support required = true and throw exception if not present in map
if (initParam.required()) {
throw new ServletConfigException(
String.format(
"Could not configure %s: Required init-parameter \"%s\" of type %s is missing",
name, paramName, method.getParameterTypes()[0]
)
);
}
else if (!initParam.defaultValue().equals(InitParam.UNDEFINED)) {
// Support default values
stringValue = initParam.defaultValue();
}
}
// Convert value based on method arguments...
return stringValue == null ? null : convertValue(method, stringValue);
}
private Object convertValue(final Method method, final String stringValue) throws ServletConfigException {
// We know it's a single parameter method
Class<?> type = method.getParameterTypes()[0];
try {
return String.class.equals(type) ? stringValue : Converter.getInstance().toObject(stringValue, type);
}
catch (ConversionException e) {
throw new ServletConfigException(e);
}
}
private String getParameterName(final Method method, final InitParam initParam) throws ServletConfigException {
String paramName = initParam.name();
if (paramName.equals(InitParam.UNDEFINED)) {
String methodName = method.getName();
if (methodName.startsWith("set") && methodName.length() > 3) {
paramName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
}
else {
throw new ServletConfigException(
String.format(
"Could not configure %s: InitParam annotated method must either specify name or follow Bean standard for properties (ie. setFoo => 'foo'): %s",
name, method
)
);
}
}
return paramName;
}
private void assertAcceptableMethod(final Method method) throws ServletConfigException {
// Try to use setAccessible, if not public
boolean isAccessible = Modifier.isPublic(method.getModifiers());
if (!isAccessible) {
try {
method.setAccessible(true);
isAccessible = true;
}
catch (SecurityException ignore) {
// Won't be accessible, we'll fail below
}
}
if (!isAccessible || method.getReturnType() != Void.TYPE || method.getParameterTypes().length != 1) {
throw new ServletConfigException(
String.format(
"Could not configure %s: InitParam annotated method must be public void and have a single parameter argument list: %s",
name, method
)
);
}
}
/**
* Gets all methods annotated with the given annotations.
*
* @param pClass the class to get annotated methods from
* @param pAnnotations the annotations to test for
* @return an iterable that allows iterating over all methods with the given annotations.
*/
private Iterable<Method> annotatedMethods(final Class<?> pClass, final Class<? extends Annotation>... pAnnotations) {
return new Iterable<Method>() {
public Iterator<Method> iterator() {
Set<Method> methods = new LinkedHashSet<Method>();
Class<?> cl = pClass;
while (cl.getSuperclass() != null) { // There's no annotations of interest on java.lang.Object
methods.addAll(Arrays.asList(cl.getDeclaredMethods()));
// TODO: What about interface methods? Do we really want them?
Class<?>[] interfaces = cl.getInterfaces();
for (Class<?> i : interfaces) {
methods.addAll(Arrays.asList(i.getDeclaredMethods()));
}
cl = cl.getSuperclass();
}
return new FilterIterator<Method>(methods.iterator(), new FilterIterator.Filter<Method>() {
public boolean accept(final Method pMethod) {
for (Class<? extends Annotation> annotation : pAnnotations) {
if (!pMethod.isAnnotationPresent(annotation) || isOverriddenWithAnnotation(pMethod, annotation)) {
return false;
}
}
return true;
}
/**
* @param pMethod the method to test for override
* @param pAnnotation the annotation that must be present
* @return {@code true} iff the method is overridden in a subclass, and has annotation
* @see <a href="http://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.4.8">The Java Language Specification: Classes: Inheritance, Overriding, and Hiding</a>
*/
private boolean isOverriddenWithAnnotation(final Method pMethod, final Class<? extends Annotation> pAnnotation) {
if (Modifier.isPrivate(pMethod.getModifiers())) {
return false;
}
Class cl = pClass;
// Loop down up from subclass to superclass declaring the method
while (cl != null && !pMethod.getDeclaringClass().equals(cl)) {
try {
Method override = cl.getDeclaredMethod(pMethod.getName(), pMethod.getParameterTypes());
// Overridden, test if it has the annotation present
if (override.isAnnotationPresent(pAnnotation)) {
return true;
}
}
catch (NoSuchMethodException ignore) {
}
cl = cl.getSuperclass();
}
return false;
}
});
}
};
}
}
}
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2009, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.util.CollectionUtil;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import static com.twelvemonkeys.lang.Validate.notNull;
/**
* ServletHeadersMapAdapter
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: ServletHeadersMapAdapter.java#1 $
*/
class ServletHeadersMapAdapter extends AbstractServletMapAdapter<List<String>> {
protected final HttpServletRequest request;
public ServletHeadersMapAdapter(final HttpServletRequest pRequest) {
request = notNull(pRequest, "request");
}
protected List<String> valueImpl(final String pName) {
@SuppressWarnings("unchecked")
Enumeration<String> headers = request.getHeaders(pName);
return headers == null ? null : toList(CollectionUtil.iterator(headers));
}
private static List<String> toList(final Iterator<String> pValues) {
List<String> list = new ArrayList<String>();
CollectionUtil.addAll(list, pValues);
return Collections.unmodifiableList(list);
}
protected Iterator<String> keysImpl() {
@SuppressWarnings("unchecked")
Enumeration<String> headerNames = request.getHeaderNames();
return headerNames == null ? null : CollectionUtil.iterator(headerNames);
}
}
@@ -0,0 +1,69 @@
/*
* Copyright (c) 2009, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.util.CollectionUtil;
import javax.servlet.ServletRequest;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import static com.twelvemonkeys.lang.Validate.notNull;
/**
* ServletParametersMapAdapter
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: ServletParametersMapAdapter.java#1 $
*/
class ServletParametersMapAdapter extends AbstractServletMapAdapter<List<String>> {
// TODO: Be able to piggyback on HttpServletRequest.getParameterMap when available?
protected final ServletRequest request;
public ServletParametersMapAdapter(final ServletRequest pRequest) {
request = notNull(pRequest, "request");
}
protected List<String> valueImpl(String pName) {
String[] values = request.getParameterValues(pName);
return values == null ? null : Arrays.asList(values);
}
protected Iterator<String> keysImpl() {
@SuppressWarnings("unchecked")
Enumeration<String> names = request.getParameterNames();
return names == null ? null : CollectionUtil.iterator(names);
}
}
@@ -0,0 +1,120 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet;
import static com.twelvemonkeys.lang.Validate.notNull;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
/**
* A delegate for handling stream support in wrapped servlet responses.
* <p>
* Client code should delegate {@code getOutputStream}, {@code getWriter},
* {@code flushBuffer} and {@code resetBuffer} methods from the servlet response.
* </p>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: ServletResponseStreamDelegate.java#2 $
*/
@Deprecated
public class ServletResponseStreamDelegate {
private Object out = null;
protected final ServletResponse response;
public ServletResponseStreamDelegate(final ServletResponse pResponse) {
response = notNull(pResponse, "response");
}
// NOTE: Intentionally NOT thread safe, as one request/response should be handled by one thread ONLY.
public final ServletOutputStream getOutputStream() throws IOException {
if (out == null) {
OutputStream out = createOutputStream();
this.out = out instanceof ServletOutputStream ? out : new OutputStreamAdapter(out);
}
else if (out instanceof PrintWriter) {
throw new IllegalStateException("getWriter() already called.");
}
return (ServletOutputStream) out;
}
// NOTE: Intentionally NOT thread safe, as one request/response should be handled by one thread ONLY.
public final PrintWriter getWriter() throws IOException {
if (out == null) {
// NOTE: getCharacterEncoding may/should not return null
OutputStream out = createOutputStream();
String charEncoding = response.getCharacterEncoding();
this.out = new PrintWriter(charEncoding != null ? new OutputStreamWriter(out, charEncoding) : new OutputStreamWriter(out));
}
else if (out instanceof ServletOutputStream) {
throw new IllegalStateException("getOutputStream() already called.");
}
return (PrintWriter) out;
}
/**
* Returns the {@code OutputStream}.
* Subclasses should override this method to provide a decorated output stream.
* This method is guaranteed to be invoked only once for a request/response
* (unless {@code resetBuffer} is invoked).
* <p>
* This implementation simply returns the output stream from the wrapped
* response.
* </p>
*
* @return the {@code OutputStream} to use for the response
* @throws IOException if an I/O exception occurs
*/
protected OutputStream createOutputStream() throws IOException {
return response.getOutputStream();
}
public void flushBuffer() throws IOException {
if (out instanceof ServletOutputStream) {
((ServletOutputStream) out).flush();
}
else if (out != null) {
((PrintWriter) out).flush();
}
}
public void resetBuffer() {
out = null;
}
}
@@ -0,0 +1,785 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet;
import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Map;
import javax.servlet.FilterConfig;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestWrapper;
import javax.servlet.ServletResponse;
import javax.servlet.ServletResponseWrapper;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.util.convert.ConversionException;
import com.twelvemonkeys.util.convert.Converter;
/**
* Various servlet related helper methods.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author Eirik Torske
* @author last modified by $Author: haku $
* @version $Id: ServletUtil.java#3 $
*/
@Deprecated
public final class ServletUtil {
/**
* {@code "javax.servlet.include.request_uri"}
*/
private final static String ATTRIB_INC_REQUEST_URI = "javax.servlet.include.request_uri";
/**
* {@code "javax.servlet.include.context_path"}
*/
private final static String ATTRIB_INC_CONTEXT_PATH = "javax.servlet.include.context_path";
/**
* {@code "javax.servlet.include.servlet_path"}
*/
private final static String ATTRIB_INC_SERVLET_PATH = "javax.servlet.include.servlet_path";
/**
* {@code "javax.servlet.include.path_info"}
*/
private final static String ATTRIB_INC_PATH_INFO = "javax.servlet.include.path_info";
/**
* {@code "javax.servlet.include.query_string"}
*/
private final static String ATTRIB_INC_QUERY_STRING = "javax.servlet.include.query_string";
/**
* {@code "javax.servlet.forward.request_uri"}
*/
private final static String ATTRIB_FWD_REQUEST_URI = "javax.servlet.forward.request_uri";
/**
* {@code "javax.servlet.forward.context_path"}
*/
private final static String ATTRIB_FWD_CONTEXT_PATH = "javax.servlet.forward.context_path";
/**
* {@code "javax.servlet.forward.servlet_path"}
*/
private final static String ATTRIB_FWD_SERVLET_PATH = "javax.servlet.forward.servlet_path";
/**
* {@code "javax.servlet.forward.path_info"}
*/
private final static String ATTRIB_FWD_PATH_INFO = "javax.servlet.forward.path_info";
/**
* {@code "javax.servlet.forward.query_string"}
*/
private final static String ATTRIB_FWD_QUERY_STRING = "javax.servlet.forward.query_string";
/**
* Don't create, static methods only
*/
private ServletUtil() {
}
/**
* Gets the value of the given parameter from the request, or if the
* parameter is not set, the default value.
*
* @param pReq the servlet request
* @param pName the parameter name
* @param pDefault the default value
* @return the value of the parameter, or the default value, if the
* parameter is not set.
*/
public static String getParameter(final ServletRequest pReq, final String pName, final String pDefault) {
String str = pReq.getParameter(pName);
return str != null ? str : pDefault;
}
/**
* Gets the value of the given parameter from the request converted to
* an Object. If the parameter is not set or not parseable, the default
* value is returned.
*
* @param pReq the servlet request
* @param pName the parameter name
* @param pType the type of object (class) to return
* @param pFormat the format to use (might be {@code null} in many cases)
* @param pDefault the default value
* @return the value of the parameter converted to a boolean, or the
* default value, if the parameter is not set.
* @throws IllegalArgumentException if {@code pDefault} is
* non-{@code null} and not an instance of {@code pType}
* @throws NullPointerException if {@code pReq}, {@code pName} or
* {@code pType} is {@code null}.
* @see Converter#toObject
*/
// TODO: Well, it's done. Need some thinking... We probably don't want default if conversion fails...
static <T> T getParameter(final ServletRequest pReq, final String pName, final Class<T> pType, final String pFormat, final T pDefault) {
// Test if pDefault is either null or instance of pType
if (pDefault != null && !pType.isInstance(pDefault)) {
throw new IllegalArgumentException("default value not instance of " + pType + ": " + pDefault.getClass());
}
String str = pReq.getParameter(pName);
if (str == null) {
return pDefault;
}
try {
return pType.cast(Converter.getInstance().toObject(str, pType, pFormat));
}
catch (ConversionException ce) {
return pDefault;
}
}
/**
* Gets the value of the given parameter from the request converted to
* a {@code boolean}.&nbsp;If the parameter is not set or not parseable, the default
* value is returned.
*
* @param pReq the servlet request
* @param pName the parameter name
* @param pDefault the default value
* @return the value of the parameter converted to a {@code boolean}, or the
* default value, if the parameter is not set.
*/
public static boolean getBooleanParameter(final ServletRequest pReq, final String pName, final boolean pDefault) {
String str = pReq.getParameter(pName);
try {
return str != null ? Boolean.valueOf(str) : pDefault;
}
catch (NumberFormatException nfe) {
return pDefault;
}
}
/**
* Gets the value of the given parameter from the request converted to
* an {@code int}.&nbsp;If the parameter is not set or not parseable, the default
* value is returned.
*
* @param pReq the servlet request
* @param pName the parameter name
* @param pDefault the default value
* @return the value of the parameter converted to an {@code int}, or the default
* value, if the parameter is not set.
*/
public static int getIntParameter(final ServletRequest pReq, final String pName, final int pDefault) {
String str = pReq.getParameter(pName);
try {
return str != null ? Integer.parseInt(str) : pDefault;
}
catch (NumberFormatException nfe) {
return pDefault;
}
}
/**
* Gets the value of the given parameter from the request converted to
* an {@code long}.&nbsp;If the parameter is not set or not parseable, the default
* value is returned.
*
* @param pReq the servlet request
* @param pName the parameter name
* @param pDefault the default value
* @return the value of the parameter converted to an {@code long}, or the default
* value, if the parameter is not set.
*/
public static long getLongParameter(final ServletRequest pReq, final String pName, final long pDefault) {
String str = pReq.getParameter(pName);
try {
return str != null ? Long.parseLong(str) : pDefault;
}
catch (NumberFormatException nfe) {
return pDefault;
}
}
/**
* Gets the value of the given parameter from the request converted to
* a {@code float}.&nbsp;If the parameter is not set or not parseable, the default
* value is returned.
*
* @param pReq the servlet request
* @param pName the parameter name
* @param pDefault the default value
* @return the value of the parameter converted to a {@code float}, or the default
* value, if the parameter is not set.
*/
public static float getFloatParameter(final ServletRequest pReq, final String pName, final float pDefault) {
String str = pReq.getParameter(pName);
try {
return str != null ? Float.parseFloat(str) : pDefault;
}
catch (NumberFormatException nfe) {
return pDefault;
}
}
/**
* Gets the value of the given parameter from the request converted to
* a {@code double}.&nbsp;If the parameter is not set or not parseable, the default
* value is returned.
*
* @param pReq the servlet request
* @param pName the parameter name
* @param pDefault the default value
* @return the value of the parameter converted to n {@code double}, or the default
* value, if the parameter is not set.
*/
public static double getDoubleParameter(final ServletRequest pReq, final String pName, final double pDefault) {
String str = pReq.getParameter(pName);
try {
return str != null ? Double.parseDouble(str) : pDefault;
}
catch (NumberFormatException nfe) {
return pDefault;
}
}
/**
* Gets the value of the given parameter from the request converted to
* a {@code Date}.&nbsp;If the parameter is not set or not parseable, the
* default value is returned.
*
* @param pReq the servlet request
* @param pName the parameter name
* @param pDefault the default value
* @return the value of the parameter converted to a {@code Date}, or the
* default value, if the parameter is not set.
* @see com.twelvemonkeys.lang.StringUtil#toDate(String)
*/
public static long getDateParameter(final ServletRequest pReq, final String pName, final long pDefault) {
String str = pReq.getParameter(pName);
try {
return str != null ? StringUtil.toDate(str).getTime() : pDefault;
}
catch (IllegalArgumentException iae) {
return pDefault;
}
}
/**
* Gets the value of the given parameter from the request converted to
* a Date.&nbsp;If the parameter is not set or not parseable, the
* default value is returned.
*
* @param pReq the servlet request
* @param pName the parameter name
* @param pFormat the date format to use
* @param pDefault the default value
* @return the value of the parameter converted to a Date, or the
* default value, if the parameter is not set.
* @see com.twelvemonkeys.lang.StringUtil#toDate(String,String)
*/
/*
public static long getDateParameter(ServletRequest pReq, String pName, String pFormat, long pDefault) {
String str = pReq.getParameter(pName);
try {
return ((str != null) ? StringUtil.toDate(str, pFormat).getTime() : pDefault);
}
catch (IllegalArgumentException iae) {
return pDefault;
}
}
*/
/**
* Builds a full-blown HTTP/HTTPS URL from a
* {@code javax.servlet.http.HttpServletRequest} object.
*
* @param pRequest The HTTP servlet request object.
* @return the reproduced URL
* @deprecated Use {@link javax.servlet.http.HttpServletRequest#getRequestURL()}
* instead.
*/
@Deprecated
static StringBuffer buildHTTPURL(final HttpServletRequest pRequest) {
StringBuffer resultURL = new StringBuffer();
// Scheme, as in http, https, ftp etc
String scheme = pRequest.getScheme();
resultURL.append(scheme);
resultURL.append("://");
resultURL.append(pRequest.getServerName());
// Append port only if not default port
int port = pRequest.getServerPort();
if (port > 0 &&
!(("http".equals(scheme) && port == 80) ||
("https".equals(scheme) && port == 443))) {
resultURL.append(":");
resultURL.append(port);
}
// Append URI
resultURL.append(pRequest.getRequestURI());
// If present, append extra path info
String pathInfo = pRequest.getPathInfo();
if (pathInfo != null) {
resultURL.append(pathInfo);
}
return resultURL;
}
/**
* Gets the URI of the resource currently included.
* The value is read from the request attribute
* {@code "javax.servlet.include.request_uri"}
*
* @param pRequest the servlet request
* @return the URI of the included resource, or {@code null} if no include
* @see HttpServletRequest#getRequestURI
* @since Servlet 2.2
*/
public static String getIncludeRequestURI(final ServletRequest pRequest) {
return (String) pRequest.getAttribute(ATTRIB_INC_REQUEST_URI);
}
/**
* Gets the context path of the resource currently included.
* The value is read from the request attribute
* {@code "javax.servlet.include.context_path"}
*
* @param pRequest the servlet request
* @return the context path of the included resource, or {@code null} if no include
* @see HttpServletRequest#getContextPath
* @since Servlet 2.2
*/
public static String getIncludeContextPath(final ServletRequest pRequest) {
return (String) pRequest.getAttribute(ATTRIB_INC_CONTEXT_PATH);
}
/**
* Gets the servlet path of the resource currently included.
* The value is read from the request attribute
* {@code "javax.servlet.include.servlet_path"}
*
* @param pRequest the servlet request
* @return the servlet path of the included resource, or {@code null} if no include
* @see HttpServletRequest#getServletPath
* @since Servlet 2.2
*/
public static String getIncludeServletPath(final ServletRequest pRequest) {
return (String) pRequest.getAttribute(ATTRIB_INC_SERVLET_PATH);
}
/**
* Gets the path info of the resource currently included.
* The value is read from the request attribute
* {@code "javax.servlet.include.path_info"}
*
* @param pRequest the servlet request
* @return the path info of the included resource, or {@code null} if no include
* @see HttpServletRequest#getPathInfo
* @since Servlet 2.2
*/
public static String getIncludePathInfo(final ServletRequest pRequest) {
return (String) pRequest.getAttribute(ATTRIB_INC_PATH_INFO);
}
/**
* Gets the query string of the resource currently included.
* The value is read from the request attribute
* {@code "javax.servlet.include.query_string"}
*
* @param pRequest the servlet request
* @return the query string of the included resource, or {@code null} if no include
* @see HttpServletRequest#getQueryString
* @since Servlet 2.2
*/
public static String getIncludeQueryString(final ServletRequest pRequest) {
return (String) pRequest.getAttribute(ATTRIB_INC_QUERY_STRING);
}
/**
* Gets the URI of the resource this request was forwarded from.
* The value is read from the request attribute
* {@code "javax.servlet.forward.request_uri"}
*
* @param pRequest the servlet request
* @return the URI of the resource, or {@code null} if not forwarded
* @see HttpServletRequest#getRequestURI
* @since Servlet 2.4
*/
public static String getForwardRequestURI(final ServletRequest pRequest) {
return (String) pRequest.getAttribute(ATTRIB_FWD_REQUEST_URI);
}
/**
* Gets the context path of the resource this request was forwarded from.
* The value is read from the request attribute
* {@code "javax.servlet.forward.context_path"}
*
* @param pRequest the servlet request
* @return the context path of the resource, or {@code null} if not forwarded
* @see HttpServletRequest#getContextPath
* @since Servlet 2.4
*/
public static String getForwardContextPath(final ServletRequest pRequest) {
return (String) pRequest.getAttribute(ATTRIB_FWD_CONTEXT_PATH);
}
/**
* Gets the servlet path of the resource this request was forwarded from.
* The value is read from the request attribute
* {@code "javax.servlet.forward.servlet_path"}
*
* @param pRequest the servlet request
* @return the servlet path of the resource, or {@code null} if not forwarded
* @see HttpServletRequest#getServletPath
* @since Servlet 2.4
*/
public static String getForwardServletPath(final ServletRequest pRequest) {
return (String) pRequest.getAttribute(ATTRIB_FWD_SERVLET_PATH);
}
/**
* Gets the path info of the resource this request was forwarded from.
* The value is read from the request attribute
* {@code "javax.servlet.forward.path_info"}
*
* @param pRequest the servlet request
* @return the path info of the resource, or {@code null} if not forwarded
* @see HttpServletRequest#getPathInfo
* @since Servlet 2.4
*/
public static String getForwardPathInfo(final ServletRequest pRequest) {
return (String) pRequest.getAttribute(ATTRIB_FWD_PATH_INFO);
}
/**
* Gets the query string of the resource this request was forwarded from.
* The value is read from the request attribute
* {@code "javax.servlet.forward.query_string"}
*
* @param pRequest the servlet request
* @return the query string of the resource, or {@code null} if not forwarded
* @see HttpServletRequest#getQueryString
* @since Servlet 2.4
*/
public static String getForwardQueryString(final ServletRequest pRequest) {
return (String) pRequest.getAttribute(ATTRIB_FWD_QUERY_STRING);
}
/**
* Gets the name of the servlet or the script that generated the servlet.
*
* @param pRequest The HTTP servlet request object.
* @return the script name.
* @see javax.servlet.http.HttpServletRequest#getServletPath()
*/
// TODO: Read the spec, seems to be a mismatch with the Servlet API...
static String getScriptName(final HttpServletRequest pRequest) {
String requestURI = pRequest.getRequestURI();
return StringUtil.getLastElement(requestURI, "/");
}
/**
* Gets the request URI relative to the current context path.
* <p>
* As an example:
* </p>
* <pre>
* requestURI = "/webapp/index.jsp"
* contextPath = "/webapp"
* </pre>
* The method will return {@code "/index.jsp"}.
*
* @param pRequest the current HTTP request
* @return the request URI relative to the current context path.
*/
public static String getContextRelativeURI(final HttpServletRequest pRequest) {
String context = pRequest.getContextPath();
if (!StringUtil.isEmpty(context)) { // "" for root context
return pRequest.getRequestURI().substring(context.length());
}
return pRequest.getRequestURI();
}
/**
* Returns a {@code URL} containing the real path for a given virtual
* path, on URL form.
* Note that this method will return {@code null} for all the same reasons
* as {@code ServletContext.getRealPath(java.lang.String)} does.
*
* @param pContext the servlet context
* @param pPath the virtual path
* @return a {@code URL} object containing the path, or {@code null}.
* @throws MalformedURLException if the path refers to a malformed URL
* @see ServletContext#getRealPath(java.lang.String)
* @see ServletContext#getResource(java.lang.String)
*/
public static URL getRealURL(final ServletContext pContext, final String pPath) throws MalformedURLException {
String realPath = pContext.getRealPath(pPath);
if (realPath != null) {
// NOTE: First convert to URI, as of Java 6 File.toURL is deprecated
return new File(realPath).toURI().toURL();
}
return null;
}
/**
* Gets the temp directory for the given {@code ServletContext} (web app).
*
* @param pContext the servlet context
* @return the temp directory
*/
public static File getTempDir(final ServletContext pContext) {
return (File) pContext.getAttribute("javax.servlet.context.tempdir");
}
/**
* Gets the unique identifier assigned to this session.
* The identifier is assigned by the servlet container and is implementation
* dependent.
*
* @param pRequest The HTTP servlet request object.
* @return the session Id
*/
public static String getSessionId(final HttpServletRequest pRequest) {
HttpSession session = pRequest.getSession();
return (session != null) ? session.getId() : null;
}
/**
* Creates an unmodifiable {@code Map} view of the given
* {@code ServletConfig}s init-parameters.
* <small>Note: The returned {@code Map} is optimized for {@code get}
* operations and iterating over it's {@code keySet}.
* For other operations it may not perform well.</small>
*
* @param pConfig the servlet configuration
* @return a {@code Map} view of the config
* @throws IllegalArgumentException if {@code pConfig} is {@code null}
*/
public static Map<String, String> asMap(final ServletConfig pConfig) {
return new ServletConfigMapAdapter(pConfig);
}
/**
* Creates an unmodifiable {@code Map} view of the given
* {@code FilterConfig}s init-parameters.
* <small>Note: The returned {@code Map} is optimized for {@code get}
* operations and iterating over it's {@code keySet}.
* For other operations it may not perform well.</small>
*
* @param pConfig the servlet filter configuration
* @return a {@code Map} view of the config
* @throws IllegalArgumentException if {@code pConfig} is {@code null}
*/
public static Map<String, String> asMap(final FilterConfig pConfig) {
return new ServletConfigMapAdapter(pConfig);
}
/**
* Creates an unmodifiable {@code Map} view of the given
* {@code ServletContext}s init-parameters.
* <small>Note: The returned {@code Map} is optimized for {@code get}
* operations and iterating over it's {@code keySet}.
* For other operations it may not perform well.</small>
*
* @param pContext the servlet context
* @return a {@code Map} view of the init parameters
* @throws IllegalArgumentException if {@code pContext} is {@code null}
*/
public static Map<String, String> initParamsAsMap(final ServletContext pContext) {
return new ServletConfigMapAdapter(pContext);
}
/**
* Creates an <em>modifiable</em> {@code Map} view of the given
* {@code ServletContext}s attributes.
*
* @param pContext the servlet context
* @return a {@code Map} view of the attributes
* @throws IllegalArgumentException if {@code pContext} is {@code null}
*/
public static Map<String, Object> attributesAsMap(final ServletContext pContext) {
return new ServletAttributesMapAdapter(pContext);
}
/**
* Creates an <em>modifiable</em> {@code Map} view of the given
* {@code ServletRequest}s attributes.
*
* @param pRequest the servlet request
* @return a {@code Map} view of the attributes
* @throws IllegalArgumentException if {@code pContext} is {@code null}
*/
public static Map<String, Object> attributesAsMap(final ServletRequest pRequest) {
return new ServletAttributesMapAdapter(pRequest);
}
/**
* Creates an unmodifiable {@code Map} view of the given
* {@code HttpServletRequest}s request parameters.
*
* @param pRequest the request
* @return a {@code Map} view of the request parameters
* @throws IllegalArgumentException if {@code pRequest} is {@code null}
*/
public static Map<String, List<String>> parametersAsMap(final ServletRequest pRequest) {
return new ServletParametersMapAdapter(pRequest);
}
/**
* Creates an unmodifiable {@code Map} view of the given
* {@code HttpServletRequest}s request headers.
*
* @param pRequest the request
* @return a {@code Map} view of the request headers
* @throws IllegalArgumentException if {@code pRequest} is {@code null}
*/
public static Map<String, List<String>> headersAsMap(final HttpServletRequest pRequest) {
return new ServletHeadersMapAdapter(pRequest);
}
/**
* Creates a wrapper that implements either {@code ServletResponse} or
* {@code HttpServletResponse}, depending on the type of
* {@code pImplementation.getResponse()}.
*
* @param pImplementation the servlet response to create a wrapper for
* @return a {@code ServletResponse} or
* {@code HttpServletResponse}, depending on the type of
* {@code pImplementation.getResponse()}
*/
public static ServletResponse createWrapper(final ServletResponseWrapper pImplementation) {
// TODO: Get all interfaces from implementation
if (pImplementation.getResponse() instanceof HttpServletResponse) {
return (HttpServletResponse) Proxy.newProxyInstance(pImplementation.getClass().getClassLoader(),
new Class[]{HttpServletResponse.class, ServletResponse.class},
new HttpServletResponseHandler(pImplementation));
}
return pImplementation;
}
/**
* Creates a wrapper that implements either {@code ServletRequest} or
* {@code HttpServletRequest}, depending on the type of
* {@code pImplementation.getRequest()}.
*
* @param pImplementation the servlet request to create a wrapper for
* @return a {@code ServletResponse} or
* {@code HttpServletResponse}, depending on the type of
* {@code pImplementation.getResponse()}
*/
public static ServletRequest createWrapper(final ServletRequestWrapper pImplementation) {
// TODO: Get all interfaces from implementation
if (pImplementation.getRequest() instanceof HttpServletRequest) {
return (HttpServletRequest) Proxy.newProxyInstance(pImplementation.getClass().getClassLoader(),
new Class[]{HttpServletRequest.class, ServletRequest.class},
new HttpServletRequestHandler(pImplementation));
}
return pImplementation;
}
private static class HttpServletResponseHandler implements InvocationHandler {
private final ServletResponseWrapper response;
HttpServletResponseHandler(final ServletResponseWrapper pResponse) {
response = pResponse;
}
public Object invoke(final Object pProxy, final Method pMethod, final Object[] pArgs) throws Throwable {
try {
// TODO: Allow partial implementing?
if (pMethod.getDeclaringClass().isInstance(response)) {
return pMethod.invoke(response, pArgs);
}
// Method is not implemented in wrapper
return pMethod.invoke(response.getResponse(), pArgs);
}
catch (InvocationTargetException e) {
// Unwrap, to avoid UndeclaredThrowableException...
throw e.getTargetException();
}
}
}
private static class HttpServletRequestHandler implements InvocationHandler {
private final ServletRequestWrapper request;
HttpServletRequestHandler(final ServletRequestWrapper pRequest) {
request = pRequest;
}
public Object invoke(final Object pProxy, final Method pMethod, final Object[] pArgs) throws Throwable {
try {
// TODO: Allow partial implementing?
if (pMethod.getDeclaringClass().isInstance(request)) {
return pMethod.invoke(request, pArgs);
}
// Method is not implemented in wrapper
return pMethod.invoke(request.getRequest(), pArgs);
}
catch (InvocationTargetException e) {
// Unwrap, to avoid UndeclaredThrowableException...
throw e.getTargetException();
}
}
}
}
@@ -0,0 +1,311 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.twelvemonkeys.io.FileUtil;
import com.twelvemonkeys.lang.StringUtil;
/**
* ThrottleFilter, a filter for easing server during heavy load.
* <p>
* Intercepts requests, and returns HTTP response code {@code 503 (Service Unavailable)},
* if there are more than a given number of concurrent
* requests, to avoid large backlogs. The number of concurrent requests and the
* response messages sent to the user agent, is configurable from the web
* descriptor.
* </p>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: ThrottleFilter.java#1 $
* @see #setMaxConcurrentThreadCount
* @see #setResponseMessages
*/
@Deprecated
public class ThrottleFilter extends GenericFilter {
/**
* Minimum free thread count, defaults to {@code 10}
*/
protected int maxConcurrentThreadCount = 10;
/**
* The number of running request threads
*/
private int runningThreads = 0;
private final Object runningThreadsLock = new Object();
/**
* Default response message sent to user agents, if the request is rejected
*/
protected final static String DEFUALT_RESPONSE_MESSAGE =
"Service temporarily unavailable, please try again later.";
/**
* Default response content type
*/
protected static final String DEFAULT_TYPE = "text/html";
/**
* The reposne message sent to user agenta, if the request is rejected
*/
private Map<String, String> responseMessageNames = new HashMap<String, String>(10);
/**
* The reposne message sent to user agents, if the request is rejected
*/
private String[] responseMessageTypes = null;
/**
* Cache for response messages
*/
private Map<String, CacheEntry> responseCache = new HashMap<String, CacheEntry>(10);
/**
* Sets the minimum free thread count.
*
* @param pMaxConcurrentThreadCount
*/
public void setMaxConcurrentThreadCount(String pMaxConcurrentThreadCount) {
if (!StringUtil.isEmpty(pMaxConcurrentThreadCount)) {
try {
maxConcurrentThreadCount = Integer.parseInt(pMaxConcurrentThreadCount);
}
catch (NumberFormatException nfe) {
// Use default
}
}
}
/**
* Sets the response message sent to the user agent, if the request is
* rejected.
* <br>
* The format is {@code &lt;mime-type&gt;=&lt;filename&gt;,
* &lt;mime-type&gt;=&lt;filename&gt;}.
* <br>
* Example: {@code &lt;text/vnd.wap.wmlgt;=&lt;/errors/503.wml&gt;,
* &lt;text/html&gt;=&lt;/errors/503.html&gt;}
*
* @param pResponseMessages
*/
public void setResponseMessages(String pResponseMessages) {
// Split string in type=filename pairs
String[] mappings = StringUtil.toStringArray(pResponseMessages, ", \r\n\t");
List<String> types = new ArrayList<String>();
for (String pair : mappings) {
// Split pairs on '='
String[] mapping = StringUtil.toStringArray(pair, "= ");
// Test for wrong mapping
if ((mapping == null) || (mapping.length < 2)) {
log("Error in init param \"responseMessages\": " + pResponseMessages);
continue;
}
types.add(mapping[0]);
responseMessageNames.put(mapping[0], mapping[1]);
}
// Create arrays
responseMessageTypes = types.toArray(new String[types.size()]);
}
/**
* @param pRequest
* @param pResponse
* @param pChain
* @throws IOException
* @throws ServletException
*/
protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
try {
if (beginRequest()) {
// Continue request
pChain.doFilter(pRequest, pResponse);
}
else {
// Send error and end request
// Get HTTP specific versions
HttpServletRequest request = (HttpServletRequest) pRequest;
HttpServletResponse response = (HttpServletResponse) pResponse;
// Get content type
String contentType = getContentType(request);
// Note: This is not the way the spec says you should do it.
// However, we handle error response this way for preformace reasons.
// The "correct" way would be to use sendError() and register a servlet
// that does the content negotiation as errorpage in the web descriptor.
response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
response.setContentType(contentType);
response.getWriter().println(getMessage(contentType));
// Log warning, as this shouldn't happen too often
log("Request denied, no more available threads for requestURI=" + request.getRequestURI());
}
}
finally {
doneRequest();
}
}
/**
* Marks the beginning of a request
*
* @return {@code true} if the request should be handled.
*/
private boolean beginRequest() {
synchronized (runningThreadsLock) {
runningThreads++;
}
return (runningThreads <= maxConcurrentThreadCount);
}
/**
* Marks the end of the request
*/
private void doneRequest() {
synchronized (runningThreadsLock) {
runningThreads--;
}
}
/**
* Gets the content type for the response, suitable for the requesting user agent.
*
* @param pRequest
* @return the content type
*/
private String getContentType(HttpServletRequest pRequest) {
if (responseMessageTypes != null) {
String accept = pRequest.getHeader("Accept");
for (String type : responseMessageTypes) {
// Note: This is not 100% correct way of doing content negotiation
// But we just want a compatible result, quick, so this is okay
if (StringUtil.contains(accept, type)) {
return type;
}
}
}
// If none found, return default
return DEFAULT_TYPE;
}
/**
* Gets the response message for the given content type.
*
* @param pContentType
* @return the message
*/
private String getMessage(String pContentType) {
String fileName = responseMessageNames.get(pContentType);
// Get cached value
CacheEntry entry = responseCache.get(fileName);
if ((entry == null) || entry.isExpired()) {
// Create and add or replace cached value
entry = new CacheEntry(readMessage(fileName));
responseCache.put(fileName, entry);
}
// Return value
return (entry.getValue() != null)
? (String) entry.getValue()
: DEFUALT_RESPONSE_MESSAGE;
}
/**
* Reads the response message from a file in the current web app.
*
* @param pFileName
* @return the message
*/
private String readMessage(String pFileName) {
try {
// Read resource from web app
InputStream is = getServletContext().getResourceAsStream(pFileName);
if (is != null) {
return new String(FileUtil.read(is));
}
else {
log("File not found: " + pFileName);
}
}
catch (IOException ioe) {
log("Error reading file: " + pFileName + " (" + ioe.getMessage() + ")");
}
return null;
}
/**
* Keeps track of Cached objects
*/
private static class CacheEntry {
private Object value;
private long timestamp = -1;
CacheEntry(Object pValue) {
value = pValue;
timestamp = System.currentTimeMillis();
}
Object getValue() {
return value;
}
boolean isExpired() {
return (System.currentTimeMillis() - timestamp) > 60000; // Cache 1 minute
}
}
}
@@ -0,0 +1,116 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
/**
* TimingFilter class description.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: TimingFilter.java#1 $
*/
@Deprecated
public class TimingFilter extends GenericFilter {
private String attribUsage = null;
/**
* Method init
*
* @throws ServletException
*/
public void init() throws ServletException {
attribUsage = getFilterName() + ".timerDelta";
}
/**
*
* @param pRequest
* @param pResponse
* @param pChain
* @throws IOException
* @throws ServletException
*/
protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain)
throws IOException, ServletException {
// Get total usage of earlier filters on same level
Object usageAttrib = pRequest.getAttribute(attribUsage);
long total = 0;
if (usageAttrib instanceof Long) {
// If set, get value, and remove attribute for nested resources
total = (Long) usageAttrib;
pRequest.removeAttribute(attribUsage);
}
// Start timing
long start = System.currentTimeMillis();
try {
// Continue chain
pChain.doFilter(pRequest, pResponse);
}
finally {
// Stop timing
long end = System.currentTimeMillis();
// Get time usage of included resources, add to total usage
usageAttrib = pRequest.getAttribute(attribUsage);
long usage = 0;
if (usageAttrib instanceof Long) {
usage = (Long) usageAttrib;
}
// Get the name of the included resource
String resourceURI = ServletUtil.getIncludeRequestURI(pRequest);
// If none, this is probably the parent page itself
if (resourceURI == null) {
resourceURI = ((HttpServletRequest) pRequest).getRequestURI();
}
long delta = end - start;
log(String.format("Request processing time for resource \"%s\": %d ms (accumulated: %d ms).", resourceURI, (delta - usage), delta));
// Store total usage
total += delta;
pRequest.setAttribute(attribUsage, total);
}
}
}
@@ -0,0 +1,246 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.ServletResponseWrapper;
/**
* Removes extra unneccessary white space from a servlet response.
* White space is defined as per {@link Character#isWhitespace(char)}.
* <p>
* This filter has no understanding of the content in the reponse, and will
* remove repeated white space anywhere in the stream. It is intended for
* removing white space from HTML or XML streams, but this limitation makes it
* less suited for filtering HTML/XHTML with embedded CSS or JavaScript,
* in case white space should be significant here. It is strongly reccommended
* you keep CSS and JavaScript in separate files (this will have the added
* benefit of further reducing the ammount of data communicated between
* server and client).
* </p>
* <p>
* <em>At the moment this filter has no concept of encoding</em>.
* This means, that if some multi-byte escape sequence contains one or more
* bytes that <em>individually</em> is treated as a white space, these bytes
* may be skipped.
* As <a href="http://en.wikipedia.org/wiki/UTF-8">UTF-8</a>
* guarantees that no bytes are repeated in this way, this filter can safely
* filter UTF-8.
* Simple 8 bit character encodings, like the
* <a href="http://en.wikipedia.org/wiki/ISO/IEC_8859">ISO/IEC 8859</a> standard, or
* <a href="http://en.wikipedia.org/wiki/Windows-1252">Windows-1252"</a>
* are always safe.
* </p>
* <p>
* <b>Configuration</b>
* <br>
* To use {@code TrimWhiteSpaceFilter} in your web-application, you simply need
* to add it to your web descriptor ({@code web.xml}).
* If using a servlet container that supports the Servlet 2.4 spec, the new
* {@code dispatcher} element should be used, and set to
* {@code REQUEST/FORWARD}, to make sure the filter is invoked only once for
* requests.
* If using an older web descriptor, set the {@code init-param}
* {@code "once-per-request"} to {@code "true"} (this will have the same effect,
* but might perform slightly worse than the 2.4 version).
* Please see the examples below.
* </p>
* <p>
* <b>Servlet 2.4 version, filter section:</b>
* </p>
* <pre>
* &lt;!-- TrimWS Filter Configuration --&gt;
* &lt;filter&gt;
* &lt;filter-name&gt;trimws&lt;/filter-name&gt;
* &lt;filter-class&gt;com.twelvemonkeys.servlet.TrimWhiteSpaceFilter&lt;/filter-class&gt;
* &lt;!-- auto-flush=true is the default, may be omitted --&gt;
* &lt;init-param&gt;
* &lt;param-name&gt;auto-flush&lt;/param-name&gt;
* &lt;param-value&gt;true&lt;/param-value&gt;
* &lt;/init-param&gt;
* &lt;/filter&gt;
* </pre>
* <b>Filter-mapping section:</b><br>
* <pre>
* &lt;!-- TimWS Filter Mapping --&gt;
* &lt;filter-mapping&gt;
* &lt;filter-name&gt;trimws&lt;/filter-name&gt;
* &lt;url-pattern&gt;*.html&lt;/url-pattern&gt;
* &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
* &lt;dispatcher&gt;FORWARD&lt;/dispatcher&gt;
* &lt;/filter-mapping&gt;
* &lt;filter-mapping&gt;
* &lt;filter-name&gt;trimws&lt;/filter-name&gt;
* &lt;url-pattern&gt;*.jsp&lt;/url-pattern&gt;
* &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
* &lt;dispatcher&gt;FORWARD&lt;/dispatcher&gt;
* &lt;/filter-mapping&gt;
* </pre>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: TrimWhiteSpaceFilter.java#2 $
*/
@Deprecated
public class TrimWhiteSpaceFilter extends GenericFilter {
private boolean autoFlush = true;
@InitParam
public void setAutoFlush(final boolean pAutoFlush) {
autoFlush = pAutoFlush;
}
public void init() throws ServletException {
super.init();
log("Automatic flushing is " + (autoFlush ? "enabled" : "disabled"));
}
protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
ServletResponseWrapper wrapped = new TrimWSServletResponseWrapper(pResponse);
pChain.doFilter(pRequest, ServletUtil.createWrapper(wrapped));
if (autoFlush) {
wrapped.flushBuffer();
}
}
static final class TrimWSFilterOutputStream extends FilterOutputStream {
boolean lastWasWS = true; // Avoids leading WS by init to true
public TrimWSFilterOutputStream(OutputStream pOut) {
super(pOut);
}
// Override this, in case the wrapped outputstream overrides...
public final void write(byte pBytes[]) throws IOException {
write(pBytes, 0, pBytes.length);
}
// Override this, in case the wrapped outputstream overrides...
public final void write(byte pBytes[], int pOff, int pLen) throws IOException {
if (pBytes == null) {
throw new NullPointerException("bytes == null");
}
else if (pOff < 0 || pLen < 0 || (pOff + pLen > pBytes.length)) {
throw new IndexOutOfBoundsException("Bytes: " + pBytes.length + " Offset: " + pOff + " Length: " + pLen);
}
for (int i = 0; i < pLen ; i++) {
write(pBytes[pOff + i]);
}
}
public void write(int pByte) throws IOException {
// TODO: Is this good enough for multi-byte encodings like UTF-16?
// Consider writing through a Writer that does that for us, and
// also buffer whitespace, so we write a linefeed every time there's
// one in the original...
// According to http://en.wikipedia.org/wiki/UTF-8:
// "[...] US-ASCII octet values do not appear otherwise in a UTF-8
// encoded character stream. This provides compatibility with file
// systems or other software (e.g., the printf() function in
// C libraries) that parse based on US-ASCII values but are
// transparent to other values."
if (!Character.isWhitespace((char) pByte)) {
// If char is not WS, just store
super.write(pByte);
lastWasWS = false;
}
else {
// TODO: Consider writing only 0x0a (LF) and 0x20 (space)
// Else, if char is WS, store first, skip the rest
if (!lastWasWS) {
if (pByte == 0x0d) { // Convert all CR/LF's to 0x0a
super.write(0x0a);
}
else {
super.write(pByte);
}
}
lastWasWS = true;
}
}
}
private static class TrimWSStreamDelegate extends ServletResponseStreamDelegate {
public TrimWSStreamDelegate(ServletResponse pResponse) {
super(pResponse);
}
protected OutputStream createOutputStream() throws IOException {
return new TrimWSFilterOutputStream(response.getOutputStream());
}
}
static class TrimWSServletResponseWrapper extends ServletResponseWrapper {
private final ServletResponseStreamDelegate streamDelegate = new TrimWSStreamDelegate(getResponse());
public TrimWSServletResponseWrapper(ServletResponse pResponse) {
super(pResponse);
}
public ServletOutputStream getOutputStream() throws IOException {
return streamDelegate.getOutputStream();
}
public PrintWriter getWriter() throws IOException {
return streamDelegate.getWriter();
}
public void setContentLength(int pLength) {
// Will be changed by filter, so don't set.
}
@Override
public void flushBuffer() throws IOException {
streamDelegate.flushBuffer();
}
@Override
public void resetBuffer() {
streamDelegate.resetBuffer();
}
// TODO: Consider picking up content-type/encoding, as we can only
// filter US-ASCII, UTF-8 and other compatible encodings?
}
}
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet.cache;
import java.net.URI;
import com.twelvemonkeys.lang.Validate;
/**
* AbstractCacheRequest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: AbstractCacheRequest.java#1 $
*/
@Deprecated
public abstract class AbstractCacheRequest implements CacheRequest {
private final URI requestURI;
private final String method;
protected AbstractCacheRequest(final URI pRequestURI, final String pMethod) {
requestURI = Validate.notNull(pRequestURI, "requestURI");
method = Validate.notNull(pMethod, "method");
}
public URI getRequestURI() {
return requestURI;
}
public String getMethod() {
return method;
}
// TODO: Consider overriding equals/hashcode
@Override
public String toString() {
return new StringBuilder(getClass().getSimpleName())
.append("[URI=").append(requestURI)
.append(", parameters=").append(getParameters())
.append(", headers=").append(getHeaders())
.append("]").toString();
}
}
@@ -0,0 +1,82 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet.cache;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* AbstractCacheResponse
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: AbstractCacheResponse.java#1 $
*/
@Deprecated
public abstract class AbstractCacheResponse implements CacheResponse {
private int status;
private final Map<String, List<String>> headers = new LinkedHashMap<String, List<String>>(); // Insertion order
private final Map<String, List<String>> readableHeaders = Collections.unmodifiableMap(headers);
public int getStatus() {
return status;
}
public void setStatus(int pStatusCode) {
status = pStatusCode;
}
public void addHeader(String pHeaderName, String pHeaderValue) {
setHeader(pHeaderName, pHeaderValue, true);
}
public void setHeader(String pHeaderName, String pHeaderValue) {
setHeader(pHeaderName, pHeaderValue, false);
}
private void setHeader(String pHeaderName, String pHeaderValue, boolean pAdd) {
List<String> values = pAdd ? headers.get(pHeaderName) : null;
if (values == null) {
values = new ArrayList<String>();
headers.put(pHeaderName, values);
}
values.add(pHeaderValue);
}
public Map<String, List<String>> getHeaders() {
return readableHeaders;
}
}
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet.cache;
/**
* CacheException
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: CacheException.java#1 $
*/
@Deprecated
public class CacheException extends Exception {
public CacheException(Throwable pCause) {
super(pCause);
}
}
+210
View File
@@ -0,0 +1,210 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet.cache;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.servlet.GenericFilter;
import com.twelvemonkeys.servlet.ServletConfigException;
import com.twelvemonkeys.servlet.ServletUtil;
/**
* A Filter that provides response caching, for HTTP {@code GET} requests.
* <p>
* Originally based on ideas and code found in the ONJava article
* <a href="http://www.onjava.com/pub/a/onjava/2003/11/19/filters.html">Two
* Servlet Filters Every Web Application Should Have</a>
* by Jayson Falkner.
* </p>
*
* @author Jayson Falkner
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: CacheFilter.java#4 $
*
*/
@Deprecated
public class CacheFilter extends GenericFilter {
HTTPCache cache;
/**
* Initializes the filter
*
* @throws javax.servlet.ServletException
*/
public void init() throws ServletException {
FilterConfig config = getFilterConfig();
// Default don't delete cache files on exit (persistent cache)
boolean deleteCacheOnExit = "TRUE".equalsIgnoreCase(config.getInitParameter("deleteCacheOnExit"));
// Default expiry time 10 minutes
int expiryTime = 10 * 60 * 1000;
String expiryTimeStr = config.getInitParameter("expiryTime");
if (!StringUtil.isEmpty(expiryTimeStr)) {
try {
// TODO: This is insane.. :-P Let the expiry time be in minutes or seconds..
expiryTime = Integer.parseInt(expiryTimeStr);
}
catch (NumberFormatException e) {
throw new ServletConfigException("Could not parse expiryTime: " + e.toString(), e);
}
}
// Default max mem cache size 10 MB
int memCacheSize = 10;
String memCacheSizeStr = config.getInitParameter("memCacheSize");
if (!StringUtil.isEmpty(memCacheSizeStr)) {
try {
memCacheSize = Integer.parseInt(memCacheSizeStr);
}
catch (NumberFormatException e) {
throw new ServletConfigException("Could not parse memCacheSize: " + e.toString(), e);
}
}
int maxCachedEntites = 10000;
try {
cache = new HTTPCache(
getTempFolder(),
expiryTime,
memCacheSize * 1024 * 1024,
maxCachedEntites,
deleteCacheOnExit,
new ServletContextLoggerAdapter(getFilterName(), getServletContext())
) {
@Override
protected File getRealFile(CacheRequest pRequest) {
String contextRelativeURI = ServletUtil.getContextRelativeURI(((ServletCacheRequest) pRequest).getRequest());
String path = getServletContext().getRealPath(contextRelativeURI);
if (path != null) {
return new File(path);
}
return null;
}
};
log("Created cache: " + cache);
}
catch (IllegalArgumentException e) {
throw new ServletConfigException("Could not create cache: " + e.toString(), e);
}
}
private File getTempFolder() {
File tempRoot = (File) getServletContext().getAttribute("javax.servlet.context.tempdir");
if (tempRoot == null) {
throw new IllegalStateException("Missing context attribute \"javax.servlet.context.tempdir\"");
}
return new File(tempRoot, getFilterName());
}
public void destroy() {
log("Destroying cache: " + cache);
cache = null;
super.destroy();
}
protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
// We can only cache HTTP GET/HEAD requests
if (!(pRequest instanceof HttpServletRequest
&& pResponse instanceof HttpServletResponse
&& isCachable((HttpServletRequest) pRequest))) {
pChain.doFilter(pRequest, pResponse); // Continue chain
}
else {
ServletCacheRequest cacheRequest = new ServletCacheRequest((HttpServletRequest) pRequest);
ServletCacheResponse cacheResponse = new ServletCacheResponse((HttpServletResponse) pResponse);
ServletResponseResolver resolver = new ServletResponseResolver(cacheRequest, cacheResponse, pChain);
// Render fast
try {
cache.doCached(cacheRequest, cacheResponse, resolver);
}
catch (CacheException e) {
if (e.getCause() instanceof ServletException) {
throw (ServletException) e.getCause();
}
else {
throw new ServletException(e);
}
}
finally {
pResponse.flushBuffer();
}
}
}
private boolean isCachable(HttpServletRequest pRequest) {
// TODO: Get Cache-Control: no-cache/max-age=0 and Pragma: no-cache from REQUEST too?
return "GET".equals(pRequest.getMethod()) || "HEAD".equals(pRequest.getMethod());
}
// TODO: Extract, complete and document this class, might be useful in other cases
// Maybe add it to the ServletUtil class
static class ServletContextLoggerAdapter extends Logger {
private final ServletContext context;
public ServletContextLoggerAdapter(String pName, ServletContext pContext) {
super(pName, null);
context = pContext;
}
@Override
public void log(Level pLevel, String pMessage) {
context.log(pMessage);
}
@Override
public void log(Level pLevel, String pMessage, Throwable pThrowable) {
context.log(pMessage, pThrowable);
}
}
}
+57
View File
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet.cache;
import java.net.URI;
import java.util.List;
import java.util.Map;
/**
* CacheRequest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: CacheRequest.java#1 $
*/
@Deprecated
public interface CacheRequest {
URI getRequestURI();
String getMethod();
Map<String, List<String>> getHeaders();
Map<String, List<String>> getParameters();
String getServerName();
int getServerPort();
}
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet.cache;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
/**
* CacheResponse
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: CacheResponse.java#1 $
*/
@Deprecated
public interface CacheResponse {
OutputStream getOutputStream() throws IOException;
void setStatus(int pStatusCode);
int getStatus();
void addHeader(String pHeaderName, String pHeaderValue);
void setHeader(String pHeaderName, String pHeaderValue);
Map<String, List<String>> getHeaders();
}
@@ -0,0 +1,266 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet.cache;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponseWrapper;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.net.HTTPUtil;
import com.twelvemonkeys.servlet.ServletResponseStreamDelegate;
/**
* CacheResponseWrapper class description.
* <p>
* Based on ideas and code found in the ONJava article
* <a href="http://www.onjava.com/pub/a/onjava/2003/11/19/filters.html">Two
* Servlet Filters Every Web Application Should Have</a>
* by Jayson Falkner.
* </p>
*
* @author Jayson Falkner
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: CacheResponseWrapper.java#3 $
*/
@Deprecated
class CacheResponseWrapper extends HttpServletResponseWrapper {
private ServletResponseStreamDelegate streamDelegate;
private CacheResponse response;
private CachedEntity cached;
private WritableCachedResponse cachedResponse;
private Boolean cacheable;
private int status;
public CacheResponseWrapper(final ServletCacheResponse pResponse, final CachedEntity pCached) {
super(pResponse.getResponse());
response = pResponse;
cached = pCached;
init();
}
/*
NOTE: This class defers determining if a response is cacheable until the
output stream is needed.
This it the reason for the somewhat complicated logic in the add/setHeader
methods below.
*/
private void init() {
cacheable = null;
status = SC_OK;
cachedResponse = cached.createCachedResponse();
streamDelegate = new ServletResponseStreamDelegate(this) {
protected OutputStream createOutputStream() throws IOException {
// Test if this request is really cacheable, otherwise,
// just write through to underlying response, and don't cache
if (isCacheable()) {
return cachedResponse.getOutputStream();
}
else {
cachedResponse.setStatus(status);
cachedResponse.writeHeadersTo(CacheResponseWrapper.this.response);
return super.getOutputStream();
}
}
};
}
CachedResponse getCachedResponse() {
return cachedResponse.getCachedResponse();
}
public boolean isCacheable() {
// NOTE: Intentionally not synchronized
if (cacheable == null) {
cacheable = isCacheableImpl();
}
return cacheable;
}
private boolean isCacheableImpl() {
if (status != SC_OK) {
return false;
}
// Vary: *
String[] values = cachedResponse.getHeaderValues(HTTPCache.HEADER_VARY);
if (values != null) {
for (String value : values) {
if ("*".equals(value)) {
return false;
}
}
}
// Cache-Control: no-cache, no-store, must-revalidate
values = cachedResponse.getHeaderValues(HTTPCache.HEADER_CACHE_CONTROL);
if (values != null) {
for (String value : values) {
if (StringUtil.contains(value, "no-cache")
|| StringUtil.contains(value, "no-store")
|| StringUtil.contains(value, "must-revalidate")) {
return false;
}
}
}
// Pragma: no-cache
values = cachedResponse.getHeaderValues(HTTPCache.HEADER_PRAGMA);
if (values != null) {
for (String value : values) {
if (StringUtil.contains(value, "no-cache")) {
return false;
}
}
}
return true;
}
public void flushBuffer() throws IOException {
streamDelegate.flushBuffer();
}
public void resetBuffer() {
// Servlet 2.3
streamDelegate.resetBuffer();
}
public void reset() {
if (Boolean.FALSE.equals(cacheable)) {
super.reset();
}
// No else, might be cacheable after all..
init();
}
public ServletOutputStream getOutputStream() throws IOException {
return streamDelegate.getOutputStream();
}
public PrintWriter getWriter() throws IOException {
return streamDelegate.getWriter();
}
public boolean containsHeader(String name) {
return cachedResponse.getHeaderValues(name) != null;
}
public void sendError(int pStatusCode, String msg) throws IOException {
// NOT cacheable
status = pStatusCode;
super.sendError(pStatusCode, msg);
}
public void sendError(int pStatusCode) throws IOException {
// NOT cacheable
status = pStatusCode;
super.sendError(pStatusCode);
}
public void setStatus(int pStatusCode, String sm) {
// NOTE: This method is deprecated
setStatus(pStatusCode);
}
public void setStatus(int pStatusCode) {
// NOT cacheable unless pStatusCode == 200 (or a FEW others?)
if (pStatusCode != SC_OK) {
status = pStatusCode;
super.setStatus(pStatusCode);
}
}
public void sendRedirect(String pLocation) throws IOException {
// NOT cacheable
status = SC_MOVED_TEMPORARILY;
super.sendRedirect(pLocation);
}
public void setDateHeader(String pName, long pValue) {
// If in write-trough-mode, set headers directly
if (Boolean.FALSE.equals(cacheable)) {
super.setDateHeader(pName, pValue);
}
cachedResponse.setHeader(pName, HTTPUtil.formatHTTPDate(pValue));
}
public void addDateHeader(String pName, long pValue) {
// If in write-trough-mode, set headers directly
if (Boolean.FALSE.equals(cacheable)) {
super.addDateHeader(pName, pValue);
}
cachedResponse.addHeader(pName, HTTPUtil.formatHTTPDate(pValue));
}
public void setHeader(String pName, String pValue) {
// If in write-trough-mode, set headers directly
if (Boolean.FALSE.equals(cacheable)) {
super.setHeader(pName, pValue);
}
cachedResponse.setHeader(pName, pValue);
}
public void addHeader(String pName, String pValue) {
// If in write-trough-mode, set headers directly
if (Boolean.FALSE.equals(cacheable)) {
super.addHeader(pName, pValue);
}
cachedResponse.addHeader(pName, pValue);
}
public void setIntHeader(String pName, int pValue) {
// If in write-trough-mode, set headers directly
if (Boolean.FALSE.equals(cacheable)) {
super.setIntHeader(pName, pValue);
}
cachedResponse.setHeader(pName, String.valueOf(pValue));
}
public void addIntHeader(String pName, int pValue) {
// If in write-trough-mode, set headers directly
if (Boolean.FALSE.equals(cacheable)) {
super.addIntHeader(pName, pValue);
}
cachedResponse.addHeader(pName, String.valueOf(pValue));
}
public final void setContentType(String type) {
setHeader(HTTPCache.HEADER_CONTENT_TYPE, type);
}
}
+78
View File
@@ -0,0 +1,78 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet.cache;
import java.io.IOException;
/**
* CachedEntity
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: CachedEntity.java#3 $
*/
@Deprecated
interface CachedEntity {
/**
* Renders the cached entity to the response.
*
* @param pRequest the request
* @param pResponse the response
* @throws java.io.IOException if an I/O exception occurs
*/
void render(CacheRequest pRequest, CacheResponse pResponse) throws IOException;
/**
* Captures (caches) the response for the given request.
*
* @param pRequest the request
* @param pResponse the response
* @throws java.io.IOException if an I/O exception occurs
*
* @see #createCachedResponse()
*/
void capture(CacheRequest pRequest, CachedResponse pResponse) throws IOException;
/**
* Tests if the content of this entity is stale for the given request.
*
* @param pRequest the request
* @return {@code true} if content is stale
*/
boolean isStale(CacheRequest pRequest);
/**
* Creates a {@code WritableCachedResponse} to use to capture the response.
*
* @return a {@code WritableCachedResponse}
*/
WritableCachedResponse createCachedResponse();
}
@@ -0,0 +1,174 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet.cache;
import java.io.IOException;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import com.twelvemonkeys.lang.Validate;
/**
* CachedEntity
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: CachedEntityImpl.java#3 $
*/
@Deprecated
class CachedEntityImpl implements CachedEntity {
private String cacheURI;
private HTTPCache cache;
CachedEntityImpl(String pCacheURI, HTTPCache pCache) {
cacheURI = Validate.notNull(pCacheURI, "cacheURI");
cache = pCache;
}
public void render(CacheRequest pRequest, CacheResponse pResponse) throws IOException {
// Get cached content
CachedResponse cached = cache.getContent(cacheURI, pRequest);
// Sanity check
if (cached == null) {
throw new IllegalStateException("Tried to render non-cached response (cache == null).");
}
// If the cached entity is not modified since the date of the browsers
// version, then simply send a "304 Not Modified" response
// Otherwise send the full response.
// TODO: WHY DID I COMMENT OUT THIS LINE AND REPLACE IT WITH THE ONE BELOW??
//long lastModified = HTTPCache.getDateHeader(cached.getHeaderValue(HTTPCache.HEADER_LAST_MODIFIED));
long lastModified = HTTPCache.getDateHeader(cached.getHeaderValue(HTTPCache.HEADER_CACHED_TIME));
// TODO: Consider handling time skews between server "now" and client "now"?
// NOTE: The If-Modified-Since is probably right according to the server
// even in a time skew situation, as the client should use either the
// Date or Last-Modifed dates from the response headers (server generated)
long ifModifiedSince = -1L;
try {
List<String> ifmh = pRequest.getHeaders().get(HTTPCache.HEADER_IF_MODIFIED_SINCE);
ifModifiedSince = ifmh != null ? HTTPCache.getDateHeader(ifmh.get(0)) : -1L;
if (ifModifiedSince != -1L) {
/*
long serverTime = DateUtil.currentTimeMinute();
long clientTime = DateUtil.roundToMinute(pRequest.getDateHeader(HTTPCache.HEADER_DATE));
// Test if time skew is greater than time skew threshold (currently 1 minute)
if (Math.abs(serverTime - clientTime) > 1) {
// TODO: Correct error in ifModifiedSince?
}
*/
// System.out.println(" << CachedEntity >> If-Modified-Since present: " + ifModifiedSince + " --> " + NetUtil.formatHTTPDate(ifModifiedSince) + "==" + pRequest.getHeader(HTTPCache.HEADER_IF_MODIFIED_SINCE));
// System.out.println(" << CachedEntity >> Last-Modified for entity: " + lastModified + " --> " + NetUtil.formatHTTPDate(lastModified));
}
}
catch (IllegalArgumentException e) {
// Seems to be a bug in FireFox 1.0.2..?!
cache.log("Error in date header from user-agent. User-Agent: " + pRequest.getHeaders().get("User-Agent"), e);
}
if (lastModified == -1L || (ifModifiedSince < (lastModified / 1000L) * 1000L)) {
pResponse.setStatus(cached.getStatus());
cached.writeHeadersTo(pResponse);
if (isStale(pRequest)) {
// Add warning header
// Warning: 110 <server>:<port> Content is stale
pResponse.addHeader(HTTPCache.HEADER_WARNING, "110 " + getHost(pRequest) + " Content is stale.");
}
// NOTE: At the moment we only ever try to cache HEAD and GET requests
if (!"HEAD".equals(pRequest.getMethod())) {
cached.writeContentsTo(pResponse.getOutputStream());
}
}
else {
pResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
// System.out.println(" << CachedEntity >> Not modified: " + toString());
if (isStale(pRequest)) {
// Add warning header
// Warning: 110 <server>:<port> Content is stale
pResponse.addHeader(HTTPCache.HEADER_WARNING, "110 " + getHost(pRequest) + " Content is stale.");
}
}
}
/* Utility method to get Host header */
private static String getHost(CacheRequest pRequest) {
return pRequest.getServerName() + ":" + pRequest.getServerPort();
}
public void capture(CacheRequest pRequest, CachedResponse pResponse) throws IOException {
// if (!(pResponse instanceof CacheResponseWrapper)) {
// throw new IllegalArgumentException("Response must be created by CachedEntity.createResponseWrapper()");
// }
//
// CacheResponseWrapper response = (CacheResponseWrapper) pResponse;
// if (response.isCacheable()) {
cache.registerContent(
cacheURI,
pRequest,
pResponse instanceof WritableCachedResponse ? ((WritableCachedResponse) pResponse).getCachedResponse() : pResponse
);
// }
// else {
// Else store that the response for this request is not cachable
// pRequest.setAttribute(ATTRIB_NOT_CACHEABLE, Boolean.TRUE);
// TODO: Store this in HTTPCache, for subsequent requests to same resource?
// }
}
public boolean isStale(CacheRequest pRequest) {
return cache.isContentStale(cacheURI, pRequest);
}
public WritableCachedResponse createCachedResponse() {
return new WritableCachedResponseImpl();
}
public int hashCode() {
return (cacheURI != null ? cacheURI.hashCode() : 0) + 1397;
}
public boolean equals(Object pOther) {
return pOther instanceof CachedEntityImpl &&
((cacheURI == null && ((CachedEntityImpl) pOther).cacheURI == null) ||
cacheURI != null && cacheURI.equals(((CachedEntityImpl) pOther).cacheURI));
}
public String toString() {
return "CachedEntity[URI=" + cacheURI + "]";
}
}
@@ -0,0 +1,98 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet.cache;
import java.io.IOException;
import java.io.OutputStream;
/**
* CachedResponse
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: CachedResponse.java#3 $
*/
@Deprecated
interface CachedResponse {
/**
* Writes the cached headers to the response
*
* @param pResponse the servlet response
*/
void writeHeadersTo(CacheResponse pResponse);
/**
* Writes the cahced content to the response
*
* @param pStream the response output stream
* @throws IOException if an I/O exception occurs during write
*/
void writeContentsTo(OutputStream pStream) throws IOException;
int getStatus();
// TODO: Map<String, List<String>> getHeaders()
/**
* Gets the header names of all headers set in this response.
*
* @return an array of {@code String}s
*/
String[] getHeaderNames();
/**
* Gets all header values set for the given header in this response. If the
* header is not set, {@code null} is returned.
*
* @param pHeaderName the header name
* @return an array of {@code String}s, or {@code null} if there is no
* such header in this response.
*/
String[] getHeaderValues(String pHeaderName);
/**
* Gets the first header value set for the given header in this response.
* If the header is not set, {@code null} is returned.
* Useful for headers that don't have multiple values, like
* {@code "Content-Type"} or {@code "Content-Length"}.
*
* @param pHeaderName the header name
* @return a {@code String}, or {@code null} if there is no
* such header in this response.
*/
String getHeaderValue(String pHeaderName);
/**
* Returns the size of this cached response in bytes.
*
* @return the size
*/
int size();
}
@@ -0,0 +1,220 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet.cache;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.twelvemonkeys.io.FastByteArrayOutputStream;
import com.twelvemonkeys.lang.Validate;
/**
* CachedResponseImpl
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: CachedResponseImpl.java#4 $
*/
@Deprecated
class CachedResponseImpl implements CachedResponse {
final protected Map<String, List<String>> headers;
protected int headersSize;
protected ByteArrayOutputStream content = null;
int status;
protected CachedResponseImpl() {
headers = new LinkedHashMap<String, List<String>>(); // Keep headers in insertion order
}
// For use by HTTPCache, when recreating CachedResponses from disk cache
CachedResponseImpl(final int pStatus, final LinkedHashMap<String, List<String>> pHeaders, final int pHeaderSize, final byte[] pContent) {
status = pStatus;
headers = Validate.notNull(pHeaders, "headers");
headersSize = pHeaderSize;
content = new FastByteArrayOutputStream(pContent);
}
public int getStatus() {
return status;
}
/**
* Writes the cached headers to the response
*
* @param pResponse the response
*/
public void writeHeadersTo(final CacheResponse pResponse) {
String[] headers = getHeaderNames();
for (String header : headers) {
// HACK...
// Strip away internal headers
if (HTTPCache.HEADER_CACHED_TIME.equals(header)) {
continue;
}
// TODO: Replace Last-Modified with X-Cached-At? See CachedEntityImpl
String[] headerValues = getHeaderValues(header);
for (int i = 0; i < headerValues.length; i++) {
String headerValue = headerValues[i];
if (i == 0) {
pResponse.setHeader(header, headerValue);
}
else {
pResponse.addHeader(header, headerValue);
}
}
}
}
/**
* Writes the cached content to the response
*
* @param pStream the response stream
* @throws java.io.IOException
*/
public void writeContentsTo(final OutputStream pStream) throws IOException {
if (content == null) {
throw new IOException("Cache is null, no content to write.");
}
content.writeTo(pStream);
}
/**
* Gets the header names of all headers set in this response.
*
* @return an array of {@code String}s
*/
public String[] getHeaderNames() {
Set<String> headers = this.headers.keySet();
return headers.toArray(new String[headers.size()]);
}
/**
* Gets all header values set for the given header in this response. If the
* header is not set, {@code null} is returned.
*
* @param pHeaderName the header name
* @return an array of {@code String}s, or {@code null} if there is no
* such header in this response.
*/
public String[] getHeaderValues(final String pHeaderName) {
List<String> values = headers.get(pHeaderName);
return values == null ? null : values.toArray(new String[values.size()]);
}
/**
* Gets the first header value set for the given header in this response.
* If the header is not set, {@code null} is returned.
* Useful for headers that don't have multiple values, like
* {@code "Content-Type"} or {@code "Content-Length"}.
*
* @param pHeaderName the header name
* @return a {@code String}, or {@code null} if there is no
* such header in this response.
*/
public String getHeaderValue(final String pHeaderName) {
List<String> values = headers.get(pHeaderName);
return (values != null && values.size() > 0) ? values.get(0) : null;
}
public int size() {
// content.size() is exact size in bytes, headersSize is an estimate
return (content != null ? content.size() : 0) + headersSize;
}
public boolean equals(final Object pOther) {
if (this == pOther) {
return true;
}
if (pOther instanceof CachedResponseImpl) {
// "Fast"
return equalsImpl((CachedResponseImpl) pOther);
}
else if (pOther instanceof CachedResponse) {
// Slow
return equalsGeneric((CachedResponse) pOther);
}
return false;
}
private boolean equalsImpl(final CachedResponseImpl pOther) {
return headersSize == pOther.headersSize &&
(content == null ? pOther.content == null : content.equals(pOther.content)) &&
headers.equals(pOther.headers);
}
private boolean equalsGeneric(final CachedResponse pOther) {
if (size() != pOther.size()) {
return false;
}
String[] headers = getHeaderNames();
String[] otherHeaders = pOther.getHeaderNames();
if (!Arrays.equals(headers, otherHeaders)) {
return false;
}
if (headers != null) {
for (String header : headers) {
String[] values = getHeaderValues(header);
String[] otherValues = pOther.getHeaderValues(header);
if (!Arrays.equals(values, otherValues)) {
return false;
}
}
}
return true;
}
public int hashCode() {
int result;
result = headers.hashCode();
result = 29 * result + headersSize;
result = 37 * result + (content != null ? content.hashCode() : 0);
return result;
}
}
@@ -0,0 +1,75 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet.cache;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* ClientCacheRequest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: ClientCacheRequest.java#1 $
*/
@Deprecated
public final class ClientCacheRequest extends AbstractCacheRequest {
private Map<String, List<String>> parameters;
private Map<String, List<String>> headers;
public ClientCacheRequest(final URI pRequestURI,final Map<String, List<String>> pParameters, final Map<String, List<String>> pHeaders) {
super(pRequestURI, "GET"); // TODO: Consider supporting more than get? At least HEAD and OPTIONS...
parameters = normalizeMap(pParameters);
headers = normalizeMap(pHeaders);
}
private <K, V> Map<K, V> normalizeMap(Map<K, V> pMap) {
return pMap == null ? Collections.<K, V>emptyMap() : Collections.unmodifiableMap(pMap);
}
public Map<String, List<String>> getParameters() {
return parameters;
}
public Map<String, List<String>> getHeaders() {
return headers;
}
public String getServerName() {
return getRequestURI().getAuthority();
}
public int getServerPort() {
return getRequestURI().getPort();
}
}
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet.cache;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* ClientCacheResponse
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: ClientCacheResponse.java#2 $
*/
@Deprecated
public final class ClientCacheResponse extends AbstractCacheResponse {
// It's quite useless to cache the data either on disk or in memory, as it already is cached in the client's cache...
// It would be nice if we could bypass that...
public OutputStream getOutputStream() throws IOException {
throw new UnsupportedOperationException("Method getOutputStream not implemented"); // TODO: Implement
}
public InputStream getInputStream() {
throw new UnsupportedOperationException("Method getInputStream not implemented"); // TODO: Implement
}
}
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More